@@ -5,9 +5,25 @@ var formidable = require("formidable"); | |||
var crypto = require("crypto"); | |||
var basepath = "/admin/api/"; | |||
var slideshow; | |||
exports.init = function(_slideshow) { | |||
slideshow = _slideshow; | |||
} | |||
var tokens = {}; | |||
function pad(str, n) { | |||
if (str.length >= n) | |||
return str; | |||
var missing = str.length; | |||
for (var i = 0; i < n - missing; ++i) | |||
str = "0" + str; | |||
return str; | |||
} | |||
// Used in every method handler to make sure the correct arguments are provided | |||
function hasargs(query, respond, expected) { | |||
var missing = []; | |||
@@ -130,12 +146,79 @@ var methods = { | |||
}); | |||
form.parse(req, (err, fields, files) => { | |||
if (err) | |||
return respond(err); | |||
}); | |||
form.on("error", err => { | |||
respond(err); | |||
}); | |||
form.on("end", () => { | |||
respond(); | |||
}); | |||
}, | |||
// Create a slide | |||
// Lots of synchronous fs stuff, we don't want races | |||
slide_create: function(query, conf, req, respond) { | |||
var dirs; | |||
try { | |||
dirs = fs.readdirSync(conf.slides); | |||
} catch (err) { | |||
return respond(err); | |||
} | |||
dirs = dirs.sort(); | |||
var biggest = dirs[dirs.length - 1]; | |||
var newId = pad((parseInt(biggest) + 1).toString(), biggest.length); | |||
var path = pathlib.join(conf.slides, newId); | |||
try { | |||
fs.mkdirSync(path); | |||
fs.writeFileSync(pathlib.join(path, "index.md"), ""); | |||
} catch (err) { | |||
return respond(err); | |||
} | |||
slideshow.updateSlides(); | |||
respond(null, newId); | |||
}, | |||
// Delete a slide | |||
// Also synchronous fs stuff | |||
slide_delete: function(query, conf, req, respond) { | |||
if (!hasargs(query, respond, [ "slide" ])) return; | |||
var path = pathlib.join(conf.slides, query.slide); | |||
var files; | |||
try { | |||
files = fs.readdirSync(path); | |||
} catch (err) { | |||
return respond(err); | |||
} | |||
for (var f of files) { | |||
try { | |||
fs.unlinkSync(pathlib.join(path, f)); | |||
} catch (err) { | |||
return respond(err); | |||
} | |||
} | |||
try { | |||
fs.rmdirSync(path); | |||
} catch (err) { | |||
return respond(err); | |||
} | |||
respond(); | |||
} | |||
} | |||
exports.canServe = function(parts) { | |||
// Temporary, while working on stuff | |||
var name = parts.pathname.replace(basepath, ""); | |||
return methods[name] !== undefined || name === "login"; | |||
@@ -163,7 +246,18 @@ function loginHandler(conf, req, respond) { | |||
respond(null, token); | |||
} | |||
function validateToken(req) { | |||
var token = req.headers["session-token"]; | |||
var cookie = req.headers.cookie; | |||
if (!cookie) | |||
return false; | |||
var token; | |||
for (var c of cookie.split(/;\s*/)) { | |||
var parts = c.split("="); | |||
if (parts[0] === "token") { | |||
token = parts[1]; | |||
break; | |||
} | |||
} | |||
if (!token) | |||
return false; | |||
@@ -205,12 +205,31 @@ function Slideshow(dir, changeInterval) { | |||
} | |||
} | |||
// This function starts the slideshow and goes through the slides | |||
// one by one. When done, it starts again by calling this function again. | |||
function init() { | |||
self.updateSlides = updateSlides; | |||
function updateSlides() { | |||
slides = fs.readdirSync(dir) | |||
.sort() | |||
.map(file => Slide(pathlib.join(dir, file))); | |||
} | |||
self.serve = serve; | |||
function serve(parts, res) { | |||
for (var slide of slides) { | |||
if (slide.name === parts.pathname.substr(1, slide.name.length)) { | |||
slide.serveFiles(parts, res); | |||
return; | |||
} | |||
} | |||
// We haven't found any matching slides | |||
res.writeHead(404); | |||
res.end("404"); | |||
} | |||
// This function starts the slideshow and goes through the slides | |||
// one by one. When done, it starts again by calling this function again. | |||
function init() { | |||
updateSlides(); | |||
slideIndex = 0; | |||
currentSlide = slides[slideIndex]; |
@@ -14,6 +14,7 @@ var index = fs.readFileSync("web/index.html", "utf-8") | |||
.replace(/<<transition_time>>/g, conf.transition_time); | |||
var slideshow = Slideshow(conf.slides, conf.interval); | |||
admin.init(slideshow); | |||
function onexit(code) { | |||
console.log("exiting", code); | |||
@@ -53,22 +54,7 @@ function handler(req, res) { | |||
// Serve slide files | |||
} else { | |||
var served = false; | |||
for (var slide of slideshow.getSlides()) { | |||
// If client requests /{slide-name}/* | |||
if (slide.name === pathname.substr(1, slide.name.length)) { | |||
slide.serveFiles(parts, res); | |||
served = true; | |||
break; | |||
} | |||
} | |||
if (!served) { | |||
res.writeHead(404); | |||
res.end("404"); | |||
} | |||
slideshow.serve(parts, res); | |||
} | |||
} | |||
@@ -9,14 +9,12 @@ | |||
window.error = error; | |||
window.$$ = $$; | |||
var sessToken = ""; | |||
function apiLogin(pass, cb) { | |||
var extraHeads = [ [ "Session-Pass", pass ] ]; | |||
api("login", {}, (err, token) => { | |||
if (token) { | |||
sessToken = token; | |||
document.cookie = "token = " + token; | |||
cb(true); | |||
} else { | |||
cb(false); | |||
@@ -32,13 +30,13 @@ | |||
function api(method, args, cb, extraHeads) { | |||
var heads = new Headers(); | |||
heads.append("Session-Token", sessToken); | |||
if (extraHeads) | |||
extraHeads.forEach(h => heads.append(h[0], h[1])); | |||
var opts = { | |||
method: "POST", | |||
headers: heads | |||
headers: heads, | |||
credentials: "same-origin" | |||
}; | |||
fetch("/admin/api/"+method+argstr(args), opts) | |||
.then(response => response.json()) |
@@ -89,6 +89,7 @@ | |||
#root.edit #fileList, | |||
#root.edit #slide > .uploader, | |||
#root.edit #slide > .delete, | |||
#root.edit #html { | |||
margin-bottom: 24px; | |||
} |
@@ -18,27 +18,53 @@ | |||
} | |||
function viewLogin(root) { | |||
function login(first) { | |||
var pass; | |||
do { | |||
if (first) | |||
pass = prompt("Password?"); | |||
else | |||
pass = prompt("Incorrect password."); | |||
} while (!pass); | |||
var msg; | |||
var pwd; | |||
elem("form", {}, [ | |||
msg = elem("div", { | |||
className: "msg", | |||
innerHTML: "Log In" | |||
}), | |||
pwd = elem("input", { | |||
type: "password", | |||
placeholder: "Password" | |||
}), | |||
elem("button", { | |||
innerHTML: "Log In" | |||
}) | |||
]).on("submit", evt => { | |||
evt.preventDefault(); | |||
apiLogin(pass, success => { | |||
apiLogin(pwd.value, success => { | |||
if (success) | |||
setView("main"); | |||
else | |||
login(false); | |||
msg.innerHTML = "Incorrect password." | |||
}); | |||
} | |||
login(true); | |||
}).appendTo(root); | |||
} | |||
function viewMain(root) { | |||
// New slide button | |||
elem("button", { | |||
className: "newSlide", | |||
innerHTML: "New Slide" | |||
}).on("click", () => { | |||
api("slide_create", {}, (err, id) => { | |||
if (err) | |||
return error(err); | |||
setView("edit", [id]); | |||
}); | |||
}).appendTo(root); | |||
var slidesEl = elem("div", { | |||
className: "slides" | |||
}).appendTo(root); | |||
// Get a list of the slides | |||
api("list_slides", {}, (err, slides) => { | |||
if (err) | |||
@@ -64,7 +90,7 @@ | |||
elem("div", { | |||
className: "overlay" | |||
}) | |||
]).appendTo(root); | |||
]).appendTo(slidesEl); | |||
// Add 'disabled' to the class if it's disabled | |||
api("slide_meta", { slide: s }, (err, res) => { | |||
@@ -155,6 +181,21 @@ | |||
slide: slide | |||
}, populateFileList), | |||
elem("button", { | |||
className: "delete", | |||
innerHTML: "Delete" | |||
}).on("click", () => { | |||
if (!confirm("Are you sure you want to delete slide "+slide+"?")) | |||
return; | |||
api("slide_delete", { slide: slide }, err => { | |||
if (err) | |||
return error(err); | |||
setView("main"); | |||
}); | |||
}), | |||
htmlTextEl = elem("textarea", { | |||
id: "html" | |||
}).on("keydown", debounce(() => { | |||
@@ -213,7 +254,6 @@ | |||
root.clear(); | |||
root.className = name; | |||
views[name].apply(null, args); | |||
} | |||