fix some issues with shares mentioned in #84;

* crash when root volume is unmapped
* rephrase login-page for shares
* add chrome support (lol)
* fix confusing helptext
* improve ux
  * placeholders in share creator
  * button to disable expiration in share creator
  * human-readable timestamps in share listing
This commit is contained in:
ed 2024-08-19 21:38:47 +00:00
parent fca70b3508
commit 7ff46966da
11 changed files with 99 additions and 40 deletions

View file

@ -757,7 +757,7 @@ this feature was made with [identity providers](#identity-providers) in mind --
when creating a share, the creator can choose any of the following options:
* password-protection
* expire after a certain time
* expire after a certain time; `0` or blank means infinite
* allow visitors to upload (if the user who creates the share has write-access)
semi-intentional limitations:
@ -768,7 +768,10 @@ semi-intentional limitations:
* when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit
* browsers wouldn't be able to resume a broken download unless the requester's IP gets allowlisted for X minutes (ref. tricky)
the links are created inside a specific toplevel folder which must be specified with server-config `--shr`, for example `--shr /share/` (this also enables the feature)
specify `--shr /foobar` to enable this feature; a toplevel virtual folder named `foobar` is then created, and that's where all the shares will be served from
* you can name it whatever, `foobar` is just an example
* if you're using config files, put `shr: /foobar` inside the `[global]` section instead
users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server

View file

@ -975,8 +975,8 @@ def add_fs(ap):
def add_share(ap):
db_path = os.path.join(E.cfg, "shares.db")
ap2 = ap.add_argument_group('share-url options')
ap2.add_argument("--shr", metavar="URL", default="", help="base url for shared files, for example [\033[32m/share\033[0m] (must be a toplevel subfolder)")
ap2.add_argument("--shr-db", metavar="PATH", default=db_path, help="database to store shares in")
ap2.add_argument("--shr", metavar="DIR", default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]")
ap2.add_argument("--shr-db", metavar="FILE", default=db_path, help="database to store shares in")
ap2.add_argument("--shr-adm", metavar="U,U", default="", help="comma-separated list of users allowed to view/delete any share")
ap2.add_argument("--shr-v", action="store_true", help="debug")

View file

@ -1508,7 +1508,6 @@ class AuthSrv(object):
import sqlite3
shv = VFS(self.log_func, "", shr, AXS(), {"d2d": True})
par = vfs.all_vols[""]
db_path = self.args.shr_db
db = sqlite3.connect(db_path)
@ -1539,7 +1538,7 @@ class AuthSrv(object):
# don't know the abspath yet + wanna ensure the user
# still has the privs they granted, so nullmap it
shv.nodes[s_k] = VFS(
self.log_func, "", "%s/%s" % (shr, s_k), s_axs, par.flags.copy()
self.log_func, "", "%s/%s" % (shr, s_k), s_axs, shv.flags.copy()
)
vfs.nodes[shr] = vfs.all_vols[shr] = shv

View file

@ -3958,6 +3958,7 @@ class HttpCli(object):
rvol=rvol,
wvol=wvol,
avol=avol,
in_shr=self.args.shr and self.vpath.startswith(self.args.shr[1:]),
vstate=vstate,
scanning=vs["scanning"],
hashq=vs["hashq"],
@ -4006,10 +4007,10 @@ class HttpCli(object):
def tx_404(self, is_403: bool = False) -> bool:
rc = 404
if self.args.vague_403:
t = '<h1 id="n">404 not found &nbsp;┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try logging in or <a href="{}/?h">go home</a></p>'
pt = "404 not found ┐( ´ -`)┌ (or maybe you don't have access -- try logging in)"
t = '<h1 id="n">404 not found &nbsp;┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try a password or <a href="{}/?h">go home</a></p>'
pt = "404 not found ┐( ´ -`)┌ (or maybe you don't have access -- try a password)"
elif is_403:
t = '<h1 id="p">403 forbiddena &nbsp;~┻━┻</h1><p id="q">you\'ll have to log in or <a href="{}/?h">go home</a></p>'
t = '<h1 id="p">403 forbiddena &nbsp;~┻━┻</h1><p id="q">use a password or <a href="{}/?h">go home</a></p>'
pt = "403 forbiddena ~┻━┻ (you'll have to log in)"
rc = 403
else:
@ -4026,7 +4027,8 @@ class HttpCli(object):
t = t.format(self.args.SR)
qv = quotep(self.vpaths) + self.ourlq()
html = self.j2s("splash", this=self, qvpath=qv, msg=t)
in_shr = self.args.shr and self.vpath.startswith(self.args.shr[1:])
html = self.j2s("splash", this=self, qvpath=qv, in_shr=in_shr, msg=t)
self.reply(html.encode("utf-8"), status=rc)
return True
@ -4382,7 +4384,8 @@ class HttpCli(object):
pw = req.get("pw") or ""
now = int(time.time())
sexp = req["exp"]
exp = now + int(sexp) * 60 if sexp else 0
exp = int(sexp) if sexp else 0
exp = now + exp * 60 if exp else 0
pr = "".join(zc for zc, zb in zip("rwmd", (s_rd, s_wr, s_mv, s_del)) if zb)
q = "insert into sh values (?,?,?,?,?,?,?,?)"

View file

@ -376,7 +376,13 @@ class SvcHub(object):
import sqlite3
al.shr = "/%s/" % (al.shr.strip("/"))
al.shr = al.shr.strip("/")
if "/" in al.shr:
t = "config error: --shr must be the name of a virtual toplevel directory to put shares inside"
self.log("root", t, 1)
raise Exception(t)
al.shr = "/%s/" % (al.shr,)
create = True
db_path = self.args.shr_db

View file

@ -3820,13 +3820,14 @@ var fileman = (function () {
'<button id="sh_rand">🎲 random</button>',
'<button id="sh_apply">✅ create share</button>',
'</td></tr>',
'<tr><td>name</td><td><input type="text" id="sh_k" ' + NOAC + ' tt="name your link" /></td></tr>',
'<tr><td>name</td><td><input type="text" id="sh_k" ' + NOAC + ' placeholder="optional link name; will be random if blank" /></td></tr>',
'<tr><td>source</td><td><input type="text" id="sh_vp" ' + NOAC + ' readonly tt="the file or folder to share" /></td></tr>',
'<tr><td>passwd</td><td><input type="text" id="sh_pw" ' + NOAC + ' tt="optional password" /></td></tr>',
'<tr><td>passwd</td><td><input type="text" id="sh_pw" ' + NOAC + ' placeholder="optional password" /></td></tr>',
'<tr><td>expiry</td><td class="exs">',
'<input type="text" id="sh_exm" ' + NOAC + ' /> min / ',
'<input type="text" id="sh_exh" ' + NOAC + ' /> hours / ',
'<input type="text" id="sh_exd" ' + NOAC + ' /> days',
'<input type="text" id="sh_exd" ' + NOAC + ' /> days / ',
'<button id="sh_noex">never</button>',
'</td></tr>',
'<tr><td>perms</td><td class="sh_axs">',
];
@ -3840,11 +3841,12 @@ var fileman = (function () {
var sh_rand = ebi('sh_rand'),
sh_abrt = ebi('sh_abrt'),
sh_apply = ebi('sh_apply'),
sh_noex = ebi('sh_noex'),
exm = ebi('sh_exm'),
exh = ebi('sh_exh'),
exd = ebi('sh_exd'),
sh_k = ebi('sh_k'),
sh_vp = ebi('sh_vp');
sh_vp = ebi('sh_vp'),
sh_pw = ebi('sh_pw');
function setexp(a, b) {
@ -3870,6 +3872,9 @@ var fileman = (function () {
exm.onfocus = exh.onfocus = exd.onfocus = function () {
this.value = '';
};
sh_noex.onclick = function () {
setexp(0, 1);
};
exm.onblur = exh.onblur = exd.onblur = setdef;
exm.onkeydown = exh.onkeydown = exd.onkeydown =
@ -3910,6 +3915,7 @@ var fileman = (function () {
};
function shr_cb() {
toast.hide();
if (this.status !== 201) {
shui.style.display = 'block';
var msg = unpre(this.responseText);
@ -3919,7 +3925,7 @@ var fileman = (function () {
var surl = this.responseText;
modal.confirm(L.fs_ok + esc(surl), function() {
cliptxt(surl, function () {
toast.ok(1, 'copied to clipboard');
toast.ok(2, 'copied to clipboard');
});
});
}
@ -3934,6 +3940,8 @@ var fileman = (function () {
plist.push(pbtns[a].textContent);
shui.style.display = 'none';
toast.inf(30, "creating share...");
var body = {
"k": sh_k.value,
"vp": sh_vp.value,

View file

@ -58,6 +58,9 @@ td, th {
text-align: left;
white-space: nowrap;
}
td+td+td+td+td+td+td+td {
font-family: var(--font-mono), monospace, monospace;
}

View file

@ -15,13 +15,13 @@
<body>
<div id="wrap">
<a id="a" href="{{ r }}/?shares" class="af">refresh</a>
<a id="a" href="{{ r }}/?h" class="af">controlpanel</a>
<a id="a" href="{{ r }}/?h" class="af">control-panel</a>
<span>axs = perms (read,write,move,delet)</span>
<span>st 1=file 2=dir</span>
<span>min/hrs = time left</span>
<table><tr>
<table id="tab"><thead><tr>
<th>delete</th>
<th>sharekey</th>
<th>pw</th>
@ -33,7 +33,7 @@
<th>expires</th>
<th>min</th>
<th>hrs</th>
</tr>
</tr></thead><tbody>
{% for k, pw, vp, pr, st, un, t0, t1 in rows %}
<tr>
<td><a href="#" k="{{ k }}">delete</a></td>
@ -45,11 +45,11 @@
<td>{{ un|e }}</td>
<td>{{ t0 }}</td>
<td>{{ t1 }}</td>
<td>{{ (t1 - now) // 60 if t1 else "never" }}</td>
<td>{{ (t1 - now) // 3600 if t1 else "never" }}</td>
<td>{{ ((t1 - now) / 60) | round(1) if t1 else "inf" }}</td>
<td>{{ ((t1 - now) / 3600) | round(1) if t1 else "inf" }}</td>
</tr>
{% endfor %}
</table>
</tbody></table>
{% if not rows %}
(you don't have any active shares btw)
{% endif %}

View file

@ -17,3 +17,21 @@ function cb() {
document.location = '?shares';
}
(function() {
var tab = ebi('tab').tBodies[0],
tr = Array.prototype.slice.call(tab.rows, 0);
var buf = [];
for (var a = 0; a < tr.length; a++)
for (var b = 7; b < 9; b++)
buf.push(parseInt(tr[a].cells[b].innerHTML));
var ibuf = 0;
for (var a = 0; a < tr.length; a++)
for (var b = 7; b < 9; b++) {
var v = buf[ibuf++];
tr[a].cells[b].innerHTML =
v ? unix2iso(v).replace(' ', ',&nbsp;') : 'never';
}
})();

View file

@ -14,6 +14,7 @@
<body>
<div id="wrap">
{%- if not in_shr %}
<a id="a" href="{{ r }}/?h" class="af">refresh</a>
<a id="v" href="{{ r }}/?hc" class="af">connect</a>
@ -23,6 +24,7 @@
<a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
<p><span id="m">welcome back,</span> <strong>{{ this.uname|e }}</strong></p>
{%- endif %}
{%- endif %}
{%- if msg %}
<div id="msg">
@ -76,6 +78,37 @@
</ul>
{%- endif %}
{%- if in_shr %}
<h1 id="z">unlock this share:</h1>
<div>
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
<input type="hidden" id="la" name="act" value="login" />
<input type="password" id="lp" name="cppwd" placeholder=" password" />
<input type="hidden" name="uhash" id="uhash" value="x" />
<input type="submit" id="ls" value="Unlock" />
{% if ahttps %}
<a id="w" href="{{ ahttps }}">switch to https</a>
{% endif %}
</form>
</div>
{%- else %}
<h1 id="l">login for more:</h1>
<div>
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
<input type="hidden" id="la" name="act" value="login" />
<input type="password" id="lp" name="cppwd" placeholder=" password" />
<input type="hidden" name="uhash" id="uhash" value="x" />
<input type="submit" id="ls" value="Login" />
{% if chpw %}
<a id="x" href="#">change password</a>
{% endif %}
{% if ahttps %}
<a id="w" href="{{ ahttps }}">switch to https</a>
{% endif %}
</form>
</div>
{%- endif %}
<h1 id="cc">other stuff:</h1>
<ul>
{%- if this.uname != '*' and this.args.shr %}
@ -94,21 +127,6 @@
<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
</ul>
<h1 id="l">login for more:</h1>
<div>
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
<input type="hidden" id="la" name="act" value="login" />
<input type="password" id="lp" name="cppwd" placeholder=" password" />
<input type="hidden" name="uhash" id="uhash" value="x" />
<input type="submit" id="ls" value="Login" />
{% if chpw %}
<a id="x" href="#">change password</a>
{% endif %}
{% if ahttps %}
<a id="w" href="{{ ahttps }}">switch to https</a>
{% endif %}
</form>
</div>
</div>
<a href="#" id="repl">π</a>
{%- if not this.args.nb %}

View file

@ -17,9 +17,9 @@ var Ls = {
"l1": "logg inn:",
"m1": "velkommen tilbake,",
"n1": "404: filen finnes ikke &nbsp;┐( ´ -`)┌",
"o1": 'eller kanskje du ikke har tilgang? prøv å logge inn eller <a href="' + SR + '/?h">gå hjem</a>',
"o1": 'eller kanskje du ikke har tilgang? prøv et passord eller <a href="' + SR + '/?h">gå hjem</a>',
"p1": "403: tilgang nektet &nbsp;~┻━┻",
"q1": 'du må logge inn eller <a href="' + SR + '/?h">gå hjem</a>',
"q1": 'prøv et passord eller <a href="' + SR + '/?h">gå hjem</a>',
"r1": "gå hjem",
".s1": "kartlegg",
"t1": "handling",
@ -29,6 +29,7 @@ var Ls = {
"w1": "bytt til https",
"x1": "bytt passord",
"y1": "dine delinger",
"z1": "lås opp område",
"ta1": "du må skrive et nytt passord først",
"ta2": "gjenta for å bekrefte nytt passord:",
"ta3": "fant en skrivefeil; vennligst prøv igjen",