mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 05:43:17 +00:00 
			
		
		
		
	list recent uploads
also makes the unpost lister 5x faster
This commit is contained in:
		
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -48,6 +48,7 @@ turn almost any device into a file server with resumable uploads/downloads using
 | 
			
		||||
    * [shares](#shares) - share a file or folder by creating a temporary link
 | 
			
		||||
    * [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
 | 
			
		||||
    * [rss feeds](#rss-feeds) - monitor a folder with your RSS reader
 | 
			
		||||
    * [recent uploads](#recent-uploads) - list all recent uploads
 | 
			
		||||
    * [media player](#media-player) - plays almost every audio format there is
 | 
			
		||||
        * [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
 | 
			
		||||
@@ -717,7 +718,7 @@ files go into `[ok]` if they exist (and you get a link to where it is), otherwis
 | 
			
		||||
 | 
			
		||||
### unpost
 | 
			
		||||
 | 
			
		||||
undo/delete accidental uploads
 | 
			
		||||
undo/delete accidental uploads  using the `[🧯]` tab in the UI
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
@@ -876,6 +877,17 @@ url parameters:
 | 
			
		||||
  * uppercase = reverse-sort; `M` = oldest file first
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## recent uploads
 | 
			
		||||
 | 
			
		||||
list all recent uploads  by clicking "show recent uploads" in the controlpanel
 | 
			
		||||
 | 
			
		||||
will show uploader IP and upload-time if the visitor has the admin permission
 | 
			
		||||
 | 
			
		||||
* global-option `--ups-when` makes upload-time visible to all users, and not just admins
 | 
			
		||||
 | 
			
		||||
note that the [🧯 unpost](#unpost) feature is better suited for viewing *your own* recent uploads, as it includes the option to undo/delete them
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## media player
 | 
			
		||||
 | 
			
		||||
plays almost every audio format there is  (if the server has FFmpeg installed for on-demand transcoding)
 | 
			
		||||
 
 | 
			
		||||
@@ -91,6 +91,9 @@ web/mde.html
 | 
			
		||||
web/mde.js
 | 
			
		||||
web/msg.css
 | 
			
		||||
web/msg.html
 | 
			
		||||
web/rups.css
 | 
			
		||||
web/rups.html
 | 
			
		||||
web/rups.js
 | 
			
		||||
web/shares.css
 | 
			
		||||
web/shares.html
 | 
			
		||||
web/shares.js
 | 
			
		||||
 
 | 
			
		||||
@@ -1250,7 +1250,6 @@ def add_optouts(ap):
 | 
			
		||||
    ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
 | 
			
		||||
    ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
 | 
			
		||||
    ap2.add_argument("--no-lifetime", action="store_true", help="do not allow clients (or server config) to schedule an upload to be deleted after a given time")
 | 
			
		||||
    ap2.add_argument("--no-up-list", action="store_true", help="don't show list of incoming files in controlpanel")
 | 
			
		||||
    ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
 | 
			
		||||
    ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader IPs into the database")
 | 
			
		||||
 | 
			
		||||
@@ -1326,7 +1325,10 @@ def add_admin(ap):
 | 
			
		||||
    ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)")
 | 
			
		||||
    ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
 | 
			
		||||
    ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
 | 
			
		||||
    ap2.add_argument("--no-ups-page", action="store_true", help="disable ?ru (list of recent uploads)")
 | 
			
		||||
    ap2.add_argument("--no-up-list", action="store_true", help="don't show list of incoming files in controlpanel")
 | 
			
		||||
    ap2.add_argument("--dl-list", metavar="LVL", type=int, default=2, help="who can see active downloads in the controlpanel? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=everyone")
 | 
			
		||||
    ap2.add_argument("--ups-when", action="store_true", help="let everyone see upload timestamps on the ?ru page, not just admins")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_thumbnail(ap):
 | 
			
		||||
 
 | 
			
		||||
@@ -1233,6 +1233,9 @@ class HttpCli(object):
 | 
			
		||||
            if "dls" in self.uparam:
 | 
			
		||||
                return self.tx_dls()
 | 
			
		||||
 | 
			
		||||
            if "ru" in self.uparam:
 | 
			
		||||
                return self.tx_rups()
 | 
			
		||||
 | 
			
		||||
        if "h" in self.uparam:
 | 
			
		||||
            return self.tx_mounts()
 | 
			
		||||
 | 
			
		||||
@@ -4919,9 +4922,9 @@ class HttpCli(object):
 | 
			
		||||
                raise Pebkac(500, "sqlite3 not found on server; unpost is disabled")
 | 
			
		||||
            raise Pebkac(500, "server busy, cannot unpost; please retry in a bit")
 | 
			
		||||
 | 
			
		||||
        filt = self.uparam.get("filter") or ""
 | 
			
		||||
        lm = "ups %r" % (filt,)
 | 
			
		||||
        self.log(lm)
 | 
			
		||||
        zs = self.uparam.get("filter") or ""
 | 
			
		||||
        filt = re.compile(zs, re.I) if zs else None
 | 
			
		||||
        lm = "ups %r" % (zs,)
 | 
			
		||||
 | 
			
		||||
        if self.args.shr and self.vpath.startswith(self.args.shr1):
 | 
			
		||||
            shr_dbv, shr_vrem = self.vn.get_dbv(self.rem)
 | 
			
		||||
@@ -4962,13 +4965,18 @@ class HttpCli(object):
 | 
			
		||||
 | 
			
		||||
            nfk, fk_alg = fk_vols.get(vol) or (0, 0)
 | 
			
		||||
 | 
			
		||||
            q = "select sz, rd, fn, at from up where ip=? and at>?"
 | 
			
		||||
            n = 2000
 | 
			
		||||
            q = "select sz, rd, fn, at from up where ip=? and at>? order by at desc"
 | 
			
		||||
            for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
 | 
			
		||||
                vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
 | 
			
		||||
                if filt and filt not in vp:
 | 
			
		||||
                if filt and not filt.search(vp):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                rv = {"vp": quotep(vp), "sz": sz, "at": at, "nfk": nfk}
 | 
			
		||||
                n -= 1
 | 
			
		||||
                if not n:
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
                rv = {"vp": vp, "sz": sz, "at": at, "nfk": nfk}
 | 
			
		||||
                if nfk:
 | 
			
		||||
                    rv["ap"] = vol.canonical(vjoin(rd, fn))
 | 
			
		||||
                    rv["fk_alg"] = fk_alg
 | 
			
		||||
@@ -4978,9 +4986,13 @@ class HttpCli(object):
 | 
			
		||||
                    ret.sort(key=lambda x: x["at"], reverse=True)  # type: ignore
 | 
			
		||||
                    ret = ret[:2000]
 | 
			
		||||
 | 
			
		||||
        if len(ret) > 2000:
 | 
			
		||||
            ret = ret[:2000]
 | 
			
		||||
 | 
			
		||||
        ret.sort(key=lambda x: x["at"], reverse=True)  # type: ignore
 | 
			
		||||
        n = 0
 | 
			
		||||
        for rv in ret[:11000]:
 | 
			
		||||
 | 
			
		||||
        for rv in ret:
 | 
			
		||||
            rv["vp"] = quotep(rv["vp"])
 | 
			
		||||
            nfk = rv.pop("nfk")
 | 
			
		||||
            if not nfk:
 | 
			
		||||
                continue
 | 
			
		||||
@@ -4997,12 +5009,6 @@ class HttpCli(object):
 | 
			
		||||
            )
 | 
			
		||||
            rv["vp"] += "?k=" + fk[:nfk]
 | 
			
		||||
 | 
			
		||||
            n += 1
 | 
			
		||||
            if n > 2000:
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        ret = ret[:2000]
 | 
			
		||||
 | 
			
		||||
        if shr_dbv:
 | 
			
		||||
            # translate vpaths from share-target to share-url
 | 
			
		||||
            # to satisfy access checks
 | 
			
		||||
@@ -5026,6 +5032,125 @@ class HttpCli(object):
 | 
			
		||||
        self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def tx_rups(self) -> bool:
 | 
			
		||||
        if self.args.no_ups_page:
 | 
			
		||||
            raise Pebkac(500, "listing of recent uploads is disabled in server config")
 | 
			
		||||
 | 
			
		||||
        idx = self.conn.get_u2idx()
 | 
			
		||||
        if not idx or not hasattr(idx, "p_end"):
 | 
			
		||||
            if not HAVE_SQLITE3:
 | 
			
		||||
                raise Pebkac(500, "sqlite3 not found on server; recent-uploads n/a")
 | 
			
		||||
            raise Pebkac(500, "server busy, cannot list recent uploads; please retry")
 | 
			
		||||
 | 
			
		||||
        sfilt = self.uparam.get("filter") or ""
 | 
			
		||||
        filt = re.compile(sfilt, re.I) if sfilt else None
 | 
			
		||||
        lm = "ru %r" % (sfilt,)
 | 
			
		||||
        self.log(lm)
 | 
			
		||||
 | 
			
		||||
        ret: list[dict[str, Any]] = []
 | 
			
		||||
        t0 = time.time()
 | 
			
		||||
        allvols = [
 | 
			
		||||
            x
 | 
			
		||||
            for x in self.asrv.vfs.all_vols.values()
 | 
			
		||||
            if "e2d" in x.flags and ("*" in x.axs.uread or self.uname in x.axs.uread)
 | 
			
		||||
        ]
 | 
			
		||||
        fk_vols = {
 | 
			
		||||
            vol: (vol.flags["fk"], 2 if "fka" in vol.flags else 1)
 | 
			
		||||
            for vol in allvols
 | 
			
		||||
            if "fk" in vol.flags and "*" not in vol.axs.uread
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for vol in allvols:
 | 
			
		||||
            cur = idx.get_cur(vol)
 | 
			
		||||
            if not cur:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            nfk, fk_alg = fk_vols.get(vol) or (0, 0)
 | 
			
		||||
            adm = "*" in vol.axs.uadmin or self.uname in vol.axs.uadmin
 | 
			
		||||
            dots = "*" in vol.axs.udot or self.uname in vol.axs.udot
 | 
			
		||||
 | 
			
		||||
            n = 2000
 | 
			
		||||
            q = "select sz, rd, fn, ip, at from up where at>0 order by at desc"
 | 
			
		||||
            for sz, rd, fn, ip, at in cur.execute(q):
 | 
			
		||||
                vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
 | 
			
		||||
                if filt and not filt.search(vp):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if not dots and "/." in vp:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                n -= 1
 | 
			
		||||
                if not n:
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
                rv = {
 | 
			
		||||
                    "vp": vp,
 | 
			
		||||
                    "sz": sz,
 | 
			
		||||
                    "ip": ip,
 | 
			
		||||
                    "at": at,
 | 
			
		||||
                    "nfk": nfk,
 | 
			
		||||
                    "adm": adm,
 | 
			
		||||
                }
 | 
			
		||||
                if nfk:
 | 
			
		||||
                    rv["ap"] = vol.canonical(vjoin(rd, fn))
 | 
			
		||||
                    rv["fk_alg"] = fk_alg
 | 
			
		||||
 | 
			
		||||
                ret.append(rv)
 | 
			
		||||
                if len(ret) > 3000:
 | 
			
		||||
                    ret.sort(key=lambda x: x["at"], reverse=True)  # type: ignore
 | 
			
		||||
                    ret = ret[:2000]
 | 
			
		||||
 | 
			
		||||
        if len(ret) > 2000:
 | 
			
		||||
            ret = ret[:2000]
 | 
			
		||||
 | 
			
		||||
        ret.sort(key=lambda x: x["at"], reverse=True)  # type: ignore
 | 
			
		||||
 | 
			
		||||
        for rv in ret:
 | 
			
		||||
            rv["evp"] = quotep(rv["vp"])
 | 
			
		||||
            nfk = rv.pop("nfk")
 | 
			
		||||
            if not nfk:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            alg = rv.pop("fk_alg")
 | 
			
		||||
            ap = rv.pop("ap")
 | 
			
		||||
            try:
 | 
			
		||||
                st = bos.stat(ap)
 | 
			
		||||
            except:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            fk = self.gen_fk(
 | 
			
		||||
                alg, self.args.fk_salt, ap, st.st_size, 0 if ANYWIN else st.st_ino
 | 
			
		||||
            )
 | 
			
		||||
            rv["vp"] += "?k=" + fk[:nfk]
 | 
			
		||||
 | 
			
		||||
        if self.args.ups_when:
 | 
			
		||||
            for rv in ret:
 | 
			
		||||
                adm = rv.pop("adm")
 | 
			
		||||
                if not adm:
 | 
			
		||||
                    rv["ip"] = "(You)" if rv["ip"] == self.ip else "(?)"
 | 
			
		||||
        else:
 | 
			
		||||
            for rv in ret:
 | 
			
		||||
                adm = rv.pop("adm")
 | 
			
		||||
                if not adm:
 | 
			
		||||
                    rv["ip"] = "(You)" if rv["ip"] == self.ip else "(?)"
 | 
			
		||||
                    rv["at"] = 0
 | 
			
		||||
 | 
			
		||||
        if self.is_vproxied:
 | 
			
		||||
            for v in ret:
 | 
			
		||||
                v["vp"] = self.args.SR + v["vp"]
 | 
			
		||||
 | 
			
		||||
        self.log("%s #%d %.2fsec" % (lm, len(ret), time.time() - t0))
 | 
			
		||||
 | 
			
		||||
        if "j" in self.ouparam:
 | 
			
		||||
            jtxt = json.dumps(ret, separators=(",\n", ": "))
 | 
			
		||||
            self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        rows = [[x["vp"], x["evp"], x["sz"], x["ip"], x["at"]] for x in ret]
 | 
			
		||||
        html = self.j2s("rups", this=self, rows=rows, filt=sfilt, now=int(time.time()))
 | 
			
		||||
        self.reply(html.encode("utf-8"), status=200)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def tx_shares(self) -> bool:
 | 
			
		||||
        if self.uname == "*":
 | 
			
		||||
            self.loud_reply("you're not logged in")
 | 
			
		||||
 
 | 
			
		||||
@@ -172,15 +172,16 @@ class HttpSrv(object):
 | 
			
		||||
        env = jinja2.Environment()
 | 
			
		||||
        env.loader = jinja2.FunctionLoader(lambda f: load_jinja2_resource(self.E, f))
 | 
			
		||||
        jn = [
 | 
			
		||||
            "splash",
 | 
			
		||||
            "shares",
 | 
			
		||||
            "svcs",
 | 
			
		||||
            "browser",
 | 
			
		||||
            "browser2",
 | 
			
		||||
            "msg",
 | 
			
		||||
            "cf",
 | 
			
		||||
            "md",
 | 
			
		||||
            "mde",
 | 
			
		||||
            "cf",
 | 
			
		||||
            "msg",
 | 
			
		||||
            "rups",
 | 
			
		||||
            "shares",
 | 
			
		||||
            "splash",
 | 
			
		||||
            "svcs",
 | 
			
		||||
        ]
 | 
			
		||||
        self.j2 = {x: env.get_template(x + ".html") for x in jn}
 | 
			
		||||
        self.prism = has_resource(self.E, "web/deps/prism.js.gz")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										113
									
								
								copyparty/web/rups.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								copyparty/web/rups.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
html {
 | 
			
		||||
	color: #333;
 | 
			
		||||
	background: #f7f7f7;
 | 
			
		||||
	font-family: sans-serif;
 | 
			
		||||
	font-family: var(--font-main), sans-serif;
 | 
			
		||||
	touch-action: manipulation;
 | 
			
		||||
}
 | 
			
		||||
#wrap {
 | 
			
		||||
	margin: 2em auto;
 | 
			
		||||
	padding: 0 1em 3em 1em;
 | 
			
		||||
	line-height: 2.3em;
 | 
			
		||||
}
 | 
			
		||||
form {
 | 
			
		||||
	display: inline;
 | 
			
		||||
	padding-left: 1em;
 | 
			
		||||
}
 | 
			
		||||
input[type=submit],
 | 
			
		||||
a {
 | 
			
		||||
	color: #047;
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	text-decoration: none;
 | 
			
		||||
	border: none;
 | 
			
		||||
	border-bottom: 1px solid #8ab;
 | 
			
		||||
	border-radius: .2em;
 | 
			
		||||
	padding: .2em .6em;
 | 
			
		||||
	margin: 0 .3em;
 | 
			
		||||
}
 | 
			
		||||
#wrap td a {
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	line-height: 1em;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	white-space: initial;
 | 
			
		||||
	font-family: var(--font-main), sans-serif;
 | 
			
		||||
}
 | 
			
		||||
#repl {
 | 
			
		||||
	border: none;
 | 
			
		||||
	background: none;
 | 
			
		||||
	color: inherit;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	bottom: .25em;
 | 
			
		||||
	left: .2em;
 | 
			
		||||
}
 | 
			
		||||
#wrap table {
 | 
			
		||||
	border-collapse: collapse;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	margin-top: 2em;
 | 
			
		||||
}
 | 
			
		||||
#wrap th {
 | 
			
		||||
	top: -1px;
 | 
			
		||||
	position: sticky;
 | 
			
		||||
	background: #f7f7f7;
 | 
			
		||||
}
 | 
			
		||||
#wrap td {
 | 
			
		||||
	font-family: var(--font-mono), monospace, monospace;
 | 
			
		||||
	white-space: pre;
 | 
			
		||||
}
 | 
			
		||||
#wrap th:first-child,
 | 
			
		||||
#wrap td:first-child {
 | 
			
		||||
	text-align: right;
 | 
			
		||||
}
 | 
			
		||||
#wrap td,
 | 
			
		||||
#wrap th {
 | 
			
		||||
	text-align: left;
 | 
			
		||||
	padding: .3em .6em;
 | 
			
		||||
	max-width: 30vw;
 | 
			
		||||
}
 | 
			
		||||
#wrap tr:hover td {
 | 
			
		||||
	background: #ddd;
 | 
			
		||||
	box-shadow: 0 -1px 0 rgba(128, 128, 128, 0.5) inset;
 | 
			
		||||
}
 | 
			
		||||
#wrap th:first-child,
 | 
			
		||||
#wrap td:first-child {
 | 
			
		||||
	border-radius: .5em 0 0 .5em;
 | 
			
		||||
}
 | 
			
		||||
#wrap th:last-child,
 | 
			
		||||
#wrap td:last-child {
 | 
			
		||||
	border-radius: 0 .5em .5em 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
html.z {
 | 
			
		||||
	background: #222;
 | 
			
		||||
	color: #ccc;
 | 
			
		||||
}
 | 
			
		||||
html.bz {
 | 
			
		||||
	background: #11121d;
 | 
			
		||||
	color: #bbd;
 | 
			
		||||
}
 | 
			
		||||
html.z input[type=submit],
 | 
			
		||||
html.z a {
 | 
			
		||||
	color: #fff;
 | 
			
		||||
	background: #057;
 | 
			
		||||
	border-color: #37a;
 | 
			
		||||
}
 | 
			
		||||
html.z input[type=text] {
 | 
			
		||||
	color: #ddd;
 | 
			
		||||
	background: #223;
 | 
			
		||||
	border: none;
 | 
			
		||||
	border-bottom: 1px solid #fc5;
 | 
			
		||||
	border-radius: .2em;
 | 
			
		||||
	padding: .2em .3em;
 | 
			
		||||
}
 | 
			
		||||
html.z #wrap th {
 | 
			
		||||
	background: #222;
 | 
			
		||||
}
 | 
			
		||||
html.bz #wrap th {
 | 
			
		||||
	background: #223;
 | 
			
		||||
}
 | 
			
		||||
html.z #wrap tr:hover td {
 | 
			
		||||
	background: #000;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										67
									
								
								copyparty/web/rups.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								copyparty/web/rups.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
	<meta charset="utf-8">
 | 
			
		||||
	<title>{{ s_doctitle }}</title>
 | 
			
		||||
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
	<meta name="viewport" content="width=device-width, initial-scale=0.8">
 | 
			
		||||
	<meta name="theme-color" content="#{{ tcolor }}">
 | 
			
		||||
	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/rups.css?_={{ ts }}">
 | 
			
		||||
	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
 | 
			
		||||
{{ html_head }}
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
	<div id="wrap">
 | 
			
		||||
        <a id="a" href="{{ r }}/?ru" class="af">refresh</a>
 | 
			
		||||
        <a id="a" href="{{ r }}/?h" class="af">control-panel</a>
 | 
			
		||||
        <form method="get" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="{{ r }}">
 | 
			
		||||
            <input type="hidden" name="ru" value="a" />
 | 
			
		||||
            Filter: <input type="text" name="filter" size="20" placeholder="documents/passwords" value="{{ filt }}" />
 | 
			
		||||
            <input type="submit" />
 | 
			
		||||
        </form>
 | 
			
		||||
        <span id="hits"></span>
 | 
			
		||||
        <table id="tab"><thead><tr>
 | 
			
		||||
            <th>size</th>
 | 
			
		||||
            <th>who</th>
 | 
			
		||||
            <th>when</th>
 | 
			
		||||
            <th>age</th>
 | 
			
		||||
            <th>dir</th>
 | 
			
		||||
            <th>file</th>
 | 
			
		||||
        </tr></thead><tbody>
 | 
			
		||||
        {% for vp, evp, sz, ip, at in rows %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>{{ sz }}</td>
 | 
			
		||||
            <td>{{ ip }}</td>
 | 
			
		||||
            <td>{{ at }}</td>
 | 
			
		||||
            <td>{{ (now-at) }}</td>
 | 
			
		||||
            <td></td>
 | 
			
		||||
            <td><a href="{{ r }}{{ evp }}">{{ vp|e }}</a></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
        </tbody></table>
 | 
			
		||||
        {% if not rows %}
 | 
			
		||||
        (the database is not aware of any uploads)
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
	<a href="#" id="repl">π</a>
 | 
			
		||||
	<script>
 | 
			
		||||
 | 
			
		||||
var SR = {{ r|tojson }},
 | 
			
		||||
    NOW = {{ now }},
 | 
			
		||||
	lang="{{ lang }}",
 | 
			
		||||
	dfavico="{{ favico }}";
 | 
			
		||||
 | 
			
		||||
var STG = window.localStorage;
 | 
			
		||||
document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme }}";
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
 | 
			
		||||
<script src="{{ r }}/.cpr/rups.js?_={{ ts }}"></script>
 | 
			
		||||
{%- if js %}
 | 
			
		||||
<script src="{{ js }}_={{ ts }}"></script>
 | 
			
		||||
{%- endif %}
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								copyparty/web/rups.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								copyparty/web/rups.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
(function() {
 | 
			
		||||
    var tab = ebi('tab').tBodies[0],
 | 
			
		||||
        tr = Array.prototype.slice.call(tab.rows, 0),
 | 
			
		||||
        rows = [];
 | 
			
		||||
 | 
			
		||||
    for (var a = 0; a < tr.length; a++) {
 | 
			
		||||
        var td = tr[a].cells,
 | 
			
		||||
            an = td[5].children[0];
 | 
			
		||||
 | 
			
		||||
        rows.push([
 | 
			
		||||
            td[0].textContent,
 | 
			
		||||
            td[2].textContent,
 | 
			
		||||
            td[3].textContent,
 | 
			
		||||
            an.textContent,
 | 
			
		||||
            an.getAttribute('href'),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (var a = 0; a < rows.length; a++) {
 | 
			
		||||
        var t = rows[a],
 | 
			
		||||
            sz = t[0],
 | 
			
		||||
            at = parseInt(t[1]),
 | 
			
		||||
            nam = vsplit(t[3]),
 | 
			
		||||
            dh = vsplit(t[4])[0];
 | 
			
		||||
 | 
			
		||||
        tr[a].cells[0].innerHTML = sz.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
 | 
			
		||||
        tr[a].cells[2].innerHTML = at ? unix2iso(at) : '(?)';
 | 
			
		||||
        tr[a].cells[3].innerHTML = at ? shumantime(t[2]) : '(?)';
 | 
			
		||||
        tr[a].cells[4].innerHTML = '<a href="' + dh + '">' + nam[0] + '</a>';
 | 
			
		||||
        tr[a].cells[5].children[0].innerHTML = nam[1].split('?')[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ebi('hits').innerHTML = '-- showing ' + rows.length + ' files';
 | 
			
		||||
})();
 | 
			
		||||
@@ -44,9 +44,10 @@ a {
 | 
			
		||||
	bottom: .25em;
 | 
			
		||||
	left: .2em;
 | 
			
		||||
}
 | 
			
		||||
table {
 | 
			
		||||
#wrap table {
 | 
			
		||||
	border-collapse: collapse;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	margin-top: 2em;
 | 
			
		||||
}
 | 
			
		||||
th {
 | 
			
		||||
	top: -1px;
 | 
			
		||||
@@ -62,6 +63,14 @@ th {
 | 
			
		||||
#wrap td+td+td+td+td+td+td+td {
 | 
			
		||||
	font-family: var(--font-mono), monospace, monospace;
 | 
			
		||||
}
 | 
			
		||||
#wrap th:first-child,
 | 
			
		||||
#wrap td:first-child {
 | 
			
		||||
	border-radius: .5em 0 0 .5em;
 | 
			
		||||
}
 | 
			
		||||
#wrap th:last-child,
 | 
			
		||||
#wrap td:last-child {
 | 
			
		||||
	border-radius: 0 .5em .5em 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -81,3 +90,6 @@ html.bz {
 | 
			
		||||
	color: #bbd;
 | 
			
		||||
	background: #11121d;
 | 
			
		||||
}
 | 
			
		||||
html.bz th {
 | 
			
		||||
	background: #223;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,8 @@
 | 
			
		||||
        {% if not rows %}
 | 
			
		||||
        (you don't have any active shares btw)
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
	<a href="#" id="repl">π</a>
 | 
			
		||||
	<script>
 | 
			
		||||
 | 
			
		||||
var SR = {{ r|tojson }},
 | 
			
		||||
 
 | 
			
		||||
@@ -157,6 +157,7 @@
 | 
			
		||||
			<blockquote id="ad">enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!</blockquote></li>
 | 
			
		||||
			{% endif %}
 | 
			
		||||
 | 
			
		||||
			<li><a id="af" href="{{ r }}/?ru">show recent uploads</a></li>
 | 
			
		||||
			<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
 | 
			
		||||
		</ul>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ var Ls = {
 | 
			
		||||
		"ac1": "skru på no304",
 | 
			
		||||
		"ad1": "no304 stopper all bruk av cache. Hvis ikke k304 var nok, prøv denne. Vil mangedoble dataforbruk!",
 | 
			
		||||
		"ae1": "utgående:",
 | 
			
		||||
		"af1": "vis nylig opplastede filer",
 | 
			
		||||
	},
 | 
			
		||||
	"eng": {
 | 
			
		||||
		"d2": "shows the state of all active threads",
 | 
			
		||||
@@ -88,6 +89,7 @@ var Ls = {
 | 
			
		||||
		"ac1": "开启 k304",
 | 
			
		||||
		"ad1": "启用 no304 将禁用所有缓存;如果 k304 不够,可以尝试此选项。这将消耗大量的网络流量!", //m
 | 
			
		||||
		"ae1": "正在下载:", //m
 | 
			
		||||
		"af1": "显示最近上传的文件", //m
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -143,6 +143,9 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
 | 
			
		||||
| GET | `?dls` | show active downloads (do this as admin) |
 | 
			
		||||
| GET | `?ups` | show recent uploads from your IP |
 | 
			
		||||
| GET | `?ups&filter=f` | ...where URL contains `f` |
 | 
			
		||||
| GET | `?ru` | show all recent uploads |
 | 
			
		||||
| GET | `?ru&filter=f` | ...where URL contains `f` |
 | 
			
		||||
| GET | `?ru&j` | ...as json |
 | 
			
		||||
| GET | `?mime=foo` | specify return mimetype `foo` |
 | 
			
		||||
| GET | `?v` | render markdown file at URL |
 | 
			
		||||
| GET | `?v` | open image/video/audio in mediaplayer |
 | 
			
		||||
 
 | 
			
		||||
@@ -105,6 +105,9 @@ copyparty/web/mde.html,
 | 
			
		||||
copyparty/web/mde.js,
 | 
			
		||||
copyparty/web/msg.css,
 | 
			
		||||
copyparty/web/msg.html,
 | 
			
		||||
copyparty/web/rups.css,
 | 
			
		||||
copyparty/web/rups.html,
 | 
			
		||||
copyparty/web/rups.js,
 | 
			
		||||
copyparty/web/shares.css,
 | 
			
		||||
copyparty/web/shares.html,
 | 
			
		||||
copyparty/web/shares.js,
 | 
			
		||||
 
 | 
			
		||||
@@ -80,6 +80,7 @@ var tl_cpanel = {
 | 
			
		||||
		"ac1": "enable no304",
 | 
			
		||||
		"ad1": "enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!",
 | 
			
		||||
		"ae1": "active downloads:",
 | 
			
		||||
        "af1": "show recent uploads",
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -115,6 +115,7 @@ var tl_cpanel = {{
 | 
			
		||||
		"ac1": "enable no304",
 | 
			
		||||
		"ad1": "enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!",
 | 
			
		||||
		"ae1": "active downloads:",
 | 
			
		||||
        "af1": "show recent uploads",
 | 
			
		||||
	}},
 | 
			
		||||
}};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user