1
0
Fork 0

Upgrade to Bootstrap 5, and improve UI

pull/17/head
Ambrose Chua 2021-05-07 20:57:15 +08:00
parent 38de494408
commit 36cdb9aa8b
26 changed files with 509 additions and 463 deletions

View File

@ -1,7 +1,7 @@
.cmd { .cmd {
word-wrap: break-word; word-wrap: break-word;
} }
pre { pre {
background-color: #eee; background-color: #eee;
min-height: 1.5em; min-height: 1.5em;
} }

View File

@ -1,6 +0,0 @@
body {
font-family: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans",
"Droid Sans", "Helvetica Neue", sans-serif;
}

View File

@ -1,4 +1,23 @@
.name { .name {
overflow: hidden; overflow: hidden;
word-wrap: break-word; word-wrap: break-word;
}
.badge-alignment {
margin-top: 0.25em;
}
.stretched-invisible-label {
display: block;
}
.stretched-invisible-label > * {
position: relative;
z-index: 1;
}
.stretched-invisible-label::after {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
} }

View File

@ -1,5 +0,0 @@
/* jshint esversion: 6 */
$(document).ready(() => {
$("[title]").tooltip();
});

View File

@ -8,12 +8,12 @@
} }
.input-group-digits input[type=number] { .input-group-digits input[type=number] {
-moz-appearance: textfield; -moz-appearance: textfield;
} }
.input-group-digits input[type=number]::-webkit-inner-spin-button, .input-group-digits input[type=number]::-webkit-inner-spin-button,
.input-group-digits input[type=number]::-webkit-outer-spin-button { .input-group-digits input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
margin: 0; margin: 0;
} }
.login { .login {

View File

@ -1,53 +1,51 @@
/* jshint esversion: 6 */ /* jshint esversion: 6 */
$(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,51 @@
/* jshint esversion: 6 */ /* jshint esversion: 6 */
$(document).ready(() => { function htmlEscape(text) {
let $select = $(".multi-select"); const p = document.createElement('p');
p.innerText = text;
return p.innerHTML;
}
let setSelected = (files) => { let $select = $(".multi-select");
$(".multi-files-value").val(JSON.stringify(files.map(f => f.name)));
$(".multi-files").html(
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>`;
}).join("")
);
};
let updateSelected = () => { let setSelected = (files) => {
let $selected = $(".multi-select:checked"); $(".multi-files-value").val(JSON.stringify(files.map(f => f.name)));
let files = []; if (files.length == 0) {
$selected.each((i, ele) => { $(".multi-files").html(`<li class="list-group-item text-muted">No files selected</li>`);
files.push({ return
name: $(ele).data("select"), }
size: $(ele).data("select-size") $(".multi-files").html(
}); files.map(f => {
}); return `
<li class="list-group-item d-flex align-items-start justify-content-between">
<span class="name">${htmlEscape(f.name)}</span>
${f.type == "directory" ? `` : `<span class="badge rounded-pill bg-secondary badge-alignment">${filesize(f.size)}</span>`}
</li>
`;
}).join("")
);
const hasDirectory = files.reduce((a, f) => a || f.type == "directory", false);
const totalSize = files.map(f => f.size).reduce((a, b) => a + b);
if (hasDirectory) {
$(".multi-files-total").text("Unknown");
} else {
$(".multi-files-total").text(filesize(totalSize));
}
};
setSelected(files); const updateSelected = () => {
} let $selected = $(".multi-select:checked");
let files = [];
$selected.each((i, ele) => {
files.push({
name: $(ele).data("select"),
type: $(ele).data("select-type"),
size: $(ele).data("select-size")
});
});
$select.on("change", updateSelected); setSelected(files);
updateSelected(); }
});
$select.on("change", updateSelected);
updateSelected();

6
assets/navbar.css Normal file
View File

@ -0,0 +1,6 @@
#navbar {
overflow-x: auto;
}
.nav-link {
white-space: nowrap;
}

View File

@ -1,11 +1,9 @@
/* jshint esversion: 6 */ /* jshint esversion: 6 */
$(document).ready(() => { const $shell = $("#shell");
let $shell = $("#shell"); const $close = $("#shell-close");
if ($shell.length < 1) {
return; if ($shell.length > 0) {
}
let $close = $("#shell-close");
const ws = new WebSocket("ws" + (window.location.protocol === "https:" ? "s" : "") + "://" + window.location.host + "/websocket?path=" + encodeURIComponent($shell.data("path"))); const ws = new WebSocket("ws" + (window.location.protocol === "https:" ? "s" : "") + "://" + window.location.host + "/websocket?path=" + encodeURIComponent($shell.data("path")));
@ -54,4 +52,5 @@ $(document).ready(() => {
}); });
}); });
});
}

5
assets/tooltip.js Normal file
View File

@ -0,0 +1,5 @@
/* jshint esversion: 6 */
document.querySelectorAll("[title]").forEach(element => {
new bootstrap.Tooltip(element);
});

View File

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

View File

@ -34,6 +34,12 @@ app.engine("handlebars", hbs({
layoutsDir: path.join(__dirname, "views", "layouts"), layoutsDir: path.join(__dirname, "views", "layouts"),
defaultLayout: "main", defaultLayout: "main",
helpers: { helpers: {
either: (a, b, options) => {
if (a || b) {
return options.fn();
}
},
filesize: filesize,
octicon: (i, options) => { octicon: (i, options) => {
if (!octicons[i]) { if (!octicons[i]) {
return new handlebars.SafeString(octicons.question.toSVG()); return new handlebars.SafeString(octicons.question.toSVG());
@ -174,7 +180,7 @@ app.put("/*", (req, res) => {
res.redirect("back"); res.redirect("back");
}); });
save.on("error", (err) => { save.on("error", (err) => {
res.flash("error", err); res.flash("error", err.toString());
res.redirect("back"); res.redirect("back");
}); });
} }
@ -230,9 +236,13 @@ app.post("/*@upload", (req, res) => {
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"); const saveName = relative(res.filename, saveas);
let save = fs.createWriteStream(relative(res.filename, saveas)); console.log("saving file to " + saveName);
let save = fs.createWriteStream(saveName);
save.on("close", () => { save.on("close", () => {
if (res.headersSent) {
return;
}
if (buff.length === 0) { if (buff.length === 0) {
req.flash("success", "File saved. Warning: empty file."); req.flash("success", "File saved. Warning: empty file.");
} }
@ -243,7 +253,7 @@ app.post("/*@upload", (req, res) => {
res.redirect("back"); res.redirect("back");
}); });
save.on("error", (err) => { save.on("error", (err) => {
req.flash("error", err); req.flash("error", err.toString());
res.redirect("back"); res.redirect("back");
}); });
save.write(buff); save.write(buff);
@ -277,7 +287,7 @@ app.post("/*@mkdir", (req, res) => {
}).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.toString());
res.redirect("back"); res.redirect("back");
return; return;
} }
@ -341,7 +351,7 @@ app.post("/*@delete", (req, res) => {
res.redirect("back"); res.redirect("back");
}); });
}).catch((err) => { }).catch((err) => {
req.flash("error", err); req.flash("error", err.toString());
res.redirect("back"); res.redirect("back");
}); });
}); });
@ -394,7 +404,7 @@ app.get("/*@download", (req, res) => {
zip.finalize(); zip.finalize();
}).catch((err) => { }).catch((err) => {
console.log(err); console.log(err);
req.flash("error", err); req.flash("error", err.toString());
res.redirect("back"); res.redirect("back");
}); });
}); });
@ -515,7 +525,7 @@ app.get("/*", (req, res) => {
resolve({ resolve({
name: f, name: f,
isdirectory: stats.isDirectory(), isdirectory: stats.isDirectory(),
size: filesize(stats.size) size: stats.size
}); });
}); });
}); });
@ -551,6 +561,9 @@ app.get("/*", (req, res) => {
} }
else if (res.stats.isFile()) { else if (res.stats.isFile()) {
res.sendFile(relative(res.filename), { res.sendFile(relative(res.filename), {
headers: {
"Content-Security-Policy": "default-src 'self'; script-src 'none'; sandbox"
},
dotfiles: "allow" dotfiles: "allow"
}); });
} }

50
package-lock.json generated
View File

@ -10,7 +10,7 @@
"dependencies": { "dependencies": {
"archiver": "^5.3.0", "archiver": "^5.3.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"bootstrap": "^4.6.0", "bootstrap": "^5.0.0",
"connect-busboy": "^0.0.2", "connect-busboy": "^0.0.2",
"connect-flash": "^0.1.1", "connect-flash": "^0.1.1",
"express": "^4.17.1", "express": "^4.17.1",
@ -32,6 +32,16 @@
"file-manager": "index.js" "file-manager": "index.js"
} }
}, },
"node_modules/@popperjs/core": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz",
"integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "1.3.7", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@ -168,16 +178,15 @@
} }
}, },
"node_modules/bootstrap": { "node_modules/bootstrap": {
"version": "4.6.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.0.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==", "integrity": "sha512-tmhPET9B9qCl8dCofvHeiIhi49iBt0EehmIsziZib65k1erBW1rHhj2s/2JsuQh5Pq+xz2E9bEbzp9B7xHG+VA==",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/bootstrap" "url": "https://opencollective.com/bootstrap"
}, },
"peerDependencies": { "peerDependencies": {
"jquery": "1.9.1 - 3", "@popperjs/core": "^2.9.2"
"popper.js": "^1.16.1"
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
@ -914,17 +923,6 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
}, },
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/printj": { "node_modules/printj": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
@ -1295,6 +1293,12 @@
} }
}, },
"dependencies": { "dependencies": {
"@popperjs/core": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz",
"integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==",
"peer": true
},
"accepts": { "accepts": {
"version": "1.3.7", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@ -1407,9 +1411,9 @@
} }
}, },
"bootstrap": { "bootstrap": {
"version": "4.6.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.0.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==", "integrity": "sha512-tmhPET9B9qCl8dCofvHeiIhi49iBt0EehmIsziZib65k1erBW1rHhj2s/2JsuQh5Pq+xz2E9bEbzp9B7xHG+VA==",
"requires": {} "requires": {}
}, },
"brace-expansion": { "brace-expansion": {
@ -1976,12 +1980,6 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
}, },
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"peer": true
},
"printj": { "printj": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",

View File

@ -8,7 +8,7 @@
"dependencies": { "dependencies": {
"archiver": "^5.3.0", "archiver": "^5.3.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"bootstrap": "^4.6.0", "bootstrap": "^5.0.0",
"connect-busboy": "^0.0.2", "connect-busboy": "^0.0.2",
"connect-flash": "^0.1.1", "connect-flash": "^0.1.1",
"express": "^4.17.1", "express": "^4.17.1",

View File

@ -2,36 +2,36 @@
<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}}
<h4>command</h4> <h4>command</h4>
<h4 class="cmd"><code>{{cmd}}</code></h4> <h4 class="cmd"><code>{{cmd}}</code></h4>
<h4>stdout</h4> <h4>stdout</h4>
<pre><code>{{stdout}}</code></pre> <pre><code>{{stdout}}</code></pre>
<h4>stderr</h4> <h4>stderr</h4>
<pre><code>{{stderr}}</code></pre> <pre><code>{{stderr}}</code></pre>
</main> </main>
</div> </div>
<nav class="navbar navbar-light bg-light fixed-bottom p-1 px-2"> <nav class="navbar navbar-light bg-light fixed-bottom p-1 px-2">
<div class="btn-group m-1" role="group"> <div class="btn-group m-1" role="group">
<a class="btn btn-primary" href="/{{path}}" data-placement="top" title="Return to folder"> <a class="btn btn-primary" href="/{{path}}" data-placement="top" title="Return to folder">
{{octicon "chevron-left"}} {{octicon "chevron-left"}}
<span class="d-none d-sm-inline">Back</span> <span class="d-none d-sm-inline">Back</span>
</a> </a>
<a class="btn btn-warning" href="@cmd" data-toggle="modal" data-target="#cmd" data-placement="top" title="Run another command"> <a class="btn btn-warning" href="@cmd" data-toggle="modal" data-target="#cmd" data-placement="top" title="Run another command">
{{octicon "terminal"}} {{octicon "terminal"}}
<span class="d-none d-sm-inline">Run command</span> <span class="d-none d-sm-inline">Run command</span>
</a> </a>
</div> </div>
</nav> </nav>
{{> dialogue-cmd}} {{> dialogue-cmd}}

View File

@ -1,38 +1,40 @@
<!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>
<!-- icons -->
<link rel="stylesheet" href="/@assets/octicons/build.css" /> <link rel="stylesheet" href="/@assets/octicons/build.css" />
<!-- bootstrap -->
<script src="/@assets/jquery/jquery.min.js"></script> <script src="/@assets/jquery/jquery.min.js" defer></script>
<link rel="stylesheet" href="/@assets/bootstrap/css/bootstrap.min.css" /> <link rel="stylesheet" href="/@assets/bootstrap/css/bootstrap.min.css" />
<script src="/@assets/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="/@assets/bootstrap/js/bootstrap.bundle.min.js" defer></script>
<link rel="stylesheet" href="/@assets/xterm/css/xterm.css" /> <!-- xterm -->
<script src="/@assets/xterm/lib/xterm.js"></script> <link rel="stylesheet" href="/@assets/xterm/css/xterm.css" />
<script src="/@assets/xterm-addon-attach/lib/xterm-addon-attach.js"></script> <script src="/@assets/xterm/lib/xterm.js" defer></script>
<script src="/@assets/xterm-addon-fit/lib/xterm-addon-fit.js"></script> <script src="/@assets/xterm-addon-attach/lib/xterm-addon-attach.js" defer></script>
<script src="/@assets/xterm-addon-fit/lib/xterm-addon-fit.js" defer></script>
<script src="/@assets/filesize/filesize.js"></script> <!-- file size math -->
<script src="/@assets/filesize/filesize.js" defer></script>
<link rel="stylesheet" href="/@assets/fonts.css" /> <!-- navbar, tooltip -->
<link rel="stylesheet" href="/@assets/navbar.css" />
<link rel="stylesheet" href="/@assets/list.css" /> <script src="/@assets/tooltip.js" defer></script>
<script src="/@assets/list.js"></script> <!-- list, multi-select -->
<script src="/@assets/multi.js"></script> <link rel="stylesheet" href="/@assets/list.css" />
<script src="/@assets/multi.js" defer></script>
<link rel="stylesheet" href="/@assets/upload.css" /> <!-- upload -->
<script src="/@assets/upload.js"></script> <link rel="stylesheet" href="/@assets/upload.css" />
<script src="/@assets/upload.js" defer></script>
<link rel="stylesheet" href="/@assets/login.css" /> <!-- login -->
<script src="/@assets/login.js"></script> <link rel="stylesheet" href="/@assets/login.css" />
<script src="/@assets/login.js" defer></script>
<link rel="stylesheet" href="/@assets/cmd.css" /> <!-- cmd -->
<script src="/@assets/shell.js"></script> <link rel="stylesheet" href="/@assets/cmd.css" />
<!-- shell -->
<script src="/@assets/shell.js" defer></script>
</head> </head>
<body> <body class="bg-light">
{{{body}}} {{{body}}}
</body> </body>
</html> </html>

View File

@ -2,37 +2,39 @@
<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"> <label for="check{{@index}}" class="stretched-invisible-label">
<input type="checkbox" class="custom-control-input multi-select" data-select="{{name}}" data-select-size="{{size}}" id="check{{@index}}"> <div class="form-check">
<label class="custom-control-label d-flex align-items-start justify-content-between" for="check{{@index}}"> <input type="checkbox" class="form-check-input multi-select" data-select="{{name}}" data-select-size="{{size}}" data-select-type="{{#if isdirectory}}directory{{else}}file{{/if}}" id="check{{@index}}">
{{#if isdirectory}} <span class="form-check-label d-flex align-items-start justify-content-between">
<a href="./{{name}}/" class="name">{{name}}/</a> {{#if isdirectory}}
{{else}} <a href="./{{name}}/" class="name">{{name}}/</a>
<a href="./{{name}}" class="name mr-auto">{{name}}</a> {{else}}
<span class="badge badge-pill badge-secondary">{{size}}</span> <a href="./{{name}}" class="name">{{name}}</a>
{{/if}} <span class="badge rounded-pill bg-secondary badge-alignment">{{filesize size}}</span>
</label> {{/if}}
</div> </span>
</li> </div>
{{else}} </label>
<li class="list-group-item"> </li>
No files {{else}}
</li> <li class="list-group-item">
{{/each}} No files
</ul> </li>
{{/each}}
</ul>
</main> </main>
</div> </div>

View File

@ -2,31 +2,31 @@
<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">
<div class="input-group input-group-lg input-group-digits"> <div class="input-group input-group-lg input-group-digits">
<input type="number" class="form-control" placeholder="&bull;" /> <input type="number" class="form-control" placeholder="&bull;" />
<input type="number" class="form-control" placeholder="&bull;" /> <input type="number" class="form-control" placeholder="&bull;" />
<input type="number" class="form-control" placeholder="&bull;" /> <input type="number" class="form-control" placeholder="&bull;" />
<input type="number" class="form-control" placeholder="&bull;" /> <input type="number" class="form-control" placeholder="&bull;" />
<input type="number" class="form-control" placeholder="&bull;" /> <input type="number" class="form-control" placeholder="&bull;" />
<input type="number" class="form-control" placeholder="&bull;" /> <input type="number" class="form-control" placeholder="&bull;" />
</div> </div>
<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,22 @@
<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-dialog-centered modal-sm">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header bg-light">
<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="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span aria-hidden="true">&times;</span> </div>
</button> <div class="modal-body">
</div> <div class="form-group">
<div class="modal-body"> <label for="cmd-cmd">Command: </label>
<div class="form-group"> <input name="cmd" class="form-control" type="text" id="cmd-cmd" placeholder="g++ sort.c" required />
<label for="cmd-cmd">Command: </label> </div>
<input name="cmd" class="form-control" type="text" id="cmd-cmd" placeholder="g++ sort.c" required /> </div>
</div> <div class="modal-footer bg-light">
</div> <button type="reset" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<div class="modal-footer"> <button type="submit" class="btn btn-primary">Run</button>
<button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button> </div>
<button type="submit" class="btn btn-primary">Run</button> </div>
</div> </div>
</div> </div>
</div>
</div>
</form> </form>

View File

@ -1,25 +1,23 @@
<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-dialog-centered modal-dialog-scrollable">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header bg-light">
<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="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span aria-hidden="true">&times;</span> </div>
</button> <div class="modal-body">
</div> <p>You are deleting the following files: </p>
<div class="modal-body"> <ul class="list-group multi-files">
<p>You are deleting the following files: </p>
<ul class="list-group multi-files"> </ul>
<input type="hidden" name="files" value="" class="multi-files-value" />
</ul> </div>
<input type="hidden" name="files" value="" class="multi-files-value" /> <div class="modal-footer bg-light">
</div> <button type="reset" class="btn btn-primary" data-bs-dismiss="modal">Cancel</button>
<div class="modal-footer"> <button type="submit" class="btn btn-danger">Delete</button>
<button type="reset" class="btn btn-primary" data-dismiss="modal">Cancel</button> </div>
<button type="submit" class="btn btn-danger">Delete</button> </div>
</div> </div>
</div> </div>
</div>
</div>
</form> </form>

View File

@ -1,25 +1,24 @@
<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-dialog-centered modal-dialog-scrollable">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header bg-light">
<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="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span aria-hidden="true">&times;</span> </div>
</button> <div class="modal-body">
</div> <p>Your archive will contain the following files: </p>
<div class="modal-body"> <ul class="list-group multi-files">
<p>Your archive will contain the following files: </p>
<ul class="list-group multi-files"> </ul>
<p class="mt-3 mb-0">Total size: <span class="multi-files-total">Unknown</span></p>
</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 bg-light">
<div class="modal-footer"> <button type="reset" class="btn btn-secondary" data-bs-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,22 @@
<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-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header bg-light">
<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="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span aria-hidden="true">&times;</span> </div>
</button> <div class="modal-body">
</div> <div>
<div class="modal-body"> <label class="form-label" for="mkdir-folder">Folder name</label>
<div class="form-group"> <input class="form-control" name="folder" type="text" id="mkdir-folder" placeholder="folder" required />
<label for="mkdir-folder">Folder name: </label> </div>
<input name="folder" class="form-control" type="text" id="mkdir-folder" placeholder="folder" required /> </div>
</div> <div class="modal-footer bg-light">
</div> <button type="reset" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<div class="modal-footer"> <button type="submit" class="btn btn-primary">Create</button>
<button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button> </div>
<button type="submit" class="btn btn-primary">Create</button> </div>
</div> </div>
</div> </div>
</div>
</div>
</form> </form>

View File

@ -1,34 +1,27 @@
<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-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header bg-light">
<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="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span aria-hidden="true">&times;</span> </div>
</button> <div class="modal-body">
</div> <input class="form-control" type="file" id="upload-file" name="file">
<div class="modal-body"> <div class="mt-3 upload-unhide">
<div class="form-group"> <label class="form-label" for="upload-file-size">Filesize</label>
<div class="custom-file"> <input class="form-control" type="text" disabled id="upload-file-size" placeholder="0B" />
<input name="file" type="file" id="upload-file" class="custom-file-input"> </div>
<label class="custom-file-label" for="upload-file">Choose file</label> <div class="mt-3 upload-unhide">
</div> <label class="form-label" for="upload-file-saveas">Save as</label>
</div> <input class="form-control" type="text" name="saveas" id="upload-file-saveas" placeholder="filename" />
<div class="form-group upload-unhide"> </div>
<label for="upload-file-size">Filesize: </label> </div>
<input class="form-control" type="text" disabled id="upload-file-size" placeholder="0B" /> <div class="modal-footer bg-light">
</div> <button type="reset" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<div class="form-group upload-unhide"> <button type="submit" class="btn btn-primary">Upload</button>
<label for="upload-file-saveas">Save as: </label> </div>
<input name="saveas" class="form-control" type="text" id="upload-file-saveas" placeholder="filename" /> </div>
</div> </div>
</div> </div>
<div class="modal-footer">
<button type="reset" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Upload</button>
</div>
</div>
</div>
</div>
</form> </form>

View File

@ -1,26 +1,28 @@
<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> <div class="container-fluid">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"> <a class="navbar-brand" href="/">File Manager</a>
<span class="navbar-toggler-icon"></span> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
</button> <span class="navbar-toggler-icon"></span>
<div class="collapse navbar-collapse" id="navbar"> </button>
<div class="navbar-nav mr-auto"> <div class="collapse navbar-collapse" id="navbar">
{{#eachpath path}} <div class="navbar-nav mr-auto">
<a class="nav-item nav-link{{#if current}} active{{/if}}" href="{{path}}">{{name}}</a> {{#eachpath path}}
{{/eachpath}} <a class="nav-item nav-link{{#if current}} active{{/if}}" href="{{path}}">{{name}}</a>
</div> {{/eachpath}}
<div class="navbar-nav"> </div>
{{#if isloginenabled}} <div class="navbar-nav">
{{#if isloggingin}} {{#if isloginenabled}}
<a class="nav-item nav-link" href="/@login" title="Sign in"> {{#if isloggingin}}
<span class="octicon octicon-sign-in"></span> <a class="nav-item nav-link" href="/@login" title="Sign in">
</a> <span class="octicon octicon-sign-in"></span>
{{else}} </a>
<a class="nav-item nav-link" href="/@logout" title="Sign out"> {{else}}
<span class="octicon octicon-sign-out"></span> <a class="nav-item nav-link" href="/@logout" title="Sign out">
</a> <span class="octicon octicon-sign-out"></span>
{{/if}} </a>
{{/if}} {{/if}}
</div> {{/if}}
</div> </div>
</div>
</div>
</nav> </nav>

View File

@ -1,44 +1,52 @@
<nav class="navbar navbar-light bg-light fixed-bottom p-1 px-2"> <nav class="navbar navbar-light bg-light fixed-bottom">
<div class="btn-group m-1" role="group"> <div class="container-fluid px-2">
<a class="btn btn-primary" href="@upload" data-toggle="modal" data-target="#upload" data-placement="top" title="Upload"> <div class="d-flex">
{{octicon "cloud-upload"}} <div class="btn-group me-1" role="group">
<span class="d-none d-sm-inline">Upload</span> <a class="btn btn-primary" href="@upload" data-bs-toggle="modal" data-bs-target="#upload" data-placement="top" title="Upload">
</a> {{octicon "cloud-upload"}}
<a class="btn btn-secondary" href="@mkdir" data-toggle="modal" data-target="#mkdir" data-placement="top" title="New folder"> <span class="d-none d-sm-inline">Upload</span>
{{octicon "file-directory"}} </a>
<span class="d-none d-md-inline">New folder</span> <a class="btn btn-secondary" href="@mkdir" data-bs-toggle="modal" data-bs-target="#mkdir" data-placement="top" title="New folder">
</a> {{octicon "file-directory"}}
</div> <span class="d-none d-md-inline">New folder</span>
<div class="btn-group m-1" role="group"> </a>
{{#if cmdable}} </div>
<a class="btn btn-info" href="@cmd" data-toggle="modal" data-target="#cmd" data-placement="top" title="Run command"> {{#either cmdable shellable}}
{{octicon "terminal"}} <div class="btn-group me-1" role="group">
<span class="d-none d-lg-inline">Run command</span> {{#if cmdable}}
</a> <a class="btn btn-info" href="@cmd" data-bs-toggle="modal" data-bs-target="#cmd" data-placement="top" title="Run command">
{{/if}} {{octicon "terminal"}}
{{#if shellable}} <span class="d-none d-lg-inline">Run command</span>
<a class="btn btn-warning" href="@shell" data-placement="top" title="Open shell"> </a>
{{octicon "terminal"}} {{/if}}
<span class="d-none d-md-inline">Open shell</span> {{#if shellable}}
</a> <a class="btn btn-warning" href="@shell" data-placement="top" title="Open shell">
{{/if}} {{octicon "terminal"}}
</div> <span class="d-none d-md-inline">Open shell</span>
<div class="btn-group m-1" role="group"> </a>
<a class="btn btn-success" href="@download" data-toggle="modal" data-target="#download" data-placement="top" title="Download files as zip"> {{/if}}
{{octicon "file-zip"}} </div>
<span class="d-none d-md-inline">Download</span> {{/either}}
</a> <div class="btn-group me-1" role="group">
</div> <a class="btn btn-success" href="@download" data-bs-toggle="modal" data-bs-target="#download" data-placement="top" title="Download files as zip">
<div class="btn-group m-1" role="group"> {{octicon "file-zip"}}
<a class="btn btn-danger" href="@delete" data-toggle="modal" data-target="#delete" data-placement="top" title="Delete files"> <span class="d-none d-md-inline">Download</span>
{{octicon "trashcan"}} </a>
<span class="d-none d-md-inline">Delete</span> </div>
</a> <div class="btn-group me-1" role="group">
</div> <a class="btn btn-danger" href="@delete" data-bs-toggle="modal" data-bs-target="#delete" data-placement="top" title="Delete files">
<div class="btn-group m-1 ml-auto" role="group"> {{octicon "trashcan"}}
<a class="btn btn-warning" href="./" data-placement="top" title="Refresh list"> <span class="d-none d-md-inline">Delete</span>
{{octicon "sync"}} </a>
<span class="d-none d-lg-inline">Refresh</span> </div>
</a> </div>
</div> <div class="d-flex">
<div class="btn-group ml-1" role="group">
<a class="btn btn-warning" href="./" data-placement="top" title="Refresh list">
{{octicon "sync"}}
<span class="d-none d-lg-inline">Refresh</span>
</a>
</div>
</div>
</div>
</nav> </nav>

View File

@ -1,18 +1,18 @@
{{> navbar}} {{> navbar}}
<div style="padding-top: 56px; padding-bottom: 56px; height: 100vh"> <div style="padding-top: 56px; padding-bottom: 56px; height: 100vh">
<main id="shell" style="height: 100%; background: #000" data-path="/{{path}}"></main> <main id="shell" style="height: 100%; background: #000" data-path="/{{path}}"></main>
</div> </div>
<nav class="navbar navbar-light bg-light fixed-bottom p-1 px-2"> <nav class="navbar navbar-light bg-light fixed-bottom p-1 px-2">
<div class="btn-group m-1" role="group"> <div class="btn-group m-1" role="group">
<a class="btn btn-danger" href="/{{path}}" id="shell-close" data-placement="top" title="Close and return to folder"> <a class="btn btn-danger" href="/{{path}}" id="shell-close" data-placement="top" title="Close and return to folder">
{{octicon "chevron-left"}} {{octicon "chevron-left"}}
<span class="d-none d-sm-inline">Close shell</span> <span class="d-none d-sm-inline">Close shell</span>
</a> </a>
<a class="btn btn-warning" href="@shell" target="_blank" data-placement="top" title="Open shell in new tab"> <a class="btn btn-warning" href="@shell" target="_blank" data-placement="top" title="Open shell in new tab">
{{octicon "terminal"}} {{octicon "terminal"}}
<span class="d-none d-sm-inline">New shell</span> <span class="d-none d-sm-inline">New shell</span>
</a> </a>
</div> </div>
</nav> </nav>