var crypto = require("crypto"); | var crypto = require("crypto"); | ||||
var basepath = "/admin/api/"; | var basepath = "/admin/api/"; | ||||
var slideshow; | |||||
exports.init = function(_slideshow) { | |||||
slideshow = _slideshow; | |||||
} | |||||
var tokens = {}; | 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 | // Used in every method handler to make sure the correct arguments are provided | ||||
function hasargs(query, respond, expected) { | function hasargs(query, respond, expected) { | ||||
var missing = []; | var missing = []; | ||||
}); | }); | ||||
form.parse(req, (err, fields, files) => { | form.parse(req, (err, fields, files) => { | ||||
if (err) | |||||
return respond(err); | |||||
}); | |||||
form.on("error", err => { | |||||
respond(err); | |||||
}); | |||||
form.on("end", () => { | |||||
respond(); | 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) { | exports.canServe = function(parts) { | ||||
// Temporary, while working on stuff | // Temporary, while working on stuff | ||||
var name = parts.pathname.replace(basepath, ""); | var name = parts.pathname.replace(basepath, ""); | ||||
return methods[name] !== undefined || name === "login"; | return methods[name] !== undefined || name === "login"; | ||||
respond(null, token); | respond(null, token); | ||||
} | } | ||||
function validateToken(req) { | 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) | if (!token) | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
// 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) | slides = fs.readdirSync(dir) | ||||
.sort() | .sort() | ||||
.map(file => Slide(pathlib.join(dir, file))); | .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; | slideIndex = 0; | ||||
currentSlide = slides[slideIndex]; | currentSlide = slides[slideIndex]; |
.replace(/<<transition_time>>/g, conf.transition_time); | .replace(/<<transition_time>>/g, conf.transition_time); | ||||
var slideshow = Slideshow(conf.slides, conf.interval); | var slideshow = Slideshow(conf.slides, conf.interval); | ||||
admin.init(slideshow); | |||||
function onexit(code) { | function onexit(code) { | ||||
console.log("exiting", code); | console.log("exiting", code); | ||||
// Serve slide files | // Serve slide files | ||||
} else { | } 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); | |||||
} | } | ||||
} | } | ||||
window.error = error; | window.error = error; | ||||
window.$$ = $$; | window.$$ = $$; | ||||
var sessToken = ""; | |||||
function apiLogin(pass, cb) { | function apiLogin(pass, cb) { | ||||
var extraHeads = [ [ "Session-Pass", pass ] ]; | var extraHeads = [ [ "Session-Pass", pass ] ]; | ||||
api("login", {}, (err, token) => { | api("login", {}, (err, token) => { | ||||
if (token) { | if (token) { | ||||
sessToken = token; | |||||
document.cookie = "token = " + token; | |||||
cb(true); | cb(true); | ||||
} else { | } else { | ||||
cb(false); | cb(false); | ||||
function api(method, args, cb, extraHeads) { | function api(method, args, cb, extraHeads) { | ||||
var heads = new Headers(); | var heads = new Headers(); | ||||
heads.append("Session-Token", sessToken); | |||||
if (extraHeads) | if (extraHeads) | ||||
extraHeads.forEach(h => heads.append(h[0], h[1])); | extraHeads.forEach(h => heads.append(h[0], h[1])); | ||||
var opts = { | var opts = { | ||||
method: "POST", | method: "POST", | ||||
headers: heads | |||||
headers: heads, | |||||
credentials: "same-origin" | |||||
}; | }; | ||||
fetch("/admin/api/"+method+argstr(args), opts) | fetch("/admin/api/"+method+argstr(args), opts) | ||||
.then(response => response.json()) | .then(response => response.json()) |
#root.edit #fileList, | #root.edit #fileList, | ||||
#root.edit #slide > .uploader, | #root.edit #slide > .uploader, | ||||
#root.edit #slide > .delete, | |||||
#root.edit #html { | #root.edit #html { | ||||
margin-bottom: 24px; | margin-bottom: 24px; | ||||
} | } |
} | } | ||||
function viewLogin(root) { | 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) | if (success) | ||||
setView("main"); | setView("main"); | ||||
else | else | ||||
login(false); | |||||
msg.innerHTML = "Incorrect password." | |||||
}); | }); | ||||
} | |||||
login(true); | |||||
}).appendTo(root); | |||||
} | } | ||||
function viewMain(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 | // Get a list of the slides | ||||
api("list_slides", {}, (err, slides) => { | api("list_slides", {}, (err, slides) => { | ||||
if (err) | if (err) | ||||
elem("div", { | elem("div", { | ||||
className: "overlay" | className: "overlay" | ||||
}) | }) | ||||
]).appendTo(root); | |||||
]).appendTo(slidesEl); | |||||
// Add 'disabled' to the class if it's disabled | // Add 'disabled' to the class if it's disabled | ||||
api("slide_meta", { slide: s }, (err, res) => { | api("slide_meta", { slide: s }, (err, res) => { | ||||
slide: slide | slide: slide | ||||
}, populateFileList), | }, 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", { | htmlTextEl = elem("textarea", { | ||||
id: "html" | id: "html" | ||||
}).on("keydown", debounce(() => { | }).on("keydown", debounce(() => { | ||||
root.clear(); | root.clear(); | ||||
root.className = name; | root.className = name; | ||||
views[name].apply(null, args); | views[name].apply(null, args); | ||||
} | } | ||||