server.js 11 KB

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