258 lines
11 KiB
JavaScript
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;
|