NetworkView: API routing fix, logout button, audit trail, port/notes editor tracking
- Fix frontend API base path (VITE_API_BASE env var, GraphPage hardcoded /api) - Add logout button to NetworkView sidebar (clears portal SSO) - Add AuditTrail component: inline change history on all entity pages - DB migration: add updated_at, last_edited_by to ports table - DB migration: add notes_last_edited_by, notes_updated_at to all entity tables - Backend: track actor on port create/update; notes editor on entity PUT - Frontend: extend types, MarkdownEditor shows last editor, port modal/list show last editor - Fix port CREATE TABLE definition to include new columns upfront - Add try/catch in handleSavePort to surface API errors in modal
This commit is contained in:
@@ -68,7 +68,9 @@ db.exec(`
|
||||
port_type TEXT DEFAULT 'RJ45',
|
||||
connected_to_port_id TEXT REFERENCES ports(id) ON DELETE SET NULL,
|
||||
notes TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_edited_by TEXT
|
||||
);
|
||||
`);
|
||||
|
||||
@@ -76,6 +78,20 @@ db.exec(`
|
||||
try { db.exec('ALTER TABLE components ADD COLUMN port_count INTEGER DEFAULT NULL'); } catch (_) {}
|
||||
try { db.exec('ALTER TABLE components ADD COLUMN sfp_count INTEGER DEFAULT NULL'); } catch (_) {}
|
||||
|
||||
// Migrate: port editor tracking
|
||||
try { db.exec('ALTER TABLE ports ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP'); } catch (_) {}
|
||||
try { db.exec('ALTER TABLE ports ADD COLUMN last_edited_by TEXT'); } catch (_) {}
|
||||
|
||||
// Migrate: notes editor tracking per entity
|
||||
try { db.exec('ALTER TABLE sites ADD COLUMN notes_last_edited_by TEXT'); } catch (_) {}
|
||||
try { db.exec('ALTER TABLE sites ADD COLUMN notes_updated_at DATETIME'); } catch (_) {}
|
||||
try { db.exec('ALTER TABLE rooms ADD COLUMN notes_last_edited_by TEXT'); } catch (_) {}
|
||||
try { db.exec('ALTER TABLE rooms ADD COLUMN notes_updated_at DATETIME'); } catch (_) {}
|
||||
try { db.exec('ALTER TABLE racks ADD COLUMN notes_last_edited_by TEXT'); } catch (_) {}
|
||||
try { db.exec('ALTER TABLE racks ADD COLUMN notes_updated_at DATETIME'); } catch (_) {}
|
||||
try { db.exec('ALTER TABLE components ADD COLUMN notes_last_edited_by TEXT'); } catch (_) {}
|
||||
try { db.exec('ALTER TABLE components ADD COLUMN notes_updated_at DATETIME'); } catch (_) {}
|
||||
|
||||
// ── Users & Audit tables ─────────────────────────────────────────────────────
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
|
||||
@@ -67,6 +67,6 @@ app.get('*', (_req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
app.listen(PORT, '127.0.0.1', () => {
|
||||
console.log(`NetworkView backend running on http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
@@ -126,12 +126,16 @@ router.put('/:id', (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
const notesChanging = notes != null;
|
||||
const actor = req.actor?.username || null;
|
||||
db.prepare(`
|
||||
UPDATE components SET
|
||||
name = ?, type = ?, position = ?, height_units = ?,
|
||||
manufacturer = ?, model = ?, serial_number = ?, asset_tag = ?,
|
||||
ip_address = ?, mac_address = ?, status = ?, notes = ?,
|
||||
port_count = ?, sfp_count = ?,
|
||||
notes_last_edited_by = CASE WHEN ? = 1 THEN ? ELSE notes_last_edited_by END,
|
||||
notes_updated_at = CASE WHEN ? = 1 THEN CURRENT_TIMESTAMP ELSE notes_updated_at END,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
@@ -149,6 +153,7 @@ router.put('/:id', (req, res) => {
|
||||
notes ?? component.notes,
|
||||
port_count !== undefined ? port_count : component.port_count,
|
||||
sfp_count !== undefined ? sfp_count : component.sfp_count,
|
||||
notesChanging ? 1 : 0, actor, notesChanging ? 1 : 0,
|
||||
req.params.id
|
||||
);
|
||||
|
||||
@@ -198,9 +203,9 @@ router.post('/:id/ports', (req, res) => {
|
||||
|
||||
const id = uuidv4();
|
||||
db.prepare(`
|
||||
INSERT INTO ports (id, component_id, port_number, label, port_type, connected_to_port_id, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(id, req.params.id, port_number, label || null, port_type || 'RJ45', connected_to_port_id || null, notes || null);
|
||||
INSERT INTO ports (id, component_id, port_number, label, port_type, connected_to_port_id, notes, last_edited_by, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
`).run(id, req.params.id, port_number, label || null, port_type || 'RJ45', connected_to_port_id || null, notes || null, req.actor?.username || null);
|
||||
|
||||
// Mirror link on the other side
|
||||
if (connected_to_port_id) {
|
||||
@@ -225,12 +230,13 @@ router.put('/:componentId/ports/:portId', (req, res) => {
|
||||
}
|
||||
|
||||
db.prepare(`
|
||||
UPDATE ports SET label = ?, port_type = ?, connected_to_port_id = ?, notes = ? WHERE id = ?
|
||||
UPDATE ports SET label = ?, port_type = ?, connected_to_port_id = ?, notes = ?, last_edited_by = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
||||
`).run(
|
||||
label ?? port.label,
|
||||
port_type ?? port.port_type,
|
||||
newLinkedId,
|
||||
notes ?? port.notes,
|
||||
req.actor?.username || null,
|
||||
req.params.portId
|
||||
);
|
||||
|
||||
|
||||
@@ -76,8 +76,12 @@ router.put('/:id', (req, res) => {
|
||||
if (total_units != null && total_units !== rack.total_units) changes.total_units = { from: rack.total_units, to: total_units };
|
||||
if (manufacturer != null && manufacturer !== rack.manufacturer) changes.manufacturer = { from: rack.manufacturer, to: manufacturer };
|
||||
if (model != null && model !== rack.model) changes.model = { from: rack.model, to: model };
|
||||
const notesChanging = notes != null;
|
||||
const actor = req.actor?.username || null;
|
||||
db.prepare(`
|
||||
UPDATE racks SET name = ?, total_units = ?, manufacturer = ?, model = ?, notes = ?,
|
||||
notes_last_edited_by = CASE WHEN ? = 1 THEN ? ELSE notes_last_edited_by END,
|
||||
notes_updated_at = CASE WHEN ? = 1 THEN CURRENT_TIMESTAMP ELSE notes_updated_at END,
|
||||
updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
||||
`).run(
|
||||
name ?? rack.name,
|
||||
@@ -85,6 +89,7 @@ router.put('/:id', (req, res) => {
|
||||
manufacturer ?? rack.manufacturer,
|
||||
model ?? rack.model,
|
||||
notes ?? rack.notes,
|
||||
notesChanging ? 1 : 0, actor, notesChanging ? 1 : 0,
|
||||
req.params.id
|
||||
);
|
||||
|
||||
|
||||
@@ -63,9 +63,15 @@ router.put('/:id', (req, res) => {
|
||||
const { name, notes } = req.body;
|
||||
const changes = {};
|
||||
if (name != null && name !== room.name) changes.name = { from: room.name, to: name };
|
||||
const notesChanging = notes != null;
|
||||
const actor = req.actor?.username || null;
|
||||
db.prepare(`
|
||||
UPDATE rooms SET name = ?, notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
||||
`).run(name ?? room.name, notes ?? room.notes, req.params.id);
|
||||
UPDATE rooms SET name = ?, notes = ?,
|
||||
notes_last_edited_by = CASE WHEN ? = 1 THEN ? ELSE notes_last_edited_by END,
|
||||
notes_updated_at = CASE WHEN ? = 1 THEN CURRENT_TIMESTAMP ELSE notes_updated_at END,
|
||||
updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
||||
`).run(name ?? room.name, notes ?? room.notes,
|
||||
notesChanging ? 1 : 0, actor, notesChanging ? 1 : 0, req.params.id);
|
||||
|
||||
logAudit(req, { action: 'update', entityType: 'room', entityId: room.id, entityName: name ?? room.name, changes });
|
||||
res.json(db.prepare('SELECT * FROM rooms WHERE id = ?').get(req.params.id));
|
||||
|
||||
@@ -56,10 +56,16 @@ router.put('/:id', (req, res) => {
|
||||
const changes = {};
|
||||
if (name != null && name !== site.name) changes.name = { from: site.name, to: name };
|
||||
if (location != null && location !== site.location) changes.location = { from: site.location, to: location };
|
||||
const notesChanging = notes != null;
|
||||
const actor = req.actor?.username || null;
|
||||
db.prepare(`
|
||||
UPDATE sites SET name = ?, location = ?, notes = ?, updated_at = CURRENT_TIMESTAMP
|
||||
UPDATE sites SET name = ?, location = ?, notes = ?,
|
||||
notes_last_edited_by = CASE WHEN ? = 1 THEN ? ELSE notes_last_edited_by END,
|
||||
notes_updated_at = CASE WHEN ? = 1 THEN CURRENT_TIMESTAMP ELSE notes_updated_at END,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`).run(name ?? site.name, location ?? site.location, notes ?? site.notes, req.params.id);
|
||||
`).run(name ?? site.name, location ?? site.location, notes ?? site.notes,
|
||||
notesChanging ? 1 : 0, actor, notesChanging ? 1 : 0, req.params.id);
|
||||
|
||||
logAudit(req, { action: 'update', entityType: 'site', entityId: site.id, entityName: name ?? site.name, changes });
|
||||
res.json(db.prepare('SELECT * FROM sites WHERE id = ?').get(req.params.id));
|
||||
|
||||
Reference in New Issue
Block a user