diff --git a/app.js b/app.js new file mode 100644 index 0000000..8896fe7 --- /dev/null +++ b/app.js @@ -0,0 +1,180 @@ +const express = require("express"); +const session = require("express-session"); +const redis = require('connect-redis'); +const { currentLoad, mem, networkStats, fsSize, dockerContainerStats } = require('systeminformation'); +const app = express(); +const routes = require("./routes"); +const PORT = 8000; +var Docker = require('dockerode'); +var docker = new Docker({ socketPath: '/var/run/docker.sock' }); +const { dashCard } = require('./components/dashCard'); + +let DockerContainers, sent_list, clicked, open_ports, ServerMetrics, card_list, external_port, internal_port; + +const redisClient = require('redis').createClient({ + legacyMode:true +}); +redisClient.connect().catch(console.log); +const RedisStore = redis(session); + +const sessionMiddleware = session({ + store:new RedisStore({client:redisClient}), + 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. + } +}) + +app.set('view engine', 'ejs'); +app.use([ + express.static("public"), + express.json(), + express.urlencoded({ extended: true }), + sessionMiddleware, + routes +]); + +const server = app.listen(PORT, async () => { + console.log(`App listening on port ${PORT}`); +}); + +const io = require('socket.io')(server); +io.engine.use(sessionMiddleware); + +io.on('connection', (socket) => { + + const user_session = socket.request.session; + + // display client connection info + console.log(`${user_session.user} connected from ${socket.handshake.headers.host} ${socket.handshake.address} \n Active Sessions: ${io.engine.clientsCount}`); + + // send list of running docker containers if sent_list contains data + if (sent_list != null) { socket.emit('cards', sent_list); } + + // check if an install is in progress + if((app.locals.install != '') && (app.locals.install != null)){ + socket.emit('install', app.locals.install); + } + + // send server metrics to client + async function Metrics() { + Promise.all([currentLoad(), mem(), networkStats(), fsSize()]).then(([cpuUsage, ramUsage, netUsage, diskUsage]) => { + let cpu = Math.round(cpuUsage.currentLoad); + let ram = Math.round(((ramUsage.active / ramUsage.total) * 100)); + let tx = netUsage[0].tx_bytes; + let rx = netUsage[0].rx_bytes; + let disk = diskUsage[0].use; + socket.emit('metrics', { cpu, ram, tx, rx, disk }); + }); + } + + async function ContainersList() { + card_list = ''; + open_ports = ''; + external_port; + internal_port; + + docker.listContainers({ all: true }, async function (err, data) { + for (const container of data) { + + let imageVersion = container.Image.split('/'); + let dockerService = imageVersion[imageVersion.length - 1].split(":")[0]; + + + let containerId = docker.getContainer(container.Id); + let containerInfo = await containerId.inspect(); + + // console.log(containerInfo.Name.split('/')[1]); + // console.log(container.Image); + // console.log(containerInfo.HostConfig.RestartPolicy.Name); + + + for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) { + console.log(`${value[0].HostPort}:${key}`); + external_port = value[0].HostPort; + internal_port = key; + } + + // console.log('Volumes:'); + // for (const [key, value] of Object.entries(containerInfo.Mounts)) { + // console.log(`${value.Source}: ${value.Destination}: ${value.RW}`); + // } + + + // console.log('Environment Variables:'); + // for (const [key, value] of Object.entries(containerInfo.Config.Env)) { + // console.log(`${key}: ${value}`); + // } + + // console.log('Labels:'); + // for (const [key, value] of Object.entries(containerInfo.Config.Labels)) { + // console.log(`${key}: ${value}`); + // } + + // dockerContainerStats(container.Id).then((data) => { + // console.log(`${container.Names[0].slice(1)} // CPU: ${Math.round(data[0].cpuPercent)} // RAM: ${Math.round(data[0].memPercent)}`); + // }); + + let dockerCard = dashCard(container.Names[0].slice(1), dockerService, container.Id, container.State, container.Image, external_port, internal_port); + // open_ports += `-L ${external_port}:localhost:${external_port} ` + card_list += dockerCard; + } + + // emit card list is it's different from what was sent last time, then clear install local + if (sent_list !== card_list) { + sent_list = card_list; + app.locals.install = ''; + socket.emit('cards', card_list); + console.log('Cards updated'); + } + }); + } + + console.log('Starting Metrics'); + ServerMetrics = setInterval(Metrics, 1000); + + console.log('Starting Containers List'); + DockerContainers = setInterval(ContainersList, 1000); + + + socket.on('clicked', (data) => { + // Prevent multiple clicks + if (clicked == true) { return; } clicked = true; + + console.log(`${socket.request.session.user} wants to: ${data.action} ${data.container}`); + + if (socket.request.session.role == 'admin') { + var containerName = docker.getContainer(data.container); + + if ((data.action == 'start') && (data.state == 'stopped')) { + containerName.start(); + } else if ((data.action == 'start') && (data.state == 'paused')) { + containerName.unpause(); + } else if ((data.action == 'stop') && (data.state != 'stopped')) { + containerName.stop(); + } else if ((data.action == 'pause') && (data.state == 'running')) { + containerName.pause(); + } else if ((data.action == 'pause') && (data.state == 'paused')) { + containerName.unpause(); + } else if (data.action == 'restart') { + containerName.restart(); + } + } else { + console.log('User is not an admin'); + } + clicked = false; + }); + + + socket.on('disconnect', () => { + console.log('Stopping Metrics'); + clearInterval(ServerMetrics); + console.log('Stopping Containers List'); + clearInterval(DockerContainers); + }); + +}); \ No newline at end of file diff --git a/components/appCard.js b/components/appCard.js new file mode 100644 index 0000000..94e41e2 --- /dev/null +++ b/components/appCard.js @@ -0,0 +1,978 @@ +function appCard(data) { + + // make data.title lowercase + let app_name = data.name || data.title.toLowerCase(); + let shortened_name = ""; + let shortened_desc = data.description.slice(0, 60) + "..."; + let modal = app_name.replaceAll(" ", "-"); + let form_id = app_name.replaceAll("-", "_"); + let note = data.note ? data.note.replaceAll(". ", ".\n") : "no notes available"; + let description = data.description.replaceAll(". ", ".\n") || "no description available"; + let command = data.command ? data.command : ""; + let command_check = command ? "checked" : ""; + + + // if data.network is set to host, bridge, or docker set the radio button to checked + let net_host, net_bridge, net_docker = ''; + let net_name = 'AppBridge'; + + if (data.network == 'host') { + net_host = 'checked'; + } else if (data.network) { + net_bridge = 'checked'; + net_name = data.network; + } else { + net_docker = 'checked'; + } + + + if (data.title.length > 28) { + shortened_name = (data.title).slice(0, 25) + "..."; + } + else { + shortened_name = data.title; + } + + + function CatagoryColor(category) { + switch (category) { + case 'Other': + return 'Other '; + case 'Productivity': + return 'Productivity '; + case 'Tools': + return 'Tools '; + case 'Dashboard': + return 'Dashboard '; + case 'Communication': + return 'Communication '; + case 'CMS': + return 'CMS '; + case 'Monitoring': + return 'Monitoring '; + case 'LDAP': + return 'LDAP '; + case 'Arr': + return 'Arr '; + case 'Database': + return 'Database '; + case 'Paid': + return 'Paid '; + case 'Gaming': + return 'Gaming '; + case 'Finance': + return 'Finance '; + case 'Networking': + return 'Networking '; + case 'Authentication': + return 'Authentication '; + case 'Development': + return 'Development '; + case 'Media Server': + return 'Media Server '; + case 'Downloaders': + return 'Downloaders '; + default: + return ''; // default to other if the category is not recognized + } + } + + // set data.catagories to 'other' if data.catagories is empty or undefined + if (data.categories == null || data.categories == undefined || data.categories == '') { + data.categories = ['Other']; + } + + let categories = ''; + + for (let i = 0; i < data.categories.length; i++) { + categories += CatagoryColor(data.categories[i]); + } + + if (data.restart_policy == null) { + data.restart_policy = 'unless-stopped'; + } + + let ports_data = [], volumes_data = [], env_data = [], label_data = []; + + for (let i = 0; i < 12; i++) { + + // Get port details + try { + let ports = data.ports[i]; + let port_check = ports ? "checked" : ""; + let port_external = ports.split(":")[0] ? ports.split(":")[0] : ports.split("/")[0]; + let port_internal = ports.split(":")[1] ? ports.split(":")[1].split("/")[0] : ports.split("/")[0]; + let port_protocol = ports.split("/")[1] ? ports.split("/")[1] : ""; + + // remove /tcp or /udp from port_external if it exists + if (port_external.includes("/")) { + port_external = port_external.split("/")[0]; + } + + ports_data.push({ + check: port_check, + external: port_external, + internal: port_internal, + protocol: port_protocol + }); + } catch { + ports_data.push({ + check: "", + external: "", + internal: "", + protocol: "" + }); + } + + // Get volume details + try { + let volumes = data.volumes[i]; + let volume_check = volumes ? "checked" : ""; + let volume_bind = volumes.bind.split(":")[0] ? volumes.bind.split(":")[0] : ""; + let volume_container = volumes.container.split(":")[0] ? volumes.container.split(":")[0] : ""; + let volume_readwrite = volumes.container.endsWith(":ro") ? "ro" : "rw"; + + volumes_data.push({ + check: volume_check, + bind: volume_bind, + container: volume_container, + readwrite: volume_readwrite + }); + } catch { + volumes_data.push({ + check: "", + bind: "", + container: "", + readwrite: "" + }); + } + + + // Get environment details + try { + let env = data.env[i]; + let env_check = env ? "checked" : ""; + let env_default = env.default ? env.default : ""; + let env_description = env.description ? env.description : ""; + let env_label = env.label ? env.label : ""; + let env_name = env.name ? env.name : ""; + + env_data.push({ + check: env_check, + default: env_default, + description: env_description, + label: env_label, + name: env_name + }); + } catch { + env_data.push({ + check: "", + default: "", + description: "", + label: "", + name: "" + }); + } + + // Get label details + + try { + let label = data.labels[i]; + let label_check = label ? "checked" : ""; + let label_name = label.name ? label.name : ""; + let label_value = label.value ? label.value : ""; + + label_data.push({ + check: label_check, + name: label_name, + value: label_value + }); + } catch { + label_data.push({ + check: "", + name: "", + value: "" + }); + } + + } + + + + return ` +