mirror of
https://github.com/9001/copyparty.git
synced 2026-06-22 14:02:53 -06:00
feat(权限管理): 添加角色权限系统及分享管理页面
新增角色权限系统,支持admin/editor/guest三种角色 添加权限管理页面,展示用户权限、角色及分享信息 扩展分享数据库表结构,增加公开状态、访问次数限制字段 实现角色权限自动应用到用户的功能
This commit is contained in:
parent
6e25d648a9
commit
d570f04d26
|
|
@ -74,6 +74,12 @@ if PY2:
|
||||||
|
|
||||||
|
|
||||||
LEELOO_DALLAS = "leeloo_dallas"
|
LEELOO_DALLAS = "leeloo_dallas"
|
||||||
|
|
||||||
|
ROLE_PERMISSIONS = {
|
||||||
|
"admin": {"r", "w", "m", "d", "g", "G", "h", "a", "."},
|
||||||
|
"editor": {"r", "w", "m", "g", "G", "h"},
|
||||||
|
"guest": {"r", "g"},
|
||||||
|
}
|
||||||
##
|
##
|
||||||
## you might be curious what Leeloo Dallas is doing here, so let me explain:
|
## you might be curious what Leeloo Dallas is doing here, so let me explain:
|
||||||
##
|
##
|
||||||
|
|
@ -432,6 +438,10 @@ class VFS(object):
|
||||||
self.shr_files: set[str] = set() # filenames to include from shr_src
|
self.shr_files: set[str] = set() # filenames to include from shr_src
|
||||||
self.shr_owner: str = "" # uname
|
self.shr_owner: str = "" # uname
|
||||||
self.shr_all_aps: list[tuple[str, list[VFS]]] = []
|
self.shr_all_aps: list[tuple[str, list[VFS]]] = []
|
||||||
|
self.shr_is_public: bool = False
|
||||||
|
self.shr_max_visits: int = 0
|
||||||
|
self.shr_visit_count: int = 0
|
||||||
|
self.shr_key: str = ""
|
||||||
self.aread: dict[str, list[str]] = {}
|
self.aread: dict[str, list[str]] = {}
|
||||||
self.awrite: dict[str, list[str]] = {}
|
self.awrite: dict[str, list[str]] = {}
|
||||||
self.amove: dict[str, list[str]] = {}
|
self.amove: dict[str, list[str]] = {}
|
||||||
|
|
@ -1097,6 +1107,7 @@ class AuthSrv(object):
|
||||||
self.sesa: dict[str, str] = {} # session->uname
|
self.sesa: dict[str, str] = {} # session->uname
|
||||||
self.defpw: dict[str, str] = {}
|
self.defpw: dict[str, str] = {}
|
||||||
self.grps: dict[str, list[str]] = {}
|
self.grps: dict[str, list[str]] = {}
|
||||||
|
self.roles: dict[str, str] = {}
|
||||||
self.re_pwd: Optional[re.Pattern] = None
|
self.re_pwd: Optional[re.Pattern] = None
|
||||||
self.cfg_files_loaded: list[str] = []
|
self.cfg_files_loaded: list[str] = []
|
||||||
self.badcfg1 = False
|
self.badcfg1 = False
|
||||||
|
|
@ -1116,6 +1127,49 @@ class AuthSrv(object):
|
||||||
if self.log_func:
|
if self.log_func:
|
||||||
self.log_func("auth", msg, c)
|
self.log_func("auth", msg, c)
|
||||||
|
|
||||||
|
def _apply_role_permissions(self, vfs: "VFS", unames: list[str]) -> None:
|
||||||
|
"""
|
||||||
|
Apply role-based permissions to users.
|
||||||
|
Roles: admin, editor, guest
|
||||||
|
"""
|
||||||
|
perm_map = {
|
||||||
|
"r": "uread",
|
||||||
|
"w": "uwrite",
|
||||||
|
"m": "umove",
|
||||||
|
"d": "udel",
|
||||||
|
"g": "uget",
|
||||||
|
"G": "upget",
|
||||||
|
"h": "uhtml",
|
||||||
|
"a": "uadmin",
|
||||||
|
".": "udot",
|
||||||
|
}
|
||||||
|
|
||||||
|
for uname in unames:
|
||||||
|
if uname == "*":
|
||||||
|
continue
|
||||||
|
|
||||||
|
role = self.roles.get(uname)
|
||||||
|
if not role:
|
||||||
|
continue
|
||||||
|
|
||||||
|
role_perms = ROLE_PERMISSIONS.get(role)
|
||||||
|
if not role_perms:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.args.vc:
|
||||||
|
self._l("roles", 5, "applying role permissions for user [%s] with role [%s]" % (uname, role))
|
||||||
|
|
||||||
|
for vol in vfs.all_vols.values():
|
||||||
|
if vol.vpath.startswith(self.args.shr1) if self.args.shr else False:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for perm_char in role_perms:
|
||||||
|
perm_attr = perm_map.get(perm_char)
|
||||||
|
if perm_attr:
|
||||||
|
perm_set = getattr(vol.axs, perm_attr)
|
||||||
|
if uname not in perm_set:
|
||||||
|
perm_set.add(uname)
|
||||||
|
|
||||||
def laggy_iter(self, iterable: Iterable[Any]) -> Generator[Any, None, None]:
|
def laggy_iter(self, iterable: Iterable[Any]) -> Generator[Any, None, None]:
|
||||||
"""returns [value,isFinalValue]"""
|
"""returns [value,isFinalValue]"""
|
||||||
it = iter(iterable)
|
it = iter(iterable)
|
||||||
|
|
@ -1401,6 +1455,7 @@ class AuthSrv(object):
|
||||||
catg = "[global]"
|
catg = "[global]"
|
||||||
cata = "[accounts]"
|
cata = "[accounts]"
|
||||||
catgrp = "[groups]"
|
catgrp = "[groups]"
|
||||||
|
catroles = "[roles]"
|
||||||
catx = "accs:"
|
catx = "accs:"
|
||||||
catf = "flags:"
|
catf = "flags:"
|
||||||
ap: Optional[str] = None
|
ap: Optional[str] = None
|
||||||
|
|
@ -1436,6 +1491,8 @@ class AuthSrv(object):
|
||||||
self._l(ln, 5, "begin user-accounts section")
|
self._l(ln, 5, "begin user-accounts section")
|
||||||
elif ln == catgrp:
|
elif ln == catgrp:
|
||||||
self._l(ln, 5, "begin user-groups section")
|
self._l(ln, 5, "begin user-groups section")
|
||||||
|
elif ln == catroles:
|
||||||
|
self._l(ln, 5, "begin user-roles section")
|
||||||
elif ln.startswith("[/"):
|
elif ln.startswith("[/"):
|
||||||
vp = ln[1:-1].strip("/")
|
vp = ln[1:-1].strip("/")
|
||||||
self._l(ln, 2, "define volume at URL [/{}]".format(vp))
|
self._l(ln, 2, "define volume at URL [/{}]".format(vp))
|
||||||
|
|
@ -1496,6 +1553,25 @@ class AuthSrv(object):
|
||||||
raise Exception(t + SBADCFG)
|
raise Exception(t + SBADCFG)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if cat == catroles:
|
||||||
|
try:
|
||||||
|
rn, zs1 = [zs.strip() for zs in ln.split(":", 1)]
|
||||||
|
uns = [zs.strip() for zs in zs1.split(",")]
|
||||||
|
rn = rn.lower()
|
||||||
|
valid_roles = ["admin", "editor", "guest"]
|
||||||
|
if rn not in valid_roles:
|
||||||
|
t = 'invalid role name "%s", valid roles are: %s'
|
||||||
|
raise Exception(t % (rn, ", ".join(valid_roles)))
|
||||||
|
t = "role [%s] = " % (rn,)
|
||||||
|
t += ", ".join("user [%s]" % (x,) for x in uns)
|
||||||
|
self._l(ln, 5, t)
|
||||||
|
for un in uns:
|
||||||
|
self.roles[un] = rn
|
||||||
|
except:
|
||||||
|
t = 'lines inside the [roles] section must be "rolename: user1, user2, user..."'
|
||||||
|
raise Exception(t + SBADCFG)
|
||||||
|
continue
|
||||||
|
|
||||||
if vp is not None and ap is None:
|
if vp is not None and ap is None:
|
||||||
if npass != 2:
|
if npass != 2:
|
||||||
continue
|
continue
|
||||||
|
|
@ -1960,10 +2036,17 @@ class AuthSrv(object):
|
||||||
cur2 = db.cursor()
|
cur2 = db.cursor()
|
||||||
now = time.time()
|
now = time.time()
|
||||||
for row in cur.execute("select * from sh"):
|
for row in cur.execute("select * from sh"):
|
||||||
s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1 = row
|
s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1 = row[:8]
|
||||||
|
s_mv = row[8] if len(row) > 8 else 0
|
||||||
|
s_vc = row[9] if len(row) > 9 else 0
|
||||||
|
s_pub = row[10] if len(row) > 10 else 0
|
||||||
|
|
||||||
if s_t1 and s_t1 < now:
|
if s_t1 and s_t1 < now:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if s_mv and s_vc >= s_mv:
|
||||||
|
continue
|
||||||
|
|
||||||
if self.args.shr_v:
|
if self.args.shr_v:
|
||||||
t = "loading %s share %r by %r => %r"
|
t = "loading %s share %r by %r => %r"
|
||||||
self.log(t % (s_pr, s_k, s_un, s_vp))
|
self.log(t % (s_pr, s_k, s_un, s_vp))
|
||||||
|
|
@ -1993,6 +2076,12 @@ class AuthSrv(object):
|
||||||
# still has the privs they granted, so nullmap it
|
# still has the privs they granted, so nullmap it
|
||||||
vp = "%s/%s" % (shr, s_k)
|
vp = "%s/%s" % (shr, s_k)
|
||||||
shv.nodes[s_k] = VFS(self.log_func, "", vp, vp, s_axs, shv.flags.copy())
|
shv.nodes[s_k] = VFS(self.log_func, "", vp, vp, s_axs, shv.flags.copy())
|
||||||
|
shn = shv.nodes[s_k]
|
||||||
|
shn.shr_key = s_k
|
||||||
|
shn.shr_is_public = bool(s_pub)
|
||||||
|
shn.shr_max_visits = s_mv
|
||||||
|
shn.shr_visit_count = s_vc
|
||||||
|
shn.shr_owner = s_un
|
||||||
|
|
||||||
vfs.nodes[shr] = vfs.all_vols[shr] = shv
|
vfs.nodes[shr] = vfs.all_vols[shr] = shv
|
||||||
for vol in shv.nodes.values():
|
for vol in shv.nodes.values():
|
||||||
|
|
@ -2005,6 +2094,8 @@ class AuthSrv(object):
|
||||||
zss.discard("*")
|
zss.discard("*")
|
||||||
unames = ["*"] + list(sorted(zss))
|
unames = ["*"] + list(sorted(zss))
|
||||||
|
|
||||||
|
self._apply_role_permissions(vfs, unames)
|
||||||
|
|
||||||
for perm in "read write move del get pget html admin dot".split():
|
for perm in "read write move del get pget html admin dot".split():
|
||||||
axs_key = "u" + perm
|
axs_key = "u" + perm
|
||||||
for vp, vol in vfs.all_vols.items():
|
for vp, vol in vfs.all_vols.items():
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ except:
|
||||||
|
|
||||||
from .__init__ import ANYWIN, RES, RESM, TYPE_CHECKING, EnvParams, unicode
|
from .__init__ import ANYWIN, RES, RESM, TYPE_CHECKING, EnvParams, unicode
|
||||||
from .__version__ import S_VERSION
|
from .__version__ import S_VERSION
|
||||||
from .authsrv import LEELOO_DALLAS, VFS # typechk
|
from .authsrv import LEELOO_DALLAS, ROLE_PERMISSIONS, VFS # typechk
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .qrkode import QrCode, qr2svg, qrgen
|
from .qrkode import QrCode, qr2svg, qrgen
|
||||||
from .star import StreamTar
|
from .star import StreamTar
|
||||||
|
|
@ -1505,6 +1505,9 @@ class HttpCli(object):
|
||||||
if "shares" in self.uparam:
|
if "shares" in self.uparam:
|
||||||
return self.tx_shares()
|
return self.tx_shares()
|
||||||
|
|
||||||
|
if "perms" in self.uparam:
|
||||||
|
return self.tx_perms()
|
||||||
|
|
||||||
if "dls" in self.uparam:
|
if "dls" in self.uparam:
|
||||||
return self.tx_dls()
|
return self.tx_dls()
|
||||||
|
|
||||||
|
|
@ -6336,6 +6339,95 @@ class HttpCli(object):
|
||||||
self.reply(html.encode("utf-8"), status=200)
|
self.reply(html.encode("utf-8"), status=200)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def tx_perms(self) -> bool:
|
||||||
|
if self.uname == "*":
|
||||||
|
self.loud_reply("you're not logged in")
|
||||||
|
return True
|
||||||
|
|
||||||
|
user_role = self.asrv.roles.get(self.uname, "")
|
||||||
|
is_admin = user_role == "admin"
|
||||||
|
|
||||||
|
volumes = []
|
||||||
|
vfs = self.asrv.vfs
|
||||||
|
for vp, vol in vfs.all_vols.items():
|
||||||
|
if vp.startswith(self.args.shr.strip("/") + "/") if self.args.shr else False:
|
||||||
|
continue
|
||||||
|
|
||||||
|
uaxs = vol.uaxs.get(self.uname, (False, False, False, False, False, False, False, False, False))
|
||||||
|
|
||||||
|
volumes.append({
|
||||||
|
"vpath": vp,
|
||||||
|
"read": uaxs[0],
|
||||||
|
"write": uaxs[1],
|
||||||
|
"move": uaxs[2],
|
||||||
|
"delete": uaxs[3],
|
||||||
|
"get": uaxs[4],
|
||||||
|
"admin": uaxs[7],
|
||||||
|
"shared": False,
|
||||||
|
})
|
||||||
|
|
||||||
|
for vol in volumes:
|
||||||
|
for share_vol in vfs.all_nodes.values():
|
||||||
|
if hasattr(share_vol, "shr_src") and share_vol.shr_src:
|
||||||
|
svn, _ = share_vol.shr_src
|
||||||
|
if svn.vpath == vol["vpath"]:
|
||||||
|
vol["shared"] = True
|
||||||
|
break
|
||||||
|
|
||||||
|
perms = self.asrv.get_perms("/", self.uname)
|
||||||
|
|
||||||
|
users = []
|
||||||
|
if is_admin:
|
||||||
|
acct = self.asrv.acct
|
||||||
|
for uname in sorted(acct.keys()):
|
||||||
|
if uname == "*":
|
||||||
|
continue
|
||||||
|
role = self.asrv.roles.get(uname, "")
|
||||||
|
user_perms = self.asrv.get_perms("/", uname)
|
||||||
|
users.append({
|
||||||
|
"name": uname,
|
||||||
|
"role": role,
|
||||||
|
"perms": user_perms,
|
||||||
|
})
|
||||||
|
|
||||||
|
shares = []
|
||||||
|
if self.args.shr:
|
||||||
|
idx = self.conn.get_u2idx()
|
||||||
|
if idx and hasattr(idx, "p_end"):
|
||||||
|
cur = idx.get_shr()
|
||||||
|
if cur:
|
||||||
|
rows = cur.execute("select * from sh").fetchall()
|
||||||
|
now = int(time.time())
|
||||||
|
for row in rows:
|
||||||
|
s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1, s_mv, s_vc, s_pub = row
|
||||||
|
if is_admin or s_un == self.uname:
|
||||||
|
shares.append({
|
||||||
|
"k": s_k,
|
||||||
|
"vp": s_vp,
|
||||||
|
"pr": s_pr,
|
||||||
|
"un": s_un,
|
||||||
|
"pub": bool(s_pub),
|
||||||
|
"t1": s_t1,
|
||||||
|
"vc": s_vc,
|
||||||
|
"mv": s_mv,
|
||||||
|
})
|
||||||
|
|
||||||
|
html = self.j2s(
|
||||||
|
"perms",
|
||||||
|
this=self,
|
||||||
|
uname=self.uname,
|
||||||
|
role=user_role,
|
||||||
|
perms=perms,
|
||||||
|
is_admin=is_admin,
|
||||||
|
volumes=volumes,
|
||||||
|
users=users,
|
||||||
|
shares=shares,
|
||||||
|
shr=self.args.shr,
|
||||||
|
now=int(time.time()),
|
||||||
|
)
|
||||||
|
self.reply(html.encode("utf-8"), status=200)
|
||||||
|
return True
|
||||||
|
|
||||||
def handle_eshare(self) -> bool:
|
def handle_eshare(self) -> bool:
|
||||||
idx = self.conn.get_u2idx()
|
idx = self.conn.get_u2idx()
|
||||||
if not idx or not hasattr(idx, "p_end"):
|
if not idx or not hasattr(idx, "p_end"):
|
||||||
|
|
@ -6458,7 +6550,11 @@ class HttpCli(object):
|
||||||
raise Pebkac(400, "you dont have all the perms you tried to grant")
|
raise Pebkac(400, "you dont have all the perms you tried to grant")
|
||||||
|
|
||||||
zs = vfs.flags["shr_who"]
|
zs = vfs.flags["shr_who"]
|
||||||
if zs == "auth" and self.uname != "*":
|
user_role = self.asrv.roles.get(self.uname)
|
||||||
|
|
||||||
|
if user_role in ["admin", "editor"]:
|
||||||
|
pass
|
||||||
|
elif zs == "auth" and self.uname != "*":
|
||||||
pass
|
pass
|
||||||
elif zs == "a" and self.uname in vfs.axs.uadmin:
|
elif zs == "a" and self.uname in vfs.axs.uadmin:
|
||||||
pass
|
pass
|
||||||
|
|
@ -6482,8 +6578,12 @@ class HttpCli(object):
|
||||||
exp = now + exp * 60 if exp else 0
|
exp = now + exp * 60 if exp else 0
|
||||||
pr = "".join(zc for zc, zb in zip("rwmdg.", s_axsd) if zb)
|
pr = "".join(zc for zc, zb in zip("rwmdg.", s_axsd) if zb)
|
||||||
|
|
||||||
q = "insert into sh values (?,?,?,?,?,?,?,?)"
|
max_visits = int(req.get("max_visits") or 0)
|
||||||
cur.execute(q, (skey, pw, vp, pr, len(fns), self.uname, now, exp))
|
visit_count = 0
|
||||||
|
is_public = 1 if req.get("is_public") else 0
|
||||||
|
|
||||||
|
q = "insert into sh values (?,?,?,?,?,?,?,?,?,?,?)"
|
||||||
|
cur.execute(q, (skey, pw, vp, pr, len(fns), self.uname, now, exp, max_visits, visit_count, is_public))
|
||||||
|
|
||||||
q = "insert into sf values (?,?)"
|
q = "insert into sf values (?,?)"
|
||||||
for fn in fns:
|
for fn in fns:
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ else:
|
||||||
|
|
||||||
VER_IDP_DB = 1
|
VER_IDP_DB = 1
|
||||||
VER_SESSION_DB = 1
|
VER_SESSION_DB = 1
|
||||||
VER_SHARES_DB = 2
|
VER_SHARES_DB = 3
|
||||||
|
|
||||||
|
|
||||||
class SvcHub(object):
|
class SvcHub(object):
|
||||||
|
|
@ -757,15 +757,14 @@ class SvcHub(object):
|
||||||
|
|
||||||
sch1 = [
|
sch1 = [
|
||||||
r"create table kv (k text, v int)",
|
r"create table kv (k text, v int)",
|
||||||
r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int)",
|
r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int, mv int, vc int, pub int)",
|
||||||
# sharekey, password, src, perms, numFiles, owner, created, expires
|
|
||||||
]
|
]
|
||||||
sch2 = [
|
sch2 = [
|
||||||
r"create table sf (k text, vp text)",
|
r"create table sf (k text, vp text)",
|
||||||
r"create index sf_k on sf(k)",
|
r"create index sf_k on sf(k)",
|
||||||
r"create index sh_k on sh(k)",
|
r"create index sh_k on sh(k)",
|
||||||
r"create index sh_t1 on sh(t1)",
|
r"create index sh_t1 on sh(t1)",
|
||||||
r"insert into kv values ('sver', 2)",
|
r"insert into kv values ('sver', 3)",
|
||||||
]
|
]
|
||||||
|
|
||||||
assert db # type: ignore # !rm
|
assert db # type: ignore # !rm
|
||||||
|
|
@ -782,6 +781,13 @@ class SvcHub(object):
|
||||||
cur.execute("update sh set st = 0")
|
cur.execute("update sh set st = 0")
|
||||||
self.log("root", "shares-db schema upgrade ok")
|
self.log("root", "shares-db schema upgrade ok")
|
||||||
|
|
||||||
|
if sver == 2:
|
||||||
|
cur.execute("alter table sh add column mv int")
|
||||||
|
cur.execute("alter table sh add column vc int")
|
||||||
|
cur.execute("alter table sh add column pub int")
|
||||||
|
cur.execute("update sh set mv = 0, vc = 0, pub = 0")
|
||||||
|
self.log("root", "shares-db schema upgrade to v3 ok")
|
||||||
|
|
||||||
if sver < VER_SHARES_DB:
|
if sver < VER_SHARES_DB:
|
||||||
cur.execute("delete from kv where k='sver'")
|
cur.execute("delete from kv where k='sver'")
|
||||||
cur.execute("insert into kv values('sver',?)", (VER_SHARES_DB,))
|
cur.execute("insert into kv values('sver',?)", (VER_SHARES_DB,))
|
||||||
|
|
|
||||||
163
copyparty/web/perms.html
Normal file
163
copyparty/web/perms.html
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" id="ht_perms">
|
||||||
|
|
||||||
|
<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="robots" content="noindex, nofollow">
|
||||||
|
<meta name="theme-color" content="#{{ tcolor }}">
|
||||||
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
|
||||||
|
{{ html_head }}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="wrap">
|
||||||
|
<a href="{{ r }}/?perms">refresh</a>
|
||||||
|
<a href="{{ r }}/?shares">my shares</a>
|
||||||
|
<a href="{{ r }}/?h">control-panel</a>
|
||||||
|
<a href="{{ r }}/">browse</a>
|
||||||
|
|
||||||
|
<h2>User Role and Permissions Management</h2>
|
||||||
|
|
||||||
|
<div id="user-info">
|
||||||
|
<h3>Your Profile</h3>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
|
<td>{{ uname }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Role</th>
|
||||||
|
<td>{{ role or "default" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Permissions</th>
|
||||||
|
<td>{{ perms }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Role Descriptions</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Permissions</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>admin</td>
|
||||||
|
<td>r, w, m, d, g, G, h, a, .</td>
|
||||||
|
<td>Full access: can read, write, move, delete, and manage all files and permissions</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>editor</td>
|
||||||
|
<td>r, w, m, g, G, h</td>
|
||||||
|
<td>Can read, write, and move files, but cannot delete or manage permissions</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>guest</td>
|
||||||
|
<td>r, g</td>
|
||||||
|
<td>Can only view public files, cannot edit or share</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Your Volumes</h3>
|
||||||
|
<table id="volumes">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Volume</th>
|
||||||
|
<th>Read</th>
|
||||||
|
<th>Write</th>
|
||||||
|
<th>Move</th>
|
||||||
|
<th>Delete</th>
|
||||||
|
<th>Get</th>
|
||||||
|
<th>Admin</th>
|
||||||
|
<th>Shared</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for vol in volumes %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ r }}/{{ vol.vpath|e }}">/{{ vol.vpath|e }}</a></td>
|
||||||
|
<td class="{{ 'yes' if vol.read else 'no' }}">{{ '✓' if vol.read else '✗' }}</td>
|
||||||
|
<td class="{{ 'yes' if vol.write else 'no' }}">{{ '✓' if vol.write else '✗' }}</td>
|
||||||
|
<td class="{{ 'yes' if vol.move else 'no' }}">{{ '✓' if vol.move else '✗' }}</td>
|
||||||
|
<td class="{{ 'yes' if vol.delete else 'no' }}">{{ '✓' if vol.delete else '✗' }}</td>
|
||||||
|
<td class="{{ 'yes' if vol.get else 'no' }}">{{ '✓' if vol.get else '✗' }}</td>
|
||||||
|
<td class="{{ 'yes' if vol.admin else 'no' }}">{{ '✓' if vol.admin else '✗' }}</td>
|
||||||
|
<td class="{{ 'yes' if vol.shared else 'no' }}">{{ '✓' if vol.shared else '✗' }}</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{%- if is_admin %}
|
||||||
|
<h3>All Users (Admin Only)</h3>
|
||||||
|
<table id="all-users">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Permissions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for user in users %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ user.name|e }}</td>
|
||||||
|
<td>{{ user.role or "default" }}</td>
|
||||||
|
<td>{{ user.perms }}</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- if shares %}
|
||||||
|
<h3>Active Shares</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Share Key</th>
|
||||||
|
<th>Source</th>
|
||||||
|
<th>Permissions</th>
|
||||||
|
<th>Owner</th>
|
||||||
|
<th>Public</th>
|
||||||
|
<th>Expires</th>
|
||||||
|
<th>Visits</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for share in shares %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ r }}{{ shr }}{{ share.k }}/">{{ share.k }}</a></td>
|
||||||
|
<td><a href="{{ r }}/{{ share.vp|e }}">/{{ share.vp|e }}</a></td>
|
||||||
|
<td>{{ share.pr }}</td>
|
||||||
|
<td>{{ share.un|e }}</td>
|
||||||
|
<td class="{{ 'yes' if share.pub else 'no' }}">{{ '✓' if share.pub else '✗' }}</td>
|
||||||
|
<td>{{ "never" if not share.t1 else "expired" if share.t1 < now else "in " ~ ((share.t1 - now) // 60) ~ " min" }}</td>
|
||||||
|
<td>{{ share.vc }}{% if share.mv %} / {{ share.mv }}{% endif %}</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{%- endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var SR="{{ r }}",
|
||||||
|
lang="{{ lang }}",
|
||||||
|
dfavico="{{ favico }}";
|
||||||
|
|
||||||
|
var STG = window.localStorage;
|
||||||
|
document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme }}";
|
||||||
|
</script>
|
||||||
|
<script src="{{ r }}/.cpr/w/util.js?_={{ ts }}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in a new issue