Files
mek-tech-consulting/racks.js
T

258 lines
11 KiB
JavaScript

const express = require('express');
const router = express.Router();
const { getDb } = require('../db');
const { parseDiagramJson, summarizeDiagram, rackDevicesToNodes, DIAGRAM_TYPE_LABELS, buildStudioDemo, getWorkspaceTab } = require('../lib/rack-diagram');
function loadRackWorkspace(db, rackId) {
const rack = db.prepare('SELECT r.*, c.name as client_name FROM racks r JOIN clients c ON c.id = r.client_id WHERE r.id = ?').get(rackId);
if (!rack) return null;
const rackDevices = db.prepare('SELECT * FROM rack_devices WHERE rack_id = ? ORDER BY position_u ASC').all(rackId);
const linkedDiagrams = db.prepare('SELECT id, name, diagram_type, updated_at FROM data_diagrams WHERE rack_id = ? ORDER BY updated_at DESC').all(rackId);
const flowDiagram = linkedDiagrams.find(d => d.diagram_type === 'application_flow') || null;
const landscapeDiagram = linkedDiagrams.find(d => d.diagram_type === 'application_landscape') || null;
return { rack, rackDevices, linkedDiagrams, flowDiagram, landscapeDiagram };
}
function renderArchEditor(res, opts) {
const diagram = opts.diagram || null;
const rack = opts.rack;
const workspaceTab = opts.workspaceTab || (diagram ? getWorkspaceTab(diagram.diagram_type) : opts.defaultDiagramType === 'application_landscape' ? 'landscape' : 'flow');
res.render('arch-editor', {
diagram,
clients: opts.clients,
clientId: opts.clientId || (rack ? rack.client_id : null),
rack,
rackDevices: opts.rackDevices || [],
linkedDiagrams: opts.linkedDiagrams || [],
flowDiagram: opts.flowDiagram || null,
landscapeDiagram: opts.landscapeDiagram || null,
defaultDiagramType: opts.defaultDiagramType || null,
workspaceTab,
embed: !!opts.embed,
error: opts.error || null
});
}
router.get('/', (req, res) => {
const db = getDb();
const racks = db.prepare(`
SELECT r.*, c.name as client_name,
(SELECT COUNT(*) FROM rack_devices d WHERE d.rack_id = r.id) as device_count
FROM racks r JOIN clients c ON c.id = r.client_id ORDER BY r.updated_at DESC
`).all();
res.render('racks-list', { racks });
});
router.get('/new', (req, res) => {
const db = getDb();
const clients = db.prepare('SELECT id, name FROM clients ORDER BY name').all();
res.render('racks-form', { rack: null, clients, selected: req.query.client_id ? parseInt(req.query.client_id, 10) : null, error: null });
});
router.get('/new/:clientId', (req, res) => {
const db = getDb();
const clients = db.prepare('SELECT id, name FROM clients ORDER BY name').all();
res.render('racks-form', { rack: null, clients, selected: parseInt(req.params.clientId), error: null });
});
router.post('/new', (req, res) => {
const db = getDb();
const { client_id, name, location, datacenter, total_units, notes } = req.body;
if (!name || !client_id) return res.redirect('/racks');
db.prepare('INSERT INTO racks (client_id, name, location, datacenter, total_units, notes) VALUES (?,?,?,?,?,?)')
.run(client_id, name, location, datacenter, total_units || 42, notes);
res.redirect('/racks');
});
router.get('/:id', (req, res) => {
const db = getDb();
const ws = loadRackWorkspace(db, req.params.id);
if (!ws) return res.redirect('/racks');
const hardwareLibrary = require('../data/hardware-library');
if (req.query.embed === '1') {
return res.render('racks-view', {
...ws,
devices: ws.rackDevices,
hardwareLibrary,
embed: true,
workspaceTab: 'rack'
});
}
res.render('racks-view', {
rack: ws.rack,
devices: ws.rackDevices,
hardwareLibrary,
linkedDiagrams: ws.linkedDiagrams,
flowDiagram: ws.flowDiagram,
landscapeDiagram: ws.landscapeDiagram,
workspaceTab: 'rack',
embed: false
});
});
router.get('/:id/edit', (req, res) => {
const db = getDb();
const rack = db.prepare('SELECT * FROM racks WHERE id = ?').get(req.params.id);
if (!rack) return res.redirect('/racks');
const clients = db.prepare('SELECT id, name FROM clients ORDER BY name').all();
res.render('racks-form', { rack, clients, selected: rack.client_id, error: null });
});
router.post('/:id/edit', (req, res) => {
const db = getDb();
const { name, location, datacenter, total_units, notes } = req.body;
db.prepare("UPDATE racks SET name=?, location=?, datacenter=?, total_units=?, notes=?, updated_at=datetime('now') WHERE id=?")
.run(name, location, datacenter, total_units, notes, req.params.id);
res.redirect('/racks/' + req.params.id);
});
router.post('/:id/delete', (req, res) => {
const db = getDb();
db.prepare('DELETE FROM racks WHERE id = ?').run(req.params.id);
res.redirect('/racks');
});
router.post('/:id/devices', (req, res) => {
const db = getDb();
const { name, device_type, model, manufacturer, position_u, height_u, specs, mgmt_ip, notes, status } = req.body;
if (!name) return res.redirect('/racks/' + req.params.id);
db.prepare('INSERT INTO rack_devices (rack_id, name, device_type, model, manufacturer, position_u, height_u, specs, mgmt_ip, notes, status) VALUES (?,?,?,?,?,?,?,?,?,?,?)')
.run(req.params.id, name, device_type || 'server', model, manufacturer, parseInt(position_u), parseInt(height_u) || 1, specs, mgmt_ip, notes, status || 'active');
res.redirect('/racks/' + req.params.id);
});
router.post('/devices/:deviceId/edit', (req, res) => {
const db = getDb();
const { name, device_type, model, manufacturer, position_u, height_u, specs, mgmt_ip, notes, status } = req.body;
const device = db.prepare('SELECT rack_id FROM rack_devices WHERE id = ?').get(req.params.deviceId);
if (!device) return res.redirect('/racks');
db.prepare("UPDATE rack_devices SET name=?, device_type=?, model=?, manufacturer=?, position_u=?, height_u=?, specs=?, mgmt_ip=?, notes=?, status=?, updated_at=datetime('now') WHERE id=?")
.run(name, device_type, model, manufacturer, position_u, height_u, specs, mgmt_ip, notes, status, req.params.deviceId);
res.redirect('/racks/' + device.rack_id);
});
router.post('/devices/:deviceId/delete', (req, res) => {
const db = getDb();
const device = db.prepare('SELECT rack_id FROM rack_devices WHERE id = ?').get(req.params.deviceId);
if (device) {
db.prepare('DELETE FROM rack_devices WHERE id = ?').run(req.params.deviceId);
res.redirect('/racks/' + device.rack_id);
} else {
res.redirect('/racks');
}
});
router.get('/:id/flow/new', (req, res) => {
const db = getDb();
const ws = loadRackWorkspace(db, req.params.id);
if (!ws) return res.redirect('/racks');
const clients = db.prepare('SELECT id, name FROM clients ORDER BY name').all();
renderArchEditor(res, {
...ws,
clients,
diagram: null,
defaultDiagramType: 'application_flow',
workspaceTab: 'flow',
embed: req.query.embed === '1'
});
});
router.get('/:id/landscape/new', (req, res) => {
const db = getDb();
const ws = loadRackWorkspace(db, req.params.id);
if (!ws) return res.redirect('/racks');
const clients = db.prepare('SELECT id, name FROM clients ORDER BY name').all();
renderArchEditor(res, {
...ws,
clients,
diagram: null,
defaultDiagramType: 'application_landscape',
workspaceTab: 'landscape',
embed: req.query.embed === '1'
});
});
router.get('/:id/diagram/:diagramId', (req, res) => {
const db = getDb();
const ws = loadRackWorkspace(db, req.params.id);
if (!ws) return res.redirect('/racks');
const diagram = db.prepare('SELECT d.*, c.name as client_name FROM data_diagrams d JOIN clients c ON c.id = d.client_id WHERE d.id = ? AND d.rack_id = ?').get(req.params.diagramId, req.params.id);
if (!diagram) return res.redirect('/racks/' + req.params.id);
const clients = db.prepare('SELECT id, name FROM clients ORDER BY name').all();
renderArchEditor(res, {
...ws,
clients,
diagram,
workspaceTab: getWorkspaceTab(diagram.diagram_type),
embed: req.query.embed === '1'
});
});
router.get('/:id/studio', (req, res) => {
const db = getDb();
const ws = loadRackWorkspace(db, req.params.id);
if (!ws) return res.redirect('/racks');
res.render('rack-studio', { ...ws, workspaceTab: 'studio' });
});
router.post('/:id/studio/demo', (req, res) => {
const db = getDb();
const ws = loadRackWorkspace(db, req.params.id);
if (!ws) return res.status(404).json({ error: 'Rack not found' });
const demo = buildStudioDemo(ws.rack, ws.rackDevices);
let flowId = ws.flowDiagram ? ws.flowDiagram.id : null;
let landscapeId = ws.landscapeDiagram ? ws.landscapeDiagram.id : null;
if (flowId) {
db.prepare("UPDATE data_diagrams SET name=?, nodes=?, edges=?, updated_at=datetime('now') WHERE id=?")
.run(demo.flow.name, demo.flow.nodes, demo.flow.edges, flowId);
} else {
const r = db.prepare('INSERT INTO data_diagrams (client_id, name, description, diagram_type, nodes, edges, rack_id) VALUES (?,?,?,?,?,?,?)')
.run(ws.rack.client_id, demo.flow.name, 'Studio demo flow', 'application_flow', demo.flow.nodes, demo.flow.edges, ws.rack.id);
flowId = r.lastInsertRowid;
}
if (landscapeId) {
db.prepare("UPDATE data_diagrams SET name=?, nodes=?, edges=?, updated_at=datetime('now') WHERE id=?")
.run(demo.landscape.name, demo.landscape.nodes, demo.landscape.edges, landscapeId);
} else {
const r = db.prepare('INSERT INTO data_diagrams (client_id, name, description, diagram_type, nodes, edges, rack_id) VALUES (?,?,?,?,?,?,?)')
.run(ws.rack.client_id, demo.landscape.name, 'Studio demo landschap', 'application_landscape', demo.landscape.nodes, demo.landscape.edges, ws.rack.id);
landscapeId = r.lastInsertRowid;
}
res.json({ success: true, flowId, landscapeId, redirect: '/racks/' + ws.rack.id + '/studio' });
});
router.get('/:id/devices.json', (req, res) => {
const db = getDb();
const rack = db.prepare('SELECT * FROM racks WHERE id = ?').get(req.params.id);
if (!rack) return res.status(404).json({ error: 'Rack not found' });
const devices = db.prepare('SELECT * FROM rack_devices WHERE rack_id = ? ORDER BY position_u ASC').all(req.params.id);
res.json({ rack, devices, nodes: rackDevicesToNodes(devices) });
});
router.get('/:id/report', (req, res) => {
const db = getDb();
const rack = db.prepare('SELECT r.*, c.name as client_name FROM racks r JOIN clients c ON c.id = r.client_id WHERE r.id = ?').get(req.params.id);
if (!rack) return res.redirect('/racks');
const devices = db.prepare('SELECT * FROM rack_devices WHERE rack_id = ? ORDER BY position_u ASC').all(req.params.id);
const diagrams = db.prepare(`
SELECT d.*, c.name as client_name FROM data_diagrams d
JOIN clients c ON c.id = d.client_id WHERE d.rack_id = ?
ORDER BY CASE d.diagram_type WHEN 'application_flow' THEN 0 WHEN 'application_landscape' THEN 1 ELSE 2 END, d.updated_at DESC
`).all(req.params.id);
const diagramSummaries = diagrams.map(d => ({
...d,
...summarizeDiagram(d),
typeLabel: DIAGRAM_TYPE_LABELS[d.diagram_type] || d.diagram_type
}));
const usedU = devices.reduce((s, d) => s + (d.height_u || 1), 0);
res.render('rack-infra-report', {
rack,
devices,
diagrams: diagramSummaries,
usedU,
typeLabels: DIAGRAM_TYPE_LABELS
});
});
module.exports = router;