mirror of
https://github.com/sol1/rustguac.git
synced 2026-10-26 13:39:54 +00:00
be866264bd
reports: show entry names as host over a tenant subline Session history and the most-used table showed "host (Tenant)" names wrapped across three lines; they now read as the host over a quieter tenant line, as on Connections and Recordings. Part of #254 sessions, recordings: show the session type with the shared badge (#254) The sessions page printed the type as plain text and the recordings page as a coloured word; connections and reports already used .type-badge from rustguac.css. All four now use the same badge. The Proxmox badge colour moves into the shared stylesheet with the others. Shared facet filters (static/filters.js) on the sessions, recordings, reports and connections pages: a dropdown per facet (Type, Folder, User, Owner, Status as each page has them), multi-select with counts that narrow as other facets apply, All/None, a find box for long lists, a Clear filters link, and selections kept per page in sessionStorage. They sit beside the existing text search and combine with it; the buttons are compacted to the height and padding of the other controls and the dropdown reserves room for its scrollbar so it no longer sits over the counts. The list pages share one toolbar: a borderless row with the search box, the facets and the page action on the right (New ad-hoc session, Export CSV), placeholders standardised to 'Search <noun> (fields…)'. Sessions gains a search (user, host, owner, ID, type) and a User facet; Reports moves from its own .filter-bar to the shared one. The redundant page titles go — the nav already names and marks the active page — and section headings centre their heading and action button on one line. Connections: the folder header is sticky at the top of the entries pane, so it stays under the nav and the active-sessions strip while a long table scrolls. All four pages show the session type with the shared .type-badge (the Proxmox colour moves into rustguac.css), reports and recordings show "host (Tenant)" names as the host over a quieter tenant line, and the key glyph before Credentials in the nav is sized to the text. Part of #254 tests: shared facet filters and the list toolbar on every list page
1013 lines
32 KiB
CSS
1013 lines
32 KiB
CSS
/* ============================================================
|
|
rustguac — shared operator-console stylesheet
|
|
- 8px baseline grid for chrome controls (px units).
|
|
- Body copy 18px, control heights 38/44/54px.
|
|
- CSS variable theming is driven by the theme picker; only
|
|
the palette tokens here are intended to be overridden.
|
|
============================================================ */
|
|
|
|
:root {
|
|
/* theme palette */
|
|
--primary: #e94560; --primary-hover: #c73652;
|
|
--accent: #5bc0be; --accent-hover: #4aa3a1;
|
|
--bg: #1a1a2e; --surface: #16213e; --input: #0f3460;
|
|
--text: #e0e0e0; --text-muted: #aaa; --border: #333;
|
|
--text-dim: #666; --text-on-primary: #fff; --btn-disabled: #555;
|
|
--status-pending: #f0c040; --status-active: #5bc0be;
|
|
--status-completed: #888; --status-error: #e94560; --status-expired: #666;
|
|
--type-ssh-bg: #1b4332; --type-ssh-fg: #52b788;
|
|
--type-rdp-bg: #3d1f00; --type-rdp-fg: #f0a050;
|
|
--type-vnc-bg: #2d1b4e; --type-vnc-fg: #b07ff0;
|
|
--type-web-bg: #1a1a4e; --type-web-fg: #7b8ff0;
|
|
--type-vdi-bg: #0e2a2a; --type-vdi-fg: #2dd4bf;
|
|
--hop-bg: #1b4332; --hop-fg: #52b788;
|
|
--bg-pattern: none;
|
|
|
|
/* spacing (4px unit) */
|
|
--s-1: 4px; --s-2: 8px; --s-3: 14px;
|
|
--s-4: 20px; --s-5: 28px; --s-6: 40px;
|
|
|
|
/* geometry */
|
|
--radius: 2px;
|
|
|
|
/* control heights */
|
|
--ctl-sm: 38px;
|
|
--ctl-md: 44px;
|
|
--ctl-lg: 54px;
|
|
|
|
/* type scale — fz-md == body */
|
|
--fz-xs: 14px;
|
|
--fz-sm: 16px;
|
|
--fz-md: 18px;
|
|
--fz-lg: 20px;
|
|
--fz-xl: 24px;
|
|
}
|
|
|
|
/* ── Reset / base ─────────────────────────────── */
|
|
body {
|
|
font-family: monospace;
|
|
background-color: var(--bg);
|
|
background-image: var(--bg-pattern);
|
|
background-attachment: fixed;
|
|
min-height: 100vh;
|
|
color: var(--text);
|
|
padding: var(--s-5) var(--s-6);
|
|
font-size: 18px;
|
|
line-height: 1.55;
|
|
margin: 0;
|
|
}
|
|
|
|
/* Narrow desktops (a 1710px MacBook display, 1600px monitors): a tighter
|
|
page gutter. Shared here so every page shifts together and switching
|
|
tabs does not snap the content sideways. */
|
|
@media (max-width: 1800px) {
|
|
body { padding: var(--s-4) var(--s-4); }
|
|
}
|
|
|
|
h1 { color: var(--primary); margin: 0; font-size: 30px; letter-spacing: 0.02em; font-weight: bold; }
|
|
h2 { color: var(--primary); font-size: var(--fz-lg); margin-top: var(--s-5); letter-spacing: 0.02em; }
|
|
h3 { color: var(--text); font-size: var(--fz-md); margin-top: var(--s-4); letter-spacing: 0.02em; }
|
|
a { color: var(--accent); }
|
|
code { background: var(--surface); padding: 2px var(--s-1); border-radius: var(--radius); font-size: 0.9em; }
|
|
|
|
.brand-row { display: flex; align-items: center; gap: var(--s-3); }
|
|
.brand-row #site-logo { height: 36px; width: auto; display: block; }
|
|
|
|
/* ── Navigation ─────────────────────────────── */
|
|
nav {
|
|
margin-top: var(--s-4);
|
|
margin-bottom: var(--s-5);
|
|
padding-bottom: var(--s-3);
|
|
border-bottom: 1px solid var(--border);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-5);
|
|
font-size: var(--fz-md);
|
|
}
|
|
nav a {
|
|
text-decoration: none;
|
|
color: var(--text-muted);
|
|
padding: var(--s-1) 0;
|
|
margin: 0;
|
|
position: relative;
|
|
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: '';
|
|
position: absolute;
|
|
left: 0; right: 0;
|
|
bottom: calc(var(--s-3) * -1 - 1px);
|
|
height: 2px;
|
|
background: var(--primary);
|
|
}
|
|
|
|
#user-menu-wrapper {
|
|
margin-left: auto;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--s-3);
|
|
position: relative;
|
|
float: none;
|
|
}
|
|
#logout-item {
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-md);
|
|
text-decoration: none;
|
|
padding: var(--s-1) 0;
|
|
line-height: 1.4;
|
|
margin: 0;
|
|
}
|
|
#logout-item:hover { color: var(--primary); }
|
|
#user-menu-btn {
|
|
cursor: pointer;
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-md);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--s-1);
|
|
padding: 0 var(--s-2);
|
|
height: var(--ctl-sm);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
line-height: 1;
|
|
box-sizing: border-box;
|
|
transition: color .12s, border-color .12s;
|
|
}
|
|
#user-menu-btn:hover { color: var(--text); border-color: var(--text-muted); }
|
|
|
|
#user-menu {
|
|
display: none;
|
|
position: absolute;
|
|
right: 0;
|
|
top: calc(var(--ctl-sm) + var(--s-2));
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
min-width: 260px;
|
|
z-index: 50;
|
|
padding: var(--s-1) 0;
|
|
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
|
}
|
|
.um-section-label {
|
|
padding: var(--s-2) var(--s-3) var(--s-1);
|
|
font-size: var(--fz-xs);
|
|
color: var(--text-dim);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
.um-item {
|
|
padding: var(--s-2) var(--s-3);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-2);
|
|
font-size: var(--fz-sm);
|
|
color: var(--text);
|
|
}
|
|
.um-item:hover { background: var(--input); }
|
|
.um-item.active { color: var(--accent); }
|
|
.um-theme-info { display: flex; flex-direction: column; }
|
|
.um-theme-name { font-weight: bold; }
|
|
.um-theme-desc { font-size: var(--fz-xs); color: var(--text-dim); }
|
|
.um-swatch {
|
|
display: inline-block;
|
|
width: 14px; height: 14px;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--border);
|
|
flex-shrink: 0;
|
|
}
|
|
.um-divider { height: 1px; background: var(--border); margin: var(--s-1) 0; }
|
|
.um-logout { color: var(--text-muted); }
|
|
.um-logout:hover { color: var(--primary); background: var(--input); }
|
|
|
|
/* ── Buttons (unified ladder) ─────────────────────────────── */
|
|
button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--s-1);
|
|
height: var(--ctl-md);
|
|
padding: 0 var(--s-3);
|
|
background: var(--primary);
|
|
color: var(--text-on-primary);
|
|
border: 1px solid var(--primary);
|
|
border-radius: var(--radius);
|
|
font-family: monospace;
|
|
font-size: var(--fz-md);
|
|
font-weight: bold;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
transition: background .12s, border-color .12s, color .12s;
|
|
box-sizing: border-box;
|
|
}
|
|
button:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
|
|
button:disabled {
|
|
background: var(--btn-disabled);
|
|
border-color: var(--btn-disabled);
|
|
color: var(--text-dim);
|
|
cursor: default;
|
|
}
|
|
|
|
button.btn-primary {
|
|
background: var(--primary);
|
|
border-color: var(--primary);
|
|
color: var(--text-on-primary);
|
|
}
|
|
|
|
.btn-connect {
|
|
background: var(--accent);
|
|
border-color: var(--accent);
|
|
color: var(--bg);
|
|
height: var(--ctl-sm);
|
|
padding: 0 var(--s-4);
|
|
font-size: var(--fz-sm);
|
|
min-width: 130px;
|
|
}
|
|
.btn-connect:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
|
|
|
.btn-add {
|
|
background: transparent;
|
|
color: var(--text);
|
|
border: 1px solid var(--border);
|
|
font-weight: normal;
|
|
height: var(--ctl-sm);
|
|
padding: 0 var(--s-3);
|
|
font-size: var(--fz-sm);
|
|
}
|
|
.btn-add:hover {
|
|
background: var(--surface);
|
|
border-color: var(--accent);
|
|
color: var(--accent);
|
|
}
|
|
|
|
.btn-small {
|
|
background: transparent;
|
|
border: 1px solid transparent;
|
|
color: var(--text-muted);
|
|
font-weight: normal;
|
|
height: var(--ctl-sm);
|
|
padding: 0 var(--s-2);
|
|
font-size: var(--fz-sm);
|
|
font-family: monospace;
|
|
cursor: pointer;
|
|
border-radius: var(--radius);
|
|
}
|
|
.btn-small:hover {
|
|
color: var(--primary);
|
|
background: transparent;
|
|
border-color: var(--border);
|
|
text-decoration: none;
|
|
}
|
|
.btn-action { color: var(--accent); }
|
|
.btn-action:hover { color: var(--accent-hover); }
|
|
|
|
/* ── Form controls (base) ─────────────────────────────── */
|
|
input[type="text"],
|
|
input[type="password"],
|
|
input[type="email"],
|
|
input[type="number"],
|
|
input[type="date"],
|
|
input[type="search"],
|
|
select,
|
|
textarea {
|
|
background: var(--input);
|
|
border: 1px solid var(--border);
|
|
color: var(--text);
|
|
font-family: monospace;
|
|
font-size: var(--fz-md);
|
|
padding: 0 var(--s-2);
|
|
height: var(--ctl-md);
|
|
border-radius: var(--radius);
|
|
box-sizing: border-box;
|
|
line-height: 1;
|
|
}
|
|
textarea { height: auto; padding: var(--s-2); line-height: 1.5; }
|
|
input:focus, select:focus, textarea:focus {
|
|
outline: none;
|
|
border-color: var(--primary);
|
|
box-shadow: 0 0 0 2px rgba(233, 69, 96, 0.18);
|
|
}
|
|
|
|
label {
|
|
display: block;
|
|
margin-top: var(--s-3);
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-xs);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
|
|
/* ── Generic tables ─────────────────────────────── */
|
|
table {
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
margin-top: var(--s-3);
|
|
border: 1px solid var(--border);
|
|
}
|
|
th, td {
|
|
text-align: left;
|
|
padding: var(--s-3) var(--s-4);
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: var(--fz-md);
|
|
vertical-align: middle;
|
|
}
|
|
th {
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-sm);
|
|
font-weight: normal;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.12em;
|
|
background: var(--surface);
|
|
line-height: 1.6;
|
|
}
|
|
tbody tr:nth-child(even) { background: rgba(255,255,255,0.02); }
|
|
tbody tr:hover { background: var(--surface); }
|
|
tbody tr:last-child td { border-bottom: none; }
|
|
|
|
/* ── Status colors (session/token lifecycle) ─────────────── */
|
|
.status-pending { color: var(--status-pending); }
|
|
.status-active { color: var(--status-active); }
|
|
.status-completed { color: var(--status-completed); }
|
|
.status-error { color: var(--status-error); }
|
|
.status-expired { color: var(--status-expired); }
|
|
.status-disabled { color: var(--primary); }
|
|
|
|
/* ── Session type badges ─────────────────────────────── */
|
|
.type-badge {
|
|
display: inline-block;
|
|
padding: 3px var(--s-2);
|
|
border-radius: var(--radius);
|
|
font-size: var(--fz-sm);
|
|
font-weight: bold;
|
|
letter-spacing: 0.12em;
|
|
text-transform: uppercase;
|
|
min-width: 52px;
|
|
text-align: center;
|
|
line-height: 1.5;
|
|
}
|
|
.type-ssh { background: var(--type-ssh-bg); color: var(--type-ssh-fg); }
|
|
.type-rdp { background: var(--type-rdp-bg); color: var(--type-rdp-fg); }
|
|
.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: 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);
|
|
}
|
|
/* A section's action button sits on the right of its heading band. */
|
|
.section-head .section-action { margin-left: auto; align-self: center; }
|
|
.section-head strong {
|
|
color: var(--accent);
|
|
font-size: var(--fz-sm);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.14em;
|
|
font-weight: bold;
|
|
}
|
|
.section-count {
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-sm);
|
|
font-weight: normal;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
/* ── Empty / error messages ─────────────────────────────── */
|
|
.empty { color: var(--text-dim); margin: var(--s-5) 0; font-size: var(--fz-md); }
|
|
#global-error, #error { color: var(--primary); margin-top: var(--s-2); white-space: pre-wrap; font-size: var(--fz-md); }
|
|
.no-vault { color: var(--text-muted); margin: var(--s-5) 0; }
|
|
|
|
.empty-state {
|
|
border: 1px dashed var(--border);
|
|
border-radius: var(--radius);
|
|
padding: var(--s-6) var(--s-5);
|
|
text-align: center;
|
|
margin: var(--s-5) 0;
|
|
}
|
|
.empty-state h3 {
|
|
color: var(--accent);
|
|
margin: 0 0 var(--s-2);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
font-size: var(--fz-lg);
|
|
}
|
|
.empty-state p { color: var(--text-muted); margin: 0 0 var(--s-4); }
|
|
.empty-state button { height: var(--ctl-lg); padding: 0 var(--s-4); font-size: var(--fz-md); }
|
|
|
|
/* ── Modal ─────────────────────────────── */
|
|
.modal-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
background: rgba(0,0,0,0.75);
|
|
z-index: 100;
|
|
justify-content: center;
|
|
align-items: center;
|
|
backdrop-filter: blur(2px);
|
|
}
|
|
.modal-overlay.active { display: flex; }
|
|
.modal {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: var(--s-5);
|
|
min-width: 440px;
|
|
max-width: 600px;
|
|
max-height: 85vh;
|
|
overflow-y: auto;
|
|
box-shadow: 0 16px 48px rgba(0,0,0,0.55);
|
|
}
|
|
.modal h3 {
|
|
color: var(--primary);
|
|
margin: 0 0 var(--s-3);
|
|
padding-bottom: var(--s-2);
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: var(--fz-lg);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
.modal label {
|
|
display: block;
|
|
margin-top: var(--s-3);
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-xs);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
.modal input, .modal select, .modal textarea {
|
|
display: block;
|
|
width: 100%;
|
|
margin-top: var(--s-1);
|
|
background: var(--input);
|
|
border: 1px solid var(--border);
|
|
color: var(--text);
|
|
font-family: monospace;
|
|
font-size: var(--fz-md);
|
|
padding: 0 var(--s-2);
|
|
height: var(--ctl-md);
|
|
border-radius: var(--radius);
|
|
box-sizing: border-box;
|
|
text-transform: none;
|
|
letter-spacing: 0;
|
|
}
|
|
.modal textarea { height: auto; padding: var(--s-2); }
|
|
/* Checkboxes / radios live inside labels and shouldn't take the full
|
|
44px input height — keep native size, inline, next to the label text. */
|
|
.modal input[type="checkbox"],
|
|
.modal input[type="radio"] {
|
|
display: inline-block;
|
|
width: auto;
|
|
height: auto;
|
|
padding: 0;
|
|
margin: 0 var(--s-1) 0 0;
|
|
vertical-align: middle;
|
|
box-shadow: none;
|
|
}
|
|
.modal label:has(> input[type="checkbox"]),
|
|
.modal label:has(> input[type="radio"]) {
|
|
text-transform: none;
|
|
letter-spacing: 0;
|
|
font-size: var(--fz-sm);
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-2);
|
|
}
|
|
.modal input:focus, .modal select:focus, .modal textarea:focus {
|
|
outline: none;
|
|
border-color: var(--primary);
|
|
box-shadow: 0 0 0 2px rgba(233, 69, 96, 0.18);
|
|
}
|
|
.modal-actions {
|
|
margin-top: var(--s-4);
|
|
padding-top: var(--s-3);
|
|
border-top: 1px solid var(--border);
|
|
display: flex;
|
|
gap: var(--s-2);
|
|
justify-content: flex-end;
|
|
}
|
|
.modal-actions .btn-cancel {
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
border-color: var(--border);
|
|
font-weight: normal;
|
|
}
|
|
.modal-actions .btn-cancel:hover {
|
|
background: var(--surface);
|
|
border-color: var(--text-muted);
|
|
color: var(--text);
|
|
}
|
|
|
|
/* My Credentials modal: pin the header (title/description/filter) and the
|
|
Save/Close footer, and scroll only the credential list, so the modal stays
|
|
usable no matter how many credential variables exist. Scoped by #id so the
|
|
generic `.modal { overflow-y:auto }` is overridden only here. */
|
|
#my-creds-modal .modal {
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
#my-creds-modal #my-creds-list {
|
|
flex: 1 1 auto;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
/* room so focus rings / scrollbar don't clip against the padding edge */
|
|
margin: 0 calc(var(--s-2) * -1);
|
|
padding: 0 var(--s-2);
|
|
}
|
|
#my-creds-search {
|
|
/* filter box lives in the pinned header */
|
|
margin: var(--s-1) 0 var(--s-2);
|
|
}
|
|
.cred-group {
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
.cred-group-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4em;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
color: var(--accent);
|
|
font-weight: bold;
|
|
font-size: 0.9em;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
padding: 0.55em 0 0.35em;
|
|
}
|
|
.cred-group-caret {
|
|
display: inline-block;
|
|
width: 1em;
|
|
font-size: 1.15em;
|
|
line-height: 1;
|
|
transition: transform 0.12s ease;
|
|
}
|
|
.cred-group.collapsed .cred-group-caret {
|
|
transform: rotate(-90deg);
|
|
}
|
|
.cred-group.collapsed .cred-group-body {
|
|
display: none;
|
|
}
|
|
.cred-group-count {
|
|
margin-left: auto;
|
|
color: var(--text-dim);
|
|
font-weight: normal;
|
|
font-size: 0.85em;
|
|
}
|
|
|
|
.field-hint {
|
|
font-size: var(--fz-xs);
|
|
color: var(--text-dim);
|
|
margin-top: var(--s-1);
|
|
text-transform: none;
|
|
letter-spacing: 0;
|
|
}
|
|
.pw-wrap { position: relative; }
|
|
.pw-wrap input { padding-right: 2.4em; }
|
|
.pw-toggle {
|
|
position: absolute;
|
|
right: var(--s-1);
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
font-size: var(--fz-sm);
|
|
padding: var(--s-1);
|
|
line-height: 1;
|
|
width: auto;
|
|
height: auto;
|
|
font-weight: normal;
|
|
}
|
|
.pw-toggle:hover {
|
|
color: var(--text);
|
|
background: none;
|
|
border-color: transparent;
|
|
}
|
|
|
|
/* ── Card (generic surface block used for status panels, cards) ── */
|
|
.card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: var(--s-4);
|
|
}
|
|
|
|
/* ── Status-card grid (used on admin dashboard) ──────────────── */
|
|
.status-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
gap: var(--s-3);
|
|
margin: var(--s-3) 0 var(--s-5);
|
|
}
|
|
.status-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: var(--s-3) var(--s-4);
|
|
}
|
|
.status-card .label {
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-xs);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
.status-card .value {
|
|
color: var(--accent);
|
|
font-size: var(--fz-xl);
|
|
font-weight: bold;
|
|
margin-top: var(--s-1);
|
|
line-height: 1.2;
|
|
}
|
|
.status-card .detail {
|
|
color: var(--text-dim);
|
|
font-size: var(--fz-xs);
|
|
margin-top: var(--s-1);
|
|
}
|
|
|
|
/* ── Small chips for feature flags, tags ──────────────────────── */
|
|
.feat-tag {
|
|
display: inline-block;
|
|
padding: 2px var(--s-2);
|
|
border-radius: var(--radius);
|
|
font-size: var(--fz-xs);
|
|
margin-right: var(--s-1);
|
|
margin-bottom: var(--s-1);
|
|
letter-spacing: 0.06em;
|
|
}
|
|
.feat-on { background: var(--type-ssh-bg); color: var(--type-ssh-fg); }
|
|
.feat-off { background: var(--border); color: var(--text-dim); }
|
|
|
|
/* ── Form card (used by sessions form, onboarding) ──────────── */
|
|
form.card,
|
|
.add-form {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: var(--s-4) var(--s-5);
|
|
margin-top: var(--s-3);
|
|
}
|
|
|
|
/* ── Hop cards (SSH jump host editor — used by addressbook + sessions) ── */
|
|
.hop-card {
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
margin-bottom: var(--s-2);
|
|
}
|
|
.hop-card-header {
|
|
display: flex; align-items: center; gap: var(--s-2);
|
|
padding: var(--s-2) var(--s-3);
|
|
cursor: pointer;
|
|
font-size: var(--fz-md);
|
|
}
|
|
.hop-card-header:hover { background: var(--surface); }
|
|
.hop-card-header .hop-num { color: var(--accent); font-weight: bold; min-width: 2em; }
|
|
.hop-card-header .hop-hostname { flex: 1; color: var(--text); }
|
|
.hop-card-header .hop-hostname input {
|
|
display: inline; width: 100%; height: var(--ctl-sm);
|
|
padding: 0 var(--s-2); font-size: var(--fz-md);
|
|
background: var(--input); border: 1px solid var(--border); color: var(--text);
|
|
font-family: monospace; border-radius: var(--radius); box-sizing: border-box;
|
|
}
|
|
.hop-card-header .hop-remove {
|
|
color: var(--primary); cursor: pointer; font-size: var(--fz-sm);
|
|
background: none; border: 1px solid transparent;
|
|
padding: 0 var(--s-2); font-family: monospace; font-weight: normal;
|
|
height: var(--ctl-sm);
|
|
}
|
|
.hop-card-header .hop-remove:hover {
|
|
text-decoration: underline;
|
|
background: none; border-color: transparent;
|
|
}
|
|
.hop-card-body { padding: var(--s-1) var(--s-3) var(--s-3) 44px; display: none; }
|
|
.hop-card-body.expanded { display: block; }
|
|
.hop-card-body label { margin-top: var(--s-2); }
|
|
.hop-card-body input, .hop-card-body textarea {
|
|
display: block; width: 100%;
|
|
padding: 0 var(--s-2); height: var(--ctl-md); margin-top: var(--s-1);
|
|
background: var(--input); border: 1px solid var(--border); color: var(--text);
|
|
font-family: monospace; font-size: var(--fz-md);
|
|
border-radius: var(--radius); box-sizing: border-box;
|
|
}
|
|
.hop-card-body textarea { height: auto; padding: var(--s-2); }
|
|
|
|
/* ── Token reveal (one-time token display, admin/tokens pages) ── */
|
|
.token-reveal {
|
|
background: var(--surface);
|
|
padding: var(--s-4);
|
|
border-radius: var(--radius);
|
|
margin-top: var(--s-3);
|
|
border: 1px solid var(--accent);
|
|
}
|
|
.token-reveal code {
|
|
background: var(--input);
|
|
padding: var(--s-2) var(--s-3);
|
|
border-radius: var(--radius);
|
|
font-size: var(--fz-sm);
|
|
word-break: break-all;
|
|
display: block;
|
|
margin: var(--s-2) 0;
|
|
user-select: all;
|
|
}
|
|
.token-reveal .warning {
|
|
color: var(--primary);
|
|
font-size: var(--fz-sm);
|
|
margin-top: var(--s-2);
|
|
}
|
|
|
|
/* ── Row states ─────────────────────────────── */
|
|
.disabled-row { opacity: 0.5; }
|
|
|
|
/* ── Status pill (used in reports / sessions grids) ──────────── */
|
|
.status-badge {
|
|
display: inline-block;
|
|
padding: 2px var(--s-2);
|
|
border-radius: var(--radius);
|
|
font-size: var(--fz-sm);
|
|
letter-spacing: 0.06em;
|
|
}
|
|
|
|
/* ── Pagination ─────────────────────────────── */
|
|
.pagination {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-3);
|
|
margin: var(--s-3) 0;
|
|
font-size: var(--fz-sm);
|
|
}
|
|
.pagination button {
|
|
background: transparent;
|
|
color: var(--text);
|
|
border: 1px solid var(--border);
|
|
font-family: monospace;
|
|
font-size: var(--fz-sm);
|
|
height: var(--ctl-sm);
|
|
padding: 0 var(--s-3);
|
|
border-radius: var(--radius);
|
|
font-weight: normal;
|
|
}
|
|
.pagination button:hover { background: var(--surface); border-color: var(--accent); color: var(--accent); }
|
|
.pagination button:disabled {
|
|
color: var(--text-dim);
|
|
border-color: var(--border);
|
|
background: transparent;
|
|
}
|
|
.pagination .page-info { color: var(--text-muted); }
|
|
|
|
/* ── Summary cards (reports top-of-page KPI strip) ─────────── */
|
|
.summary-cards {
|
|
display: flex;
|
|
gap: var(--s-3);
|
|
margin: var(--s-4) 0;
|
|
flex-wrap: wrap;
|
|
}
|
|
.summary-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: var(--s-3) var(--s-4);
|
|
flex: 1;
|
|
min-width: 180px;
|
|
}
|
|
.summary-card .card-label {
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-xs);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
.summary-card .card-value {
|
|
color: var(--accent);
|
|
font-size: var(--fz-xl);
|
|
font-weight: bold;
|
|
margin-top: var(--s-1);
|
|
line-height: 1.2;
|
|
}
|
|
|
|
/* ── Flow diagram (you → hop → target path) ─────────────────── */
|
|
.flow-diagram {
|
|
display: flex; align-items: center; gap: 0;
|
|
flex-wrap: wrap; margin-bottom: var(--s-3);
|
|
font-size: var(--fz-sm);
|
|
}
|
|
.flow-node {
|
|
display: inline-flex; align-items: center;
|
|
height: var(--ctl-sm); padding: 0 var(--s-2);
|
|
border-radius: var(--radius); white-space: nowrap;
|
|
}
|
|
.flow-node-you { background: var(--input); color: var(--text-muted); }
|
|
.flow-node-hop { background: var(--hop-bg); color: var(--hop-fg); }
|
|
.flow-node-target { background: var(--input); color: var(--accent); font-weight: bold; }
|
|
.flow-arrow { color: var(--text-muted); margin: 0 var(--s-1); }
|
|
|
|
/* ── Connections quick-find ─────────────────────────────────── */
|
|
.connections-search {
|
|
flex: 1 1 220px;
|
|
min-width: 180px;
|
|
max-width: 480px;
|
|
height: var(--ctl-md);
|
|
margin-left: var(--s-2);
|
|
margin-right: auto;
|
|
}
|
|
.connections-search:disabled {
|
|
opacity: 0.6;
|
|
cursor: progress;
|
|
}
|
|
.entries-table mark {
|
|
background: var(--accent);
|
|
color: var(--bg);
|
|
padding: 0 0.15em;
|
|
border-radius: 2px;
|
|
}
|
|
.search-folder-cell {
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-sm);
|
|
}
|
|
.search-folder-cell .folder-scope {
|
|
color: var(--accent);
|
|
margin-right: 0.2em;
|
|
}
|
|
.search-open-folder {
|
|
color: var(--accent);
|
|
text-decoration: none;
|
|
font-size: var(--fz-sm);
|
|
white-space: nowrap;
|
|
}
|
|
.search-open-folder:hover { text-decoration: underline; }
|
|
.search-more {
|
|
margin-top: var(--s-2);
|
|
padding: var(--s-2) var(--s-3);
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-sm);
|
|
font-style: italic;
|
|
text-align: center;
|
|
border-top: 1px dashed var(--border);
|
|
}
|
|
|
|
/* ── Role-gated navigation ──────────────────────
|
|
The server writes data-role-level (0 anonymous … 4 admin) on <html> for
|
|
the user it serves a page to, so gated links and admin-only controls are
|
|
right on the first paint: no fetch-then-show, no reflow, nothing popping
|
|
in after the header has drawn. A page reconciles the attribute from
|
|
/api/me for identities the page GET could not carry (API keys). */
|
|
nav a[data-min-role] { display: none; }
|
|
html[data-role-level="2"] nav a[data-min-role="operator"],
|
|
html[data-role-level="3"] nav a[data-min-role="operator"],
|
|
html[data-role-level="3"] nav a[data-min-role="poweruser"],
|
|
html[data-role-level="4"] nav a[data-min-role] { display: inline; }
|
|
html:not([data-role-level="4"]) .admin-only { display: none !important; }
|
|
|
|
/* ── Entry names ──
|
|
"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; }
|