/* ============================================ Spaxel Fleet Status Page Styles ============================================ */ /* CSS Variables */ :root { --bg-primary: #1a1a2e; --bg-secondary: #16213e; --bg-tertiary: #0f0f23; --text-primary: #eee; --text-secondary: #aaa; --text-muted: #666; --border-color: rgba(255, 255, 255, 0.1); --accent-primary: #4fc3f7; --accent-secondary: #29b6f6; --success: #66bb6a; --warning: #ffa726; --danger: #ef5350; --offline: #9e9e9e; --updating: #ffca28; } /* Reset and Base Styles */ * { box-sizing: border-box; margin: 0; padding: 0; } body.fleet-page { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.5; min-height: 100vh; } /* ============================================ Navigation ============================================ */ .fleet-nav { background: var(--bg-secondary); border-bottom: 1px solid var(--border-color); padding: 0 24px; height: 56px; display: flex; align-items: center; } .nav-content { display: flex; align-items: center; justify-content: space-between; width: 100%; max-width: 1400px; margin: 0 auto; } .nav-logo { display: flex; align-items: center; gap: 8px; text-decoration: none; color: var(--text-primary); font-weight: 600; font-size: 18px; } .logo-icon { font-size: 24px; } .nav-title { font-size: 20px; font-weight: 600; margin-left: 20px; } .nav-actions { display: flex; gap: 8px; } .nav-btn { display: flex; align-items: center; gap: 6px; padding: 8px 16px; background: transparent; border: 1px solid var(--border-color); border-radius: 6px; color: var(--text-secondary); text-decoration: none; font-size: 13px; transition: all 0.2s; cursor: pointer; } .nav-btn:hover { background: rgba(255, 255, 255, 0.05); color: var(--text-primary); border-color: rgba(255, 255, 255, 0.2); } .nav-btn .icon { font-size: 16px; } /* ============================================ Page Header ============================================ */ .fleet-header { background: var(--bg-secondary); border-bottom: 1px solid var(--border-color); padding: 20px 24px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; } .header-content h1 { font-size: 24px; font-weight: 600; margin-bottom: 8px; } .fleet-summary { display: flex; gap: 24px; font-size: 14px; } .summary-item { display: flex; gap: 6px; } .summary-label { color: var(--text-secondary); } .summary-value { font-weight: 600; color: var(--text-primary); } .summary-value.online { color: var(--success); } .header-actions { display: flex; gap: 8px; } /* ============================================ Buttons ============================================ */ .btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; border: none; transition: all 0.2s; text-decoration: none; } .btn .icon { font-size: 14px; } .btn-primary { background: var(--accent-primary); color: var(--bg-primary); } .btn-primary:hover { background: var(--accent-secondary); } .btn-secondary { background: rgba(255, 255, 255, 0.08); color: var(--text-primary); border: 1px solid var(--border-color); } .btn-secondary:hover { background: rgba(255, 255, 255, 0.12); } .btn-action { background: rgba(102, 187, 106, 0.2); color: var(--success); border: 1px solid rgba(102, 187, 106, 0.4); } .btn-action:hover { background: rgba(102, 187, 106, 0.3); } .btn-danger { background: rgba(239, 83, 80, 0.2); color: var(--danger); border: 1px solid rgba(239, 83, 80, 0.4); } .btn-danger:hover { background: rgba(239, 83, 80, 0.3); } .btn-link { background: none; border: none; color: var(--text-secondary); padding: 8px 12px; } .btn-link:hover { color: var(--text-primary); } .btn:disabled { opacity: 0.5; cursor: not-allowed; } /* ============================================ Toolbar ============================================ */ .fleet-toolbar { background: var(--bg-tertiary); border-bottom: 1px solid var(--border-color); padding: 12px 24px; } .toolbar-section { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; } .filter-group { display: flex; gap: 8px; flex-wrap: wrap; } .search-input, .filter-select { padding: 8px 12px; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 4px; color: var(--text-primary); font-size: 13px; } .search-input { width: 200px; } .search-input:focus, .filter-select:focus { outline: none; border-color: var(--accent-primary); } .filter-select { min-width: 120px; } .active-filters { display: flex; gap: 8px; flex-wrap: wrap; } .filter-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; background: rgba(79, 195, 247, 0.15); border: 1px solid rgba(79, 195, 247, 0.3); border-radius: 16px; font-size: 12px; color: var(--accent-primary); } .filter-chip .filter-dismiss { cursor: pointer; font-size: 14px; opacity: 0.7; } .filter-chip .filter-dismiss:hover { opacity: 1; } /* ============================================ Bulk Actions Bar ============================================ */ .bulk-actions-bar { background: rgba(79, 195, 247, 0.1); border-bottom: 1px solid rgba(79, 195, 247, 0.3); padding: 12px 24px; } .bulk-content { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; } .bulk-count { font-size: 14px; font-weight: 500; } .bulk-buttons { display: flex; gap: 8px; } /* ============================================ Main Content ============================================ */ .fleet-main { padding: 0; max-width: 1400px; margin: 0 auto; } .table-container { overflow-x: auto; } /* ============================================ Fleet Table ============================================ */ .fleet-table { width: 100%; border-collapse: collapse; font-size: 13px; } .fleet-table thead { position: sticky; top: 0; background: var(--bg-secondary); z-index: 10; } .fleet-table th { padding: 12px 16px; text-align: left; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; font-size: 11px; letter-spacing: 0.5px; border-bottom: 2px solid var(--border-color); white-space: nowrap; user-select: none; } .fleet-table th.sortable { cursor: pointer; } .fleet-table th.sortable:hover { background: rgba(255, 255, 255, 0.05); } .fleet-table th.sortable::after { content: ' ⇅'; opacity: 0.3; margin-left: 4px; } .fleet-table th.sort-asc::after { content: ' ▲'; opacity: 1; color: var(--accent-primary); } .fleet-table th.sort-desc::after { content: ' ▼'; opacity: 1; color: var(--accent-primary); } .fleet-table td { padding: 12px 16px; border-bottom: 1px solid var(--border-color); } .fleet-table tbody tr { transition: background 0.15s; } .fleet-table tbody tr:hover { background: rgba(255, 255, 255, 0.03); } .fleet-table tbody tr.selected { background: rgba(79, 195, 247, 0.1); } .fleet-table tbody tr.selected:hover { background: rgba(79, 195, 247, 0.15); } /* Column widths */ .col-checkbox { width: 40px; text-align: center; } .col-label { min-width: 150px; } .col-mac { min-width: 140px; font-family: monospace; } .col-status { min-width: 100px; } .col-firmware { min-width: 120px; font-family: monospace; font-size: 12px; } .col-uptime { min-width: 100px; font-family: monospace; } .col-role { min-width: 90px; } .col-health { min-width: 120px; } .col-packet-rate { min-width: 100px; } .col-temperature { min-width: 90px; } .col-actions { min-width: 120px; text-align: right; } /* Checkbox */ .checkbox { width: 16px; height: 16px; cursor: pointer; accent-color: var(--accent-primary); } /* Label (editable) */ .node-label { cursor: text; padding: 4px 8px; border-radius: 4px; border: 1px solid transparent; transition: all 0.2s; } .node-label:hover { background: rgba(255, 255, 255, 0.05); border-color: rgba(255, 255, 255, 0.1); } .node-label.editing { background: var(--bg-secondary); border-color: var(--accent-primary); outline: none; } .node-label-empty { color: var(--text-muted); font-style: italic; } /* MAC Address */ .mac-address { font-family: monospace; color: var(--accent-primary); } .mac-full { font-size: 13px; } .mac-truncated { font-size: 12px; } .mac-tooltip { position: relative; cursor: help; } /* Status Badge */ .status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; } .status-dot { width: 8px; height: 8px; border-radius: 50%; } .status-badge.online .status-dot { background: var(--success); } .status-badge.online { background: rgba(102, 187, 106, 0.15); color: var(--success); } .status-badge.offline .status-dot { background: var(--offline); } .status-badge.offline { background: rgba(158, 158, 158, 0.15); color: var(--offline); } .status-badge.updating .status-dot { background: var(--updating); animation: pulse 1s infinite; } .status-badge.updating { background: rgba(255, 202, 40, 0.15); color: var(--updating); } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } /* Firmware Version */ .firmware-version { display: flex; align-items: center; gap: 6px; } .firmware-current { color: var(--text-secondary); } .firmware-outdated { color: var(--warning); } .firmware-indicator { display: inline-flex; align-items: center; gap: 4px; padding: 2px 6px; background: rgba(255, 167, 38, 0.15); border-radius: 4px; font-size: 11px; } .firmware-arrow { color: var(--warning); } /* Role Badge */ .role-badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; } .role-badge.tx { background: rgba(239, 68, 68, 0.2); color: #ef5350; } .role-badge.rx { background: rgba(59, 130, 246, 0.2); color: #3f81f6; } .role-badge.tx_rx { background: rgba(168, 85, 247, 0.2); color: #a855f7; } .role-badge.passive { background: rgba(158, 158, 158, 0.2); color: #9e9e9e; } /* Health Bar */ .health-bar-container { display: flex; align-items: center; gap: 8px; } .health-bar { width: 60px; height: 6px; background: rgba(255, 255, 255, 0.1); border-radius: 3px; overflow: hidden; } .health-bar-fill { height: 100%; transition: width 0.3s ease; } .health-bar-fill.good { background: linear-gradient(90deg, #66bb6a, #43a047); } .health-bar-fill.fair { background: linear-gradient(90deg, #ffa726, #fb8c00); } .health-bar-fill.poor { background: linear-gradient(90deg, #ef5350, #c62828); } .health-value { font-size: 11px; color: var(--text-secondary); min-width: 35px; text-align: right; } /* Packet Rate */ .packet-rate { font-family: monospace; font-size: 12px; } .packet-rate.good { color: var(--success); } .packet-rate.fair { color: var(--warning); } .packet-rate.poor { color: var(--danger); } /* Temperature */ .temperature { font-family: monospace; font-size: 12px; color: var(--text-secondary); } .temperature.alert { color: var(--danger); } /* Action Buttons */ .action-buttons { display: flex; gap: 4px; justify-content: flex-end; } .action-btn { background: none; border: none; color: var(--text-secondary); cursor: pointer; padding: 6px; font-size: 16px; border-radius: 4px; transition: all 0.2s; } .action-btn:hover { color: var(--accent-primary); background: rgba(79, 195, 247, 0.1); } .action-btn.disabled { opacity: 0.4; cursor: not-allowed; } .more-actions { position: relative; } .dropdown-menu { position: absolute; right: 0; top: 100%; margin-top: 4px; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); z-index: 100; min-width: 180px; display: none; } .dropdown-menu.visible { display: block; } .dropdown-item { width: 100%; padding: 10px 16px; background: none; border: none; color: var(--text-primary); text-align: left; font-size: 13px; cursor: pointer; transition: background 0.2s; display: flex; align-items: center; gap: 8px; } .dropdown-item:hover { background: rgba(255, 255, 255, 0.08); } .dropdown-item.dropdown-divider { padding: 0; height: 1px; background: var(--border-color); cursor: default; } .dropdown-item.dropdown-danger { color: var(--danger); } .dropdown-item.dropdown-danger:hover { background: rgba(239, 83, 80, 0.1); } .dropdown-item .dropdown-icon { font-size: 14px; width: 20px; } /* ============================================ Loading and Empty States ============================================ */ .loading-row { text-align: center; } .loading-spinner { display: inline-block; width: 20px; height: 20px; border: 2px solid rgba(255, 255, 255, 0.1); border-top-color: var(--accent-primary); border-radius: 50%; animation: spin 1s linear infinite; margin-right: 12px; vertical-align: middle; } @keyframes spin { to { transform: rotate(360deg); } } .empty-state { text-align: center; padding: 60px 20px; color: var(--text-muted); } .empty-icon { font-size: 64px; margin-bottom: 16px; opacity: 0.5; } .empty-state h3 { font-size: 20px; color: var(--text-secondary); margin-bottom: 8px; } .empty-state p { margin-bottom: 24px; } /* ============================================ Modals ============================================ */ .modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; z-index: 1000; backdrop-filter: blur(4px); } .modal-content { background: var(--bg-secondary); border-radius: 12px; width: 90%; max-width: 500px; max-height: 85vh; overflow: hidden; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); border: 1px solid var(--border-color); } .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border-color); } .modal-header h3 { font-size: 18px; font-weight: 600; } .modal-close { background: none; border: none; color: var(--text-secondary); font-size: 24px; cursor: pointer; padding: 0; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 4px; } .modal-close:hover { background: rgba(255, 255, 255, 0.1); color: var(--text-primary); } .modal-body { padding: 20px; max-height: 50vh; overflow-y: auto; } .modal-footer { display: flex; justify-content: flex-end; gap: 12px; padding: 16px 20px; border-top: 1px solid var(--border-color); background: rgba(0, 0, 0, 0.2); } /* OTA Modal */ .ota-info { margin-top: 16px; padding: 12px; background: rgba(0, 0, 0, 0.2); border-radius: 6px; } .info-item { display: flex; justify-content: space-between; padding: 6px 0; } .info-label { color: var(--text-secondary); } .info-value { font-weight: 500; } .version-current { color: var(--text-secondary); } .version-latest { color: var(--accent-primary); font-weight: 600; } /* Role Modal */ .role-options { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; } .role-option { display: flex; align-items: center; gap: 12px; padding: 12px; background: rgba(0, 0, 0, 0.2); border-radius: 6px; cursor: pointer; transition: background 0.2s; } .role-option:hover { background: rgba(0, 0, 0, 0.3); } .role-option input[type="radio"] { width: 16px; height: 16px; accent-color: var(--accent-primary); } .role-desc { font-size: 12px; color: var(--text-muted); } /* Remove Modal */ .remove-warning { margin: 16px 0; padding: 12px; background: rgba(239, 83, 80, 0.1); border: 1px solid rgba(239, 83, 80, 0.3); border-radius: 6px; } .remove-warning ul { margin: 8px 0 0 20px; font-size: 13px; } .remove-warning-text { color: var(--danger); font-size: 13px; } /* ============================================ Toast Notifications ============================================ */ .toast-container { position: fixed; bottom: 24px; right: 24px; z-index: 1100; display: flex; flex-direction: column; gap: 8px; } .toast { padding: 12px 16px; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); display: flex; align-items: center; gap: 12px; min-width: 280px; animation: slideIn 0.3s ease; } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } .toast.success { border-left: 4px solid var(--success); } .toast.error { border-left: 4px solid var(--danger); } .toast.warning { border-left: 4px solid var(--warning); } .toast.info { border-left: 4px solid var(--accent-primary); } .toast-icon { font-size: 18px; } .toast.success .toast-icon { color: var(--success); } .toast.error .toast-icon { color: var(--danger); } .toast.warning .toast-icon { color: var(--warning); } .toast.info .toast-icon { color: var(--accent-primary); } .toast-message { flex: 1; font-size: 13px; } .toast-dismiss { background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 16px; padding: 0; } .toast-dismiss:hover { color: var(--text-primary); } /* ============================================ Responsive Design ============================================ */ @media (max-width: 768px) { .fleet-header { flex-direction: column; align-items: flex-start; } .header-actions { width: 100%; justify-content: space-between; } .header-actions .btn { flex: 1; justify-content: center; } .header-actions .btn .label { display: none; } .fleet-toolbar { padding: 12px 16px; } .filter-group { width: 100%; } .search-input { width: 100%; } .filter-select { flex: 1; min-width: 80px; } .bulk-actions-bar { padding: 12px 16px; } .bulk-content { flex-direction: column; align-items: stretch; } .bulk-buttons { flex-wrap: wrap; } .fleet-table { font-size: 12px; } .fleet-table th, .fleet-table td { padding: 8px 10px; } /* Hide less important columns on mobile */ .col-health, .col-packet-rate, .col-temperature { display: none; } .nav-title { font-size: 16px; } .nav-btn .label { display: none; } } @media (max-width: 480px) { .fleet-nav { padding: 0 12px; } .nav-content { gap: 8px; } .nav-title { margin-left: 8px; } .header-content h1 { font-size: 20px; } .fleet-summary { flex-direction: column; gap: 4px; font-size: 12px; } .modal-content { width: 95%; margin: 12px; } .toast-container { right: 12px; left: 12px; bottom: 12px; } .toast { min-width: auto; } }