summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/style.css
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-20 08:56:23 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-20 08:56:23 +0200
commitc8af746c846b5dbc792f7e4f0d806647d513cc5c (patch)
tree56c54ec5c703ec042589697b22b53e42451a61e4 /packages/meshbay-hub/src/meshbay_hub/static/style.css
parent90c3c6a01f46c9b29c5d92702d7383f32e8951d7 (diff)
downloadmeshbay-c8af746c846b5dbc792f7e4f0d806647d513cc5c.tar.gz
feat(node): full Node admin panel — CLI parity, hot-reload, group lifecycle
Node admin panel (NodePage) now covers every CLI operation over MNP: group attach/detach, roster, member unpin, GEK rotate, denylist, reload. Daemon hot-loads new groups and tears down removed ones on config reload instead of requiring a full restart. Group attach/detach via MNP or local API triggers an automatic reload so the group is live immediately. Fixed GroupPage hang on first visit to a newly created group: the JWT issued at login didn't include the new group, the node rejected with not_a_member, and the token-refresh path returned without re-triggering the connect effect (Boolean(token) didn't change). Now bumps retryKey after a successful refresh so the effect re-runs with the fresh token. NodePage marks groups hosted by the node but absent from the hub with a "not on hub" badge so stale groups are visible and easy to remove. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/style.css')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css174
1 files changed, 174 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index 9ff5923..ac3d08f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -2048,3 +2048,177 @@ a.transfer-name {
margin-left: 6px;
}
.dir-row .root-offline { white-space: nowrap; }
+
+/* ── Node management (D5) ────────────────────────────────────────────────── */
+
+.node-page { max-width: 700px; }
+
+.node-group {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 16px 20px;
+ margin-bottom: 16px;
+}
+.node-group-stale {
+ opacity: 0.65;
+ border-color: var(--warn, #d97706);
+}
+.node-group-header {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 12px;
+ flex-wrap: wrap;
+ margin-bottom: 12px;
+}
+.node-group-header h3 { font-size: 1.05em; margin: 0; }
+.node-group-stats {
+ display: flex;
+ gap: 12px;
+ font-size: 0.85em;
+ color: var(--text-secondary);
+}
+.node-warn { color: var(--warn, #d97706); font-weight: 500; }
+
+.node-roots-header { margin-bottom: 8px; }
+
+.node-root {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ padding: 6px 0;
+}
+.node-root + .node-root { border-top: 1px solid var(--border); }
+.node-root-info {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-wrap: wrap;
+ min-width: 0;
+}
+.node-root-name {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-weight: 500;
+}
+.node-root-name .icon { width: 16px; height: 16px; flex-shrink: 0; }
+.node-root-badge {
+ font-size: 0.75em;
+ padding: 1px 6px;
+ border-radius: 4px;
+ background: var(--accent);
+ color: var(--accent-text);
+}
+.node-root-badge-warn {
+ background: var(--warn, #d97706);
+ color: #fff;
+}
+.node-root-kind {
+ font-size: 0.8em;
+ color: var(--text-dim);
+}
+.node-root-unavailable { opacity: 0.6; }
+
+.node-message {
+ font-size: 0.9em;
+ color: var(--text-secondary);
+ margin-bottom: 12px;
+ padding: 8px 12px;
+ background: var(--bg-raised);
+ border-radius: 6px;
+}
+
+.btn-small {
+ padding: 4px 10px;
+ font-size: 0.82em;
+ border-radius: 5px;
+}
+
+.node-roots > .btn-small { margin-top: 8px; }
+
+.node-header {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 4px;
+}
+.node-header h2 { margin-bottom: 0; }
+
+.node-section { margin-top: 16px; }
+.node-section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ margin-bottom: 8px;
+}
+.node-hint {
+ font-size: 0.85em;
+ color: var(--text-secondary);
+ margin: 4px 0;
+}
+
+.node-table {
+ width: 100%;
+ font-size: 0.85em;
+ border-collapse: collapse;
+ margin-top: 4px;
+}
+.node-table th {
+ text-align: left;
+ padding: 4px 8px;
+ border-bottom: 1px solid var(--border);
+ color: var(--text-secondary);
+ font-weight: 500;
+}
+.node-table td {
+ padding: 4px 8px;
+ border-bottom: 1px solid var(--border);
+}
+
+.node-deny-entry {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ padding: 4px 0;
+ font-size: 0.85em;
+}
+.node-deny-entry + .node-deny-entry { border-top: 1px solid var(--border); }
+
+.node-attach-form {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-top: 8px;
+ max-width: 400px;
+}
+.node-attach-form label {
+ font-size: 0.82em;
+ color: var(--text-secondary);
+ margin-top: 4px;
+}
+.node-attach-form input,
+.node-attach-form select {
+ padding: 6px 10px;
+ border: 1px solid var(--border);
+ border-radius: 5px;
+ background: var(--bg-base);
+ color: var(--text);
+ font-size: 0.9em;
+ cursor: pointer;
+}
+.node-attach-form select option {
+ background: var(--bg-base);
+ color: var(--text);
+}
+.node-attach-dir-row {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+}
+.node-attach-dir-row input { flex: 1; }