mirror of
https://github.com/sol1/rustguac.git
synced 2026-10-26 21:47:27 +00:00
d75446f040
Ad-hoc sessions had their own form on the Sessions page with a subset
of the options a connection entry has. "Connect to a host" now opens
the Connections entry editor in an ad-hoc mode instead, so an ad-hoc
connection has every option an entry has, now and as entries gain more.
The button sits beside the global search box on every page that has
one. It is shown to poweruser and above under the same rules as the
search box, and opens the editor in place on Connections or goes to
/connections.html#adhoc elsewhere. Ad-hoc mode has no name or folder,
Connect instead of Save, and for admins a "Save as entry" action.
POST /api/addressbook/adhoc/connect takes the entry as the editor
builds it. It resolves only the caller's own credential variables,
never lends folder defaults, and goes through apply_adhoc_policy and
the session rate limit like every other ad-hoc route.
POST /api/sessions is unchanged.
session_request_from_entry() replaces the two copies of the entry to
session request conversion, used by the Connections connect endpoint
and /api/connect, so an option added to entries works in all three.
can_connect_adhoc() is the one place that decides who may connect
ad-hoc.
The Sessions ad-hoc form is removed. loadFolders() now runs its
callback, so #open= links work again.
Refs sol1/rustguac#295
(cherry picked from commit dd40bf08ec)
1174 lines
40 KiB
CSS
1174 lines
40 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); }
|
|
/* Connecting state: keep the button's size, hide the label, spin in place. */
|
|
.btn-connect.loading { color: transparent !important; position: relative; pointer-events: none; }
|
|
.btn-connect.loading::after {
|
|
content: ""; position: absolute; top: 50%; left: 50%;
|
|
width: 15px; height: 15px; margin: -8px 0 0 -8px;
|
|
border: 2px solid color-mix(in srgb, var(--bg) 35%, transparent);
|
|
border-top-color: var(--bg);
|
|
border-radius: 50%;
|
|
animation: rg-btn-spin 0.7s linear infinite;
|
|
}
|
|
@keyframes rg-btn-spin { to { transform: rotate(360deg); } }
|
|
|
|
.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);
|
|
/* Above the brand row (300), which the global search lifts over its own
|
|
results overlay (200): a dialog must cover the search box too. */
|
|
z-index: 1000;
|
|
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 ─────────────────────────────────── */
|
|
.entries-table mark {
|
|
background: var(--accent);
|
|
color: var(--bg);
|
|
padding: 0 0.15em;
|
|
border-radius: 2px;
|
|
}
|
|
.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); }
|
|
|
|
/* ── Global connection search (search.js) and the results overlay ──
|
|
Shared by every page and by the connections folder view, so the markup
|
|
is generated in one place (search.js) and styled in one place here. */
|
|
.brand-row { position: relative; }
|
|
.brand-row:has(.global-search) { z-index: 300; } /* lift the search above the overlay */
|
|
.global-search {
|
|
position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
|
|
width: min(640px, 42vw); min-width: 320px;
|
|
display: flex; align-items: center;
|
|
}
|
|
.global-search input[type="search"] {
|
|
width: 100%; height: 48px;
|
|
padding: 0 52px 0 46px;
|
|
font-size: var(--fz-lg);
|
|
background: var(--input); border: 1px solid var(--border); border-radius: 6px;
|
|
color: var(--text); box-sizing: border-box; font-family: monospace;
|
|
transition: border-color 0.15s, box-shadow 0.15s;
|
|
}
|
|
.global-search input[type="search"]:focus {
|
|
outline: none; border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
|
|
}
|
|
.global-search input[type="search"]:disabled { opacity: 0.6; cursor: progress; }
|
|
.global-search-icon {
|
|
position: absolute; left: 16px; top: 50%; transform: translateY(-50%) rotate(-45deg);
|
|
color: var(--text-dim); font-size: 22px; line-height: 1; pointer-events: none;
|
|
}
|
|
.global-search:focus-within .global-search-icon { color: var(--accent); }
|
|
.global-search-key {
|
|
position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
|
|
font-family: monospace; font-size: var(--fz-xs); color: var(--text-dim);
|
|
border: 1px solid var(--border); border-radius: 4px; padding: 1px 7px; line-height: 1.5;
|
|
pointer-events: none;
|
|
}
|
|
.global-search:focus-within .global-search-key, .global-search.has-value .global-search-key { display: none; }
|
|
/* "Connect to a host" (search.js, poweruser and up): beside the centred box
|
|
on wide screens, after it in the row on narrow ones. */
|
|
.global-adhoc {
|
|
position: absolute; top: 50%; transform: translateY(-50%);
|
|
left: calc(50% + max(min(640px, 42vw), 320px) / 2 + var(--s-3));
|
|
height: 48px; white-space: nowrap;
|
|
}
|
|
@media (max-width: 1180px) {
|
|
/* Relative, so the icon and key hint stay on the box rather than on
|
|
whatever follows it in the row. */
|
|
.global-search { position: relative; left: auto; top: auto; transform: none; margin-left: auto; width: 360px; min-width: 0; }
|
|
.global-adhoc { position: static; transform: none; height: var(--ctl-sm); }
|
|
}
|
|
/* Phones: a 16px gutter, and the box takes a row of its own under the logo
|
|
and the button. */
|
|
@media (max-width: 640px) {
|
|
body { padding-left: 16px; padding-right: 16px; }
|
|
.brand-row:has(.global-search), .brand-row:has(.global-adhoc) { flex-wrap: wrap; row-gap: var(--s-2); }
|
|
.global-search { order: 3; width: 100%; margin-left: 0; }
|
|
.global-search input[type="search"] { height: 40px; }
|
|
.global-adhoc { margin-left: auto; }
|
|
}
|
|
#search-overlay {
|
|
display: none;
|
|
position: fixed; inset: 0;
|
|
z-index: 200;
|
|
background: color-mix(in srgb, var(--bg) 62%, transparent);
|
|
backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
|
|
padding: 86px var(--s-5) var(--s-5);
|
|
justify-content: center; align-items: flex-start;
|
|
}
|
|
#search-overlay.open { display: flex; }
|
|
#search-overlay-panel {
|
|
width: min(1680px, 100%);
|
|
max-height: calc(100vh - 110px);
|
|
display: flex; flex-direction: column;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.6);
|
|
overflow: hidden;
|
|
animation: rg-search-in 140ms ease-out both;
|
|
}
|
|
@keyframes rg-search-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
|
|
#search-overlay-head {
|
|
display: flex; flex-direction: column; gap: var(--s-2);
|
|
padding: var(--s-4) var(--s-4) var(--s-3);
|
|
border-bottom: 1px solid var(--border);
|
|
flex: none;
|
|
}
|
|
#search-overlay-head .list-tools { margin: 0; }
|
|
.search-summary { color: var(--text-muted); font-size: var(--fz-sm); margin: 0; }
|
|
.search-summary strong { color: var(--text); font-weight: bold; }
|
|
#search-results { overflow-y: auto; overflow-x: hidden; padding: 0 var(--s-4) var(--s-3); }
|
|
/* The results table styles itself fully here (not via any page's inline
|
|
.entries-table), so it looks identical on every page. */
|
|
#search-results .entries-table { border-collapse: collapse; width: 100%; margin-top: 0; border: 0; }
|
|
#search-results .entries-table th,
|
|
#search-results .entries-table td {
|
|
padding: var(--s-2) var(--s-3);
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: var(--fz-sm);
|
|
vertical-align: middle;
|
|
}
|
|
#search-results .entries-table th {
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
font-size: var(--fz-xs);
|
|
font-weight: normal;
|
|
position: sticky; top: 0;
|
|
background: var(--surface);
|
|
z-index: 1;
|
|
}
|
|
#search-results .entries-table tbody tr:hover { background: var(--input); }
|
|
#search-results .entries-table tbody tr:last-child td { border-bottom: none; }
|
|
/* Override any page's own table rules so the results look the same everywhere. */
|
|
#search-results .entries-table tbody tr:nth-child(even) { background: transparent; }
|
|
#search-results .folder-cell .folder-scope {
|
|
display: inline; width: auto; height: auto;
|
|
font-size: inherit; color: var(--text-dim); cursor: default;
|
|
margin-right: var(--s-1);
|
|
}
|
|
#search-results .empty-state { padding: 2.5em 1em; text-align: center; }
|
|
/* Results table: fixed columns, one line per cell, actions on the right. */
|
|
.search-results { table-layout: fixed; }
|
|
.search-results .c-name { width: 24%; }
|
|
.search-results .c-type { width: 7%; }
|
|
.search-results .c-host { width: 18%; }
|
|
.search-results .c-user { width: 9%; }
|
|
.search-results .c-folder { width: 17%; }
|
|
.search-results .c-actions { width: 25%; }
|
|
.search-results td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; }
|
|
.search-results .entry-name, .search-results .entry-sub { display: block; overflow: hidden; text-overflow: ellipsis; }
|
|
.search-results .entry-sub { color: var(--text-muted); font-size: var(--fz-xs); }
|
|
.search-results .folder-cell .fname { display: block; overflow: hidden; text-overflow: ellipsis; }
|
|
.search-results .folder-cell .fkey { display: block; overflow: hidden; text-overflow: ellipsis; color: var(--text-dim); font-size: var(--fz-xs); letter-spacing: 0.04em; }
|
|
.search-results .folder-cell .folder-scope { margin-right: var(--s-1); }
|
|
.search-results .host-cell { color: var(--text-muted); }
|
|
.search-results .actions-cell { text-align: right; overflow: visible; text-overflow: clip; white-space: nowrap; }
|
|
.search-results .actions-cell .btn-connect { vertical-align: middle; }
|
|
.search-results .btn-connect-win {
|
|
margin-left: var(--s-1); min-width: 0; width: auto; flex: none;
|
|
padding: 0 var(--s-1); font-size: var(--fz-md); line-height: 1; vertical-align: middle;
|
|
}
|
|
.search-results .search-open-folder { margin-left: var(--s-3); white-space: nowrap; color: var(--accent); text-decoration: none; font-size: var(--fz-sm); }
|
|
.search-results .search-open-folder:hover { text-decoration: underline; }
|
|
.search-more { color: var(--text-muted); font-size: var(--fz-sm); padding: var(--s-3) 0; }
|
|
|
|
/* ── Embedded fullscreen session (search.js rgLaunchFullscreen) ──
|
|
The launcher tab goes fullscreen on the Connect click (the only moment a
|
|
gesture is available) and the session loads in a full-viewport iframe here,
|
|
so there is no second click. */
|
|
#rg-session-overlay { position: fixed; inset: 0; z-index: 5000; background: #000; }
|
|
#rg-session-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
|
|
#rg-session-spinner {
|
|
position: absolute; top: 50%; left: 50%; width: 34px; height: 34px; margin: -17px 0 0 -17px;
|
|
border: 3px solid rgba(255,255,255,0.25); border-top-color: var(--accent, #5bc0be);
|
|
border-radius: 50%; animation: rg-btn-spin 0.8s linear infinite;
|
|
}
|
|
#rg-session-exit { position: absolute; top: 10px; right: 14px; }
|
|
#rg-session-fs { position: absolute; top: 10px; right: 104px; }
|
|
#rg-session-fs[hidden] { display: none; }
|
|
.rg-session-btn {
|
|
z-index: 5001;
|
|
height: 32px; padding: 0 var(--s-3, 14px);
|
|
background: rgba(20,24,38,0.72); color: var(--text, #e0e0e0);
|
|
border: 1px solid var(--border, #333); border-radius: var(--radius, 2px);
|
|
font-family: monospace; font-size: var(--fz-sm, 16px); cursor: pointer;
|
|
opacity: 0.35; transition: opacity 0.15s;
|
|
}
|
|
.rg-session-btn:hover { opacity: 1; background: var(--surface, #16213e); border-color: var(--primary, #e94560); color: var(--text, #e0e0e0); }
|