| 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); | ||||
| } | } | ||||