1
0
Fork 0

UI improvements

pull/17/head
Ambrose Chua 2021-05-08 01:54:37 +08:00
parent a265519ffb
commit c91e94d0a2
10 changed files with 63 additions and 73 deletions

View File

@ -58,6 +58,10 @@ file-manager
The following environmental variables can be used to configure `file-manager`. The following environmental variables can be used to configure `file-manager`.
### SESSION_KEY=
Express session key, generate something random.
### SHELL= ### SHELL=
Enable the shell feature, which allows users to start a login shell (when set to `login`) or the binary specified by this option (example: `/bin/bash`). Be careful when enabling this feature as anyone with access to this portal can execute any command on your machine. Enable the shell feature, which allows users to start a login shell (when set to `login`) or the binary specified by this option (example: `/bin/bash`). Be careful when enabling this feature as anyone with access to this portal can execute any command on your machine.

View File

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

View File

@ -77,7 +77,7 @@ app.use("/@assets/xterm-addon-attach", express.static(path.join(__dirname, "node
app.use("/@assets/xterm-addon-fit", express.static(path.join(__dirname, "node_modules/xterm-addon-fit"))); app.use("/@assets/xterm-addon-fit", express.static(path.join(__dirname, "node_modules/xterm-addon-fit")));
app.use(session({ app.use(session({
secret: "meowmeow" secret: process.env.SESSION_KEY || "meowmeow"
})); }));
app.use(flash()); app.use(flash());
app.use(busboy()); app.use(busboy());
@ -103,7 +103,6 @@ app.get("/@login", (req, res) => {
}); });
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(" ", ""));
if (pass) { if (pass) {
req.session.login = true; req.session.login = true;
res.redirect("/"); res.redirect("/");
@ -168,34 +167,6 @@ app.all("/*", (req, res, next) => {
}); });
}); });
// currently unused
app.put("/*", (req, res) => {
if (res.stats.error) {
req.busboy.on("file", (key, file, filename) => {
if (key == "file") {
let save = fs.createWriteStream(relative(res.filename));
file.pipe(save);
save.on("close", () => {
res.flash("success", "File saved. ");
res.redirect("back");
});
save.on("error", (err) => {
res.flash("error", err.toString());
res.redirect("back");
});
}
});
req.busboy.on("field", (key, value) => {
});
req.pipe(req.busboy);
}
else {
req.flash("error", "File exists, cannot overwrite. ");
res.redirect("back");
}
});
app.post("/*@upload", (req, res) => { app.post("/*@upload", (req, res) => {
res.filename = req.params[0]; res.filename = req.params[0];
@ -233,6 +204,7 @@ app.post("/*@upload", (req, res) => {
}); });
fileExists.then((stats) => { fileExists.then((stats) => {
console.warn("file exists, cannot overwrite");
req.flash("error", "File exists, cannot overwrite. "); req.flash("error", "File exists, cannot overwrite. ");
res.redirect("back"); res.redirect("back");
}).catch((err) => { }).catch((err) => {
@ -253,6 +225,7 @@ app.post("/*@upload", (req, res) => {
res.redirect("back"); res.redirect("back");
}); });
save.on("error", (err) => { save.on("error", (err) => {
console.warn(err);
req.flash("error", err.toString()); req.flash("error", err.toString());
res.redirect("back"); res.redirect("back");
}); });
@ -287,6 +260,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) {
console.warn(err);
req.flash("error", err.toString()); req.flash("error", err.toString());
res.redirect("back"); res.redirect("back");
return; return;
@ -347,10 +321,12 @@ app.post("/*@delete", (req, res) => {
req.flash("success", "Files deleted. "); req.flash("success", "Files deleted. ");
res.redirect("back"); res.redirect("back");
}).catch((err) => { }).catch((err) => {
console.warn(err);
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) => {
console.warn(err);
req.flash("error", err.toString()); req.flash("error", err.toString());
res.redirect("back"); res.redirect("back");
}); });
@ -386,6 +362,7 @@ app.get("/*@download", (req, res) => {
Promise.all(promises).then((files) => { Promise.all(promises).then((files) => {
let zip = archiver("zip", {}); let zip = archiver("zip", {});
zip.on("error", function(err) { zip.on("error", function(err) {
console.warn(err);
res.status(500).send({ res.status(500).send({
error: err.message error: err.message
}); });
@ -403,7 +380,7 @@ app.get("/*@download", (req, res) => {
zip.finalize(); zip.finalize();
}).catch((err) => { }).catch((err) => {
console.log(err); console.warn(err);
req.flash("error", err.toString()); req.flash("error", err.toString());
res.redirect("back"); res.redirect("back");
}); });
@ -425,12 +402,14 @@ if (shellable || cmdable) {
if (!cmd || cmd.length < 1) { if (!cmd || cmd.length < 1) {
return res.status(400).end(); return res.status(400).end();
} }
console.log("running command " + cmd);
child_process.exec(cmd, { child_process.exec(cmd, {
cwd: relative(res.filename), cwd: relative(res.filename),
timeout: 60 * 1000, timeout: 60 * 1000,
}, (err, stdout, stderr) => { }, (err, stdout, stderr) => {
if (err) { if (err) {
console.log("command run failed: " + JSON.stringify(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, {
@ -455,7 +434,6 @@ if (shellable || cmdable) {
const ws = new WebSocket.Server({ server: http }); const ws = new WebSocket.Server({ server: http });
ws.on("connection", (socket, request) => { ws.on("connection", (socket, request) => {
console.log(request.url);
const { path } = querystring.parse(request.url.split("?")[1]); const { path } = querystring.parse(request.url.split("?")[1]);
let cwd = relative(path); let cwd = relative(path);
let term = pty.spawn(exec, args, { let term = pty.spawn(exec, args, {
@ -529,6 +507,7 @@ app.get("/*", (req, res) => {
const promises = filenames.map(f => new Promise((resolve, reject) => { const promises = filenames.map(f => new Promise((resolve, reject) => {
fs.stat(relative(res.filename, f), (err, stats) => { fs.stat(relative(res.filename, f), (err, stats) => {
if (err) { if (err) {
console.warn(err);
return resolve({ return resolve({
name: f, name: f,
error: err error: err
@ -551,6 +530,7 @@ app.get("/*", (req, res) => {
files: files, files: files,
})); }));
}).catch((err) => { }).catch((err) => {
console.error(err);
res.render("list", flashify(req, { res.render("list", flashify(req, {
shellable: shellable, shellable: shellable,
cmdable: cmdable, cmdable: cmdable,
@ -561,6 +541,7 @@ app.get("/*", (req, res) => {
})); }));
}); });
}).catch((err) => { }).catch((err) => {
console.warn(err);
res.render("list", flashify(req, { res.render("list", flashify(req, {
shellable: shellable, shellable: shellable,
cmdable: cmdable, cmdable: cmdable,

View File

@ -21,16 +21,20 @@
</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">
<div class="btn-group m-1" role="group"> <div class="container-fluid px-2">
<a class="btn btn-primary" href="/{{path}}" data-placement="top" title="Return to folder"> <div class="d-flex">
{{octicon "chevron-left"}} <div class="btn-group me-2" role="group">
<span class="d-none d-sm-inline">Back</span> <a class="btn btn-secondary" href="/{{path}}">
</a> {{octicon "chevron-left"}}
<a class="btn btn-warning" href="@cmd" data-toggle="modal" data-target="#cmd" data-placement="top" title="Run another command"> <span class="d-none d-sm-inline">Back</span>
{{octicon "terminal"}} </a>
<span class="d-none d-sm-inline">Run command</span> <a class="btn btn-info" href="@cmd" data-bs-toggle="modal" data-bs-target="#cmd" title="Run another command" data-bs-placement="top">
</a> {{octicon "terminal"}}
<span class="d-none d-sm-inline">Run command</span>
</a>
</div>
</div>
</div> </div>
</nav> </nav>

View File

@ -26,6 +26,7 @@
{{else}} {{else}}
{{#if error}} {{#if error}}
<a href="./{{name}}/" class="name" title="{{error}}">{{name}}/</a> <a href="./{{name}}/" class="name" title="{{error}}">{{name}}/</a>
<span class="badge rounded-pill bg-danger badge-alignment">err</span>
{{else}} {{else}}
<a href="./{{name}}" class="name">{{name}}</a> <a href="./{{name}}" class="name">{{name}}</a>
<span class="badge rounded-pill bg-secondary badge-alignment">{{filesize size}}</span> <span class="badge rounded-pill bg-secondary badge-alignment">{{filesize size}}</span>

View File

@ -7,10 +7,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <input class="form-control" name="cmd" type="text" id="cmd-cmd" placeholder="g++ sort.c" required />
<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>
<div class="modal-footer bg-light"> <div class="modal-footer bg-light">
<button type="reset" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="reset" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>

View File

@ -7,10 +7,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div> <input class="form-control" name="folder" type="text" id="mkdir-folder" placeholder="folder-name" required />
<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>
<div class="modal-footer bg-light"> <div class="modal-footer bg-light">
<button type="reset" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="reset" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>

View File

@ -5,7 +5,7 @@
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbar"> <div class="collapse navbar-collapse" id="navbar">
<div class="navbar-nav mr-auto"> <div class="navbar-nav me-auto">
{{#eachpath path}} {{#eachpath path}}
<a class="nav-item nav-link{{#if current}} active{{/if}}" href="{{path}}">{{name}}</a> <a class="nav-item nav-link{{#if current}} active{{/if}}" href="{{path}}">{{name}}</a>
{{/eachpath}} {{/eachpath}}
@ -13,12 +13,12 @@
<div class="navbar-nav"> <div class="navbar-nav">
{{#if isloginenabled}} {{#if isloginenabled}}
{{#if isloggingin}} {{#if isloggingin}}
<a class="nav-item nav-link" href="/@login" title="Sign in"> <a class="nav-item nav-link" href="/@login" title="Sign in" data-bs-placement="left">
<span class="octicon octicon-sign-in"></span> {{octicon "sign-in"}}
</a> </a>
{{else}} {{else}}
<a class="nav-item nav-link" href="/@logout" title="Sign out"> <a class="nav-item nav-link" href="/@logout" title="Sign out" data-bs-placement="left">
<span class="octicon octicon-sign-out"></span> {{octicon "sign-out"}}
</a> </a>
{{/if}} {{/if}}
{{/if}} {{/if}}

View File

@ -2,11 +2,11 @@
<div class="container-fluid px-2"> <div class="container-fluid px-2">
<div class="d-flex"> <div class="d-flex">
<div class="btn-group me-2" role="group"> <div class="btn-group me-2" role="group">
<a class="btn btn-primary" href="@upload" data-bs-toggle="modal" data-bs-target="#upload" data-placement="top" title="Upload"> <a class="btn btn-primary" href="@upload" data-bs-toggle="modal" data-bs-target="#upload" title="Upload a file" data-bs-placement="top">
{{octicon "cloud-upload"}} {{octicon "cloud-upload"}}
<span class="d-none d-sm-inline">Upload</span> <span class="d-none d-sm-inline">Upload</span>
</a> </a>
<a class="btn btn-secondary" href="@mkdir" data-bs-toggle="modal" data-bs-target="#mkdir" data-placement="top" title="New folder"> <a class="btn btn-secondary" href="@mkdir" data-bs-toggle="modal" data-bs-target="#mkdir" title="Create a new folder" data-bs-placement="top">
{{octicon "file-directory"}} {{octicon "file-directory"}}
<span class="d-none d-md-inline">New folder</span> <span class="d-none d-md-inline">New folder</span>
</a> </a>
@ -14,13 +14,13 @@
{{#either cmdable shellable}} {{#either cmdable shellable}}
<div class="btn-group me-2" role="group"> <div class="btn-group me-2" role="group">
{{#if cmdable}} {{#if cmdable}}
<a class="btn btn-info" href="@cmd" data-bs-toggle="modal" data-bs-target="#cmd" data-placement="top" title="Run command"> <a class="btn btn-info" href="@cmd" data-bs-toggle="modal" data-bs-target="#cmd" title="Run a command" data-bs-placement="top">
{{octicon "terminal"}} {{octicon "terminal"}}
<span class="d-none d-lg-inline">Run command</span> <span class="d-none d-lg-inline">Run command</span>
</a> </a>
{{/if}} {{/if}}
{{#if shellable}} {{#if shellable}}
<a class="btn btn-warning" href="@shell" data-placement="top" title="Open shell"> <a class="btn btn-warning" href="@shell" title="Open a new shell" data-bs-placement="top">
{{octicon "terminal"}} {{octicon "terminal"}}
<span class="d-none d-md-inline">Open shell</span> <span class="d-none d-md-inline">Open shell</span>
</a> </a>
@ -28,13 +28,13 @@
</div> </div>
{{/either}} {{/either}}
<div class="btn-group me-2" role="group"> <div class="btn-group me-2" role="group">
<a class="btn btn-success" href="@download" data-bs-toggle="modal" data-bs-target="#download" data-placement="top" title="Download file archive"> <a class="btn btn-success" href="@download" data-bs-toggle="modal" data-bs-target="#download" title="Download file archive" data-bs-placement="top">
{{octicon "file-zip"}} {{octicon "file-zip"}}
<span class="d-none d-md-inline">Download</span> <span class="d-none d-md-inline">Download</span>
</a> </a>
</div> </div>
<div class="btn-group me-2" role="group"> <div class="btn-group me-2" role="group">
<a class="btn btn-danger" href="@delete" data-bs-toggle="modal" data-bs-target="#delete" data-placement="top" title="Delete files"> <a class="btn btn-danger" href="@delete" data-bs-toggle="modal" data-bs-target="#delete" title="Delete selected files" data-bs-placement="top">
{{octicon "trashcan"}} {{octicon "trashcan"}}
<span class="d-none d-md-inline">Delete</span> <span class="d-none d-md-inline">Delete</span>
</a> </a>
@ -42,7 +42,7 @@
</div> </div>
<div class="d-flex"> <div class="d-flex">
<div class="btn-group ms-2" role="group"> <div class="btn-group ms-2" role="group">
<a class="btn btn-warning" href="./" data-placement="top" title="Refresh list"> <a class="btn btn-warning" href="./" title="Refresh list" data-bs-placement="top">
{{octicon "sync"}} {{octicon "sync"}}
<span class="d-none d-lg-inline">Refresh</span> <span class="d-none d-lg-inline">Refresh</span>
</a> </a>

View File

@ -4,15 +4,19 @@
<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">
<div class="btn-group m-1" role="group"> <div class="container-fluid px-2">
<a class="btn btn-danger" href="/{{path}}" id="shell-close" data-placement="top" title="Close and return to folder"> <div class="d-flex">
{{octicon "chevron-left"}} <div class="btn-group me-2" role="group">
<span class="d-none d-sm-inline">Close shell</span> <a class="btn btn-danger" href="/{{path}}" id="shell-close">
</a> {{octicon "chevron-left"}}
<a class="btn btn-warning" href="@shell" target="_blank" data-placement="top" title="Open shell in new tab"> <span class="d-none d-sm-inline">Exit shell</span>
{{octicon "terminal"}} </a>
<span class="d-none d-sm-inline">New shell</span> <a class="btn btn-warning" href="@shell" target="_blank" title="Open a new shell tab" data-bs-placement="top">
</a> {{octicon "terminal"}}
<span class="d-none d-sm-inline">New shell</span>
</a>
</div>
</div>
</div> </div>
</nav> </nav>