mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
textfile-streaming fixes;
* add optional max duration, default-infinite * add optional wordwrap, default-enabled * url-param `...&tail` enables tailing in textviewer too * hide bottom tray while tailing
This commit is contained in:
parent
8cae7a715b
commit
6ecf4fdceb
|
@ -56,7 +56,7 @@ made in Norway 🇳🇴
|
|||
* [creating a playlist](#creating-a-playlist) - with a standalone mediaplayer or copyparty
|
||||
* [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
||||
* [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings
|
||||
* [textfile viewer](#textfile-viewer) - with realtime streaming of logfiles and such
|
||||
* [textfile viewer](#textfile-viewer) - with realtime streaming of logfiles and such ([demo](https://a.ocv.me/pub/demo/logtail/?doc=lipsum.txt&tail))
|
||||
* [markdown viewer](#markdown-viewer) - and there are *two* editors
|
||||
* [markdown vars](#markdown-vars) - dynamic docs with serverside variable expansion
|
||||
* [other tricks](#other-tricks)
|
||||
|
@ -1131,14 +1131,13 @@ due to phone / app settings, android phones may randomly stop playing music whe
|
|||
|
||||
## textfile viewer
|
||||
|
||||
with realtime streaming of logfiles and such , and terminal colors work too
|
||||
|
||||
(TODO: add screenshots)
|
||||
with realtime streaming of logfiles and such ([demo](https://a.ocv.me/pub/demo/logtail/?doc=lipsum.txt&tail)) , and terminal colors work too
|
||||
|
||||
click `-txt-` next to a textfile to open the viewer, which has the following toolbar buttons:
|
||||
|
||||
* `✏️ edit` opens the textfile editor
|
||||
* `📡 follow` starts monitoring the file for changes, streaming new lines in realtime
|
||||
* similar to `tail -f`
|
||||
|
||||
|
||||
## markdown viewer
|
||||
|
|
|
@ -1405,6 +1405,7 @@ def add_tail(ap):
|
|||
ap2 = ap.add_argument_group('tailing options (realtime streaming of a growing file)')
|
||||
ap2.add_argument("--tail-who", metavar="LVL", type=int, default=2, help="who can tail? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=authenticated-with-read-access, [\033[32m3\033[0m]=everyone-with-read-access (volflag=tail_who)")
|
||||
ap2.add_argument("--tail-cmax", metavar="N", type=int, default=64, help="do not allow starting a new tail if more than \033[33mN\033[0m active downloads")
|
||||
ap2.add_argument("--tail-tmax", metavar="SEC", type=float, default=0, help="terminate connection after \033[33mSEC\033[0m seconds; [\033[32m0\033[0m]=never (volflag=tail_tmax)")
|
||||
ap2.add_argument("--tail-rate", metavar="SEC", type=float, default=0.2, help="check for new data every \033[33mSEC\033[0m seconds (volflag=tail_rate)")
|
||||
ap2.add_argument("--tail-ka", metavar="SEC", type=float, default=3.0, help="send a zerobyte if connection is idle for \033[33mSEC\033[0m seconds to prevent disconnect")
|
||||
ap2.add_argument("--tail-fd", metavar="SEC", type=float, default=1.0, help="check if file was replaced (new fd) if idle for \033[33mSEC\033[0m seconds (volflag=tail_fd)")
|
||||
|
|
|
@ -2075,12 +2075,13 @@ class AuthSrv(object):
|
|||
if vf not in vol.flags:
|
||||
vol.flags[vf] = getattr(self.args, ga)
|
||||
|
||||
zs = "forget_ip nrand u2abort u2ow ups_who zip_who"
|
||||
zs = "forget_ip nrand tail_who u2abort u2ow ups_who zip_who"
|
||||
for k in zs.split():
|
||||
if k in vol.flags:
|
||||
vol.flags[k] = int(vol.flags[k])
|
||||
|
||||
for k in ("convt",):
|
||||
zs = "convt tail_fd tail_rate tail_tmax"
|
||||
for k in zs.split():
|
||||
if k in vol.flags:
|
||||
vol.flags[k] = float(vol.flags[k])
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@ def vf_vmap() -> dict[str, str]:
|
|||
"sort",
|
||||
"tail_fd",
|
||||
"tail_rate",
|
||||
"tail_tmax",
|
||||
"tail_who",
|
||||
"tcolor",
|
||||
"unlist",
|
||||
|
@ -311,8 +312,9 @@ flagcats = {
|
|||
},
|
||||
"tailing": {
|
||||
"notail": "disable ?tail (download a growing file continuously)",
|
||||
"tail_fd=1": "interval for checking if file was replaced (new fd)",
|
||||
"tail_rate=0.2": "interval for checking for new data",
|
||||
"tail_fd=1": "check if file was replaced (new fd) every 1 sec",
|
||||
"tail_rate=0.2": "check for new data every 0.2 sec",
|
||||
"tail_tmax=30": "kill connection after 30 sec",
|
||||
"tail_who=2": "restrict ?tail access (1=admins,2=authed,3=everyone)",
|
||||
},
|
||||
"others": {
|
||||
|
|
|
@ -4246,11 +4246,13 @@ class HttpCli(object):
|
|||
status: int,
|
||||
mime: str,
|
||||
) -> None:
|
||||
vf = self.vn.flags
|
||||
self.send_headers(length=None, status=status, mime=mime)
|
||||
abspath: bytes = open_args[0]
|
||||
sec_rate = self.args.tail_rate
|
||||
sec_rate = vf["tail_rate"]
|
||||
sec_max = vf["tail_tmax"]
|
||||
sec_fd = vf["tail_fd"]
|
||||
sec_ka = self.args.tail_ka
|
||||
sec_fd = self.args.tail_fd
|
||||
wr_slp = self.args.s_wr_slp
|
||||
wr_sz = self.args.s_wr_sz
|
||||
dls = self.conn.hsrv.dls
|
||||
|
@ -4264,6 +4266,7 @@ class HttpCli(object):
|
|||
except:
|
||||
ofs = 0
|
||||
|
||||
t0 = time.time()
|
||||
ofs0 = ofs
|
||||
f = None
|
||||
try:
|
||||
|
@ -4307,6 +4310,13 @@ class HttpCli(object):
|
|||
assert f # !rm
|
||||
buf = f.read(4096)
|
||||
now = time.time()
|
||||
|
||||
if sec_max and now - t0 >= sec_max:
|
||||
self.log("max duration exceeded; kicking client", 6)
|
||||
zb = b"\n\n*** max duration exceeded; disconnecting ***\n"
|
||||
self.s.sendall(zb)
|
||||
break
|
||||
|
||||
if buf:
|
||||
t_fd = t_ka = now
|
||||
self.s.sendall(buf)
|
||||
|
|
|
@ -1839,14 +1839,10 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||
white-space: pre;
|
||||
padding-left: .3em;
|
||||
}
|
||||
#tail2end,
|
||||
#tailansi,
|
||||
#tailnb {
|
||||
#tailbtns {
|
||||
display: none;
|
||||
}
|
||||
#taildoc.on+#tail2end,
|
||||
#taildoc.on+#tail2end+#tailansi,
|
||||
#taildoc.on+#tail2end+#tailansi+#tailnb {
|
||||
#taildoc.on+#tailbtns {
|
||||
display: inherit;
|
||||
display: unset;
|
||||
}
|
||||
|
@ -1946,6 +1942,9 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||
padding: 1em 0 1em 0;
|
||||
border-radius: .3em;
|
||||
}
|
||||
#doc.wrap {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
html.y #doc {
|
||||
box-shadow: 0 0 .3em var(--bg-u5);
|
||||
background: #f7f7f7;
|
||||
|
|
|
@ -443,6 +443,7 @@ var Ls = {
|
|||
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
||||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||
"tvt_tail": "monitor file for changes; show new lines in real time\">📡 follow",
|
||||
"tvt_wrap": "word-wrap\">↵",
|
||||
"tvt_atail": "lock scroll to bottom of page\">⚓",
|
||||
"tvt_ctail": "decode terminal colors (ansi escape codes)\">🌈",
|
||||
"tvt_ntail": "scrollback limit (how many bytes of text to keep loaded)",
|
||||
|
@ -1066,6 +1067,7 @@ var Ls = {
|
|||
"tvt_sel": "markér filen ( for utklipp / sletting / ... )$NSnarvei: S\">merk",
|
||||
"tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre",
|
||||
"tvt_tail": "overvåk filen for endringer og vis nye linjer i sanntid\">📡 følg",
|
||||
"tvt_wrap": "tekstbryting\">↵",
|
||||
"tvt_atail": "hold de nyeste linjene synlig (lås til bunnen av siden)\">⚓",
|
||||
"tvt_ctail": "forstå og vis terminalfarger (ansi-sekvenser)\">🌈",
|
||||
"tvt_ntail": "maks-grense for antall bokstaver som skal vises i vinduet",
|
||||
|
@ -1689,6 +1691,7 @@ var Ls = {
|
|||
"tvt_sel": "选择文件 (用于剪切/删除/...)$N快捷键: S\">选择",
|
||||
"tvt_edit": "在文本编辑器中打开文件$N快捷键: E\">✏️ 编辑",
|
||||
"tvt_tail": "监视文件更改,并实时显示新增的行\">📡 跟踪", //m
|
||||
"tvt_wrap": "自动换行\">↵", //m
|
||||
"tvt_atail": "锁定到底部,显示最新内容\">⚓", //m
|
||||
"tvt_ctail": "解析终端颜色(ANSI 转义码)\">🌈", //m
|
||||
"tvt_ntail": "滚动历史上限(保留多少字节的文本)", //m
|
||||
|
@ -2984,6 +2987,9 @@ var widget = (function () {
|
|||
ebi('bplay').innerHTML = paused ? '▶' : '⏸';
|
||||
}
|
||||
};
|
||||
r.setvis = function () {
|
||||
widget.style.display = !has(perms, "read") || showfile.abrt ? 'none' : '';
|
||||
};
|
||||
wtico.onclick = function (e) {
|
||||
if (!touchmode)
|
||||
r.toggle(e);
|
||||
|
@ -5942,6 +5948,7 @@ var showfile = (function () {
|
|||
|
||||
r.tail = function (url, no_push) {
|
||||
r.abrt = new AbortController();
|
||||
widget.setvis();
|
||||
render([url, '', ''], no_push);
|
||||
var me = r.tail_id = Date.now(),
|
||||
wfp = ebi('wfp'),
|
||||
|
@ -5988,7 +5995,9 @@ var showfile = (function () {
|
|||
if (!r.abrt)
|
||||
return;
|
||||
r.abrt.abort();
|
||||
r.abrt = null;
|
||||
r.tail_id = -1;
|
||||
widget.setvis();
|
||||
};
|
||||
|
||||
r.show = function (url, no_push) {
|
||||
|
@ -6099,6 +6108,8 @@ var showfile = (function () {
|
|||
else
|
||||
import_js(SR + '/.cpr/deps/prism.js', function () { fun(); });
|
||||
}
|
||||
if (!txt && r.wrap)
|
||||
el.className = 'wrap';
|
||||
}
|
||||
|
||||
wr.appendChild(el);
|
||||
|
@ -6232,6 +6243,10 @@ var showfile = (function () {
|
|||
r.show(r.url, true);
|
||||
};
|
||||
|
||||
r.tglwrap = function () {
|
||||
r.show(r.url, true);
|
||||
};
|
||||
|
||||
var bdoc = ebi('bdoc');
|
||||
bdoc.className = 'line-numbers';
|
||||
bdoc.innerHTML = (
|
||||
|
@ -6243,19 +6258,24 @@ var showfile = (function () {
|
|||
'<a href="#" class="btn" id="seldoc" tt="' + L.tvt_sel + '</a>\n' +
|
||||
'<a href="#" class="btn" id="editdoc" tt="' + L.tvt_edit + '</a>\n' +
|
||||
'<a href="#" class="btn tgl" id="taildoc" tt="' + L.tvt_tail + '</a>\n' +
|
||||
'<div id="tailbtns">\n' +
|
||||
'<a href="#" class="btn tgl" id="wrapdoc" tt="' + L.tvt_wrap + '</a>\n' +
|
||||
'<a href="#" class="btn tgl" id="tail2end" tt="' + L.tvt_atail + '</a>\n' +
|
||||
'<a href="#" class="btn tgl" id="tailansi" tt="' + L.tvt_ctail + '</a>\n' +
|
||||
'<input type="text" id="tailnb" value="" ' + NOAC + ' style="width:4em" tt="' + L.tvt_ntail + '" />' +
|
||||
'</div>\n' +
|
||||
'</div>'
|
||||
);
|
||||
ebi('xdoc').onclick = function () {
|
||||
r.untail();
|
||||
thegrid.setvis(true);
|
||||
bcfg_bind(r, 'taildoc', 'taildoc', false, r.tgltail);
|
||||
};
|
||||
ebi('dldoc').setAttribute('download', '');
|
||||
ebi('prevdoc').onclick = function () { tree_neigh(-1); };
|
||||
ebi('nextdoc').onclick = function () { tree_neigh(1); };
|
||||
ebi('seldoc').onclick = r.tglsel;
|
||||
bcfg_bind(r, 'wrap', 'wrapdoc', true, r.tglwrap);
|
||||
bcfg_bind(r, 'taildoc', 'taildoc', false, r.tgltail);
|
||||
bcfg_bind(r, 'tail2end', 'tail2end', true);
|
||||
bcfg_bind(r, 'tailansi', 'tailansi', false, r.tgltail);
|
||||
|
@ -6265,6 +6285,11 @@ var showfile = (function () {
|
|||
swrite('tailnb', r.tailnb = this.value);
|
||||
};
|
||||
|
||||
if (/[?&]tail\b/.exec(sloc0)) {
|
||||
clmod(ebi('taildoc'), 'on', 1);
|
||||
r.taildoc = true;
|
||||
}
|
||||
|
||||
return r;
|
||||
})();
|
||||
|
||||
|
@ -8690,7 +8715,7 @@ function apply_perms(res) {
|
|||
if (up2k)
|
||||
up2k.set_fsearch();
|
||||
|
||||
ebi('widget').style.display = have_read ? '' : 'none';
|
||||
widget.setvis();
|
||||
thegrid.setvis();
|
||||
if (!have_read && have_write)
|
||||
goto('up2k');
|
||||
|
|
|
@ -155,7 +155,7 @@ class Cfg(Namespace):
|
|||
ex = "hash_mt hsortn safe_dedup srch_time tail_fd tail_rate u2abort u2j u2sz"
|
||||
ka.update(**{k: 1 for k in ex.split()})
|
||||
|
||||
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody tail_who th_convt ups_who zip_who"
|
||||
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who zip_who"
|
||||
ka.update(**{k: 9 for k in ex.split()})
|
||||
|
||||
ex = "db_act forget_ip k304 loris no304 nosubtle re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs"
|
||||
|
|
Loading…
Reference in a new issue