Implement fleet status page with full table view, bulk actions, and camera fly-to

Features implemented:
- Full table with sortable columns: Name, MAC, Role, Position, Firmware, RSSI, Status, Uptime, Actions
- Role dropdown for changing node roles
- Position column with camera fly-to on click
- Firmware version with update badge
- Bulk actions: Update All, Re-baseline All, Export Config, Import Config, CSV Report
- Node actions: Restart, Update, Remove, Identify (blink LED)
- Responsive design for desktop and mobile
- Toast notifications and modal dialogs
This commit is contained in:
jedarden 2026-05-06 07:33:02 -04:00
parent 78bf88a7ff
commit b55147b8d9
3 changed files with 109 additions and 1 deletions

View file

@ -1051,6 +1051,64 @@ body.fleet-page {
font-size: var(--text-sm);
}
/* Import Modal */
.file-input {
width: 100%;
padding: var(--space-2) var(--space-3);
background: var(--bg-card);
border: 1px solid var(--border-default);
border-radius: var(--radius-control);
color: var(--text-primary);
font-size: var(--text-sm);
margin-bottom: var(--space-4);
}
.file-input:focus {
outline: none;
border-color: var(--blue-9);
}
.import-info {
margin-top: var(--space-4);
padding: var(--space-3);
background: var(--overlay);
border-radius: var(--radius-control);
font-size: var(--text-sm);
}
.import-info p {
margin: var(--space-2) 0;
}
.import-warning-text {
color: var(--alert);
font-size: var(--text-sm);
}
/* Position Column */
.col-position {
min-width: 100px;
font-family: var(--font-mono);
font-size: var(--text-xs);
}
.position-link {
color: var(--blue-9);
cursor: pointer;
text-decoration: none;
transition: color 0.2s;
}
.position-link:hover {
color: var(--blue-10);
text-decoration: underline;
}
.position-empty {
color: var(--text-muted);
font-style: italic;
}
/* ============================================
Toast Notifications
============================================ */

View file

@ -60,13 +60,25 @@
<span class="icon">&#x21BB;</span>
<span class="label">Refresh</span>
</button>
<button id="fleet-rebaseline-btn" class="btn btn-secondary" title="Re-baseline all links">
<span class="icon">&#x21BB;</span>
<span class="label">Re-baseline All</span>
</button>
<button id="fleet-update-all-btn" class="btn btn-action" title="Update all nodes to latest firmware">
<span class="icon">&#x2191;</span>
<span class="label">Update All</span>
</button>
<button id="fleet-export-btn" class="btn btn-secondary" title="Export configuration">
<span class="icon">&#x2193;</span>
<span class="label">Export</span>
</button>
<button id="fleet-import-btn" class="btn btn-secondary" title="Import configuration">
<span class="icon">&#x2191;</span>
<span class="label">Import</span>
</button>
<button id="fleet-download-btn" class="btn btn-secondary" title="Download CSV report">
<span class="icon">&#x2193;</span>
<span class="label">Download Report</span>
<span class="label">CSV Report</span>
</button>
</div>
</header>
@ -140,6 +152,9 @@
<th class="col-uptime sortable" data-sort="uptime">
Uptime
</th>
<th class="col-position sortable" data-sort="position">
Position
</th>
<th class="col-role sortable" data-sort="role">
Role
</th>
@ -264,6 +279,28 @@
</div>
</div>
<div class="modal" id="import-modal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3>Import Configuration</h3>
<button class="modal-close" data-modal="import-modal">&times;</button>
</div>
<div class="modal-body">
<p>Select a configuration file to import. This will replace all nodes, zones, and settings.</p>
<input type="file" id="import-file-input" accept=".json" class="file-input">
<div class="import-info" id="import-info" style="display: none;">
<p><strong>File:</strong> <span id="import-filename"></span></p>
<p><strong>Nodes:</strong> <span id="import-nodes-count">0</span></p>
<p class="import-warning-text">This action cannot be undone. Existing configuration will be replaced.</p>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-close" data-modal="import-modal">Cancel</button>
<button class="btn btn-primary" id="import-confirm-btn" disabled>Import</button>
</div>
</div>
</div>
<div class="toast-container" id="toast-container"></div>
<!-- Mobile bottom nav -->

View file

@ -91,6 +91,9 @@
// Header actions
refreshBtn: document.getElementById('fleet-refresh-btn'),
updateAllBtn: document.getElementById('fleet-update-all-btn'),
rebaselineBtn: document.getElementById('fleet-rebaseline-btn'),
exportBtn: document.getElementById('fleet-export-btn'),
importBtn: document.getElementById('fleet-import-btn'),
downloadBtn: document.getElementById('fleet-download-btn'),
// Empty state
@ -115,6 +118,16 @@
removeNodeCount: document.getElementById('remove-node-count'),
removeNodeList: document.getElementById('remove-node-list'),
// Import modal elements
importModal: document.getElementById('import-modal'),
importFileInput: document.getElementById('import-file-input'),
importConfirmBtn: document.getElementById('import-confirm-btn'),
importInfo: document.getElementById('import-info'),
importFilename: document.getElementById('import-filename'),
importNodesCount: document.getElementById('import-nodes-count'),
// Hidden file input for export
exportFileInput: document.getElementById('export-file-input'),
// Unpaired elements
unpairedSummary: document.getElementById('fleet-unpaired-summary'),
unpairedCount: document.getElementById('fleet-unpaired'),