Nodes that connect without a valid token during the migration window are now accepted and flagged as Unpaired rather than rejected. Fleet health surfaces the flag so the dashboard can show an amber Unpaired badge in the MAC column, an "Unpaired" status badge, and a ↺ re-provision action button that re-opens the onboarding wizard in reprove mode (skips the firmware-flash step, targets the specific node's MAC during detect). - ingestion/server: migrationDeadline + Unpaired flag on NodeConnection - fleet/fleethandler: UnpairedProvider interface, merges unpaired MACs into fleet health response - config: SPAXEL_MIGRATION_WINDOW_HOURS (default 24 h, range 0-168) - main: wires migration deadline and unpaired provider at startup - onboard.js: reprove(mac) public API, skip-flash + targeted-detect mode - fleet.js: Unpaired badge, Re-provision button, unpaired banner, ⚠ in role list, reproveNode public API - wizard.css: .wizard-reprove-banner amber styling Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
267 lines
5.5 KiB
CSS
267 lines
5.5 KiB
CSS
/* ───────────────────────────────────────────────────────────────
|
|
Onboarding Wizard — inline-to-external extraction from simple.html
|
|
─────────────────────────────────────────────────────────────── */
|
|
|
|
#wizard-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: var(--overlay-strong);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
#wizard-card {
|
|
background: var(--bg-card);
|
|
border-radius: var(--radius-modal);
|
|
padding: var(--space-6) var(--space-8) var(--space-5);
|
|
max-width: 560px;
|
|
width: 92%;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
box-shadow: var(--shadow-xl);
|
|
}
|
|
|
|
#wizard-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: var(--space-5);
|
|
}
|
|
|
|
#wizard-header h1 {
|
|
font-size: var(--text-lg);
|
|
font-weight: var(--fw-heading);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.wizard-close {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-muted);
|
|
font-size: var(--text-2xl);
|
|
cursor: pointer;
|
|
padding: 0 var(--space-1);
|
|
line-height: 1;
|
|
}
|
|
|
|
.wizard-close:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
#wizard-steps {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: var(--space-6);
|
|
gap: 0;
|
|
}
|
|
|
|
.wizard-step-dot {
|
|
width: var(--space-6);
|
|
height: var(--space-6);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--fw-heading);
|
|
background: var(--bg-active);
|
|
color: var(--slate-7);
|
|
transition: all var(--transition-base);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.wizard-step-dot.active {
|
|
background: var(--color-primary);
|
|
color: var(--text-on-accent);
|
|
box-shadow: 0 0 var(--space-3) var(--blue-border);
|
|
}
|
|
|
|
.wizard-step-dot.completed {
|
|
background: var(--ok);
|
|
color: var(--text-on-accent);
|
|
}
|
|
|
|
.wizard-step-line {
|
|
width: var(--space-5);
|
|
height: 2px;
|
|
background: var(--bg-active);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.wizard-step-line.completed {
|
|
background: var(--ok);
|
|
}
|
|
|
|
#wizard-content {
|
|
min-height: 180px;
|
|
}
|
|
|
|
.wizard-step-content {
|
|
text-align: center;
|
|
}
|
|
|
|
.wizard-step-content h2 {
|
|
font-size: var(--text-lg);
|
|
margin-bottom: var(--space-2);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.wizard-step-content p {
|
|
color: var(--text-secondary);
|
|
font-size: var(--text-sm);
|
|
line-height: var(--lh-body);
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.wizard-muted {
|
|
color: var(--text-muted) !important;
|
|
font-size: var(--text-xs) !important;
|
|
}
|
|
|
|
.wizard-center-msg {
|
|
padding: var(--space-6) 0;
|
|
text-align: center;
|
|
}
|
|
|
|
.wizard-center-msg p {
|
|
color: var(--text-muted);
|
|
margin-top: var(--space-3);
|
|
}
|
|
|
|
.wizard-error {
|
|
color: var(--alert);
|
|
font-size: var(--text-sm);
|
|
padding: var(--space-3);
|
|
background: var(--alert-bg);
|
|
border-radius: var(--radius-control);
|
|
text-align: left;
|
|
margin-top: var(--space-2);
|
|
}
|
|
|
|
.wizard-success {
|
|
color: var(--ok);
|
|
font-size: var(--text-sm);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.wizard-reprove-banner {
|
|
background: rgba(251, 191, 36, 0.12);
|
|
border: 1px solid rgba(251, 191, 36, 0.4);
|
|
border-radius: var(--radius-control);
|
|
color: #fbbf24;
|
|
font-size: var(--text-sm);
|
|
padding: var(--space-3);
|
|
margin-bottom: var(--space-4);
|
|
}
|
|
|
|
.wizard-icon-large {
|
|
font-size: var(--text-2xl);
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.wizard-list {
|
|
text-align: left;
|
|
color: var(--text-secondary);
|
|
font-size: var(--text-sm);
|
|
line-height: 1.8;
|
|
margin: var(--space-3) 0;
|
|
padding-left: var(--space-5);
|
|
}
|
|
|
|
.wizard-spinner {
|
|
display: inline-block;
|
|
width: var(--space-8);
|
|
height: var(--space-8);
|
|
border: 3px solid var(--blue-border);
|
|
border-top-color: var(--color-primary);
|
|
border-radius: 50%;
|
|
animation: wizard-spin .8s linear infinite;
|
|
}
|
|
|
|
.spinner {
|
|
display: inline-block;
|
|
width: var(--space-8);
|
|
height: var(--space-8);
|
|
border: 3px solid var(--blue-border);
|
|
border-top-color: var(--color-primary);
|
|
border-radius: 50%;
|
|
animation: wizard-spin .8s linear infinite;
|
|
}
|
|
|
|
@keyframes wizard-spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.wizard-progress {
|
|
margin: var(--space-4) 0;
|
|
}
|
|
|
|
.progress-bar {
|
|
width: 100%;
|
|
height: var(--space-2);
|
|
background: var(--bg-active);
|
|
border-radius: var(--space-1);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, var(--blue-9), var(--blue-10));
|
|
border-radius: var(--space-1);
|
|
transition: width var(--transition-base);
|
|
width: 0%;
|
|
}
|
|
|
|
.wizard-progress p {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-muted);
|
|
margin-top: var(--space-2);
|
|
text-align: center;
|
|
}
|
|
|
|
.wizard-btn {
|
|
padding: var(--space-3) var(--space-6);
|
|
border-radius: var(--radius-control);
|
|
font-size: var(--text-sm);
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
border: none;
|
|
transition: background var(--transition-base), opacity var(--transition-base);
|
|
}
|
|
|
|
.wizard-btn:disabled {
|
|
opacity: .5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.wizard-btn-primary {
|
|
background: var(--color-primary);
|
|
color: var(--slate-1);
|
|
}
|
|
|
|
.wizard-btn-primary:hover:not(:disabled) {
|
|
background: var(--color-primary-hover);
|
|
}
|
|
|
|
.wizard-btn-secondary {
|
|
background: var(--bg-hover);
|
|
color: var(--text-secondary);
|
|
border: 1px solid var(--border-strong);
|
|
}
|
|
|
|
.wizard-btn-secondary:hover:not(:disabled) {
|
|
background: var(--bg-active);
|
|
}
|
|
|
|
#wizard-nav {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-top: var(--space-5);
|
|
gap: var(--space-3);
|
|
}
|