1
0
Fork 0

Refactor tabs to spaces

pull/2/head
Ambrose Chua 2018-03-12 00:33:41 +09:00
parent 812130e22a
commit fe78962db0
14 changed files with 676 additions and 671 deletions

View File

@ -1,53 +1,53 @@
/* jshint esversion: 6 */ /* jshint esversion: 6 */
$(document).ready(() => { $(document).ready(() => {
let $inputs = $(".input-group-digits"); let $inputs = $(".input-group-digits");
$inputs.each((i, input) => { $inputs.each((i, input) => {
let cleanup = () => { let cleanup = () => {
$(input).find("input").each((i, ele) => { $(input).find("input").each((i, ele) => {
let cleaned = $(ele).val().replace(/[^0-9]/, ""); let cleaned = $(ele).val().replace(/[^0-9]/, "");
$(ele).val(cleaned); $(ele).val(cleaned);
}); });
}; };
let update = (e) => { let update = (e) => {
$digits = $(input).find("input"); $digits = $(input).find("input");
// Cleanup // Cleanup
cleanup(); cleanup();
// Shift characters // Shift characters
let excess = ""; let excess = "";
$digits.each((i, ele) => { $digits.each((i, ele) => {
let now = excess + $(ele).val(); let now = excess + $(ele).val();
$(ele).val(now.charAt(0)); $(ele).val(now.charAt(0));
excess = now.substr(1); excess = now.substr(1);
}); });
// Move cursor to empty // Move cursor to empty
$digits.each((i, ele) => { $digits.each((i, ele) => {
if (!$(ele).val()) { if (!$(ele).val()) {
$(ele).focus(); $(ele).focus();
if (e.which == 8) { if (e.which == 8) {
$(ele).prev().focus().val(""); $(ele).prev().focus().val("");
} }
return false; return false;
} }
}); });
// Submit if last digit is filled // Submit if last digit is filled
if ($($digits[$digits.length - 1]).val()) { if ($($digits[$digits.length - 1]).val()) {
let token = $.map( let token = $.map(
$digits, $digits,
d => $(d).val() d => $(d).val()
).join(""); ).join("");
let $value = $(input).parent().find("#login-token-value"); let $value = $(input).parent().find("#login-token-value");
$value.val(token); $value.val(token);
$value.closest("form").submit(); $value.closest("form").submit();
} }
}; };
$digits = $(input).find("input"); $digits = $(input).find("input");
$digits.on("keyup", update); $digits.on("keyup", update);
$digits.on("change", update); $digits.on("change", update);
}); });
}); });

View File

@ -1,30 +1,30 @@
/* jshint esversion: 6 */ /* jshint esversion: 6 */
$(document).ready(() => { $(document).ready(() => {
let $select = $(".multi-select"); let $select = $(".multi-select");
let setSelected = (files) => { let setSelected = (files) => {
$(".multi-files-value").val(JSON.stringify(files.map(f => f.name))); $(".multi-files-value").val(JSON.stringify(files.map(f => f.name)));
$(".multi-files").html( $(".multi-files").html(
files.map(f => { files.map(f => {
return `<li class="list-group-item d-flex align-items-start justify-content-between"><span class="name">${f.name}</span> <span class="badge badge-pill badge-secondary">${f.size}</span></li>`; return `<li class="list-group-item d-flex align-items-start justify-content-between"><span class="name">${f.name}</span> <span class="badge badge-pill badge-secondary">${f.size}</span></li>`;
}).join("") }).join("")
); );
}; };
let updateSelected = () => { let updateSelected = () => {
let $selected = $(".multi-select:checked"); let $selected = $(".multi-select:checked");
let files = []; let files = [];
$selected.each((i, ele) => { $selected.each((i, ele) => {
files.push({ files.push({
name: $(ele).data("select"), name: $(ele).data("select"),
size: $(ele).data("select-size") size: $(ele).data("select-size")
}); });
}); });
setSelected(files); setSelected(files);
} }
$select.on("change", updateSelected); $select.on("change", updateSelected);
updateSelected(); updateSelected();
}); });

View File

@ -1,7 +1,7 @@
/* jshint esversion: 6 */ /* jshint esversion: 6 */
$(document).ready(() => { $(document).ready(() => {
let $shell = $("#shell"); let $shell = $("#shell");
if (!$shell) { if (!$shell) {
return; return;
} }

View File

@ -1,24 +1,24 @@
/* jshint esversion: 6 */ /* jshint esversion: 6 */
$(document).ready(() => { $(document).ready(() => {
let $form = $("form[action='@upload']"); let $form = $("form[action='@upload']");
let $file = $("#upload-file"); let $file = $("#upload-file");
$(".upload-unhide").fadeOut(); $(".upload-unhide").fadeOut();
$file.on("change", () => { $file.on("change", () => {
let file = $file[0].files[0]; let file = $file[0].files[0];
let fnElement = $file.parent().find(".custom-file-label"); let fnElement = $file.parent().find(".custom-file-label");
fnElement.addClass("file-selected"); fnElement.addClass("file-selected");
fnElement.text(file.name); fnElement.text(file.name);
$form.find("#upload-file-size").val(filesize(file.size)); $form.find("#upload-file-size").val(filesize(file.size));
$form.find("[name=saveas]").val(file.name); $form.find("[name=saveas]").val(file.name);
$(".upload-unhide").fadeIn(); $(".upload-unhide").fadeIn();
}); });
$form.on("submit", () => { $form.on("submit", () => {
let putresource = $form.find("[name=saveas]").val(); let putresource = $form.find("[name=saveas]").val();
// TODO: do XHR to PUT at putresource // TODO: do XHR to PUT at putresource
}); });
}); });

833
index.js
View File

@ -26,28 +26,28 @@ let http = app.listen(process.env.PORT || 8080);
app.set("views", path.join(__dirname, "views")); app.set("views", path.join(__dirname, "views"));
app.engine("handlebars", hbs({ app.engine("handlebars", hbs({
partialsDir: path.join(__dirname, "views", "partials"), partialsDir: path.join(__dirname, "views", "partials"),
layoutsDir: path.join(__dirname, "views", "layouts"), layoutsDir: path.join(__dirname, "views", "layouts"),
defaultLayout: "main", defaultLayout: "main",
helpers: { helpers: {
eachpath: (path, options) => { eachpath: (path, options) => {
if (typeof path != "string") { if (typeof path != "string") {
return ""; return "";
} }
let out = ""; let out = "";
path = path.split("/"); path = path.split("/");
path.splice(path.length - 1, 1); path.splice(path.length - 1, 1);
path.unshift(""); path.unshift("");
path.forEach((folder, index) => { path.forEach((folder, index) => {
out += options.fn({ out += options.fn({
name: folder + "/", name: folder + "/",
path: "/" + path.slice(1, index + 1).join("/"), path: "/" + path.slice(1, index + 1).join("/"),
current: index === path.length - 1 current: index === path.length - 1
}); });
}); });
return out; return out;
} }
} }
})); }));
app.set("view engine", "handlebars"); app.set("view engine", "handlebars");
@ -58,7 +58,7 @@ app.use("/filesize", express.static(path.join(__dirname, "node_modules/filesize/
app.use("/assets", express.static(path.join(__dirname, "assets"))); app.use("/assets", express.static(path.join(__dirname, "assets")));
app.use(session({ app.use(session({
secret: "meowmeow" secret: "meowmeow"
})); }));
app.use(flash()); app.use(flash());
app.use(busboy()); app.use(busboy());
@ -69,462 +69,467 @@ app.use(bodyparser.urlencoded());
const KEY = process.env.KEY ? base32.decode(process.env.KEY.replace(/ /g, "")) : null; const KEY = process.env.KEY ? base32.decode(process.env.KEY.replace(/ /g, "")) : null;
app.get("/@logout", (req, res) => { app.get("/@logout", (req, res) => {
if (KEY) { if (KEY) {
req.session.login = false; req.session.login = false;
req.flash("success", "Signed out."); req.flash("success", "Signed out.");
res.redirect("/@login"); res.redirect("/@login");
return return
} }
req.flash("error", "You were never logged in..."); req.flash("error", "You were never logged in...");
res.redirect("back"); res.redirect("back");
}); });
app.get("/@login", (req, res) => { app.get("/@login", (req, res) => {
res.render("login", flashify(req, {})); res.render("login", flashify(req, {}));
}); });
app.post("/@login", (req, res) => { app.post("/@login", (req, res) => {
let pass = notp.totp.verify(req.body.token.replace(" ", ""), KEY); let pass = notp.totp.verify(req.body.token.replace(" ", ""), KEY);
console.log(pass, req.body.token.replace(" ", "")); console.log(pass, req.body.token.replace(" ", ""));
if (pass) { if (pass) {
req.session.login = true; req.session.login = true;
res.redirect("/"); res.redirect("/");
return; return;
} }
req.flash("error", "Bad token."); req.flash("error", "Bad token.");
res.redirect("/@login"); res.redirect("/@login");
}); });
app.use((req, res, next) => { app.use((req, res, next) => {
if (!KEY) { if (!KEY) {
return next(); return next();
} }
if (req.session.login === true) { if (req.session.login === true) {
return next(); return next();
} }
req.flash("error", "Please sign in."); req.flash("error", "Please sign in.");
res.redirect("/@login"); res.redirect("/@login");
}); });
function relative(...paths) { function relative(...paths) {
return paths.reduce((a, b) => path.join(a, b), process.cwd()); return paths.reduce((a, b) => path.join(a, b), process.cwd());
} }
function flashify(req, obj) { function flashify(req, obj) {
let error = req.flash("error"); let error = req.flash("error");
if (error && error.length > 0) { if (error && error.length > 0) {
if (!obj.errors) { if (!obj.errors) {
obj.errors = []; obj.errors = [];
} }
obj.errors.push(error); obj.errors.push(error);
} }
let success = req.flash("success"); let success = req.flash("success");
if (success && success.length > 0) { if (success && success.length > 0) {
if (!obj.successes) { if (!obj.successes) {
obj.successes = []; obj.successes = [];
} }
obj.successes.push(success); obj.successes.push(success);
} }
obj.isloginenabled = !!KEY; obj.isloginenabled = !!KEY;
return obj; return obj;
} }
app.all("/*", (req, res, next) => { app.all("/*", (req, res, next) => {
res.filename = req.params[0]; res.filename = req.params[0];
let fileExists = new Promise((resolve, reject) => { let fileExists = new Promise((resolve, reject) => {
// check if file exists // check if file exists
fs.stat(relative(res.filename), (err, stats) => { fs.stat(relative(res.filename), (err, stats) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
return resolve(stats); return resolve(stats);
}); });
}); });
fileExists.then((stats) => { fileExists.then((stats) => {
res.stats = stats; res.stats = stats;
next(); next();
}).catch((err) => { }).catch((err) => {
res.stats = { error: err }; res.stats = { error: err };
next(); next();
}); });
}); });
// currently unused // currently unused
app.put("/*", (req, res) => { app.put("/*", (req, res) => {
if (res.stats.error) { if (res.stats.error) {
req.busboy.on("file", (key, file, filename) => { req.busboy.on("file", (key, file, filename) => {
if (key == "file") { if (key == "file") {
let save = fs.createWriteStream(relative(res.filename)); let save = fs.createWriteStream(relative(res.filename));
file.pipe(save); file.pipe(save);
save.on("close", () => { save.on("close", () => {
res.flash("success", "File saved. "); res.flash("success", "File saved. ");
res.redirect("back"); res.redirect("back");
}); });
save.on("error", (err) => { save.on("error", (err) => {
res.flash("error", err); res.flash("error", err);
res.redirect("back"); res.redirect("back");
}); });
} }
}); });
req.busboy.on("field", (key, value) => { req.busboy.on("field", (key, value) => {
}); });
req.pipe(req.busboy); req.pipe(req.busboy);
} }
else { else {
req.flash("error", "File exists, cannot overwrite. "); req.flash("error", "File exists, cannot overwrite. ");
res.redirect("back"); res.redirect("back");
} }
}); });
app.post("/*@upload", (req, res) => { app.post("/*@upload", (req, res) => {
res.filename = req.params[0]; res.filename = req.params[0];
let buff = null; let buff = null;
let saveas = null; let saveas = null;
req.busboy.on("file", (key, stream, filename) => { req.busboy.on("file", (key, stream, filename) => {
if (key == "file") { if (key == "file") {
let buffs = []; let buffs = [];
stream.on("data", (d) => { stream.on("data", (d) => {
buffs.push(d); buffs.push(d);
}); });
stream.on("end", () => { stream.on("end", () => {
buff = Buffer.concat(buffs); buff = Buffer.concat(buffs);
buffs = null; buffs = null;
}); });
} }
}); });
req.busboy.on("field", (key, value) => { req.busboy.on("field", (key, value) => {
if (key == "saveas") { if (key == "saveas") {
saveas = value; saveas = value;
} }
}); });
req.busboy.on("finish", () => { req.busboy.on("finish", () => {
if (!buff || !saveas) { if (!buff || !saveas) {
return res.status(400).end(); return res.status(400).end();
} }
let fileExists = new Promise((resolve, reject) => { let fileExists = new Promise((resolve, reject) => {
// check if file exists // check if file exists
fs.stat(relative(res.filename, saveas), (err, stats) => { fs.stat(relative(res.filename, saveas), (err, stats) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
return resolve(stats); return resolve(stats);
}); });
}); });
fileExists.then((stats) => { fileExists.then((stats) => {
req.flash("error", "File exists, cannot overwrite. "); req.flash("error", "File exists, cannot overwrite. ");
res.redirect("back"); res.redirect("back");
}).catch((err) => { }).catch((err) => {
console.log("saving"); console.log("saving");
let save = fs.createWriteStream(relative(res.filename, saveas)); let save = fs.createWriteStream(relative(res.filename, saveas));
save.on("close", () => { save.on("close", () => {
if (buff.length === 0) { if (buff.length === 0) {
req.flash("success", "File saved. Warning: empty file."); req.flash("success", "File saved. Warning: empty file.");
} }
else { else {
buff = null; buff = null;
req.flash("success", "File saved. "); req.flash("success", "File saved. ");
} }
res.redirect("back"); res.redirect("back");
}); });
save.on("error", (err) => { save.on("error", (err) => {
req.flash("error", err); req.flash("error", err);
res.redirect("back"); res.redirect("back");
}); });
save.write(buff); save.write(buff);
save.end(); save.end();
}); });
}); });
req.pipe(req.busboy); req.pipe(req.busboy);
}); });
app.post("/*@mkdir", (req, res) => { app.post("/*@mkdir", (req, res) => {
res.filename = req.params[0]; res.filename = req.params[0];
let folder = req.body.folder; let folder = req.body.folder;
if (!folder || folder.length < 1) { if (!folder || folder.length < 1) {
return res.status(400).end(); return res.status(400).end();
} }
let fileExists = new Promise((resolve, reject) => { let fileExists = new Promise((resolve, reject) => {
// Check if file exists // Check if file exists
fs.stat(relative(res.filename, folder), (err, stats) => { fs.stat(relative(res.filename, folder), (err, stats) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
return resolve(stats); return resolve(stats);
}); });
}); });
fileExists.then((stats) => { fileExists.then((stats) => {
req.flash("error", "Folder exists, cannot overwrite. "); req.flash("error", "Folder exists, cannot overwrite. ");
res.redirect("back"); res.redirect("back");
}).catch((err) => { }).catch((err) => {
fs.mkdir(relative(res.filename, folder), (err) => { fs.mkdir(relative(res.filename, folder), (err) => {
if (err) { if (err) {
req.flash("error", err); req.flash("error", err);
res.redirect("back"); res.redirect("back");
return; return;
} }
req.flash("success", "Folder created. "); req.flash("success", "Folder created. ");
res.redirect("back"); res.redirect("back");
}); });
}); });
}); });
app.post("/*@delete", (req, res) => { app.post("/*@delete", (req, res) => {
res.filename = req.params[0]; res.filename = req.params[0];
let files = JSON.parse(req.body.files); let files = JSON.parse(req.body.files);
if (!files || !files.map) { if (!files || !files.map) {
req.flash("error", "No files selected."); req.flash("error", "No files selected.");
res.redirect("back"); res.redirect("back");
return; // res.status(400).end(); return; // res.status(400).end();
} }
let promises = files.map(f => { let promises = files.map(f => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.stat(relative(res.filename, f), (err, stats) => { fs.stat(relative(res.filename, f), (err, stats) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
resolve({ resolve({
name: f, name: f,
isdirectory: stats.isDirectory(), isdirectory: stats.isDirectory(),
isfile: stats.isFile() isfile: stats.isFile()
}); });
}); });
}); });
}); });
Promise.all(promises).then((files) => { Promise.all(promises).then((files) => {
let promises = files.map(f => { let promises = files.map(f => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let op = null; let op = null;
if (f.isdirectory) { if (f.isdirectory) {
op = fs.rmdir; op = fs.rmdir;
} }
else if (f.isfile) { else if (f.isfile) {
op = fs.unlink; op = fs.unlink;
} }
if (op) { if (op) {
op(relative(res.filename, f.name), (err) => { op(relative(res.filename, f.name), (err) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
resolve(); resolve();
}); });
} }
}); });
}); });
Promise.all(promises).then(() => { Promise.all(promises).then(() => {
req.flash("success", "Files deleted. "); req.flash("success", "Files deleted. ");
res.redirect("back"); res.redirect("back");
}).catch((err) => { // TODO: recursive rmdir https://github.com/isaacs/rimraf }).catch((err) => { // TODO: recursive rmdir https://github.com/isaacs/rimraf
req.flash("error", "Unable to delete some files: " + err); req.flash("error", "Unable to delete some files: " + err);
res.redirect("back"); res.redirect("back");
}); });
}).catch((err) => { }).catch((err) => {
req.flash("error", err); req.flash("error", err);
res.redirect("back"); res.redirect("back");
}); });
}); });
app.get("/*@download", (req, res) => { app.get("/*@download", (req, res) => {
res.filename = req.params[0]; res.filename = req.params[0];
let files = null; let files = null;
try { try {
files = JSON.parse(req.query.files); files = JSON.parse(req.query.files);
} catch (e) {} } catch (e) {}
if (!files || !files.map) { if (!files || !files.map) {
req.flash("error", "No files selected."); req.flash("error", "No files selected.");
res.redirect("back"); res.redirect("back");
return; // res.status(400).end(); return; // res.status(400).end();
} }
let promises = files.map(f => { let promises = files.map(f => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.stat(relative(res.filename, f), (err, stats) => { fs.stat(relative(res.filename, f), (err, stats) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
resolve({ resolve({
name: f, name: f,
isdirectory: stats.isDirectory(), isdirectory: stats.isDirectory(),
isfile: stats.isFile() isfile: stats.isFile()
}); });
}); });
}); });
}); });
Promise.all(promises).then((files) => { Promise.all(promises).then((files) => {
let zip = archiver.create("zip", {}); let zip = archiver.create("zip", {});
zip.on("error", function(err) { zip.on("error", function(err) {
res.status(500).send({ res.status(500).send({
error: err.message error: err.message
}); });
}); });
files.filter(f => f.isfile).forEach((f) => { files.filter(f => f.isfile).forEach((f) => {
zip.file(relative(res.filename, f.name), { name: f.name }); zip.file(relative(res.filename, f.name), { name: f.name });
}); });
files.filter(f => f.isdirectory).forEach((f) => { files.filter(f => f.isdirectory).forEach((f) => {
zip.directory(relative(res.filename, f.name), f.name); zip.directory(relative(res.filename, f.name), f.name);
}); });
res.attachment("Archive.zip"); res.attachment("Archive.zip");
zip.pipe(res); zip.pipe(res);
zip.finalize(); zip.finalize();
}).catch((err) => { }).catch((err) => {
console.log(err); console.log(err);
req.flash("error", err); req.flash("error", err);
res.redirect("back"); res.redirect("back");
}); });
}); });
const shellable = process.env.SHELL != "false" && process.env.SHELL; const shellable = process.env.SHELL != "false" && process.env.SHELL;
if (shellable) { const cmdable = process.env.CMD != "false" && process.env.CMD;
const exec = process.env.SHELL == "login" ? "/usr/bin/env" : process.env.SHELL; if (shellable || cmdable) {
const args = process.env.SHELL == "login" ? ["login"] : []; const exec = process.env.SHELL == "login" ? "/usr/bin/env" : process.env.SHELL;
const args = process.env.SHELL == "login" ? ["login"] : [];
const child_process = require("child_process"); const child_process = require("child_process");
// currently unused // currently unused
app.post("/*@cmd", (req, res) => { app.post("/*@cmd", (req, res) => {
res.filename = req.params[0]; res.filename = req.params[0];
let cmd = req.body.cmd; let cmd = req.body.cmd;
if (!cmd || cmd.length < 1) { if (!cmd || cmd.length < 1) {
return res.status(400).end(); return res.status(400).end();
} }
child_process.exec(cmd, { child_process.exec(cmd, {
shell: shell, shell: shell,
cwd: res.filename, cwd: res.filename,
timeout: 60 * 1000, timeout: 60 * 1000,
}, (err, stdout, stderr) => { }, (err, stdout, stderr) => {
if (err) { if (err) {
req.flash("error", "Command failed due to non-zero exit code"); req.flash("error", "Command failed due to non-zero exit code");
} }
res.render("cmd", flashify(req, { res.render("cmd", flashify(req, {
path: res.filename, path: res.filename,
cmd: cmd, cmd: cmd,
stdout: stdout, stdout: stdout,
stderr: stderr, stderr: stderr,
})); }));
}); });
}); });
const pty = require("pty.js"); const pty = require("pty.js");
const io = require("socket.io")(http); const io = require("socket.io")(http);
app.get("/*@shell", (req, res) => { app.get("/*@shell", (req, res) => {
res.filename = req.params[0]; res.filename = req.params[0];
res.render("shell", flashify(req, { res.render("shell", flashify(req, {
path: res.filename, path: res.filename,
})); }));
}); });
io.on("connection", (socket) => { io.on("connection", (socket) => {
let cwd = socket.handshake.query.path; let cwd = socket.handshake.query.path;
let term = pty.spawn(exec, args, { let term = pty.spawn(exec, args, {
name: "xterm-256color", name: "xterm-256color",
cols: 80, cols: 80,
rows: 30, rows: 30,
cwd: cwd, cwd: cwd,
}); });
console.log("pid " + term.pid + " shell " + process.env.SHELL + " started in " + cwd); console.log("pid " + term.pid + " shell " + process.env.SHELL + " started in " + cwd);
term.on("data", (data) => { term.on("data", (data) => {
socket.emit("output", data); socket.emit("output", data);
}); });
term.on("exit", (code) => { term.on("exit", (code) => {
console.log("pid " + term.pid + " ended") console.log("pid " + term.pid + " ended")
socket.disconnect(); socket.disconnect();
}); });
socket.on("resize", (data) => { socket.on("resize", (data) => {
term.resize(data.col, data.row); term.resize(data.col, data.row);
}); });
socket.on("input", (data) => { socket.on("input", (data) => {
term.write(data); term.write(data);
}); });
socket.on("disconnect", () => { socket.on("disconnect", () => {
term.end(); term.end();
}); });
}); });
} }
app.get("/*", (req, res) => { app.get("/*", (req, res) => {
if (res.stats.error) { if (res.stats.error) {
res.render("list", flashify(req, { res.render("list", flashify(req, {
shellable: shellable, shellable: shellable,
path: res.filename, cmdable: cmdable,
errors: [ path: res.filename,
res.stats.error errors: [
] res.stats.error
})); ]
} }));
else if (res.stats.isDirectory()) { }
if (!req.url.endsWith("/")) { else if (res.stats.isDirectory()) {
return res.redirect(req.url + "/"); if (!req.url.endsWith("/")) {
} return res.redirect(req.url + "/");
}
let readDir = new Promise((resolve, reject) => { let readDir = new Promise((resolve, reject) => {
fs.readdir(relative(res.filename), (err, filenames) => { fs.readdir(relative(res.filename), (err, filenames) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
return resolve(filenames); return resolve(filenames);
}); });
}); });
readDir.then((filenames) => { readDir.then((filenames) => {
let promises = filenames.map(f => { let promises = filenames.map(f => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.stat(relative(res.filename, f), (err, stats) => { fs.stat(relative(res.filename, f), (err, stats) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
resolve({ resolve({
name: f, name: f,
isdirectory: stats.isDirectory(), isdirectory: stats.isDirectory(),
size: filesize(stats.size) size: filesize(stats.size)
}); });
}); });
}); });
}); });
Promise.all(promises).then((files) => { Promise.all(promises).then((files) => {
res.render("list", flashify(req, { res.render("list", flashify(req, {
shellable: shellable, shellable: shellable,
path: res.filename, cmdable: cmdable,
files: files, path: res.filename,
})); files: files,
}).catch((err) => { }));
res.render("list", flashify(req, { }).catch((err) => {
shellable: shellable, res.render("list", flashify(req, {
path: res.filename, shellable: shellable,
errors: [ cmdable: cmdable,
err path: res.filename,
] errors: [
})); err
}); ]
}).catch((err) => { }));
res.render("list", flashify(req, { });
shellable: shellable, }).catch((err) => {
path: res.filename, res.render("list", flashify(req, {
errors: [ shellable: shellable,
err cmdable: cmdable,
] path: res.filename,
})); errors: [
}); err
} ]
else if (res.stats.isFile()) { }));
res.download(relative(res.filename)); });
} }
else if (res.stats.isFile()) {
res.download(relative(res.filename));
}
}); });

View File

@ -1,29 +1,29 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<title>File Manager</title> <title>File Manager</title>
<link rel="stylesheet" href="/octicons/font/octicons.min.css" /> <link rel="stylesheet" href="/octicons/font/octicons.min.css" />
<link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css" /> <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css" />
<script src="/jquery/jquery.min.js"></script> <script src="/jquery/jquery.min.js"></script>
<script src="/bootstrap/js/bootstrap.min.js"></script> <script src="/bootstrap/js/bootstrap.min.js"></script>
<script src="/filesize/filesize.js"></script> <script src="/filesize/filesize.js"></script>
<link rel="stylesheet" href="/assets/fonts.css" /> <link rel="stylesheet" href="/assets/fonts.css" />
<link rel="stylesheet" href="/assets/list.css" /> <link rel="stylesheet" href="/assets/list.css" />
<script src="/assets/multi.js"></script> <script src="/assets/multi.js"></script>
<link rel="stylesheet" href="/assets/upload.css" /> <link rel="stylesheet" href="/assets/upload.css" />
<script src="/assets/upload.js"></script> <script src="/assets/upload.js"></script>
<link rel="stylesheet" href="/assets/login.css" /> <link rel="stylesheet" href="/assets/login.css" />
<script src="/assets/login.js"></script> <script src="/assets/login.js"></script>
<link rel="stylesheet" href="/assets/cmd.css" /> <link rel="stylesheet" href="/assets/cmd.css" />
<link rel="stylesheet" href="/assets/shell.css" /> <link rel="stylesheet" href="/assets/shell.css" />
<script src="/socket.io/socket.io.js"></script> <script src="/socket.io/socket.io.js"></script>
<script src="/assets/hterm_all.js"></script> <script src="/assets/hterm_all.js"></script>
<script src="/assets/shell.js"></script> <script src="/assets/shell.js"></script>
</head> </head>
<body> <body>
{{{body}}} {{{body}}}
</body> </body>
</html> </html>

View File

@ -2,21 +2,21 @@
<div style="padding-top: 56px; padding-bottom: 56px;"> <div style="padding-top: 56px; padding-bottom: 56px;">
<main class="container my-4"> <main class="container my-4">
{{#each errors as |error|}} {{#each errors as |error|}}
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
{{error}} {{error}}
</div> </div>
{{/each}} {{/each}}
{{#each successes as |success|}} {{#each successes as |success|}}
<div class="alert alert-success" role="alert"> <div class="alert alert-success" role="alert">
{{success}} {{success}}
</div> </div>
{{/each}} {{/each}}
<ul class="list-group"> <ul class="list-group">
{{#each files}} {{#each files}}
<li class="list-group-item"> <li class="list-group-item">
<div class="custom-control custom-checkbox"> <div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input multi-select" data-select="{{name}}" data-select-size="{{size}}" id="check{{@index}}"> <input type="checkbox" class="custom-control-input multi-select" data-select="{{name}}" data-select-size="{{size}}" id="check{{@index}}">
<label class="custom-control-label d-flex align-items-start justify-content-between" for="check{{@index}}"> <label class="custom-control-label d-flex align-items-start justify-content-between" for="check{{@index}}">
{{#if isdirectory}} {{#if isdirectory}}
<a href="{{name}}/" class="name">{{name}}/</a> <a href="{{name}}/" class="name">{{name}}/</a>
@ -25,14 +25,14 @@
<span class="badge badge-pill badge-secondary">{{size}}</span> <span class="badge badge-pill badge-secondary">{{size}}</span>
{{/if}} {{/if}}
</label> </label>
</div> </div>
</li> </li>
{{else}} {{else}}
<li class="list-group-item"> <li class="list-group-item">
No files No files
</li> </li>
{{/each}} {{/each}}
</ul> </ul>
</main> </main>
</div> </div>

View File

@ -2,17 +2,17 @@
<div style="padding-top: 56px; padding-bottom: 56px;"> <div style="padding-top: 56px; padding-bottom: 56px;">
<main class="container my-4"> <main class="container my-4">
<div class="login mx-auto"> <div class="login mx-auto">
{{#each errors as |error|}} {{#each errors as |error|}}
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
{{error}} {{error}}
</div> </div>
{{/each}} {{/each}}
{{#each successes as |success|}} {{#each successes as |success|}}
<div class="alert alert-success" role="alert"> <div class="alert alert-success" role="alert">
{{success}} {{success}}
</div> </div>
{{/each}} {{/each}}
<form class="py-4" action="/@login" method="post"> <form class="py-4" action="/@login" method="post">
<h4>One-time token:</h4> <h4>One-time token:</h4>
<div class="form-group"> <div class="form-group">
@ -27,6 +27,6 @@
<input name="token" id="login-token-value" type="hidden" /> <input name="token" id="login-token-value" type="hidden" />
</div> </div>
</form> </form>
</div> </div>
</main> </main>
</div> </div>

View File

@ -1,24 +1,24 @@
<form action="@cmd" method="post"> <form action="@cmd" method="post">
<div id="cmd" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true"> <div id="cmd" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Run command</h5> <h5 class="modal-title">Run command</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="form-group">
<label for="cmd-cmd">Command: </label> <label for="cmd-cmd">Command: </label>
<input name="cmd" class="form-control" type="text" id="cmd-cmd" placeholder="g++ sort.c" required /> <input name="cmd" class="form-control" type="text" id="cmd-cmd" placeholder="g++ sort.c" required />
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button> <button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Run</button> <button type="submit" class="btn btn-primary">Run</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,25 +1,25 @@
<form action="@delete" method="post"> <form action="@delete" method="post">
<div id="delete" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true"> <div id="delete" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Are you sure?</h5> <h5 class="modal-title">Are you sure?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>You are deleting the following files: </p> <p>You are deleting the following files: </p>
<ul class="list-group multi-files"> <ul class="list-group multi-files">
</ul> </ul>
<input type="hidden" name="files" value="" class="multi-files-value" /> <input type="hidden" name="files" value="" class="multi-files-value" />
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="reset" class="btn btn-primary" data-dismiss="modal">Cancel</button> <button type="reset" class="btn btn-primary" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger">Delete</button> <button type="submit" class="btn btn-danger">Delete</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,25 +1,25 @@
<form action="@download" method="get"> <form action="@download" method="get">
<div id="download" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true"> <div id="download" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Download file archive?</h5> <h5 class="modal-title">Download file archive?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Your archive will contain the following files: </p> <p>Your archive will contain the following files: </p>
<ul class="list-group multi-files"> <ul class="list-group multi-files">
</ul> </ul>
<input type="hidden" name="files" value="" class="multi-files-value" /> <input type="hidden" name="files" value="" class="multi-files-value" />
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button> <button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Download</button> <button type="submit" class="btn btn-primary">Download</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,24 +1,24 @@
<form action="@mkdir" method="post"> <form action="@mkdir" method="post">
<div id="mkdir" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true"> <div id="mkdir" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Create a folder</h5> <h5 class="modal-title">Create a folder</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="form-group">
<label for="mkdir-folder">Folder name: </label> <label for="mkdir-folder">Folder name: </label>
<input name="folder" class="form-control" type="text" id="mkdir-folder" placeholder="folder" required /> <input name="folder" class="form-control" type="text" id="mkdir-folder" placeholder="folder" required />
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button> <button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Create</button> <button type="submit" class="btn btn-primary">Create</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,34 +1,34 @@
<form action="@upload" method="post" enctype="multipart/form-data"> <form action="@upload" method="post" enctype="multipart/form-data">
<div id="upload" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true"> <div id="upload" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Upload a file</h5> <h5 class="modal-title">Upload a file</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="form-group">
<div class="custom-file"> <div class="custom-file">
<input name="file" type="file" id="upload-file" class="custom-file-input"> <input name="file" type="file" id="upload-file" class="custom-file-input">
<label class="custom-file-label" for="upload-file">Choose file</label> <label class="custom-file-label" for="upload-file">Choose file</label>
</div> </div>
</div> </div>
<div class="form-group upload-unhide"> <div class="form-group upload-unhide">
<label for="upload-file-size">Filesize: </label> <label for="upload-file-size">Filesize: </label>
<input class="form-control" type="text" disabled id="upload-file-size" placeholder="0B" /> <input class="form-control" type="text" disabled id="upload-file-size" placeholder="0B" />
</div> </div>
<div class="form-group upload-unhide"> <div class="form-group upload-unhide">
<label for="upload-file-saveas">Save as: </label> <label for="upload-file-saveas">Save as: </label>
<input name="saveas" class="form-control" type="text" id="upload-file-saveas" placeholder="filename" /> <input name="saveas" class="form-control" type="text" id="upload-file-saveas" placeholder="filename" />
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button> <button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Upload</button> <button type="submit" class="btn btn-primary">Upload</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,5 +1,5 @@
<nav class="navbar navbar-dark bg-primary fixed-top navbar-expand-sm"> <nav class="navbar navbar-dark bg-primary fixed-top navbar-expand-sm">
<a class="navbar-brand" href="/">File Manager</a> <a class="navbar-brand" href="/">File Manager</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>