diff --git a/static/admin.html b/static/admin.html index 275b0b3..c67657e 100644 --- a/static/admin.html +++ b/static/admin.html @@ -22,7 +22,7 @@ Docs Tokens Admin - 🔑 Credentials + 🔑 Credentials Logout⚙ Settings
diff --git a/static/connections.html b/static/connections.html index 9282cd4..7ec87d3 100644 --- a/static/connections.html +++ b/static/connections.html @@ -216,8 +216,6 @@ display: flex; gap: var(--s-1); align-items: center; } .session-card-info .card-meta .type-badge { font-size: 10px; min-width: 36px; } - /* Proxmox VE brand orange badge */ - .type-badge.type-proxmox { background: #e57000; color: #000; padding: 1px 6px; border-radius: 3px; font-weight: 600; } .session-card.dormant { opacity: 0.75; } .session-card.dormant .session-card-thumb { filter: brightness(0.7); } .card-actions { @@ -561,6 +559,18 @@ background: rgba(255,255,255,0.015); } .main { min-height: 0; overflow-y: auto; overflow-x: hidden; padding-right: var(--s-2); } + /* The folder header (name, description, search, actions, filters) + pins to the top of the entries pane, so it stays under the nav and + the active-sessions strip while a long table scrolls beneath it. */ + #entries-header { + position: sticky; top: 0; z-index: 30; + background: var(--bg); + padding-bottom: var(--s-2); + margin-bottom: var(--s-2); + box-shadow: 0 6px 10px -8px rgba(0, 0, 0, 0.6); + } + #entries-header .folder-actions { margin-bottom: 0; } + #entries-header #entries-filters { margin: var(--s-2) 0 0 0; } /* Sidebar panel: one pinned header band (label, new-folder button, filter) on a surface tone, then the list with hairline dividers. @@ -611,7 +621,7 @@ Docs Tokens Admin - 🔑 Credentials + 🔑 Credentials Logout ⚙ Settings @@ -681,6 +691,7 @@ +

Select a folder to view entries.

@@ -1334,7 +1345,8 @@
- + + @@ -174,12 +175,10 @@ Docs Tokens Admin - 🔑 Credentials + 🔑 Credentials Logout⚙ Settings
-

Recordings

-
@@ -197,7 +196,10 @@
- +
+ +
+
@@ -373,9 +375,24 @@ function recSearchQuery() { return (document.getElementById('rec-search').value || '').toLowerCase(); } + // Shared facet filters (filters.js): Type, Folder, User; applied on top + // of the text search. Selections persist in sessionStorage for this tab. + var recFilters = rgFilters.create({ + mount: document.getElementById('rec-filters'), + id: 'recordings', + facets: [ + { key: 'type', label: 'Type', value: function(r) { return r.session_type; }, + render: function(v) { return '' + escapeHtml(v) + ''; } }, + { key: 'folder', label: 'Folder', value: function(r) { return r.folder; } }, + { key: 'user', label: 'User', value: function(r) { return r.user; } } + ], + onChange: function() { recPage = 1; renderRecordings(filterRecordings(recSearchQuery())); } + }); function filterRecordings(q) { - if (!q) return allRecordings; - return allRecordings.filter(function(r) { + recFilters.update(allRecordings); + var list = recFilters.apply(allRecordings); + if (!q) return list; + return list.filter(function(r) { return (r.name || '').toLowerCase().indexOf(q) >= 0 || (r.user || '').toLowerCase().indexOf(q) >= 0 || (r.entry_display_name || '').toLowerCase().indexOf(q) >= 0 || @@ -453,7 +470,7 @@ slice.forEach(function(r) { var date = r.modified ? new Date(r.modified).toLocaleString() : (r.created_at ? new Date(r.created_at).toLocaleString() : ''); var entryName = r.entry_display_name || r.address_book_entry || r.name; - var typeBadge = r.session_type ? '' + r.session_type + '' : ''; + var typeBadge = r.session_type ? '' + escapeHtml(r.session_type) + '' : ''; html += '' + '' + entryNameHtml(entryName) + '' + '' + escapeHtml(r.user || '') + '' + diff --git a/static/reports.html b/static/reports.html index 0d6fe3c..75ceb42 100644 --- a/static/reports.html +++ b/static/reports.html @@ -5,17 +5,6 @@ rustguac - Reports + @@ -85,12 +75,10 @@ Docs Tokens Admin - 🔑 Credentials + 🔑 Credentials Logout⚙ Settings
-

Reports

-
Total Sessions
@@ -112,9 +100,10 @@

Session History

-
- - +
+ +
+
@@ -215,6 +204,13 @@ return h; } + // "host (Tenant)" display names, as the NetBox sync writes them, read + // as the host over a quieter tenant line (same as Connections). + function entryNameHtml(label) { + var m = /^(.*\S)\s+\(([^()]+)\)$/.exec(label || ''); + if (!m) return escapeHtml(label || ''); + return '' + escapeHtml(m[1]) + '' + escapeHtml(m[2]) + ''; + } function escapeHtml(s) { if (!s) return ''; var d = document.createElement('div'); @@ -319,7 +315,9 @@ return 0; }); - // Apply text filter + // Facet filters (filters.js), then the text filter + historyFilters.update(sessionsData); + sorted = historyFilters.apply(sorted); var filterText = (document.getElementById('filter-input').value || '').toLowerCase(); if (filterText) { sorted = sorted.filter(function(s) { @@ -350,7 +348,7 @@ } html += '' + '' + - '' + + '' + '' + '' + '' + @@ -415,6 +413,19 @@ document.getElementById('filter-input').addEventListener('input', function() { applySortAndRender(); }); + // Shared facet filters (filters.js): Type, Folder, User, Status. + // Selections persist in sessionStorage for this tab. + var historyFilters = rgFilters.create({ + mount: document.getElementById('history-filters'), + id: 'reports', + facets: [ + { key: 'type', label: 'Type', value: function(s) { return s.session_type; }, render: typeBadge }, + { key: 'folder', label: 'Folder', value: function(s) { return s.address_book_folder; } }, + { key: 'user', label: 'User', value: function(s) { return s.created_by; } }, + { key: 'status', label: 'Status', value: function(s) { return s.status; } } + ], + onChange: function() { applySortAndRender(); } + }); // ── Export CSV ── document.getElementById('export-csv-btn').addEventListener('click', function() { @@ -446,7 +457,7 @@ var html = ''; data.forEach(function(c) { html += '' + - '' + + '' + '' + '' + '' + diff --git a/static/rustguac.css b/static/rustguac.css index 57dcb2e..1bb5727 100644 --- a/static/rustguac.css +++ b/static/rustguac.css @@ -93,6 +93,8 @@ nav a { line-height: 1.4; } nav a:hover { color: var(--text); text-decoration: none; } +/* The key glyph before Credentials renders as a large colour emoji; keep it to text height. */ +nav .nav-key { font-size: 0.75em; vertical-align: 0.05em; margin-right: 0.1em; } nav a.active { color: var(--primary); font-weight: bold; } nav a.active::after { content: ''; @@ -353,12 +355,14 @@ tbody tr:last-child td { border-bottom: none; } .type-vnc { background: var(--type-vnc-bg); color: var(--type-vnc-fg); } .type-web { background: var(--type-web-bg); color: var(--type-web-fg); } .type-vdi { background: var(--type-vdi-bg); color: var(--type-vdi-fg); } +.type-proxmox { background: #e57000; color: #000; } /* ── Section heading strip (used above grids/lists) ──────── */ .section-head { display: flex; - align-items: baseline; + align-items: center; gap: var(--s-2); + min-height: var(--ctl-sm); margin-bottom: var(--s-3); padding-bottom: var(--s-2); border-bottom: 1px solid var(--border); @@ -870,3 +874,139 @@ html:not([data-role-level="4"]) .admin-only { display: none !important; } "host (Tenant)" display names read as the host over a quieter subline. */ .entry-name { display: block; color: var(--text); } .entry-sub { display: block; font-size: var(--fz-xs); color: var(--text-dim); margin-top: 1px; letter-spacing: 0.02em; } + +/* ── Facet filters (filters.js) ──────────────────────────── */ +.rg-filters { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--s-2); +} +.rg-filters-label { + color: var(--text-muted); + font-size: var(--fz-sm); + text-transform: uppercase; + letter-spacing: 0.12em; +} +.rg-facet { position: relative; } +.rg-facet[hidden] { display: none; } +.rg-facet-btn { + display: inline-flex; + align-items: center; + gap: var(--s-1); + height: var(--ctl-sm); + padding: 0 var(--s-2); + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-muted); + font-family: monospace; + font-size: var(--fz-sm); + cursor: pointer; + white-space: nowrap; +} +/* The generic button:hover paints the primary colour; these stay on the input tone. */ +.rg-facet-btn:hover, .rg-facet-btn:focus, .rg-facet.open .rg-facet-btn { background: var(--input); border-color: var(--accent); color: var(--text); } +.rg-facet-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } +.rg-facet.active .rg-facet-btn { border-color: var(--accent); color: var(--accent); } +.rg-facet.active .rg-facet-btn:hover, .rg-facet.active .rg-facet-btn:focus { color: var(--accent); } +.rg-facet-n { + background: var(--accent); + color: var(--bg); + border-radius: 999px; + padding: 0 0.55em; + font-size: var(--fz-xs); + font-weight: bold; + line-height: 1.6; + max-width: 180px; + overflow: hidden; + text-overflow: ellipsis; +} +.rg-facet-n.one { background: transparent; color: var(--accent); padding: 0; font-weight: normal; font-size: var(--fz-sm); } +.rg-facet-n.one::before { content: "\00b7\00a0"; color: var(--text-dim); } +.rg-facet-n[hidden] { display: none; } +.rg-facet-caret { font-size: 0.7em; opacity: 0.6; margin-left: 1px; } +.rg-facet.open .rg-facet-caret { transform: rotate(180deg); } +.rg-facet-menu { + position: absolute; + top: calc(100% + 4px); + left: 0; + z-index: 600; + min-width: 260px; + max-width: min(440px, calc(100vw - 32px)); + padding: var(--s-2); + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: 0 10px 28px rgba(0, 0, 0, 0.5); +} +.rg-facet-menu.right { left: auto; right: 0; } +.rg-facet-menu[hidden] { display: none; } +.rg-facet-tools-row { display: flex; align-items: center; gap: var(--s-1); margin-bottom: var(--s-2); } +.rg-facet-tools { flex: 1; min-width: 0; display: flex; } +.rg-facet-tools input[type="search"] { flex: 1; min-width: 0; height: var(--ctl-sm); font-size: var(--fz-sm); } +.rg-facet-tool { + background: transparent; + border: 1px solid transparent; + border-radius: var(--radius); + color: var(--text-muted); + font-family: monospace; + font-size: var(--fz-xs); + height: var(--ctl-sm); + padding: 0 var(--s-2); + cursor: pointer; +} +.rg-facet-tool:hover, .rg-facet-tool:focus { background: transparent; color: var(--accent); border-color: var(--border); } +.rg-facet-list { max-height: 340px; overflow-y: auto; overflow-x: hidden; padding-right: var(--s-2); scrollbar-gutter: stable; } +.rg-facet-opt { + display: flex; + align-items: center; + gap: var(--s-2); + margin: 0; + padding: 4px var(--s-1); + border-radius: var(--radius); + color: var(--text); + font-size: var(--fz-sm); + font-weight: normal; + text-transform: none; + letter-spacing: normal; + cursor: pointer; +} +.rg-facet-opt:hover { background: var(--input); } +.rg-facet-opt.zero { opacity: 0.55; } +.rg-facet-opt input[type="checkbox"] { margin: 0; flex: none; accent-color: var(--accent); width: 15px; height: 15px; } +.rg-facet-val { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.rg-facet-val .type-badge { font-size: var(--fz-xs); min-width: 44px; padding: 1px var(--s-2); } +.rg-facet-count { color: var(--text-dim); font-size: var(--fz-xs); flex: none; } +.rg-facet-empty { color: var(--text-dim); font-size: var(--fz-sm); padding: var(--s-2) var(--s-1); } +.rg-filters-clear { + background: transparent; + border: 1px solid transparent; + border-radius: var(--radius); + color: var(--text-muted); + font-family: monospace; + font-size: var(--fz-sm); + height: var(--ctl-sm); + padding: 0 var(--s-2); + cursor: pointer; +} +.rg-filters-clear:hover, .rg-filters-clear:focus { background: transparent; color: var(--primary); border-color: var(--border); } +.rg-filters-clear[hidden] { display: none; } + +/* ── List tool rows (search box + facet filters above a table) ── */ +.list-tools { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--s-2); + margin-bottom: var(--s-3); +} +.list-tools:empty { display: none; } +.list-tools .list-search { flex: 1 1 260px; min-width: 200px; max-width: 520px; height: var(--ctl-sm); font-size: var(--fz-sm); } +/* A page-specific action (New ad-hoc session, Export CSV) pinned to the right + of the shared search+filters row. */ +.list-tools .list-action { margin-left: auto; } + +/* ── Modal extras ─────────────────────────────────────── */ +.modal .modal-hint { color: var(--text-muted); font-size: var(--fz-sm); margin: 0 0 var(--s-3); } +.modal.adhoc-modal { min-width: 520px; max-width: 640px; } diff --git a/static/sessions.html b/static/sessions.html index 05b337b..b587925 100644 --- a/static/sessions.html +++ b/static/sessions.html @@ -64,7 +64,7 @@ DocsTokensAdmin - 🔑 Credentials + 🔑 CredentialsLogout⚙ Settings
@@ -188,12 +188,12 @@
-
- Sessions - - +
+ +
+
-
+
' + escapeHtml(s.created_by || s.username || '') + '' + escapeHtml(s.entry_display_name || s.address_book_entry || '') + '' + entryNameHtml(s.entry_display_name || s.address_book_entry || '') + '' + escapeHtml(s.address_book_folder || '') + '' + typeBadge(s.session_type) + '' + escapeHtml(s.hostname || '') + '
' + escapeHtml(c.name || c.address_book_entry || '') + '' + entryNameHtml(c.name || c.address_book_entry || '') + '' + typeBadge(c.session_type) + '' + (c.session_count || 0) + '' + (c.total_hours != null ? c.total_hours.toFixed(1) : '0') + '
@@ -203,6 +203,7 @@ +
IDTypeHostUserOwnerStatusViewers