325 lines
11 KiB
JavaScript
325 lines
11 KiB
JavaScript
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;
|
|
// });
|
|
|