import express from 'express'; import session from 'express-session'; import compression from 'compression'; import helmet from 'helmet'; import Docker from 'dockerode'; import cors from 'cors'; import { Readable } from 'stream'; import { instrument } from '@socket.io/admin-ui' import { router } from './router/index.js'; import { createServer } from 'node:http'; import { Server } from 'socket.io'; import { sequelize, Container } from './database/models.js'; import { currentLoad, mem, networkStats, fsSize, dockerContainerStats, dockerImages, networkInterfaces } from 'systeminformation'; import { containerCard } from './components/containerCard.js'; export const app = express(); const server = createServer(app); const port = process.env.PORT || 8000; export var docker = new Docker(); let [cpu, ram, tx, rx, disk] = [0, 0, 0, 0, 0]; let [hidden, clicked, dockerEvents] = ['', false, '']; let metricsInterval, cardsInterval, graphsInterval; let cardList = ''; const statsArray = {}; // socket.io admin ui export const io = new Server(server, { connectionStateRecovery: {}, cors: { origin: ['http://localhost:8000', 'https://admin.socket.io'], methods: ['GET', 'POST'], credentials: true } }); instrument(io, { auth: false, readonly: true }); // Session middleware const sessionMiddleware = session({ secret: "keyboard cat", resave: false, saveUninitialized: false, cookie:{ secure:false, // Only set to true if you are using HTTPS. httpOnly:false, // Only set to true if you are using HTTPS. maxAge:3600000 * 8 // Session max age in milliseconds. 3600000 = 1 hour. } }); io.engine.use(sessionMiddleware); // Express middleware app.set('view engine', 'ejs'); app.use([ compression(), cors(), helmet({contentSecurityPolicy: false}), express.static("public"), express.json(), express.urlencoded({ extended: true }), sessionMiddleware, router ]); // Initialize server server.listen(port, () => { async function init() { try { await sequelize.authenticate(); console.log('[Connected to DB]'); } catch (error) { console.log('[Could not connect to DB]', error); } try { await sequelize.sync(); console.log('[Models Synced]'); hidden = await Container.findAll({ where: {visibility:false}}); containerCards(); } catch (error) { console.log('[Could not Sync Models]', error); } console.log(`\nServer listening on http://localhost:${port}`); } init(); }); // Server metrics let serverMetrics = async () => { currentLoad().then(data => { cpu = Math.round(data.currentLoad); }); mem().then(data => { ram = Math.round((data.active / data.total) * 100); }); networkStats().then(data => { tx = data[0].tx_bytes / (1024 * 1024); rx = data[0].rx_bytes / (1024 * 1024); }); fsSize().then(data => { disk = data[0].use; }); } // List docker containers let containerCards = async () => { let list = ''; const allContainers = await docker.listContainers({ all: true }); for (const container of allContainers) { if (!hidden.includes(container.Names[0].slice(1))) { let imageVersion = container.Image.split('/'); let service = imageVersion[imageVersion.length - 1].split(':')[0]; let containerId = docker.getContainer(container.Id); let containerInfo = await containerId.inspect(); let ports_list = []; try { for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) { let ports = { check: 'checked', external: value[0].HostPort, internal: key.split('/')[0], protocol: key.split('/')[1] } ports_list.push(ports); } } catch {} let external_port = ports_list[0]?.external || 0; let internal_port = ports_list[0]?.internal || 0; let container_info = { name: container.Names[0].slice(1), service: service, id: container.Id, state: container.State, image: container.Image, external_port: external_port, internal_port: internal_port, ports: ports_list, link: 'localhost', } let card = containerCard(container_info); list += card; } } cardList = list; } // Container metrics let containerStats = async () => { const data = await docker.listContainers({ all: true }); for (const container of data) { if (!hidden.includes(container.Names[0].slice(1))) { const stats = await dockerContainerStats(container.Id); const name = container.Names[0].slice(1); if (!statsArray[name]) { statsArray[name] = { cpuArray: Array(15).fill(0), ramArray: Array(15).fill(0) }; } statsArray[name].cpuArray.push(Math.round(stats[0].cpuPercent)); statsArray[name].ramArray.push(Math.round(stats[0].memPercent)); statsArray[name].cpuArray = statsArray[name].cpuArray.slice(-15); statsArray[name].ramArray = statsArray[name].ramArray.slice(-15); } } } // Store docker events docker.getEvents((err, stream) => { if (err) throw err; stream.on('data', (chunk) => { dockerEvents += chunk.toString('utf8'); }); }); // Check for docker events setInterval( () => { if (dockerEvents != '') { getHidden(); containerCards(); dockerEvents = ''; } }, 1000); // Get hidden containers async function getHidden() { hidden = await Container.findAll({ where: {visibility:false}}); } // Socket.io io.on('connection', (socket) => { let sessionData = socket.request.session; let sent = ''; if (sessionData.user != undefined) { console.log(`${sessionData.user} connected from ${socket.handshake.headers.host}`); // Start intervals if not already started if (!metricsInterval) { metricsInterval = setInterval(serverMetrics, 1000); console.log('Metrics interval started'); } if (!cardsInterval) { cardsInterval = setInterval(containerCards, 1000); console.log('Cards interval started'); } if (!graphsInterval) { graphsInterval = setInterval(containerStats, 1000); console.log('Graphs interval started'); } setInterval(() => { socket.emit('metrics', [cpu, ram, tx, rx, disk]); if (sent != cardList) { sent = cardList; socket.emit('containers', cardList); } socket.emit('containerStats', statsArray); }, 1000); socket.on('clicked', (data) => { if (clicked == true) { return; } clicked = true; let { name, id, value } = data; console.log(`${sessionData.user} clicked: ${id} ${value} ${name}`); if (id == 'logs'){ function containerLogs (data) { return new Promise((resolve, reject) => { let logString = ''; var options = { follow: false, stdout: true, stderr: false, timestamps: false }; var containerName = docker.getContainer(data); containerName.logs(options, function (err, stream) { if (err) { reject(err); return; } const readableStream = Readable.from(stream); readableStream.on('data', function (chunk) { logString += chunk.toString('utf8'); }); readableStream.on('end', function () { resolve(logString); }); }); }); }; containerLogs(name).then((data) => { socket.emit('logs', data); }).catch((err) => { console.log(err); }); } if (id == 'start' || id == 'stop' || id == 'pause' || id == 'restart'){ var containerName = docker.getContainer(name); if ((id == 'start') && (value == 'stopped')) { containerName.start(); } else if ((id == 'start') && (value == 'paused')) { containerName.unpause(); } else if ((id == 'stop') && (value != 'stopped')) { containerName.stop(); } else if ((id == 'pause') && (value == 'running')) { containerName.pause(); } else if ((id == 'pause') && (value == 'paused')) { containerName.unpause(); } else if (id == 'restart') { containerName.restart(); } } clicked = false; }); socket.on('disconnect', () => { console.log(`${sessionData.user} disconnected`); socket.disconnect(); // clear intervals if no users are connected if (io.engine.clientsCount == 0) { clearInterval(metricsInterval); clearInterval(cardsInterval); clearInterval(graphsInterval); metricsInterval = null; cardsInterval = null; graphsInterval = null; console.log('All intervals cleared'); } }); } else { console.log('Missing session data'); } }); // let link = ''; // networkInterfaces().then(data => { // link = data[0].ip4; // });