server.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import express from 'express';
  2. import session from 'express-session';
  3. import compression from 'compression';
  4. import helmet from 'helmet';
  5. import Docker from 'dockerode';
  6. import cors from 'cors';
  7. import { Readable } from 'stream';
  8. import { rateLimit } from 'express-rate-limit';
  9. import { instrument } from '@socket.io/admin-ui'
  10. import { router } from './router/index.js';
  11. import { createServer } from 'node:http';
  12. import { Server } from 'socket.io';
  13. import { sequelize, Container } from './database/models.js';
  14. import { currentLoad, mem, networkStats, fsSize, dockerContainerStats, dockerImages, networkInterfaces } from 'systeminformation';
  15. import { containerCard } from './components/containerCard.js';
  16. export const app = express();
  17. const server = createServer(app);
  18. const port = process.env.PORT || 8000;
  19. export var docker = new Docker();
  20. let [cpu, ram, tx, rx, disk] = [0, 0, 0, 0, 0];
  21. let [hidden, clicked, dockerEvents] = ['', false, ''];
  22. let metricsInterval, cardsInterval, graphsInterval;
  23. let cardList = '';
  24. const statsArray = {};
  25. // Socket.io admin ui
  26. export const io = new Server(server, {
  27. cors: {
  28. origin: ['http://localhost:8000', 'https://admin.socket.io'],
  29. methods: ['GET', 'POST'],
  30. credentials: true
  31. }
  32. });
  33. instrument(io, {
  34. auth: false,
  35. readonly: true
  36. });
  37. // Session middleware
  38. const sessionMiddleware = session({
  39. secret: "keyboard cat",
  40. resave: false,
  41. saveUninitialized: false,
  42. cookie:{
  43. secure:false, // Only set to true if you are using HTTPS.
  44. httpOnly:false, // Only set to true if you are using HTTPS.
  45. maxAge:3600000 * 8 // Session max age in milliseconds. 3600000 = 1 hour.
  46. }
  47. });
  48. // Make session data available to socket.io
  49. io.engine.use(sessionMiddleware);
  50. // Rate limiter
  51. const limiter = rateLimit({
  52. windowMs: 5 * 60 * 1000, // 5 minutes
  53. limit: 50, // Limit each IP to 50 requests per `window`.
  54. standardHeaders: 'draft-7',
  55. legacyHeaders: false,
  56. })
  57. // Express middleware
  58. app.set('view engine', 'ejs');
  59. app.use([
  60. compression(),
  61. cors(),
  62. helmet({contentSecurityPolicy: false}),
  63. express.static("public"),
  64. express.json(),
  65. express.urlencoded({ extended: true }),
  66. sessionMiddleware,
  67. router,
  68. limiter
  69. ]);
  70. // Initialize server
  71. server.listen(port, async () => {
  72. async function init() {
  73. try { await sequelize.authenticate().then(() => { console.log('[Connected to DB]') }); }
  74. catch { console.log('[Could not connect to DB]'); }
  75. try { await sequelize.sync().then(() => { console.log('[Models Synced]') }); }
  76. catch { console.log('[Could not Sync Models]', error); }
  77. await getHidden();
  78. containerCards();
  79. }
  80. await init();
  81. app.emit("appStarted");
  82. console.log(`\nServer listening on http://localhost:${port}`);
  83. });
  84. // Server metrics
  85. let serverMetrics = async () => {
  86. currentLoad().then(data => {
  87. cpu = Math.round(data.currentLoad);
  88. });
  89. mem().then(data => {
  90. ram = Math.round((data.active / data.total) * 100);
  91. });
  92. networkStats().then(data => {
  93. tx = data[0].tx_bytes / (1024 * 1024);
  94. rx = data[0].rx_bytes / (1024 * 1024);
  95. });
  96. fsSize().then(data => {
  97. disk = data[0].use;
  98. });
  99. }
  100. // List docker containers
  101. let containerCards = async () => {
  102. let list = '';
  103. const allContainers = await docker.listContainers({ all: true });
  104. for (const container of allContainers) {
  105. if (!hidden.includes(container.Names[0].slice(1))) {
  106. let imageVersion = container.Image.split('/');
  107. let service = imageVersion[imageVersion.length - 1].split(':')[0];
  108. let containerId = docker.getContainer(container.Id);
  109. let containerInfo = await containerId.inspect();
  110. let ports_list = [];
  111. try {
  112. for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) {
  113. let ports = {
  114. check: 'checked',
  115. external: value[0].HostPort,
  116. internal: key.split('/')[0],
  117. protocol: key.split('/')[1]
  118. }
  119. ports_list.push(ports);
  120. }
  121. } catch {}
  122. let external_port = ports_list[0]?.external || 0;
  123. let internal_port = ports_list[0]?.internal || 0;
  124. let container_info = {
  125. name: container.Names[0].slice(1),
  126. service: service,
  127. id: container.Id,
  128. state: container.State,
  129. image: container.Image,
  130. external_port: external_port,
  131. internal_port: internal_port,
  132. ports: ports_list,
  133. link: 'localhost',
  134. }
  135. let card = containerCard(container_info);
  136. list += card;
  137. }
  138. }
  139. cardList = list;
  140. }
  141. // Container metrics
  142. let containerStats = async () => {
  143. const data = await docker.listContainers({ all: true });
  144. for (const container of data) {
  145. if (!hidden.includes(container.Names[0].slice(1))) {
  146. const stats = await dockerContainerStats(container.Id);
  147. const name = container.Names[0].slice(1);
  148. if (!statsArray[name]) {
  149. statsArray[name] = {
  150. cpuArray: Array(15).fill(0),
  151. ramArray: Array(15).fill(0)
  152. };
  153. }
  154. statsArray[name].cpuArray.push(Math.round(stats[0].cpuPercent));
  155. statsArray[name].ramArray.push(Math.round(stats[0].memPercent));
  156. statsArray[name].cpuArray = statsArray[name].cpuArray.slice(-15);
  157. statsArray[name].ramArray = statsArray[name].ramArray.slice(-15);
  158. }
  159. }
  160. }
  161. // Store docker events
  162. docker.getEvents((err, stream) => {
  163. if (err) throw err;
  164. stream.on('data', (chunk) => {
  165. dockerEvents += chunk.toString('utf8');
  166. });
  167. });
  168. // Check for docker events
  169. setInterval(async () => {
  170. if (dockerEvents != '') {
  171. await getHidden();
  172. await containerCards();
  173. dockerEvents = '';
  174. }
  175. }, 1000);
  176. // Get hidden containers
  177. async function getHidden() {
  178. hidden = await Container.findAll({ where: {visibility:false}});
  179. hidden = hidden.map((container) => container.name);
  180. }
  181. // Socket.io
  182. io.on('connection', (socket) => {
  183. let sessionData = socket.request.session;
  184. let sent = '';
  185. if (sessionData.user != undefined) {
  186. console.log(`${sessionData.user} connected from ${socket.handshake.headers.host}`);
  187. // Start intervals if not already started
  188. if (!metricsInterval) {
  189. serverMetrics();
  190. metricsInterval = setInterval(serverMetrics, 1000);
  191. console.log('Metrics interval started');
  192. }
  193. if (!cardsInterval) {
  194. containerCards();
  195. cardsInterval = setInterval(containerCards, 1000);
  196. console.log('Cards interval started');
  197. }
  198. if (!graphsInterval) {
  199. containerStats();
  200. graphsInterval = setInterval(containerStats, 1000);
  201. console.log('Graphs interval started');
  202. }
  203. setInterval(() => {
  204. socket.emit('metrics', [cpu, ram, tx, rx, disk]);
  205. if (sent != cardList) {
  206. sent = cardList;
  207. socket.emit('containers', cardList);
  208. }
  209. socket.emit('containerStats', statsArray);
  210. }, 1000);
  211. // Client input
  212. socket.on('clicked', (data) => {
  213. let { name, id, value } = data;
  214. console.log(`${sessionData.user} clicked: ${id} ${value} ${name}`);
  215. if (clicked == true) { return; } clicked = true;
  216. // View container logs
  217. if (id == 'logs'){
  218. function containerLogs (data) {
  219. return new Promise((resolve, reject) => {
  220. let logString = '';
  221. var options = {
  222. follow: false,
  223. stdout: true,
  224. stderr: false,
  225. timestamps: false
  226. };
  227. var containerName = docker.getContainer(data);
  228. containerName.logs(options, function (err, stream) {
  229. if (err) { reject(err); return; }
  230. const readableStream = Readable.from(stream);
  231. readableStream.on('data', function (chunk) {
  232. logString += chunk.toString('utf8');
  233. });
  234. readableStream.on('end', function () {
  235. resolve(logString);
  236. });
  237. });
  238. });
  239. };
  240. containerLogs(name).then((data) => {
  241. socket.emit('logs', data);
  242. });
  243. }
  244. // start, stop, pause, restart container
  245. if (id == 'start' || id == 'stop' || id == 'pause' || id == 'restart'){
  246. var containerName = docker.getContainer(name);
  247. if ((id == 'start') && (value == 'stopped')) {
  248. containerName.start();
  249. } else if ((id == 'start') && (value == 'paused')) {
  250. containerName.unpause();
  251. } else if ((id == 'stop') && (value != 'stopped')) {
  252. containerName.stop();
  253. } else if ((id == 'pause') && (value == 'running')) {
  254. containerName.pause();
  255. } else if ((id == 'pause') && (value == 'paused')) {
  256. containerName.unpause();
  257. } else if (id == 'restart') {
  258. containerName.restart();
  259. }
  260. }
  261. // hide container
  262. if (id == 'hide') {
  263. async function hideContainer() {
  264. let containerExists = await Container.findOne({ where: {name: name}});
  265. if(!containerExists) {
  266. const newContainer = await Container.create({ name: name, visibility: false, });
  267. getHidden();
  268. } else {
  269. containerExists.update({ visibility: false });
  270. getHidden();
  271. }
  272. }
  273. hideContainer();
  274. }
  275. // unhide containers
  276. if (id == 'resetView') {
  277. Container.update({ visibility: true }, { where: {} });
  278. getHidden();
  279. }
  280. clicked = false;
  281. });
  282. socket.on('disconnect', () => {
  283. console.log(`${sessionData.user} disconnected`);
  284. socket.disconnect();
  285. // clear intervals if no users are connected
  286. if (io.engine.clientsCount == 0) {
  287. clearInterval(metricsInterval);
  288. clearInterval(cardsInterval);
  289. clearInterval(graphsInterval);
  290. metricsInterval = null;
  291. cardsInterval = null;
  292. graphsInterval = null;
  293. console.log('All intervals cleared');
  294. }
  295. });
  296. } else {
  297. console.log('Missing session data');
  298. }
  299. });
  300. // let link = '';
  301. // networkInterfaces().then(data => {
  302. // link = data[0].ip4;
  303. // });