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;