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 {
word-wrap: break-word;
word-wrap: break-word;
}
pre {
background-color: #eee;
min-height: 1.5em;
background-color: #eee;
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 {
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] {
-moz-appearance: textfield;
-moz-appearance: textfield;
}
.input-group-digits input[type=number]::-webkit-inner-spin-button,
.input-group-digits input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
-webkit-appearance: none;
margin: 0;
}
.login {

View File

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

View File

@ -1,30 +1,51 @@
/* jshint esversion: 6 */
$(document).ready(() => {
let $select = $(".multi-select");
function htmlEscape(text) {
const p = document.createElement('p');
p.innerText = text;
return p.innerHTML;
}
let setSelected = (files) => {
$(".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 $select = $(".multi-select");
let updateSelected = () => {
let $selected = $(".multi-select:checked");
let files = [];
$selected.each((i, ele) => {
files.push({
name: $(ele).data("select"),
size: $(ele).data("select-size")
});
});
let setSelected = (files) => {
$(".multi-files-value").val(JSON.stringify(files.map(f => f.name)));
if (files.length == 0) {
$(".multi-files").html(`<li class="list-group-item text-muted">No files selected</li>`);
return
}
$(".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);
updateSelected();
});
setSelected(files);
}
$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 */
$(document).ready(() => {
let $shell = $("#shell");
if ($shell.length < 1) {
return;
}
let $close = $("#shell-close");
const $shell = $("#shell");
const $close = $("#shell-close");
if ($shell.length > 0) {
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 */
$(document).ready(() => {
let $form = $("form[action='@upload']");
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);
const $form = $("form[action='@upload']");
const $file = $("#upload-file");
$form.find("#upload-file-size").val(filesize(file.size));
$form.find("[name=saveas]").val(file.name);
$(".upload-unhide").fadeIn();
});
$(".upload-unhide").fadeOut();
$form.on("submit", () => {
let putresource = $form.find("[name=saveas]").val();
// TODO: do XHR to PUT at putresource
});
$file.on("change", () => {
const file = $file[0].files[0];
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"),
defaultLayout: "main",
helpers: {
either: (a, b, options) => {
if (a || b) {
return options.fn();
}
},
filesize: filesize,
octicon: (i, options) => {
if (!octicons[i]) {
return new handlebars.SafeString(octicons.question.toSVG());
@ -174,7 +180,7 @@ app.put("/*", (req, res) => {
res.redirect("back");
});
save.on("error", (err) => {
res.flash("error", err);
res.flash("error", err.toString());
res.redirect("back");
});
}
@ -230,9 +236,13 @@ app.post("/*@upload", (req, res) => {
req.flash("error", "File exists, cannot overwrite. ");
res.redirect("back");
}).catch((err) => {
console.log("saving");
let save = fs.createWriteStream(relative(res.filename, saveas));
const saveName = relative(res.filename, saveas);
console.log("saving file to " + saveName);
let save = fs.createWriteStream(saveName);
save.on("close", () => {
if (res.headersSent) {
return;
}
if (buff.length === 0) {
req.flash("success", "File saved. Warning: empty file.");
}
@ -243,7 +253,7 @@ app.post("/*@upload", (req, res) => {
res.redirect("back");
});
save.on("error", (err) => {
req.flash("error", err);
req.flash("error", err.toString());
res.redirect("back");
});
save.write(buff);
@ -277,7 +287,7 @@ app.post("/*@mkdir", (req, res) => {
}).catch((err) => {
fs.mkdir(relative(res.filename, folder), (err) => {
if (err) {
req.flash("error", err);
req.flash("error", err.toString());
res.redirect("back");
return;
}
@ -341,7 +351,7 @@ app.post("/*@delete", (req, res) => {
res.redirect("back");
});
}).catch((err) => {
req.flash("error", err);
req.flash("error", err.toString());
res.redirect("back");
});
});
@ -394,7 +404,7 @@ app.get("/*@download", (req, res) => {
zip.finalize();
}).catch((err) => {
console.log(err);
req.flash("error", err);
req.flash("error", err.toString());
res.redirect("back");
});
});
@ -515,7 +525,7 @@ app.get("/*", (req, res) => {
resolve({
name: f,
isdirectory: stats.isDirectory(),
size: filesize(stats.size)
size: stats.size
});
});
});
@ -551,6 +561,9 @@ app.get("/*", (req, res) => {
}
else if (res.stats.isFile()) {
res.sendFile(relative(res.filename), {
headers: {
"Content-Security-Policy": "default-src 'self'; script-src 'none'; sandbox"
},
dotfiles: "allow"
});
}

50
package-lock.json generated
View File

@ -10,7 +10,7 @@
"dependencies": {
"archiver": "^5.3.0",
"body-parser": "^1.19.0",
"bootstrap": "^4.6.0",
"bootstrap": "^5.0.0",
"connect-busboy": "^0.0.2",
"connect-flash": "^0.1.1",
"express": "^4.17.1",
@ -32,6 +32,16 @@
"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": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@ -168,16 +178,15 @@
}
},
"node_modules/bootstrap": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.0.tgz",
"integrity": "sha512-tmhPET9B9qCl8dCofvHeiIhi49iBt0EehmIsziZib65k1erBW1rHhj2s/2JsuQh5Pq+xz2E9bEbzp9B7xHG+VA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
},
"peerDependencies": {
"jquery": "1.9.1 - 3",
"popper.js": "^1.16.1"
"@popperjs/core": "^2.9.2"
}
},
"node_modules/brace-expansion": {
@ -914,17 +923,6 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"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": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
@ -1295,6 +1293,12 @@
}
},
"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": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@ -1407,9 +1411,9 @@
}
},
"bootstrap": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.0.tgz",
"integrity": "sha512-tmhPET9B9qCl8dCofvHeiIhi49iBt0EehmIsziZib65k1erBW1rHhj2s/2JsuQh5Pq+xz2E9bEbzp9B7xHG+VA==",
"requires": {}
},
"brace-expansion": {
@ -1976,12 +1980,6 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"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": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",

View File

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

View File

@ -2,36 +2,36 @@
<div style="padding-top: 56px; padding-bottom: 56px;">
<main class="container my-4">
{{#each errors as |error|}}
<div class="alert alert-danger" role="alert">
{{error}}
</div>
{{/each}}
{{#each successes as |success|}}
<div class="alert alert-success" role="alert">
{{success}}
</div>
{{/each}}
<h4>command</h4>
<h4 class="cmd"><code>{{cmd}}</code></h4>
<h4>stdout</h4>
<pre><code>{{stdout}}</code></pre>
<h4>stderr</h4>
<pre><code>{{stderr}}</code></pre>
{{#each errors as |error|}}
<div class="alert alert-danger" role="alert">
{{error}}
</div>
{{/each}}
{{#each successes as |success|}}
<div class="alert alert-success" role="alert">
{{success}}
</div>
{{/each}}
<h4>command</h4>
<h4 class="cmd"><code>{{cmd}}</code></h4>
<h4>stdout</h4>
<pre><code>{{stdout}}</code></pre>
<h4>stderr</h4>
<pre><code>{{stderr}}</code></pre>
</main>
</div>
<nav class="navbar navbar-light bg-light fixed-bottom p-1 px-2">
<div class="btn-group m-1" role="group">
<a class="btn btn-primary" href="/{{path}}" data-placement="top" title="Return to folder">
{{octicon "chevron-left"}}
<span class="d-none d-sm-inline">Back</span>
</a>
<a class="btn btn-warning" href="@cmd" data-toggle="modal" data-target="#cmd" data-placement="top" title="Run another command">
{{octicon "terminal"}}
<span class="d-none d-sm-inline">Run command</span>
</a>
</div>
<div class="btn-group m-1" role="group">
<a class="btn btn-primary" href="/{{path}}" data-placement="top" title="Return to folder">
{{octicon "chevron-left"}}
<span class="d-none d-sm-inline">Back</span>
</a>
<a class="btn btn-warning" href="@cmd" data-toggle="modal" data-target="#cmd" data-placement="top" title="Run another command">
{{octicon "terminal"}}
<span class="d-none d-sm-inline">Run command</span>
</a>
</div>
</nav>
{{> dialogue-cmd}}

View File

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

View File

@ -2,37 +2,39 @@
<div style="padding-top: 56px; padding-bottom: 56px;">
<main class="container my-4">
{{#each errors as |error|}}
<div class="alert alert-danger" role="alert">
{{error}}
</div>
{{/each}}
{{#each successes as |success|}}
<div class="alert alert-success" role="alert">
{{success}}
</div>
{{/each}}
<ul class="list-group">
{{#each files}}
<li class="list-group-item">
<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}}">
<label class="custom-control-label d-flex align-items-start justify-content-between" for="check{{@index}}">
{{#if isdirectory}}
<a href="./{{name}}/" class="name">{{name}}/</a>
{{else}}
<a href="./{{name}}" class="name mr-auto">{{name}}</a>
<span class="badge badge-pill badge-secondary">{{size}}</span>
{{/if}}
</label>
</div>
</li>
{{else}}
<li class="list-group-item">
No files
</li>
{{/each}}
</ul>
{{#each errors as |error|}}
<div class="alert alert-danger" role="alert">
{{error}}
</div>
{{/each}}
{{#each successes as |success|}}
<div class="alert alert-success" role="alert">
{{success}}
</div>
{{/each}}
<ul class="list-group">
{{#each files}}
<li class="list-group-item">
<label for="check{{@index}}" class="stretched-invisible-label">
<div class="form-check">
<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}}">
<span class="form-check-label d-flex align-items-start justify-content-between">
{{#if isdirectory}}
<a href="./{{name}}/" class="name">{{name}}/</a>
{{else}}
<a href="./{{name}}" class="name">{{name}}</a>
<span class="badge rounded-pill bg-secondary badge-alignment">{{filesize size}}</span>
{{/if}}
</span>
</div>
</label>
</li>
{{else}}
<li class="list-group-item">
No files
</li>
{{/each}}
</ul>
</main>
</div>

View File

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

View File

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

View File

@ -1,25 +1,23 @@
<form action="@delete" method="post">
<div id="delete" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Are you sure?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<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" />
</div>
<div class="modal-footer">
<button type="reset" class="btn btn-primary" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<div id="delete" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header bg-light">
<h5 class="modal-title">Are you sure?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<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" />
</div>
<div class="modal-footer bg-light">
<button type="reset" class="btn btn-primary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger">Delete</button>
</div>
</div>
</div>
</div>
</form>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,18 @@
{{> navbar}}
<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>
<nav class="navbar navbar-light bg-light fixed-bottom p-1 px-2">
<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">
{{octicon "chevron-left"}}
<span class="d-none d-sm-inline">Close shell</span>
</a>
<a class="btn btn-warning" href="@shell" target="_blank" data-placement="top" title="Open shell in new tab">
{{octicon "terminal"}}
<span class="d-none d-sm-inline">New shell</span>
</a>
</div>
<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">
{{octicon "chevron-left"}}
<span class="d-none d-sm-inline">Close shell</span>
</a>
<a class="btn btn-warning" href="@shell" target="_blank" data-placement="top" title="Open shell in new tab">
{{octicon "terminal"}}
<span class="d-none d-sm-inline">New shell</span>
</a>
</div>
</nav>