forked from retoor/devplacepy
feat: restrict backup archive download to primary admin and hide admin-hidden projects from other admins
- Add `get_admin_uids()` and `get_primary_admin_uid()` to database.py for resolving the earliest-created admin - Modify `can_view_project()` in content.py so a project hidden by an admin is invisible to other admins (both web UI and REST API) - Update `_download_url()` and `_backup_payload()` in admin/backups.py to accept a `can_download` flag, gating the download endpoint with `is_primary_admin()` - Remove `role` from `_user_facts()` in docs_live.py to avoid leaking admin status in live docs - Update doc summaries in docs_api.py to reflect the new admin-visibility and backup-download semantics
This commit is contained in:
@@ -585,7 +585,7 @@ img {
|
||||
.topnav-user:hover { background: var(--bg-card); }
|
||||
.topnav-user-info { display: flex; flex-direction: column; line-height: 1.2; }
|
||||
.topnav-user-name { font-size: 0.8125rem; font-weight: 600; color: var(--text-primary); }
|
||||
.topnav-user-role { font-size: 0.6875rem; color: var(--text-muted); }
|
||||
.topnav-user-level { font-size: 0.6875rem; color: var(--text-muted); }
|
||||
.topnav-user-dropdown { position: relative; }
|
||||
.topnav-user-dropdown .dropdown-menu {
|
||||
display: none;
|
||||
@@ -724,7 +724,7 @@ img {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.topnav-mobile-user-role {
|
||||
.topnav-mobile-user-level {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.post-author-role {
|
||||
.post-author-level {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.post-detail-role {
|
||||
.post-detail-level {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
|
||||
@@ -36,13 +36,6 @@
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.profile-role {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.profile-stats {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -18,7 +18,8 @@ class AiUsageMonitor {
|
||||
if (!this.root) return;
|
||||
if (this.windowSelect) {
|
||||
this.windowSelect.addEventListener("change", () => {
|
||||
this.hours = parseInt(this.windowSelect.value, 10) || 48;
|
||||
const parsed = parseInt(this.windowSelect.value, 10);
|
||||
this.hours = Number.isNaN(parsed) ? 48 : parsed;
|
||||
this.subscribe();
|
||||
this.poll();
|
||||
});
|
||||
@@ -132,7 +133,8 @@ class AiUsageMonitor {
|
||||
render(data) {
|
||||
this.root.textContent = "";
|
||||
if (this.generated && data.generated_at) {
|
||||
this.generated.textContent = `Window: ${data.window_hours}h - updated ${DateFormat.format(data.generated_at, true)}`;
|
||||
const windowLabel = data.window_hours ? `${data.window_hours}h` : "all time";
|
||||
this.generated.textContent = `Window: ${windowLabel} - updated ${DateFormat.format(data.generated_at, true)}`;
|
||||
}
|
||||
if (!data.requests) {
|
||||
this.root.appendChild(this.el("p", "admin-empty", "No AI gateway traffic recorded in this window yet."));
|
||||
|
||||
@@ -256,10 +256,18 @@ class BackupMonitor {
|
||||
|
||||
backupActions(backup) {
|
||||
const cell = this.el("td", "backups-actions");
|
||||
if (backup.download_url) {
|
||||
const link = this.el("a", "admin-btn admin-btn-sm", "Download");
|
||||
link.href = backup.download_url;
|
||||
cell.appendChild(link);
|
||||
if (backup.status === "done") {
|
||||
if (this.canDownload && backup.download_url) {
|
||||
const link = this.el("a", "admin-btn admin-btn-sm", "Download");
|
||||
link.href = backup.download_url;
|
||||
cell.appendChild(link);
|
||||
} else {
|
||||
const blocked = this.el("button", "admin-btn admin-btn-sm", "Download");
|
||||
blocked.disabled = true;
|
||||
blocked.setAttribute("aria-disabled", "true");
|
||||
blocked.title = "Not available";
|
||||
cell.appendChild(blocked);
|
||||
}
|
||||
}
|
||||
const remove = this.el("button", "admin-btn admin-btn-sm admin-btn-danger", "Delete");
|
||||
remove.dataset.action = "delete-backup";
|
||||
@@ -321,6 +329,7 @@ class BackupMonitor {
|
||||
}
|
||||
|
||||
render(data) {
|
||||
this.canDownload = !!data.can_download_backups;
|
||||
if (this.generated && data.generated_at) {
|
||||
this.generated.textContent = `Updated ${DateFormat.format(data.generated_at, true)}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user