From 9b2bc907aa035752686c6d9746225dd0d3c08b8b Mon Sep 17 00:00:00 2001 From: ameniboukattaya Date: Sun, 21 Jun 2026 18:05:52 +0100 Subject: [PATCH] Change : display --- platform/static/css/style.css | 107 +++++++++++++++++++++++++- platform/static/js/platform.js | 39 +++++++--- platform/templates/pages/restore.html | 82 ++++++++++++++------ platform/templates/pages/users.html | 40 ++++++++-- 4 files changed, 227 insertions(+), 41 deletions(-) diff --git a/platform/static/css/style.css b/platform/static/css/style.css index 8028b1b..6021230 100644 --- a/platform/static/css/style.css +++ b/platform/static/css/style.css @@ -454,7 +454,7 @@ a.sidebar-brand-link { .btn-lg, .btn.lg { padding: 12px 24px; font-size: 14px; } /* ── RESTORE FORM ──────────────────────────────────────────── */ -.restore-form { max-width: 700px; display: flex; flex-direction: column; gap: 0; } +.restore-form { max-width: 820px; display: flex; flex-direction: column; gap: 0; } .form-section { padding: 20px 0; border-bottom: 1px solid var(--border); margin-bottom: 0; } .form-section:last-child { border-bottom: none; } .form-section-title { @@ -476,6 +476,7 @@ a.sidebar-brand-link { .form-input[readonly] { opacity: 0.6; cursor: default; } .form-row { display: flex; gap: 12px; } .form-row .form-group { flex: 1; } +.form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 14px; align-items: end; } .form-note { font-size: 12px; color: var(--text3); margin-top: 8px; } .form-check { display: flex; align-items: center; gap: 8px; font-size: 13px; cursor: pointer; } .form-check input { accent-color: var(--accent); width: 14px; height: 14px; } @@ -498,6 +499,106 @@ a.sidebar-brand-link { .radio-desc { font-size: 11px; color: var(--text3); font-family: var(--mono); margin-top: 2px; } .radio-desc code { background: var(--border2); padding: 1px 5px; border-radius: 4px; font-family: var(--mono); } +/* ── APP PICKER (restore step 3) ───────────────────────────── */ +.app-picker-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); + gap: 10px; +} +.app-picker-card { cursor: pointer; display: block; position: relative; } +.app-picker-card input { + position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; +} +.app-picker-body { + display: flex; align-items: center; gap: 12px; + padding: 13px 14px; border: 1px solid var(--border2); + border-radius: var(--radius); background: var(--surface2); + transition: border-color var(--trans), background var(--trans), opacity var(--trans); +} +.app-picker-card:hover .app-picker-body { border-color: var(--border2); background: var(--bg4); } +.app-picker-card input:checked + .app-picker-body { + border-color: var(--accent); background: rgba(59,130,246,0.08); +} +.app-picker-card input:not(:checked) + .app-picker-body { opacity: 0.62; } +[data-theme="light"] .app-picker-card input:checked + .app-picker-body { background: rgba(37,99,235,0.06); } +.app-picker-icon { font-size: 20px; line-height: 1; flex-shrink: 0; } +.app-picker-name { font-size: 13px; font-weight: 600; color: var(--text); line-height: 1.3; } +.app-picker-desc { font-size: 11px; color: var(--text3); margin-top: 2px; } +.app-picker-check { + margin-left: auto; width: 22px; height: 22px; border-radius: 50%; + border: 1.5px solid var(--border2); display: flex; align-items: center; justify-content: center; + font-size: 10px; color: transparent; flex-shrink: 0; + transition: background var(--trans), border-color var(--trans), color var(--trans); +} +.app-picker-card input:checked + .app-picker-body .app-picker-check { + background: var(--accent); border-color: var(--accent); color: #fff; +} +.app-picker-actions { margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; } +.app-picker-note { + color: var(--text3); font-size: 12px; line-height: 1.55; margin-bottom: 14px; + padding: 10px 14px; border-radius: 8px; + background: rgba(59,130,246,0.06); border: 1px solid rgba(59,130,246,0.12); + display: flex; align-items: flex-start; gap: 10px; +} +.app-picker-note i { color: var(--accent2); margin-top: 2px; flex-shrink: 0; } + +/* ── USERS GRID ────────────────────────────────────────────── */ +.users-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 12px; +} +.user-card { + padding: 16px; border-radius: 12px; + background: var(--surface2); border: 1px solid var(--border); + cursor: pointer; + transition: border-color 0.15s, background 0.15s, box-shadow 0.15s; +} +.user-card:hover { border-color: var(--accent); background: var(--bg4); } +.user-card.selected { + border-color: var(--accent); + background: rgba(59,130,246,0.08); + box-shadow: 0 0 0 1px rgba(59,130,246,0.15); +} +[data-theme="light"] .user-card.selected { background: rgba(37,99,235,0.06); } +.user-card-top { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; } +.user-avatar { + width: 42px; height: 42px; border-radius: 10px; + display: flex; align-items: center; justify-content: center; + font-weight: 700; font-size: 17px; color: #fff; flex-shrink: 0; +} +.user-name { font-size: 14px; font-weight: 600; color: var(--text); line-height: 1.3; } +.user-uid { font-size: 11px; color: var(--text3); font-family: var(--mono); margin-top: 2px; } +.user-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; min-height: 24px; } +.user-tag { + display: inline-flex; align-items: center; gap: 5px; + padding: 3px 9px; border-radius: 20px; font-size: 10px; font-weight: 600; + font-family: var(--mono); letter-spacing: 0.03em; +} +.user-tag.docker { background: rgba(37,99,235,0.12); color: #60a5fa; border: 1px solid rgba(37,99,235,0.2); } +.user-tag.linger { background: rgba(167,139,250,0.12); color: #a78bfa; border: 1px solid rgba(167,139,250,0.2); } +.user-tag.vdisk { background: rgba(34,211,238,0.12); color: var(--cyan); border: 1px solid rgba(34,211,238,0.2); } +.user-stats { + display: flex; gap: 10px; padding-top: 12px; + border-top: 1px solid var(--border); +} +.user-stat { + flex: 1; display: flex; flex-direction: column; gap: 3px; + font-size: 12px; color: var(--text3); +} +.user-stat-label { + font-size: 9px; font-weight: 700; letter-spacing: 0.1em; + text-transform: uppercase; font-family: var(--mono); +} +.user-stat strong { font-size: 13px; color: var(--text); font-family: var(--mono); } +.user-detail-table-wrap { + padding: 0 4px 4px; overflow-x: auto; +} +.user-containers-table { margin-top: 4px; } +.user-containers-table th { white-space: nowrap; } +.user-containers-table .ct-image { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.user-containers-table .ct-ports { font-family: var(--mono); font-size: 11px; color: var(--text3); max-width: 180px; } + /* ── LOG CONSOLE ───────────────────────────────────────────── */ .log-header { display: flex; align-items: center; justify-content: space-between; @@ -522,8 +623,8 @@ a.sidebar-brand-link { background: var(--surface2); gap: 10px; flex-wrap: wrap; border: 1px solid var(--border); } -.backup-name { font-family: var(--mono); font-size: 12px; color: var(--text2); } -.backup-actions { display: flex; gap: 6px; align-items: center; flex-shrink: 0; } +.backup-name { font-family: var(--mono); font-size: 12px; color: var(--text2); flex: 1; min-width: 0; word-break: break-all; } +.backup-actions { display: flex; gap: 5px; align-items: center; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; } .btn-delete-backup { color: var(--red) !important; opacity: 0.7; transition: opacity 0.15s; } .btn-delete-backup:hover { opacity: 1; } .btn-audit { color: var(--cyan) !important; opacity: 0.85; } diff --git a/platform/static/js/platform.js b/platform/static/js/platform.js index 7c11589..340ff65 100644 --- a/platform/static/js/platform.js +++ b/platform/static/js/platform.js @@ -382,6 +382,10 @@ async function refreshBackupsList() { } catch (_) {} } +function jsStr(s) { + return String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'"); +} + function renderBackupList(items, id, source) { const node = el(id); if (!node) return; @@ -389,15 +393,26 @@ function renderBackupList(items, id, source) { node.innerHTML = '
No backups
'; return; } - node.innerHTML = items.map((b) => ` -
- ${b} + node.innerHTML = items.map((b) => { + const safe = jsStr(b); + const uploadBtn = source === 'local' && typeof quickUploadR2 === 'function' + ? `` + : ''; + const detailsBtn = typeof showBackupDetails === 'function' + ? `` + : ''; + return ` +
+ ${escapeHtml(b)}
- - - + ${detailsBtn} + + + ${uploadBtn} +
-
`).join(''); +
`; + }).join(''); } async function auditBackup(source, filename, btn) { @@ -537,13 +552,19 @@ async function loadUsers() { grid.innerHTML = '
No users
'; return; } - grid.innerHTML = users.map((u, i) => `
${u.name[0].toUpperCase()}
${u.name}
uid ${u.uid}
${u.has_docker ? ' docker' : ''}${u.linger ? 'linger' : ''}${u.has_vdisk ? '💾 vdisk' : ''}
Disk: ${u.disk_used}
Ctrs: ${u.container_count}
`).join(''); + grid.innerHTML = users.map((u, i) => `
${escapeHtml(u.name[0].toUpperCase())}
${escapeHtml(u.name)}
uid ${u.uid}
${u.has_docker ? ' docker' : ''}${u.linger ? 'linger' : ''}${u.has_vdisk ? ' vdisk' : ''}
Disk${escapeHtml(u.disk_used)}
Containers${u.container_count}
`).join(''); } catch (_) {} } async function loadUserContainers(username) { selectedUser = username; - if (el('user-detail-panel')) el('user-detail-panel').style.display = ''; + document.querySelectorAll('.user-card').forEach((card) => { + card.classList.toggle('selected', card.dataset.user === username); + }); + if (el('user-detail-panel')) { + el('user-detail-panel').style.display = ''; + el('user-detail-panel').scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } setText('user-detail-title', `${username}'s Containers`); const body = el('user-containers-body'); if (!body) return; diff --git a/platform/templates/pages/restore.html b/platform/templates/pages/restore.html index 880a8b0..403d51d 100644 --- a/platform/templates/pages/restore.html +++ b/platform/templates/pages/restore.html @@ -115,36 +115,71 @@
STEP 3 — SELECT APPS TO RESTORE
-

- All apps are restored by default. Uncheck the ones you don't want touched — - only the checked apps will be restored, and they're force-restored even if - currently running and healthy. +

+ + All apps are selected by default. Uncheck any app you want to leave untouched — + only checked apps will be restored, and they are force-restored even if currently running and healthy.

-
-
{% for u in users %} -
-
{{ u.name[0].upper() }}
{{ u.name }}
uid {{ u.uid }}
-
{% if u.has_docker %} docker{% endif %}{% if u.linger %}linger{% endif %}{% if u.has_vdisk %}💾 vdisk{% endif %}
-
Disk: {{ u.disk_used }}
Ctrs: {{ u.container_count }}
+
+
+
{{ u.name[0].upper() }}
+
+
{{ u.name }}
+
uid {{ u.uid }}
+
+
+
+ {% if u.has_docker %} docker{% endif %} + {% if u.linger %} linger{% endif %} + {% if u.has_vdisk %} vdisk{% endif %} +
+
+
Disk{{ u.disk_used }}
+
Containers{{ u.container_count }}
+
{% else %}
No non-system users
@@ -36,9 +49,24 @@ {% endblock %}