浏览代码

The rewrite. v0.20

lllllllillllllillll 1 年之前
父节点
当前提交
0cbf9226e5
共有 100 个文件被更改,包括 2005 次插入3138 次删除
  1. 1 2
      .github/FUNDING.yml
  2. 1 1
      .github/dependabot.yml
  3. 4 0
      .gitignore
  4. 17 1
      CHANGELOG.md
  5. 1 2
      Dockerfile
  6. 12 22
      README.md
  7. 278 115
      app.js
  8. 0 1
      caddyfiles/Caddyfile
  9. 4 6
      components/appCard.js
  10. 118 0
      components/containerCard.js
  11. 0 1110
      components/dashCard.js
  12. 0 18
      components/siteCard.js
  13. 17 20
      controllers/account.js
  14. 14 81
      controllers/apps.js
  15. 0 160
      controllers/auth.js
  16. 8 199
      controllers/dashboard.js
  17. 9 5
      controllers/images.js
  18. 72 0
      controllers/login.js
  19. 5 6
      controllers/networks.js
  20. 96 0
      controllers/register.js
  21. 2 7
      controllers/settings.js
  22. 36 0
      controllers/syslogs.js
  23. 25 13
      controllers/users.js
  24. 3 9
      controllers/volumes.js
  25. 0 72
      database/ContainerModel.js
  26. 0 46
      database/ServerModel.js
  27. 0 63
      database/UserModel.js
  28. 162 0
      database/models.js
  29. 5 9
      docker-compose.yaml
  30. 0 205
      functions/compose.js
  31. 0 194
      functions/package_manager.js
  32. 0 382
      functions/system.js
  33. 875 17
      package-lock.json
  34. 19 9
      package.json
  35. 1 1
      public/css/meters.css
  36. 0 72
      public/css/tabler.min.css
  37. 二进制
      public/fonts/Inter-Black.woff2
  38. 二进制
      public/fonts/Inter-BlackItalic.woff2
  39. 二进制
      public/fonts/Inter-Bold.woff2
  40. 二进制
      public/fonts/Inter-BoldItalic.woff2
  41. 二进制
      public/fonts/Inter-ExtraBold.woff2
  42. 二进制
      public/fonts/Inter-ExtraBoldItalic.woff2
  43. 二进制
      public/fonts/Inter-ExtraLight.woff2
  44. 二进制
      public/fonts/Inter-ExtraLightItalic.woff2
  45. 二进制
      public/fonts/Inter-Italic.woff2
  46. 二进制
      public/fonts/Inter-Light.woff2
  47. 二进制
      public/fonts/Inter-LightItalic.woff2
  48. 二进制
      public/fonts/Inter-Medium.woff2
  49. 二进制
      public/fonts/Inter-MediumItalic.woff2
  50. 二进制
      public/fonts/Inter-Regular.woff2
  51. 二进制
      public/fonts/Inter-SemiBold.woff2
  52. 二进制
      public/fonts/Inter-SemiBoldItalic.woff2
  53. 二进制
      public/fonts/Inter-Thin.woff2
  54. 二进制
      public/fonts/Inter-ThinItalic.woff2
  55. 二进制
      public/fonts/InterDisplay-Black.woff2
  56. 二进制
      public/fonts/InterDisplay-BlackItalic.woff2
  57. 二进制
      public/fonts/InterDisplay-Bold.woff2
  58. 二进制
      public/fonts/InterDisplay-BoldItalic.woff2
  59. 二进制
      public/fonts/InterDisplay-ExtraBold.woff2
  60. 二进制
      public/fonts/InterDisplay-ExtraBoldItalic.woff2
  61. 二进制
      public/fonts/InterDisplay-ExtraLight.woff2
  62. 二进制
      public/fonts/InterDisplay-ExtraLightItalic.woff2
  63. 二进制
      public/fonts/InterDisplay-Italic.woff2
  64. 二进制
      public/fonts/InterDisplay-Light.woff2
  65. 二进制
      public/fonts/InterDisplay-LightItalic.woff2
  66. 二进制
      public/fonts/InterDisplay-Medium.woff2
  67. 二进制
      public/fonts/InterDisplay-MediumItalic.woff2
  68. 二进制
      public/fonts/InterDisplay-Regular.woff2
  69. 二进制
      public/fonts/InterDisplay-SemiBold.woff2
  70. 二进制
      public/fonts/InterDisplay-SemiBoldItalic.woff2
  71. 二进制
      public/fonts/InterDisplay-Thin.woff2
  72. 二进制
      public/fonts/InterDisplay-ThinItalic.woff2
  73. 二进制
      public/fonts/InterVariable-Italic.woff2
  74. 二进制
      public/fonts/InterVariable.woff2
  75. 59 0
      public/fonts/inter.css
  76. 0 0
      public/img/avatars/burns.jpg
  77. 二进制
      public/img/avatars/duffman.png
  78. 0 0
      public/img/avatars/frank.jpg
  79. 0 0
      public/img/avatars/moe.jpg
  80. 二进制
      public/img/avatars/moleman.png
  81. 0 0
      public/img/avatars/poochie.jpg
  82. 0 0
      public/img/avatars/rus.jpg
  83. 0 0
      public/img/avatars/skinner.jpg
  84. 1 1
      public/js/demo-theme.js
  85. 100 155
      public/js/main.js
  86. 0 0
      public/static/logo-dark.svg
  87. 0 0
      public/static/logo-sm-black.svg
  88. 0 0
      public/static/logo-sm-white.svg
  89. 0 3
      public/static/logo-small-white.svg
  90. 0 0
      public/static/logo-small.svg
  91. 0 2
      public/static/logo-white.svg
  92. 0 0
      public/static/logo.svg
  93. 60 0
      router/index.js
  94. 0 59
      routes/index.js
  95. 二进制
      screenshots/account.png
  96. 二进制
      screenshots/apps.png
  97. 二进制
      screenshots/dashboard.png
  98. 二进制
      screenshots/logs.png
  99. 二进制
      screenshots/register.png
  100. 0 70
      setup.sh

+ 1 - 2
.github/FUNDING.yml

@@ -1,2 +1 @@
-github: [lllllllillllllillll]
-patreon: DweebUI
+patreon: DweebUI

+ 1 - 1
.github/dependabot.yml

@@ -15,6 +15,6 @@ updates:
   - package-ecosystem: "npm"
     directory: "/"
     schedule:
-      interval: "daily"
+      interval: "weekly"
     labels:
       - "🤖 Dependencies"

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+node_modules
+database/database.sqlite
+test
+.dockerignore

+ 17 - 1
CHANGELOG.md

@@ -1,4 +1,20 @@
-## v0.09 (dev)
+## v0.20 (dev) - The rewrite. Jumping all the way to v0.20.
+* Changed to ES6 imports.
+* Cleaned up file structure and code layout.
+* Updated DweebUI logo.
+* Visual tweaks to login and registration pages.
+* Added .gitignore and .dockerignore files.
+* Syslogs - View logs for sign-in and registration attempts. :new: 
+* Docker socket now uses default connection.
+* Updated Users page displays 'inactive' if no sign-ins within 30 days.
+* Dashboard updates now triggered by Docker events.
+* Massive reduction in the amount of HTML, CSS, and JS on client side.
+* Container graphs are significantly more efficent and no longer use localStorage.
+* Made dark mode the default theme.
+* Created intervals to allow application to idle or scale bettery with more users.
+* Pages for images, volumes, and networks (non-functional at the moment). :new: 
+
+## <del>v0.09 (dev)</del> dead. (It had so many problems that I essentially rewrote everything)
 * Added authentication middleware to router.
 * Added gzip compression.
 * Added PM2.

+ 1 - 2
Dockerfile

@@ -1,8 +1,7 @@
-# syntax=docker/dockerfile:1
-
 FROM node:21-alpine
 
 ENV NODE_ENV=production
+ENV LOGGER=true
 
 WORKDIR /app
 

+ 12 - 22
README.md

@@ -1,13 +1,18 @@
 # DweebUI
-DweebUI is a simple Docker web interface created using Javascript, Node.JS, and Express.
+DweebUI is a web interface for managing Docker, with a zero-config dashboard for your containers.
 
-Alpha v0.09 ( :fire: Experimental :fire: )
+Alpha v0.20 ( :fire: Experimental :fire: )
+
+:warning: DweebUI is a management interface and should not be directly exposed to the internet:warning:   
+:warning: External access should be done through a VPN or secure SSH connection :warning:
 
 [![GitHub Stars](https://img.shields.io/github/stars/lllllllillllllillll/DweebUI)](https://github.com/lllllllillllllillll)
 [![GitHub Activity](https://img.shields.io/github/commit-activity/y/lllllllillllllillll/DweebUI)](https://github.com/lllllllillllllillll)
 [![Docker Pulls](https://img.shields.io/docker/pulls/lllllllillllllillll/dweebui)](https://hub.docker.com/repository/docker/lllllllillllllillll/dweebui)
 [![GitHub License](https://img.shields.io/github/license/lllllllillllllillll/DweebUI)](https://github.com/lllllllillllllillll/DweebUI/blob/main/LICENSE)
 
+* This is a personal project that I started to get more familiar with Javascript and Node.js.
+* I probably should have waited a lot longer to share this :|
 
 <a href="https://raw.githubusercontent.com//lllllllillllllillll/DweebUI/main/screenshots/dashboard.png"><img src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI/main/screenshots/dashboard.png" width="50%"/></a>
 
@@ -37,12 +42,11 @@ Docker Compose:
 ```
 version: "3.9"
 services:
-
   dweebui:
     container_name: dweebui
-    image: lllllllillllllillll/dweebui:v0.09-dev
-    # build:
-    #   context: .
+    # image: lllllllillllllillll/dweebui:v0.20
+    build:
+      context: .
     environment:
       NODE_ENV: production
       PORT: 8000
@@ -52,21 +56,15 @@ services:
       - 8000:8000
     volumes:
       - dweebui:/app
-      - caddyfiles:/app/caddyfiles
       - /var/run/docker.sock:/var/run/docker.sock
-      #- ./custom-templates.json:/app/custom-templates.json
-      #- ./composefiles:/app/composefiles
     networks:
-      - dweeb_network
-
+      - dweebui_net
 
 volumes:
   dweebui:
-  caddyfiles:
-
 
 networks:
-  dweeb_network:
+  dweebui_net:
     driver: bridge
 ```
 
@@ -77,14 +75,6 @@ Compose setup:
 * You may need to use ```docker-compose up -d``` or execute the command as root with either ```sudo docker compose up -d``` or ```sudo docker-compose up -d```.
 
 
-Using setup.sh: 
-```
-Extract DweebUI.zip and navigate to /DweebUI
-cd DweebUI
-chmod +x setup.sh
-sudo ./setup.sh
-```
-
 
 ## Credits
 

+ 278 - 115
app.js

@@ -1,23 +1,43 @@
-// Express
-const express = require("express");
-const app = express();
-const session = require("express-session");
-const compression = require('compression');
-const helmet = require('helmet');
-const PORT = process.env.PORT || 8000;
+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';
 
-// Router
-const routes = require("./routes");
-
-// Functions and variables
-const { serverStats, containerList, containerStats, containerAction, containerLogs, hiddenContainers, dockerImages, dockerVolumes, dockerNetworks } = require('./functions/system');
-let sentList, clicked;
-app.locals.site_list = '';
-
-const Containers = require('./database/ContainerModel');
+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
+});
 
-// Configure Session
+// Session middleware
 const sessionMiddleware = session({
     secret: "keyboard cat", 
     resave: false, 
@@ -26,137 +46,280 @@ const sessionMiddleware = session({
         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); 
 
-// Middleware
+// Express middleware
 app.set('view engine', 'ejs');
 app.use([
     compression(),
+    cors(),
     helmet({contentSecurityPolicy: false}),
     express.static("public"),
     express.json(),
     express.urlencoded({ extended: true }),
     sessionMiddleware,
-    routes
+    router
 ]);
 
-// Start Express server
-const server = app.listen(PORT, async () => {
-    console.log(`App listening on port ${PORT}`);
+// 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();
 });
 
-// Start Socket.io
-const io = require('socket.io')(server);
-io.engine.use(sessionMiddleware);
-
-io.on('connection', (socket) => {
+// 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; 
+    });
+}
 
-    // Set user session
-    const user_session = socket.request.session;
-    console.log(`${user_session.user} connected from ${socket.handshake.headers.host} ${socket.handshake.address}`);
+// 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))) {
 
-    // Check if a list of containers or an install card needs to be sent
-    if (sentList != null) { socket.emit('cards', sentList); }
-    if((app.locals.install != '') && (app.locals.install != null)){ socket.emit('install', app.locals.install); }
+            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;
 
-    async function dockerStuff(){
-        let i = await dockerImages();
-        let v = await dockerVolumes();
-        let n = await dockerNetworks();
-        
-        // console.log(i, v, n);
+            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;
+}
 
-    dockerStuff();
+// 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);
 
-    // Send server metrics
-    let ServerStats = setInterval(async () => {
-        socket.emit('metrics', await serverStats());
-    }, 1000);
+            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));
 
-    // Send list of containers
-    let ContainerList = setInterval(async () => {
-        let cardList = await containerList();
-        if (sentList !== cardList) {
-            sentList = cardList;
-            app.locals.install = '';
-            socket.emit('cards', cardList);
+            statsArray[name].cpuArray = statsArray[name].cpuArray.slice(-15);
+            statsArray[name].ramArray = statsArray[name].ramArray.slice(-15);
         }
-    }, 1000);
+    }
+}
+
+// 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}});
+}
 
-    // Send container metrics
-    let ContainerStats = setInterval(async () => {
-        let stats = await containerStats();
-        for (let i = 0; i < stats.length; i++) {
-            socket.emit('containerStats', stats[i]);
+// 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');
         }
-    }, 1000);
-
-    // Container controls
-    socket.on('clicked', (data) => {
-        if (clicked == true) { return; } clicked = true;
-        let buttonPress = {
-            user: socket.request.session.user,
-            role: socket.request.session.role,
-            action: data.action,
-            container: data.container,
-            state: data.state
+        if (!cardsInterval) {
+            cardsInterval = setInterval(containerCards, 1000);
+            console.log('Cards interval started');
+        }
+        if (!graphsInterval) {
+            graphsInterval = setInterval(containerStats, 1000);
+            console.log('Graphs interval started');
         }
-        containerAction(buttonPress);
-        clicked = false;
-    });
 
-    
-    socket.on('hide', async (data) => {
-        console.log(`Hide ${data.container}`);
+        setInterval(() => {
+            socket.emit('metrics', [cpu, ram, tx, rx, disk]);
+            if (sent != cardList) {
+                sent = cardList;
+                socket.emit('containers', cardList);
+            }
+            socket.emit('containerStats', statsArray);
+        }, 1000);
 
-        let containerExists = await Containers.findOne({ where: {name:data.container}});
 
-        if(!containerExists){
-            const container = await Containers.create({ 
-                name: data.container,
-                visibility: false,
-            });
-            hiddenContainers();
-            console.log(`[Created] Container ${data.container} hidden`)
 
-            let containerData = await Containers.findOne({ where: {name:data.container}});
-            console.log(containerData);
+        
+        socket.on('clicked', (data) => {
+            if (clicked == true) { return; } clicked = true;
+            let { name, id, value } = data;
+            console.log(`${sessionData.user} clicked: ${id} ${value} ${name}`);
 
-        } else {
-            containerExists.update({ visibility: false });
-            console.log(`[Updated] Container ${data.container} hidden`)
-            hiddenContainers();
-        }
-    });
 
-    socket.on('reset', (data) => {
-        // set visibility to true for all containers
-        Containers.update({ visibility: true }, { where: {} });
-        console.log('All containers visible');
-        hiddenContainers();
-    });
+            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;
+        });
 
-    // Container logs
-    socket.on('logs', (data) => {
-        containerLogs(data.container)
-        .then(logs => {
-            console.log(`Refreshed logs for ${data.container}`)
-            socket.emit('logString', logs);
-        })
-        .catch(err => {
-            console.error(err);
+        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');
+    }
+});
+
+
+
+
+
+
+
 
-    // On disconnect
-    socket.on('disconnect', () => {                
-        clearInterval(ServerStats);
-        clearInterval(ContainerList);
-        clearInterval(ContainerStats);
-    }); 
+// let link = '';
+// networkInterfaces().then(data => {
+//     link = data[0].ip4;
+// });
 
-});

+ 0 - 1
caddyfiles/Caddyfile

@@ -1 +0,0 @@
-import ./sites/*

+ 4 - 6
components/appCard.js

@@ -1,4 +1,4 @@
-function appCard(data) {
+export const appCard = (data) => {
 
   // make data.title lowercase
   let app_name = data.name || data.title.toLowerCase();
@@ -227,11 +227,11 @@ function appCard(data) {
         </div>
       </div>
       <div class="d-flex">
-        <a href="#" class="card-btn" data-bs-toggle="modal" data-bs-target="#${modal}-info"><!-- Download SVG icon from http://tabler-icons.io/i/mail -->
+        <a href="#" class="card-btn" data-bs-toggle="modal" data-bs-target="#${modal}-info">
           <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-article" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"></path> <path d="M7 8h10"></path> <path d="M7 12h10"></path> <path d="M7 16h10"></path></svg>
             Learn More
         </a>
-        <a href="#" class="card-btn" data-bs-toggle="modal" data-bs-target="#${modal}-install"><!-- Download SVG icon from http://tabler-icons.io/i/phone -->
+        <a href="#" class="card-btn" data-bs-toggle="modal" data-bs-target="#${modal}-install">
         <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-bar-to-down" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M4 20l16 0"></path> <path d="M12 14l0 -10"></path> <path d="M12 14l4 -4"></path> <path d="M12 14l-4 -4"></path></svg>
           Install
         </a>
@@ -989,6 +989,4 @@ function appCard(data) {
               </div>`;
 
 
-}
-
-module.exports = { appCard };
+}

+ 118 - 0
components/containerCard.js

@@ -0,0 +1,118 @@
+// export for app.js
+export const containerCard = (data) => {
+  
+  let { name, service, state, external_port, internal_port, ports, link } = data;
+  let wrapped = name;
+  let chart = name;
+
+  if (name.length > 13) {
+    wrapped = name.slice(0, 10) + '...';
+  }
+
+  //disable controls for a docker container depending on its name
+  let actions = "";
+  if (name.startsWith('dweebui')) {
+    actions = 'disabled=""';
+  }
+
+  if ( external_port == undefined ) { external_port = 0; }
+  if ( internal_port == undefined ) { internal_port = 0; }
+
+
+  let state_indicator = 'green';
+  if (state == 'exited') {
+    state = 'stopped';
+    state_indicator = 'red';
+  } else if (state == 'paused') {
+    state_indicator = 'orange';
+  }
+
+  let ports_data = [];
+  if (ports) {
+    ports_data = ports;
+  } else {
+    for (let i = 0; i < 12; i++) {
+
+      let port_check = "checked";
+      let external = i;
+      let internal = i;
+      let protocol = "tcp";
+
+      ports_data.push({
+        check: port_check,
+        external: external,
+        internal: internal,
+        protocol: protocol
+      });
+    }
+  }
+
+
+  return `
+    <div class="col-sm-6 col-lg-3 deleteme">
+      <div class="card">
+        <div class="card-body">
+          <div class="card-stamp card-stamp-sm">
+            <img width="100px" src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/${service}.png" onerror="this.onerror=null;this.src='https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/docker.png';"></img>
+          </div>
+          <div class="d-flex align-items-center">
+            <div class="subheader text-yellow">${external_port}:${internal_port}</div>
+            <div class="ms-auto lh-1">
+              <div class="card-actions btn-actions">
+                <div class="card-actions btn-actions">
+                  <button onclick="clicked(this)" name="${name}" value="${state}" id="start" class="btn-action" title="Start" ${actions}><!-- player-play -->
+                    <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-play" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 4v16l13 -8z"></path></svg>
+                  </button>
+                  <button onclick="clicked(this)" name="${name}" value="${state}" id="stop" class="btn-action" title="Stop" ${actions}><!-- player-stop -->
+                    <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-stop" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 5m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"></path></svg>
+                  </button>
+                  <button onclick="clicked(this)" name="${name}" value="${state}" id="pause" class="btn-action" title="Pause" ${actions}><!-- player-pause -->
+                    <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-pause" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path><path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path></svg>
+                  </button>
+                  <button onclick="clicked(this)" name="${name}" value="${state}" id="restart" class="btn-action" title="Restart" ${actions}><!-- reload -->
+                    <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-reload" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path><path d="M20 4v5h-5"></path></svg>                          
+                  </button>
+                  <div class="dropdown">
+                    <a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                      <svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
+                    </a>
+                    <div class="dropdown-menu dropdown-menu-end">
+                      <button class="dropdown-item text-secondary" onclick="clicked(this)" id="details" data-bs-toggle="modal" data-bs-target="#details_modal" href="#">Details</button>
+                      <button class="dropdown-item text-secondary" onclick="clicked(this)" name="${name}" id="logs" data-bs-toggle="modal" data-bs-target="#log_view" href="#">Logs</button>
+                      <button class="dropdown-item text-secondary" onclick="clicked(this)" name="${name}" id="edit" href="#">Edit</button>
+                      <button class="dropdown-item text-primary" onclick="clicked(this)" name="${name}" id="update" href="#">Update</button>
+                      <button class="dropdown-item text-danger" onclick="clicked(this)" name="${name}" id="remove" data-bs-toggle="modal" data-bs-target="#remove_modal" href="#">Remove</button>
+                    </div>
+                  </div>
+                  <div class="dropdown">
+                    <a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                      <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /> </svg>
+                    </a>
+                    <div class="dropdown-menu dropdown-menu-end">
+                      <button class="dropdown-item" onclick="clicked(this)" name="${name}" id="hide" value="hide">Hide</button>
+                      <button class="dropdown-item" onclick="clicked(this)" name="${name}" id="resetView" value="resetView">Reset View</button>
+                      <button class="dropdown-item" onclick="clicked(this)" name="${name}" id="permissions" value="permissions">Permissions</button>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="d-flex align-items-baseline">
+            <div class="h1 me-2" title="${name}" style="margin-bottom: 0;">
+              <a href="http://${link}:${external_port}" target="_blank">
+                ${wrapped}
+              </a>
+            </div>
+            <div class="ms-auto">
+              <span class="text-${state_indicator} align-items-center lh-1">
+                <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-point-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 7a5 5 0 1 1 -4.995 5.217l-.005 -.217l.005 -.217a5 5 0 0 1 4.995 -4.783z" stroke-width="0" fill="currentColor"></path></svg>
+                ${state}
+              </span>
+            </div>
+          </div>
+          <div id="${chart}_chart" class="chart-sm"></div>
+        </div>
+      </div>
+    </div>`;
+}

+ 0 - 1110
components/dashCard.js

@@ -1,1110 +0,0 @@
-module.exports.dashCard = function dashCard(data) {
-  
-  let { name, service, id, state, image, external_port, internal_port, ports, volumes, environment_variables, labels, IPv4, style } = data;
-
-  let margin, iconSize, fontSize = '';
-
-  if (style == "Large") {
-    iconSize = 'width="150px"'
-  } else if ((style == "Compact") || (style == undefined)) {
-    iconSize = 'width="100px"'
-    margin = 'style="margin-bottom: 0;"'
-  } else if (style == "Row") {
-    iconSize = 'width="50px"'
-    margin = 'style="margin-bottom: 0;"'
-  }
-
-
-  //disable controls for a docker container depending on its name
-  let actions = "";
-  if (name.startsWith('dweebui')) {
-    actions = 'disabled=""';
-  }
-
-  if ( external_port == undefined ) { external_port = 0; }
-  if ( internal_port == undefined ) { internal_port = 0; }
-
-
-  let shortened_name = name;
-  if (name.length > 13) {
-    shortened_name = name.slice(0, 10) + '...';
-  }
-
-  let state_indicator = 'green';
-  if (state == 'exited') {
-    state = 'stopped';
-    state_indicator = 'red';
-  } else if (state == 'paused') {
-    state_indicator = 'orange';
-  }
-
-
-  let app_name = name
-  let modal = app_name.replaceAll(" ", "-");
-  let form_id = app_name.replaceAll("-", "_");
-
-  let restart_policy = 'unless-stopped';
-  
-  let ports_data = [];
-  if (ports) {
-    ports_data = ports;
-  } else {
-    for (let i = 0; i < 12; i++) {
-
-      let port_check = "checked";
-      let external = i;
-      let internal = i;
-      let protocol = "tcp";
-
-      ports_data.push({
-        check: port_check,
-        external: external,
-        internal: internal,
-        protocol: protocol
-      });
-    }
-  }
-
-  let volumes_data = [];
-  if (volumes) {
-    volumes_data = volumes;
-  } else {
-    for (let i = 0; i < 12; i++) {
-
-      let vol_check = "checked";
-      let bind = i;
-      let container = i;
-      let readwrite = "rw";
-
-      volumes_data.push({
-        check: vol_check,
-        bind: bind,
-        container: container,
-        readwrite: readwrite
-      });
-    }
-  }
-
-
-  let env_data = [];
-  if (environment_variables) {
-    env_data = environment_variables;
-  } else {
-    for (let i = 0; i < 12; i++) {
-
-      let env_check = "checked";
-      let env_name = i;
-      let env_default = i;
-
-      env_data.push({
-        check: env_check,
-        name: env_name,
-        default: env_default
-      });
-    }
-  }
-
-
-  let label_data = [];
-  if (labels) {
-    label_data = labels;
-  } else {
-    for (let i = 0; i < 12; i++) {
-
-      let label_check = "checked";
-      let label_name = i;
-      let label_default = i;
-      
-      label_data.push({
-        check: label_check,
-        name: label_name,
-        value: label_default
-      });
-    }
-  }
-
-  return `
-    <div class="col-sm-6 col-lg-3 deleteme">
-      <div class="card">
-        <div class="card-body">
-          <div class="card-stamp card-stamp-sm">
-            <img ${iconSize} src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/${service}.png" onerror="this.onerror=null;this.src='https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/docker.png';"></img>
-          </div>
-          <div class="d-flex align-items-center">
-            <div class="subheader text-yellow">${external_port}:${internal_port}</div>
-            <div class="ms-auto lh-1">
-              <div class="card-actions btn-actions">
-                <div class="card-actions btn-actions">
-                  <button onclick="buttonAction(this)" name="${name}" value="start" id="${state}" class="btn-action" title="Start" ${actions}><!-- player-play -->
-                    <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-play" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 4v16l13 -8z"></path></svg>
-                  </button>
-                  <button onclick="buttonAction(this)" name="${name}" value="stop" id="${state}" class="btn-action" title="Stop" ${actions}><!-- player-stop -->
-                    <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-stop" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 5m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"></path></svg>
-                  </button>
-                  <button onclick="buttonAction(this)" name="${name}" value="pause" id="${state}" class="btn-action" title="Pause" ${actions}><!-- player-pause -->
-                    <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-pause" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path><path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path></svg>
-                  </button>
-                  <button onclick="buttonAction(this)" name="${name}" value="restart" id="${state}" class="btn-action" title="Restart" ${actions}><!-- reload -->
-                    <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-reload" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path><path d="M20 4v5h-5"></path></svg>                          
-                  </button>
-                  <div class="dropdown">
-                    <a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><!-- Download SVG icon from http://tabler-icons.io/i/dots-vertical -->
-                      <svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
-                    </a>
-                    <div class="dropdown-menu dropdown-menu-end">
-                      <a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#${name}_modal-details" href="#">Details</a>
-                      <a class="dropdown-item" onclick="viewLogs(this)" name="${name}" data-bs-toggle="modal" data-bs-target="#log_view" href="#">Logs</a>
-                      <a class="dropdown-item" href="#">Edit</a>
-                      <a class="dropdown-item text-primary" href="#">Update</a>
-                      <a class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#${name}_modal-danger" href="#">Remove</a>
-                    </div>
-                  </div>
-                  <div class="dropdown">
-                    <a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><!-- Download SVG icon from http://tabler-icons.io/i/dots-vertical -->
-                      <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /> </svg>
-                    </a>
-                    <div class="dropdown-menu dropdown-menu-end">
-                      <a class="dropdown-item" onclick="hideContainer(this)" name="${name}" href="#">Hide</a>
-                      <a class="dropdown-item" onclick="resetView()" name="${name}" href="#">Reset View</a>
-                      <a class="dropdown-item" href="#">Permissions</a>
-                    </div>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-          <div class="d-flex align-items-baseline">
-            <div class="h1 me-2" title="${name}" ${margin}>
-              <a href="http://${IPv4}:${external_port}" target="_blank">
-                ${shortened_name}
-              </a>
-            </div>
-            <div class="ms-auto">
-              <span class="text-${state_indicator} align-items-center lh-1">
-                <svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-point-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 7a5 5 0 1 1 -4.995 5.217l-.005 -.217l.005 -.217a5 5 0 0 1 4.995 -4.783z" stroke-width="0" fill="currentColor"></path></svg>
-                ${state} <!-- Download SVG icon from http://tabler-icons.io/i/minus -->
-              </span>
-            </div>
-          </div>
-          <div id="${name}_chart" class="chart-sm"></div>
-        </div>
-      </div>
-    </div>
-    
-
-
-
-
-    
-
-
-    <div class="modal modal-blur fade deleteme" id="${name}_modal-danger" tabindex="-1" style="display: none;" aria-hidden="true">
-      <div class="modal-dialog modal-sm modal-dialog-centered" role="document">
-        <div class="modal-content">
-          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
-          <div class="modal-status bg-danger"></div>
-          <div class="modal-body text-center py-3">
-            <!-- Download SVG icon from http://tabler-icons.io/i/alert-triangle -->
-            <svg xmlns="http://www.w3.org/2000/svg" class="icon mb-2 text-danger icon-lg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M12 9v2m0 4v.01"></path><path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path></svg>
-            <h3>Remove ${name}?</h3>
-            <form action="/uninstall" id="${name}_uninstall" method="POST">
-            <input type="text" class="form-control" name="service_name" value="${name}" hidden/>
-            <div class="mb-3"> </div>
-            
-            <div class="mb-2">
-              <div class="divide-y">
-                <div class="row">
-                  <div class="col-9">
-                    <label class="row text-start">
-                      <span class="col">Remove Volumes</span>
-                    </label>
-                  </div>
-                  <div class="col-3">
-                    <label class="form-check form-check-single form-switch text-end">
-                      <input class="form-check-input" type="checkbox" checked="" name="remove_volumes">
-                    </label>
-                  </div>
-                </div>
-                <div class="row">
-                  <div class="col-9">
-                    <label class="row text-start">
-                      <span class="col">
-                        Remove Image
-                      </span>
-                    </label>
-                  </div>
-                  <div class="col-3">
-                    <label class="form-check form-check-single form-switch text-end">
-                      <input class="form-check-input" type="checkbox" checked="" name="remove_image">
-                    </label>
-                  </div>
-                </div>
-                <div class="row">
-                  <div class="col-9">
-                    <label class="row text-start">
-                      <span class="col">
-                        Remove Backups
-                      </span>
-                    </label>
-                  </div>
-                  <div class="col-3">
-                    <label class="form-check form-check-single form-switch text-end">
-                      <input class="form-check-input" type="checkbox" checked="" name="remove_backups">
-                    </label>
-                  </div>
-                </div>
-              </div>
-            </div>
-            <div class="mt-1"> </div>
-            <div class="text-muted">Enter "Yes" below to remove the container.</div>
-            <input type="text" class="form-control mb-2" name="confirm" autocomplete="off">
-            </form>
-          </div>
-          <div class="modal-footer">
-            <div class="w-100">
-              <div class="row">
-                <div class="col">
-                  <a href="#" class="btn w-100" data-bs-dismiss="modal">
-                    Cancel
-                  </a>
-                </div>
-                <div class="col">
-                  <input type="submit" form="${name}_uninstall" class="btn btn-danger w-100" value="Uninstall"/>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-    
-
-    
-    <div class="modal modal-blur fade" id="${name}_modal-details" tabindex="-1" role="dialog" aria-hidden="true">
-                <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
-                  <div class="modal-content">
-                    <div class="modal-header">
-                      <h5 class="modal-title">Install ${name}</h5>
-                      <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
-                    </div>
-            
-                    
-                    <div class="modal-body">
-                      
-                    <pre class="text-secondary">note</pre>
-                    
-                      <form action="" id="details_modal" method="POST">
-                      
-                      <div class="row mb-3 align-items-end">
-                        
-                        <div class="col-lg-6">
-                          <label class="form-label">Container Name: </label>
-                          <input type="text" class="form-control" name="service_name" value="${app_name}" hidden/>
-                          <input type="text" class="form-control" name="name" value="${app_name}"/>
-                        </div>
-                        <div class="col-lg-3">
-                          <label class="form-label">Image: </label>
-                          <input type="text" class="form-control" name="image" value="${image}"/>
-                        </div>
-                        <div class="col-lg-3">
-                          <label class="form-label">Restart Policy: </label>
-                          <select class="form-select" name="restart_policy" value="${restart_policy}">
-                            <option value="1">unless-stopped</option>
-                            <option value="2">on-failure</option>
-                            <option value="3">never</option>
-                            <option value="4">always</option>
-                          </select>
-                        </div>
-                      </div>
-            
-                      <label class="form-label">Network Mode</label>
-                        <div class="form-selectgroup-boxes row mb-3">
-                          <div class="col">
-                            <label class="form-selectgroup-item">
-                              <input type="radio" name="report-type" value="1" class="form-selectgroup-input">
-                              <span class="form-selectgroup-label d-flex align-items-center p-3">
-                                <span class="me-3">
-                                  <span class="form-selectgroup-check"></span>
-                                </span>
-                                <span class="form-selectgroup-label-content">
-                                  <span class="form-selectgroup-title strong mb-1">Host Network</span>
-                                  <span class="d-block text-secondary">Same as host. No isolation. ex.127.0.0.1</span>
-                                </span>
-                              </span>
-                            </label>
-                          </div>
-                          <div class="col">
-                            <label class="form-selectgroup-item">
-                              <input type="radio" name="report-type" class="form-selectgroup-input">
-                              <span class="form-selectgroup-label d-flex align-items-center p-3">
-                                <span class="me-3">
-                                  <span class="form-selectgroup-check"></span>
-                                </span>
-                                <span class="form-selectgroup-label-content">
-                                  <span class="form-selectgroup-title strong mb-1">Bridge Network</span>
-                                  <span class="d-block text-secondary">Containers can communicate using names.</span>
-                                </span>
-                              </span>
-                            </label>
-                          </div>
-                          <div class="col">
-                          <label class="form-selectgroup-item">
-                            <input type="radio" name="report-type" class="form-selectgroup-input">
-                            <span class="form-selectgroup-label d-flex align-items-center p-3">
-                              <span class="me-3">
-                                <span class="form-selectgroup-check"></span>
-                              </span>
-                              <span class="form-selectgroup-label-content">
-                                <span class="form-selectgroup-title strong mb-1">Docker Network</span>
-                                <span class="d-block text-secondary">Isolated on the docker network. ex.172.0.34.2</span>
-                              </span>
-                            </span>
-                          </label>
-                        </div>
-                      </div>
-
-
-
-
-            
-                      <div class="accordion" id="${modal}-accordion">
-                        <div class="accordion-item">
-                          <h2 class="accordion-header" id="heading-1">
-                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-1" aria-expanded="false">
-                              Ports
-                            </button>
-                          </h2>
-                          <div id="collapse-1" class="accordion-collapse collapse" data-bs-parent="#${modal}-accordion">
-                            <div class="accordion-body pt-0">
-            
-
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_0_check" type="checkbox" ${ports_data[0].check}>
-                                </div>
-                                <div class="col">
-                                  <label class="form-label">External Port</label>
-                                  <input type="text" class="form-control" name="port_0_external" value="${ports_data[0].external}"/>
-                                </div>
-                                <div class="col">
-                                  <label class="form-label">Internal Port</label>
-                                  <input type="text" class="form-control" name="port_0_internal" value="${ports_data[0].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <label class="form-label">Protocol</label>
-                                  <select class="form-select" name="port_0_protocol">
-                                    <option value="${ports_data[0].protocol}" selected hidden>${ports_data[0].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_1_check" type="checkbox" ${ports_data[1].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_1_external" value="${ports_data[1].external}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_1_internal" value="${ports_data[1].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <select class="form-select" name="port_1_protocol">
-                                    <option value="${ports_data[1].protocol}" selected hidden>${ports_data[1].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_2_check" type="checkbox" ${ports_data[2].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_2_external" value="${ports_data[2].external}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_2_internal" value="${ports_data[2].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <select class="form-select" name="port_2_protocol">
-                                    <option value="${ports_data[2].protocol}" selected hidden>${ports_data[2].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_3_check" type="checkbox" ${ports_data[3].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_3_external" value="${ports_data[3].external}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_3_internal" value="${ports_data[3].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <select class="form-select" name="port_3_protocol">
-                                    <option value="${ports_data[3].protocol}" selected hidden>${ports_data[3].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_4_check" type="checkbox" ${ports_data[4].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_4_external" value="${ports_data[4].external}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_4_internal" value="${ports_data[4].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <select class="form-select" name="port_4_protocol">
-                                    <option value="${ports_data[4].protocol}" selected hidden>${ports_data[4].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_5_check" type="checkbox" ${ports_data[5].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_5_external" value="${ports_data[5].external}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_5_internal" value="${ports_data[5].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <select class="form-select" name="port_5_protocol">
-                                    <option value="${ports_data[5].protocol}" selected hidden>${ports_data[5].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-            
-                            </div>
-                          </div>
-                        </div>
-                        <div class="accordion-item">
-                          <h2 class="accordion-header" id="heading-2">
-                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-2" aria-expanded="false">
-                              Volumes
-                            </button>
-                          </h2>
-                          <div id="collapse-2" class="accordion-collapse collapse" data-bs-parent="#${modal}-accordion">
-                            <div class="accordion-body pt-0">
-            
-            
-                            <div class="row mb-1 align-items-end">
-                            <div class="col-auto">
-                              <input class="form-check-input" name="volume_0_check" type="checkbox" ${volumes_data[0].check}>
-                            </div>
-                            <div class="col">
-                              <input type="text" class="form-control" name="volume_0_bind" value="${volumes_data[0].bind}"/>
-                            </div>
-                            <div class="col">
-                              <input type="text" class="form-control" name="volume_0_container" value="${volumes_data[0].container}"/>
-                            </div>
-                            <div class="col-lg-2">
-                              <select class="form-select" name="volume_0_readwrite">
-                                <option value="${volumes_data[0].readwrite}" selected hidden>${volumes_data[0].readwrite}</option>
-                                <option value="rw">rw</option>
-                                <option value="ro">ro</option>
-                              </select>
-                            </div>
-                          </div>
-            
-                          <div class="row mb-1 align-items-end">
-                            <div class="col-auto">
-                              <input class="form-check-input" name="volume_1_check" type="checkbox" ${volumes_data[1].check}>
-                            </div>
-                            <div class="col">
-                              <input type="text" class="form-control" name="volume_1_bind" value="${volumes_data[1].bind}"/>
-                            </div>
-                            <div class="col">
-                              <input type="text" class="form-control" name="volume_1_container" value="${volumes_data[1].container}"/>
-                            </div>
-                            <div class="col-lg-2">
-                              <select class="form-select" name="volume_1_readwrite">
-                                <option value="${volumes_data[1].readwrite}" selected hidden>${volumes_data[1].readwrite}</option>
-                                <option value="rw">rw</option>
-                                <option value="ro">ro</option>
-                              </select>
-                            </div>
-                          </div>
-            
-                          <div class="row mb-1 align-items-end">
-                            <div class="col-auto">
-                              <input class="form-check-input" name="volume_2_check" type="checkbox" ${volumes_data[2].check}>
-                            </div>
-                            <div class="col">
-                              <input type="text" class="form-control" name="volume_2_bind" value="${volumes_data[2].bind}"/>
-                            </div>
-                            <div class="col">
-                              <input type="text" class="form-control" name="volume_2_container" value="${volumes_data[2].container}"/>
-                            </div>
-                            <div class="col-lg-2">
-                              <select class="form-select" name="volume_2_readwrite">
-                                <option value="${volumes_data[2].readwrite}" selected hidden>${volumes_data[2].readwrite}</option>
-                                <option value="rw">rw</option>
-                                <option value="ro">ro</option>
-                              </select>
-                            </div>
-                          </div>
-            
-                          <div class="row mb-1 align-items-end">
-                            <div class="col-auto">
-                              <input class="form-check-input" name="volume_3_check" type="checkbox" ${volumes_data[3].check}>
-                            </div>
-                            <div class="col">
-                              <input type="text" class="form-control" name="volume_3_bind" value="${volumes_data[3].bind}"/>
-                            </div>
-                            <div class="col">
-                              <input type="text" class="form-control" name="volume_3_container" value="${volumes_data[3].container}"/>
-                            </div>
-                            <div class="col-lg-2">
-                              <select class="form-select" name="volume_3_readwrite">
-                                <option value="${volumes_data[3].readwrite}" selected hidden>${volumes_data[3].readwrite}</option>
-                                <option value="rw">rw</option>
-                                <option value="ro">ro</option>
-                              </select>
-                            </div>
-                          </div>
-            
-                          <div class="row mb-1 align-items-end">
-                            <div class="col-auto">
-                              <input class="form-check-input" name="volume_4_check" type="checkbox" ${volumes_data[4].check}>
-                            </div>
-                            <div class="col">
-                              <input type="text" class="form-control" name="volume_4_bind" value="${volumes_data[4].bind}"/>
-                            </div>
-                            <div class="col">
-                              <input type="text" class="form-control" name="volume_4_container" value="${volumes_data[4].container}"/>
-                            </div>
-                            <div class="col-lg-2">
-                              <select class="form-select" name="volume_4_readwrite">
-                                <option value="${volumes_data[4].readwrite}" selected hidden>${volumes_data[4].readwrite}</option>
-                                <option value="rw">rw</option>
-                                <option value="ro">ro</option>
-                              </select>
-                            </div>
-                          </div>
-            
-                          <div class="row mb-1 align-items-end">
-                          <div class="col-auto">
-                            <input class="form-check-input" name="volume_5_check" type="checkbox" ${volumes_data[5].check}>
-                          </div>
-                          <div class="col">
-                            <input type="text" class="form-control" name="volume_5_bind" value="${volumes_data[5].bind}"/>
-                          </div>
-                          <div class="col">
-                            <input type="text" class="form-control" name="volume_5_container" value="${volumes_data[5].container}"/>
-                          </div>
-                          <div class="col-lg-2">
-                            <select class="form-select" name="volume_5_readwrite">
-                              <option value="${volumes_data[5].readwrite}" selected hidden>${volumes_data[5].readwrite}</option>
-                              <option value="rw">rw</option>
-                              <option value="ro">ro</option>
-                            </select>
-                          </div>
-                        </div>
-            
-            
-                            </div>
-                          </div>
-                        </div>
-                        <div class="accordion-item">
-                          <h2 class="accordion-header" id="heading-3">
-                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-3" aria-expanded="false">
-                              Environment Variables
-                            </button>
-                          </h2>
-                          <div id="collapse-3" class="accordion-collapse collapse" data-bs-parent="#${modal}-accordion">
-                            <div class="accordion-body pt-0">
-            
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_0_check" ${env_data[0].check}>
-                                </div>
-                                <div class="col">
-                                  <label class="form-label">Variable</label>
-                                  <input type="text" class="form-control" name="env_0_name" value="${env_data[0].name}"/>
-                                </div>
-                                <div class="col">
-                                  <label class="form-label">Value</label>
-                                  <input type="text" class="form-control" name="env_0_default" value="${env_data[0].default}"/>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_1_check" ${env_data[1].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_1_name" value="${env_data[1].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_1_default" value="${env_data[1].default}"/>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_2_check" ${env_data[2].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_2_name" value="${env_data[2].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_2_default" value="${env_data[2].default}"/>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_3_check" ${env_data[3].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_3_name" value="${env_data[3].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_3_default" value="${env_data[3].default}"/>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_4_check" ${env_data[4].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_4_name" value="${env_data[4].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_4_default" value="${env_data[4].default}"/>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_5_check" ${env_data[5].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_5_name" value="${env_data[5].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_5_default" value="${env_data[5].default}"/>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_6_check" ${env_data[6].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_6_name" value="${env_data[6].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_6_default" value="${env_data[6].default}"/>
-                                </div>
-                              </div>
-            
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_7_check" ${env_data[7].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_7_name" value="${env_data[7].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_7_default" value="${env_data[7].default}"/>
-                                </div>
-                              </div>
-            
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_8_check" ${env_data[8].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_8_name" value="${env_data[8].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_8_default" value="${env_data[8].default}"/>
-                                </div>
-                              </div>
-            
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_9_check" ${env_data[9].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_9_name" value="${env_data[9].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_9_default" value="${env_data[9].default}"/>
-                                </div>
-                              </div>
-            
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_10_check" ${env_data[10].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_10_name" value="${env_data[10].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_10_default" value="${env_data[10].default}"/>
-                                </div>
-                              </div>
-            
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" type="checkbox" name="env_11_check" ${env_data[11].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_11_name" value="${env_data[11].name}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="env_11_default" value="${env_data[11].default}"/>
-                                </div>
-                              </div>
-            
-            
-            
-            
-                            </div>
-                          </div>
-                        </div>
-                        <div class="accordion-item">
-                          <h2 class="accordion-header" id="heading-4">
-                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-4" aria-expanded="false">
-                              Labels
-                            </button>
-                          </h2>
-                          <div id="collapse-4" class="accordion-collapse collapse" data-bs-parent="#${modal}-accordion">
-                            <div class="accordion-body pt-0">
-            
-            
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_0_check" ${label_data[0].check}>
-                              </div>
-                              <div class="col">
-                                <label class="form-label">Variable</label>
-                                <input type="text" class="form-control" name="label_0_name" value="${label_data[0].name}"/>
-                              </div>
-                              <div class="col">
-                                <label class="form-label">Value</label>
-                                <input type="text" class="form-control" name="label_0_value" value="${label_data[0].value}"/>
-                              </div>
-                            </div>
-            
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_1_check" ${label_data[1].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_1_name" value="${label_data[1].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_1_value" value="${label_data[1].value}"/>
-                              </div>
-                            </div>
-            
-                              
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_2_check" ${label_data[2].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_2_name" value="${label_data[2].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_2_value" value="${label_data[2].value}"/>
-                              </div>
-                            </div>
-            
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_3_check" ${label_data[3].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_3_name" value="${label_data[3].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_3_value" value="${label_data[3].value}"/>
-                              </div>
-                            </div>
-            
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_4_check" ${label_data[4].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_4_name" value="${label_data[4].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_4_value" value="${label_data[4].value}"/>
-                              </div>
-                            </div>
-
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_5_check" ${label_data[5].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_5_name" value="${label_data[5].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_5_value" value="${label_data[5].value}"/>
-                              </div>
-                            </div>
-
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_6_check" ${label_data[6].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_6_name" value="${label_data[6].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_6_value" value="${label_data[6].value}"/>
-                              </div>
-                            </div>
-
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_7_check" ${label_data[7].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_7_name" value="${label_data[7].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_7_value" value="${label_data[7].value}"/>
-                              </div>
-                            </div>
-
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_8_check" ${label_data[8].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_8_name" value="${label_data[8].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_8_value" value="${label_data[8].value}"/>
-                              </div>
-                            </div>
-
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_9_check" ${label_data[9].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_9_name" value="${label_data[9].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_9_value" value="${label_data[9].value}"/>
-                              </div>
-                            </div>
-
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_10_check" ${label_data[10].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_10_name" value="${label_data[10].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_10_value" value="${label_data[10].value}"/>
-                              </div>
-                            </div>
-
-                            <div class="row mb-1 align-items-end">
-                              <div class="col-auto">
-                                <input class="form-check-input" type="checkbox" name="label_11_check" ${label_data[11].check}>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_11_name" value="${label_data[11].name}"/>
-                              </div>
-                              <div class="col">
-                                <input type="text" class="form-control" name="label_11_value" value="${label_data[11].value}"/>
-                              </div>
-                            </div>
-            
-            
-                            </div>
-                          </div>
-                        </div>
-
-
-                        <div class="accordion-item">
-                          <h2 class="accordion-header" id="heading-5">
-                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-5" aria-expanded="false">
-                              Extras
-                            </button>
-                          </h2>
-                          <div id="collapse-5" class="accordion-collapse collapse" data-bs-parent="#${modal}-accordion">
-                            <div class="accordion-body pt-0">
-            
-
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_0_check" type="checkbox" ${ports_data[0].check}>
-                                </div>
-                                <div class="col">
-                                  <label class="form-label">External Port</label>
-                                  <input type="text" class="form-control" name="port_0_external" value="${ports_data[0].external}"/>
-                                </div>
-                                <div class="col">
-                                  <label class="form-label">Internal Port</label>
-                                  <input type="text" class="form-control" name="port_0_internal" value="${ports_data[0].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <label class="form-label">Protocol</label>
-                                  <select class="form-select" name="port_0_protocol">
-                                    <option value="${ports_data[0].protocol}" selected hidden>${ports_data[0].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_1_check" type="checkbox" ${ports_data[1].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_1_external" value="${ports_data[1].external}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_1_internal" value="${ports_data[1].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <select class="form-select" name="port_1_protocol">
-                                    <option value="${ports_data[1].protocol}" selected hidden>${ports_data[1].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_2_check" type="checkbox" ${ports_data[2].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_2_external" value="${ports_data[2].external}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_2_internal" value="${ports_data[2].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <select class="form-select" name="port_2_protocol">
-                                    <option value="${ports_data[2].protocol}" selected hidden>${ports_data[2].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_3_check" type="checkbox" ${ports_data[3].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_3_external" value="${ports_data[3].external}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_3_internal" value="${ports_data[3].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <select class="form-select" name="port_3_protocol">
-                                    <option value="${ports_data[3].protocol}" selected hidden>${ports_data[3].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="port_4_check" type="checkbox" ${ports_data[4].check}>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_4_external" value="${ports_data[4].external}"/>
-                                </div>
-                                <div class="col">
-                                  <input type="text" class="form-control" name="port_4_internal" value="${ports_data[4].internal}"/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <select class="form-select" name="port_4_protocol">
-                                    <option value="${ports_data[4].protocol}" selected hidden>${ports_data[4].protocol}</option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-                              <div class="row mb-1 align-items-end">
-                                <div class="col-auto">
-                                  <input class="form-check-input" name="" type="checkbox" >
-                                </div>
-                                <div class="col">
-                                  <label class="form-label">External Port</label>
-                                  <input type="text" class="form-control" name="" value=""/>
-                                </div>
-                                <div class="col">
-                                  <label class="form-label">Internal Port</label>
-                                  <input type="text" class="form-control" name="" value=""/>
-                                </div>
-                                <div class="col-lg-2">
-                                  <label class="form-label">Protocol</label>
-                                  <select class="form-select" name="">
-                                    <option value="" selected hidden></option>
-                                    <option value="tcp">tcp</option>
-                                    <option value="udp">udp</option>
-                                  </select>
-                                </div>
-                              </div>
-            
-            
-                            </div>
-                          </div>
-                        </div>
-
-
-
-                      </div>
-            
-            
-                      
-                      </form>
-                    </div>
-                    <div class="modal-footer">
-                      <button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
-                      <input type="submit" form="${form_id}_install" class="btn btn-success" value="Install"/>
-                    </div>
-                  </div>
-                </div>
-              </div>`;
-}

+ 0 - 18
components/siteCard.js

@@ -1,18 +0,0 @@
-function siteCard(type, domain, host, port, id) {
-  
-  let site = `<tr>`
-  site += `<td><input class="form-check-input m-0 align-middle" name="select${id}" value="${domain}" type="checkbox" aria-label="Select invoice"></td>`
-  site += `<td><span class="text-muted">${id}</span></td>`
-  site += `<td><a href="https://${domain}" class="text-reset" tabindex="-1" target="_blank">${domain}</a></td>`
-  site += `<td>${type}</td>`
-  site += `<td>${host}</td>`
-  site += `<td>${port}</td>`
-  site += `<td><span class="badge bg-success me-1"></span> Enabled</td>`
-  site += `<td><span class="badge bg-success me-1"></span> Enabled</td>`
-  site += `<td class="text-end"><a class="btn" href="#"> Edit </a></td>`
-  site += `</tr>`
-
-  return site;
-}
-
-module.exports = { siteCard };

+ 17 - 20
controllers/account.js

@@ -1,22 +1,19 @@
-const User = require('../database/UserModel');
+import { User } from "../database/models.js";
+
+export const Account = async (req, res) => {
+    
+
+    let user = await User.findOne({ where: { UUID: req.session.UUID }});
+
+    res.render("account", {
+        first_name: user.name,
+        last_name: user.name,
+        name: user.name,
+        id: user.id,
+        email: user.email,
+        role: user.role,
+        avatar: user.avatar,
+    });
+
 
-exports.Account = async function(req, res) {
-    if (req.session.user) {
-        // Get the user.
-        let user = await User.findOne({ where: { UUID: req.session.UUID }});
-        // Render the home page
-        res.render("pages/account", {
-            first_name: user.first_name,
-            last_name: user.last_name,
-            name: user.first_name + ' ' + user.last_name,
-            id: user.id,
-            email: user.email,
-            role: user.role,
-            avatar: user.avatar,
-            isLoggedIn: true
-        });
-    } else {
-        // Redirect to the login page
-        res.redirect("/login");
-    }
 }

+ 14 - 81
controllers/apps.js

@@ -1,27 +1,23 @@
-const { appCard } = require('../components/appCard')
-const { dashCard } = require('../components/dashCard');
-const { install, uninstall } = require('../functions/package_manager');
+import { readFileSync } from 'fs';
+import { appCard } from '../components/appCard.js';
 
-const templates_json = require('../templates.json');
-let templates = templates_json.templates;
+let templatesJSON = readFileSync('./templates.json');
+let templates = JSON.parse(templatesJSON).templates;
 
-// sort templates alphabetically
 templates = templates.sort((a, b) => {
     if (a.name < b.name) {
       return -1;
     }
-  });
-  
-
-exports.Apps = async function(req, res) {
+});
 
+export const Apps = (req, res) => {
     let page = Number(req.params.page) || 1;
-    let list_start = (page - 1) * 28;
-    let list_end = (page * 28);
-    let last_page = Math.ceil(templates.length / 28);
+    let list_start = (page-1)*28;
+    let list_end = (page*28);
+    let last_page = Math.ceil(templates.length/28);
 
-    let prev = '/apps/' + (page - 1);
-    let next = '/apps/' + (page + 1);
+    let prev = '/apps/' + (page-1);
+    let next = '/apps/' + (page+1);
     if (page == 1) {
         prev = '/apps/' + (page);
     }
@@ -36,12 +32,10 @@ exports.Apps = async function(req, res) {
         apps_list += app_card;
     }
     
-    // Render the home page
-    res.render("pages/apps", {
+    res.render("apps", {
         name: req.session.user,
         role: req.session.role,
         avatar: req.session.avatar,
-        isLoggedIn: true,
         list_start: list_start + 1,
         list_end: list_end,
         app_count: templates.length,
@@ -54,7 +48,7 @@ exports.Apps = async function(req, res) {
 
 
 
-exports.searchApps = async function(req, res) {
+export const searchApps = async (req, res) => {
 
     let page = Number(req.query.page) || 1;
     let list_start = (page - 1) * 28;
@@ -98,12 +92,10 @@ exports.searchApps = async function(req, res) {
         apps_list += app_card;
     }
     
-    // Render the home page
-    res.render("pages/apps", {
+    res.render("apps", {
         name: req.session.user,
         role: req.session.role,
         avatar: req.session.avatar,
-        isLoggedIn: true,
         list_start: list_start + 1,
         list_end: list_end,
         app_count: templates.length,
@@ -112,63 +104,4 @@ exports.searchApps = async function(req, res) {
         apps_list: apps_list
     });
 
-}
-
-
-
-
-
-
-
-
-
-exports.Install = async function (req, res) {
-    
-    if (req.session.role == "admin") {
-
-        console.log(`Starting install for: ${req.body.name}`)
-
-        install(req.body);
-
-        let container_info = {
-            name: req.body.name,
-            service: req.body.service_name,
-            state: 'installing',
-            image: req.body.image,
-            restart_policy: req.body.restart_policy
-        }
-
-        let installCard = dashCard(container_info);
-
-        req.app.locals.install = installCard;
-
-        
-        // Redirect to the home page
-        res.redirect("/");
-    } else {
-        // Redirect to the login page
-        res.redirect("/login");
-    }
-}
-
-
-
-exports.Uninstall = async function (req, res) {
-    
-    if (req.session.role == "admin") {
-
-
-        if (req.body.confirm == 'Yes') {
-
-            uninstall(req.body);
-
-        }
-
-
-        // Redirect to the home page
-        res.redirect("/");
-    } else {
-        // Redirect to the login page
-        res.redirect("/login");
-    }
 }

+ 0 - 160
controllers/auth.js

@@ -1,160 +0,0 @@
-const User = require('../database/UserModel');
-const bcrypt = require('bcrypt');
-
-
-exports.Login = function(req,res){
-
-     // check whether we have a session
-     if(req.session.user){
-        // Redirect to log out.
-        res.redirect("/logout");
-    }else{
-        // Render the login page.
-        res.render("pages/login",{
-            "error":"",
-            "isLoggedIn": false
-        });
-    }
-}
-
-exports.processLogin = async function(req,res){
-    // get the data.
-    let email = req.body.email;
-    let password = req.body.password;
-    // check if we have data.
-    if(email && password){
-        // check if the user exists.
-        let existingUser = await User.findOne({ where: {email:email}});
-        if(existingUser){
-            // compare the password.
-            let match = await bcrypt.compare(password,existingUser.password);
-            if(match){
-
-                // set the session.
-                req.session.user = existingUser.username;
-                req.session.UUID = existingUser.UUID;
-                req.session.role = existingUser.role;
-                req.session.avatar = existingUser.avatar;
-
-
-                // Redirect to the home page.
-                res.redirect("/");
-            }else{
-                // return an error.
-                res.render("pages/login",{
-                    "error":"Invalid password",
-                    isLoggedIn: false
-                });
-            }
-        }else{
-            // return an error.
-            res.render("pages/login",{
-                "error":"User with that email does not exist.",
-                isLoggedIn:false
-            });
-        }
-    }else{
-        res.status(400);
-        res.render("pages/login",{
-            "error":"Please fill in all the fields.",
-            isLoggedIn:false
-        });
-    }
-}
-
-
-exports.Logout = function(req,res){
-    // clear the session.
-    req.session.destroy();
-    // Redirect to the login page.
-    res.redirect("/login");    
-}
-
-
-
-exports.Register = function(req,res){
-    // Check whether we have a session
-    if(req.session.user){
-        // Redirect to log out.
-        res.redirect("/logout");
-    } else {
-        // Render the signup page.
-        res.render("pages/register",{
-            "error":"",
-            isLoggedIn:false
-        });
-    }
-}
-
-exports.processRegister = async function(req,res){
-
-    // Get the data.
-    let { first_name, last_name, username, email, password, avatar, tos, secret } = req.body;
-    let role = "user";
-
-    // Check the data.
-    if((first_name && last_name && email && password && username && tos) && (secret == process.env.SECRET)){
-
-        // Check if there is an existing user with that username.
-        let existingUser = await User.findOne({ where: {username:username}});
-
-        let adminUser = await User.findOne({ where: {role:"admin"}});
-
-        if(!existingUser){
-            // hash the password.
-            let hashedPassword = bcrypt.hashSync(password,10);
-
-            if(!adminUser){
-                console.log('Creating admin User');
-                role = "admin";
-            }
-
-            try {
-                const user = await User.create({ 
-                    first_name: first_name,
-                    last_name: last_name,
-                    username: username,
-                    email: email,
-                    password: hashedPassword,
-                    role: role,
-                    group: 'all',
-                    avatar: `<img src="./static/avatars/${avatar}">`
-                 });
-
-                let newUser = await User.findOne({ where: {email:email}});
-
-                let match = await bcrypt.compare(password,newUser.password);
-                if(match){  
-                    console.log(`User session created for ${newUser.username}`) 
-                    req.session.user = newUser.username;
-                    req.session.UUID = newUser.UUID;
-                    req.session.role = newUser.role;
-                    req.session.avatar = newUser.avatar;
-                }
-
-                // Redirect to the home page.
-                res.redirect("/");
-            }
-            catch (err) {
-                // return an error.
-                res.render("pages/register",{
-                    "error":"Something went wrong when creating account.",
-                    isLoggedIn:false
-                });
-            }
-
-        }else{
-            // return an error.
-            res.render("pages/register",{
-                "error":"User with that username already exists.",
-                isLoggedIn:false
-            });
-        }
-    }else{
-        // Redirect to the signup page.
-        res.render("pages/register",{
-            "error":"Please fill in all the fields and accept TOS.",
-            isLoggedIn:false
-        });
-    }
-}

+ 8 - 199
controllers/dashboard.js

@@ -1,215 +1,24 @@
-const { readFileSync, writeFileSync, appendFileSync, readdirSync } = require('fs');
-const { execSync } = require("child_process");
-const { siteCard } = require('../components/siteCard');
-const { containerExec } = require('../functions/system')
 
+export const Dashboard = (req, res) => {
 
 
-exports.Dashboard = async function (req, res) {
 
-    let caddy = 'd-none';
-
-    if (process.env.Proxy_Manager == 'enabled') {
-        caddy = '';
-    }
-
-    // Render the home page
-    res.render("pages/dashboard", {
+    res.render("dashboard", {
         name: req.session.user,
         role: req.session.role,
         avatar: req.session.avatar,
-        isLoggedIn: true,
-        site_list: req.app.locals.site_list,
-        caddy: caddy
     });
 
 }
 
+export const searchDashboard = (req, res) => {
 
+    console.log(req.params);
 
-exports.AddSite = async function (req, res) {
-
-    let { domain, type, host, port } = req.body;
-
-    if ( domain && type && host && port) {
-
-
-        let { domain, type, host, port } = req.body;
-
-        // build caddyfile
-        let caddyfile = `${domain} {`
-        caddyfile += `\n\t${type} ${host}:${port}`
-        caddyfile += `\n\theader {`
-        caddyfile += `\n\t\tStrict-Transport-Security "max-age=31536000; includeSubDomains; preload"`
-        caddyfile += `\n\t}`
-        caddyfile += `\n}`
-
-        
-        // save caddyfile
-        writeFileSync(`./caddyfiles/sites/${domain}.Caddyfile`, caddyfile, function (err) { console.log(err) });
-
-
-        // format caddyfile
-        let format = {
-            container: 'DweebProxy',
-            command: `caddy fmt --overwrite /etc/caddy/sites/${domain}.Caddyfile`
-        }
-        await containerExec(format, function(err, data) {
-            if (err) {
-                console.error(err);
-                return;
-            }
-            console.log(`Formatted ${domain}.Caddyfile`);
-        });
-        
-        ///////////////// convert caddyfile to json
-        let convert = {
-            container: 'DweebProxy',
-            command: `caddy adapt --config /etc/caddy/sites/${domain}.Caddyfile --pretty >> /etc/caddy/sites/${domain}.json`
-        }
-        await containerExec(convert, function(err, data) {
-            if (err) {
-                console.error(err);
-                return;
-            }
-            console.log(`Converted ${domain}.Caddyfile to JSON`);
-        });
-
-        ////////////// reload caddy
-        let reload = {
-            container: 'DweebProxy',
-            command: `caddy reload --config /etc/caddy/Caddyfile`
-        }
-        await containerExec(reload, function(err, data) {
-            if (err) {
-                console.error(err);
-                return;
-            }
-            console.log(`Reloaded Caddy Config`);
-        });
-
-        let site = siteCard(type, domain, host, port, 0);
-
-        req.app.locals.site_list += site;
-
-
-        res.redirect("/");
-    } else {
-        // Redirect
-        console.log('missing info')
-        res.redirect("/");
-    }
-}
-
-
-exports.RemoveSite = async function (req, res) {
-
-    for (const [key, value] of Object.entries(req.body)) {
-
-        execSync(`rm ./caddyfiles/sites/${value}.Caddyfile`, (err, stdout, stderr) => {
-            if (err) { console.error(`error: ${err.message}`); return; }
-            if (stderr) { console.error(`stderr: ${stderr}`); return; }
-            console.log(`removed ${value}.Caddyfile`);
-        });
-
-    }
-
-    let reload = {
-        container: 'DweebProxy',
-        command: `caddy reload --config /etc/caddy/Caddyfile`
-    }
-    await containerExec(reload);
-
-    
-    console.log('Removed Site(s)')
-
-    res.redirect("/refreshsites");
-
-    
-}
-
-
-exports.RefreshSites = async function (req, res) {
-
-    let domain, type, host, port;
-    let id = 1;
-
-    // Clear site_list.ejs
-    req.app.locals.site_list = "";
-    
-
-    // check if ./caddyfiles/sites contains any .json files, then delete them
-    try {
-        let files = readdirSync('./caddyfiles/sites/');
-        files.forEach(file => {
-            if (file.includes(".json")) {
-                execSync(`rm ./caddyfiles/sites/${file}`, (err, stdout, stderr) => {
-                    if (err) { console.error(`error: ${err.message}`); return; }
-                    if (stderr) { console.error(`stderr: ${stderr}`); return; }
-                    console.log(`removed ${file}`);
-                });
-            }
-        });
-    } catch (error) { console.log("No .json files to delete") }
-
-    // get list of Caddyfiles
-    let sites = readdirSync('./caddyfiles/sites/');
-
-
-    sites.forEach(site_name => {
-        // convert the caddyfile of each site to json
-        let convert = {
-            container: 'DweebProxy',
-            command: `caddy adapt --config ./caddyfiles/sites/${site_name} --pretty >> ./caddyfiles/sites/${site_name}.json`
-        }
-        containerExec(convert);
-
-        try {
-        // read the json file
-        let site_file = readFileSync(`./caddyfiles/sites/${site_name}.json`, 'utf8');
-        // fix whitespace and parse the json file
-        site_file = site_file.replace(/        /g, "  ");
-        site_file = JSON.parse(site_file);
-        } catch (error) { console.log("No .json file to read") }
-
-
-        // get the domain, type, host, and port from the json file
-        try { domain = site_file.apps.http.servers.srv0.routes[0].match[0].host[0] } catch (error) { console.log("No Domain") }
-        try { type = site_file.apps.http.servers.srv0.routes[0].handle[0].routes[0].handle[1].handler } catch (error) { console.log("No Type") }
-        try { host = site_file.apps.http.servers.srv0.routes[0].handle[0].routes[0].handle[1].upstreams[0].dial.split(":")[0] } catch (error) { console.log("Not Localhost") }
-        try { port = site_file.apps.http.servers.srv0.routes[0].handle[0].routes[0].handle[1].upstreams[0].dial.split(":")[1] } catch (error) { console.log("No Port") }
-
-        // build the site card
-        let site = siteCard(type, domain, host, port, id);
-
-        // append the site card to site_list
-        req.app.locals.site_list += site;
-        
-        id++;
+    res.render("dashboard", {
+        name: req.session.user,
+        role: req.session.role,
+        avatar: req.session.avatar,
     });
-    
-
-    res.redirect("/");
- 
-}
-
-
-
-exports.DisableSite = async function (req, res) {
-        
-    console.log(req.body)
-    console.log('Disable Site')
-
-    res.redirect("/");
-
-}
-
-
-exports.EnableSite = async function (req, res) {
-   
-    console.log(req.body)
-    console.log('Enable Site')
-
-    res.redirect("/");
 
 }

+ 9 - 5
controllers/images.js

@@ -1,14 +1,18 @@
+import { docker } from '../app.js';
 
+export const Images = async function(req, res) {
 
-exports.Images = async function(req, res) {
-    
+    const allImages = await docker.listImages({ all: true });
+
+    for (let i = 0; i < allImages.length; i++) {
+        console.log(`Image ${i}:`)
+        console.log(`repoTags: ${allImages[i].repoTags}`)
+    }
     
-    // Render the home page
-    res.render("pages/images", {
+    res.render("images", {
         name: req.session.user,
         role: req.session.role,
         avatar: req.session.avatar,
-        isLoggedIn: true,
     });
 
 }

+ 72 - 0
controllers/login.js

@@ -0,0 +1,72 @@
+import { User } from '../database/models.js';
+import { Syslog } from '../database/models.js';
+import bcrypt from 'bcrypt';
+
+export const Login = function(req,res){
+    if(req.session.user){
+        res.redirect("/logout");
+    }else{
+        res.render("login",{
+            "error":"",
+        });
+    }
+}
+
+export const submitLogin = async function(req,res){
+
+    let { email, password } = req.body;
+
+    if(email && password){
+
+        let existingUser = await User.findOne({ where: {email:email}});
+        if(existingUser){
+
+            let match = await bcrypt.compare(password,existingUser.password);
+
+            if(match){
+
+                let currentDate = new Date();
+                let newLogin = currentDate.toLocaleString();
+                await User.update({lastLogin: newLogin}, {where: {UUID:existingUser.UUID}});
+
+                req.session.user = existingUser.username;
+                req.session.UUID = existingUser.UUID;
+                req.session.role = existingUser.role;
+                req.session.avatar = existingUser.avatar;
+
+                const syslog = await Syslog.create({
+                    user: req.session.user,
+                    email: email,
+                    event: "Successful Login",
+                    message: "User logged in successfully",
+                    ip: req.socket.remoteAddress
+                });
+
+
+                res.redirect("/");
+            }else{
+
+                const syslog = await Syslog.create({
+                    user: null,
+                    email: email,
+                    event: "Bad Login",
+                    message: "Invalid password",
+                    ip: req.socket.remoteAddress
+                });
+
+                res.render("login",{
+                    "error":"Invalid password",
+                });
+            }
+        }else{
+            res.render("login",{
+                "error":"User with that email does not exist.",
+            });
+        }
+    }else{
+        res.status(400);
+        res.render("login",{
+            "error":"Please fill in all the fields.",
+        });
+    }
+}

+ 5 - 6
controllers/networks.js

@@ -1,14 +1,13 @@
-const User = require('../database/UserModel');
+import { docker } from '../app.js';
 
-exports.Networks = async function(req, res) {
 
-    
-    // Render the home page
-    res.render("pages/users", {
+export const Networks = async function(req, res) {
+
+
+    res.render("networks", {
         name: req.session.user,
         role: req.session.role,
         avatar: req.session.avatar,
-        isLoggedIn: true,
     });
 
 }

+ 96 - 0
controllers/register.js

@@ -0,0 +1,96 @@
+import { User, Syslog } from '../database/models.js';
+import bcrypt from 'bcrypt';
+
+let SECRET = process.env.SECRET || "MrWiskers"
+
+export const Register = function(req,res){
+    if(req.session.user){
+        res.redirect("/logout");
+    } else {
+        res.render("register",{
+            "error":"",
+        });
+    }
+}
+
+
+
+export const submitRegister = async function(req,res){
+
+    let { name, username, email, password, confirmPassword, avatar, tos, secret } = req.body;
+
+
+    if (secret != SECRET) {
+        const syslog = await Syslog.create({
+            user: username,
+            email: email,
+            event: "Failed Registration",
+            message: "Invalid secret",
+            ip: req.socket.remoteAddress
+        });
+    }
+
+    if((name && email && password && confirmPassword && username && tos) && (secret == SECRET) && (password == confirmPassword)){
+
+        async function userRole () {
+            let userCount = await User.count();
+            if(userCount == 0){
+                return "admin";
+            }else{
+                return "user";
+            }
+        }
+
+        let existingUser = await User.findOne({ where: {email:email}});
+        if(!existingUser){
+
+            try {
+                const user = await User.create({ 
+                    name: name,
+                    username: username,
+                    email: email,
+                    password: bcrypt.hashSync(password,10),
+                    role: await userRole(),
+                    group: 'all',
+                    avatar: `<img src="img/avatars/${avatar}">`
+                });
+
+                // make sure the user was created and get the UUID.
+                let newUser = await User.findOne({ where: {email:email}});
+                let match = await bcrypt.compare(password,newUser.password);
+
+                if(match){  
+                    req.session.user = newUser.username;
+                    req.session.UUID = newUser.UUID;
+                    req.session.role = newUser.role;
+                    req.session.avatar = newUser.avatar;
+
+                    const syslog = await Syslog.create({
+                        user: req.session.user,
+                        email: email,
+                        event: "Successful Registration",
+                        message: "User registered successfully",
+                        ip: req.socket.remoteAddress
+                    });
+
+                    res.redirect("/");
+                }
+            } catch(err) {
+                res.render("register",{
+                    "error":"Something went wrong when creating account.",
+                });
+            }
+
+        } else {
+                // return an error.
+                res.render("register",{
+                    "error":"User with that email already exists.",
+                });
+            }
+    } else {
+        // Redirect to the signup page.
+        res.render("register",{
+            "error":"Please fill in all the fields and accept TOS.",
+        });
+    }
+}

+ 2 - 7
controllers/settings.js

@@ -1,14 +1,9 @@
-const User = require('../database/UserModel.js');
-const Server = require('../database/ServerModel.js');
 
-exports.Settings = async function(req, res) {
+export const Settings = (req, res) => {
 
-    // Render the home page
-    res.render("pages/settings", {
+    res.render("settings", {
         name: req.session.user,
         role: req.session.role,
         avatar: req.session.avatar,
-        isLoggedIn: true
     });
-
 }

+ 36 - 0
controllers/syslogs.js

@@ -0,0 +1,36 @@
+import { Syslog } from '../database/models.js';
+
+export const Syslogs = async function(req, res) {
+
+    let logs = '';
+
+    const syslogs = await Syslog.findAll({
+        order: [
+            ['id', 'DESC']
+        ]
+    });
+
+    for (const log of syslogs) {
+        let date = (log.createdAt).toDateString();
+        let time = (log.createdAt).toLocaleTimeString();
+        let datetime = `${time} ${date}`;
+
+        logs += `<tr>
+                    <td class="sort-id">${log.id}</td>
+                    <td class="sort-user">${log.user}</td>
+                    <td class="sort-email">${log.email}</td>
+                    <td class="sort-event">${log.event}</td>
+                    <td class="sort-message">${log.message}</td>
+                    <td class="sort-ip">${log.ip}</td>
+                    <td class="sort-datetime">${datetime}</td>
+                </tr>`
+    }
+    
+    res.render("syslogs", {
+        name: req.session.user || 'Dev',
+        role: req.session.role || 'Dev',
+        avatar: req.session.avatar || '<img src="/img/avatars/rus.jpg">',
+        logs: logs
+    });
+
+}

+ 25 - 13
controllers/users.js

@@ -1,6 +1,6 @@
-const User = require('../database/UserModel');
+import { User } from '../database/models.js';
 
-exports.Users = async function(req, res) {
+export const Users = async (req, res) => {
    
     let user_list = `
     <tr>
@@ -12,36 +12,48 @@ exports.Users = async function(req, res) {
         <th>Email</th>
         <th>UUID</th>
         <th>Role</th>
+        <th>Last Login</th>
         <th>Status</th>
         <th>Actions</th>
     </tr>`
 
-    let users = await User.findAll();
-    users.forEach((account) => {
-        full_name = account.first_name + ' ' + account.last_name;
-        user_info = `
+    let allUsers = await User.findAll();
+    allUsers.forEach((account) => {
+
+        let active = '<span class="badge badge-outline text-green">Active</span>'
+        let lastLogin = new Date(account.lastLogin);
+        let currentDate = new Date();
+        let days = Math.floor((currentDate - lastLogin) / (1000 * 60 * 60 * 24));
+
+        if (days > 30) {
+            active = '<span class="badge badge-outline text-grey">Inactive</span>';
+        }
+
+
+
+        let info = `
         <tr>
             <td><input class="form-check-input" type="checkbox"></td>
-            <td>${user.id}</td>
+            <td>${account.id}</td>
             <td><span class="avatar me-2">${account.avatar}</span></td>
-            <td>${full_name}</td>
+            <td>${account.name}</td>
             <td>${account.username}</td>
             <td>${account.email}</td>
             <td>${account.UUID}</td>
             <td>${account.role}</td>
-            <td><span class="badge badge-outline text-green">Active</span></td>
+            <td>${account.lastLogin}</td>
+            <td>${active}</td>
             <td><a href="#" class="btn">Edit</a></td>
         </tr>`
 
-        user_list += user_info;
+        user_list += info;
     });
 
-    // Render the home page
-    res.render("pages/users", {
+
+    res.render("users", {
         name: req.session.user,
         role: req.session.role,
         avatar: req.session.avatar,
-        isLoggedIn: true,
         user_list: user_list
     });
 

+ 3 - 9
controllers/volumes.js

@@ -1,15 +1,9 @@
-const User = require('../database/UserModel');
+import { docker } from '../app.js';
 
-exports.Volumes = async function(req, res) {
-
-
-
-    // Render the home page
-    res.render("pages/volumes", {
+export const Volumes = (req, res) => {
+    res.render("volumes", {
         name: req.session.user,
         role: req.session.role,
         avatar: req.session.avatar,
-        isLoggedIn: true,
     });
-
 }

+ 0 - 72
database/ContainerModel.js

@@ -1,72 +0,0 @@
-const { Sequelize, DataTypes } = require('sequelize');
-
-const sequelize = new Sequelize({
-  dialect: 'sqlite',
-  storage: './database/db.sqlite',
-  logging: false
-});
-
-
-const Containers = sequelize.define('Containers', {
-  // Model attributes are defined here
-  id: {
-    type: DataTypes.INTEGER,
-    autoIncrement: true,
-    primaryKey: true
-  },
-  name: {
-    type: DataTypes.STRING,
-    allowNull: false
-  },
-  visibility: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  size: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  group: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  start: {
-    type: DataTypes.JSON
-    // allowNull defaults to true
-  },
-  stop: {
-    type: DataTypes.JSON
-    // allowNull defaults to true
-  },
-  pause: {
-    type: DataTypes.JSON
-    // allowNull defaults to true
-  },
-  restart: {
-    type: DataTypes.JSON
-    // allowNull defaults to true
-  },
-  remove: {
-    type: DataTypes.JSON
-    // allowNull defaults to true
-  },
-  logs: {
-    type: DataTypes.JSON
-    // allowNull defaults to true
-  },
-  update: {
-    type: DataTypes.JSON
-    // allowNull defaults to true
-  },
-
-});
-
-async function syncModel() {
-  await sequelize.sync();
-  console.log('Containers model synced');
-}
-
-syncModel();
-
-
-module.exports = Containers;

+ 0 - 46
database/ServerModel.js

@@ -1,46 +0,0 @@
-const { Sequelize, DataTypes } = require('sequelize');
-
-const sequelize = new Sequelize({
-  dialect: 'sqlite',
-  storage: './database/db.sqlite',
-  logging: false
-});
-
-
-const Server = sequelize.define('Server', {
-  // Model attributes are defined here
-  timezone: {
-    type: DataTypes.STRING,
-    allowNull: false
-  },
-  hwa: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  media: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  pgid: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  puid: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  caddy: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-});
-
-async function syncModel() {
-  await sequelize.sync();
-  console.log('Server model synced');
-}
-
-syncModel();
-
-
-module.exports = Server;

+ 0 - 63
database/UserModel.js

@@ -1,63 +0,0 @@
-const { Sequelize, DataTypes } = require('sequelize');
-
-const sequelize = new Sequelize({
-  dialect: 'sqlite',
-  storage: './database/db.sqlite',
-  logging: false
-});
-
-
-const User = sequelize.define('User', {
-  // Model attributes are defined here
-  id: {
-    type: DataTypes.INTEGER,
-    autoIncrement: true,
-    primaryKey: true
-  },
-  first_name: {
-    type: DataTypes.STRING,
-    allowNull: false
-  },
-  last_name: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  username: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  email: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  password: {
-    type: DataTypes.STRING,
-    // allowNull: false
-  },
-  role: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  group: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  avatar: {
-    type: DataTypes.STRING
-    // allowNull defaults to true
-  },
-  UUID: {
-    type: DataTypes.UUID,
-    defaultValue: DataTypes.UUIDV4
-  }
-});
-
-async function syncModel() {
-  await sequelize.sync();
-  console.log('User model synced');
-}
-
-syncModel();
-
-
-module.exports = User;

+ 162 - 0
database/models.js

@@ -0,0 +1,162 @@
+import { Sequelize, DataTypes } from 'sequelize';
+
+// let SQLITE_PASS = process.env.SQLITE_PASS || 'some_long_elaborate_password';
+
+// export const sequelize = new Sequelize('dweebui', 'dweebui', SQLITE_PASS, { 
+//   dialect: 'sqlite',
+//   dialectModulePath: '@journeyapps/sqlcipher',
+//   storage: './database/database.sqlite',
+//   logging: false,
+// });
+
+export const sequelize = new Sequelize({ 
+  dialect: 'sqlite',
+  storage: './database/database.sqlite',
+  logging: false,
+});
+
+export const User = sequelize.define('User', {
+  id: {
+    type: DataTypes.INTEGER,
+    autoIncrement: true,
+    primaryKey: true
+  },
+  name: {
+    type: DataTypes.STRING
+  },
+  username: {
+    type: DataTypes.STRING,
+    allowNull: false
+  },
+  email: {
+    type: DataTypes.STRING,
+    allowNull: false
+  },
+  password: {
+    type: DataTypes.STRING,
+    allowNull: false
+  },
+  role: {
+    type: DataTypes.STRING
+  },
+  group: {
+    type: DataTypes.STRING
+  },
+  avatar: {
+    type: DataTypes.STRING
+  },
+  lastLogin: {
+    type: DataTypes.STRING
+  },
+  UUID: {
+    type: DataTypes.UUID,
+    defaultValue: DataTypes.UUIDV4,
+  }
+});
+
+export const Container = sequelize.define('Container', {
+  id: {
+    type: DataTypes.INTEGER,
+    autoIncrement: true,
+    primaryKey: true
+  },
+  name: {
+    type: DataTypes.STRING,
+    allowNull: false
+  },
+  visibility: {
+    type: DataTypes.STRING
+  },
+  size: {
+    type: DataTypes.STRING
+  },
+  group: {
+    type: DataTypes.STRING
+  }
+});
+
+export const Permission = sequelize.define('Permission', {
+  id: {
+    type: DataTypes.INTEGER,
+    autoIncrement: true,
+    primaryKey: true
+  },
+  containerName: {
+    type: DataTypes.STRING,
+    allowNull: false
+  },
+  containerID: {
+    type: DataTypes.STRING,
+    allowNull: false
+  },
+  user: {
+    type: DataTypes.STRING,
+    allowNull: false
+  },
+  userID: {
+    type: DataTypes.STRING,
+    allowNull: false
+  },
+  install: {
+    type: DataTypes.STRING,
+  },
+  uninstall: {
+    type: DataTypes.STRING
+  },
+  edit: {
+    type: DataTypes.STRING
+  },
+  upgrade: {
+    type: DataTypes.STRING
+  },
+  start: {
+    type: DataTypes.STRING
+  },
+  stop: {
+    type: DataTypes.STRING
+  },
+  restart: {
+    type: DataTypes.STRING
+  },
+  pause: {
+    type: DataTypes.STRING
+  },
+  logs: {
+    type: DataTypes.STRING
+  },
+  hide: {
+    type: DataTypes.STRING
+  },
+  view: {
+    type: DataTypes.STRING
+  },
+  reset_view: {
+    type: DataTypes.STRING
+  },
+});
+
+
+export const Syslog = sequelize.define('Syslog', {
+  id: {
+    type: DataTypes.INTEGER,
+    autoIncrement: true,
+    primaryKey: true
+  },
+  user: {
+    type: DataTypes.STRING
+  },
+  email: {
+    type: DataTypes.STRING
+  },
+  event: {
+    type: DataTypes.STRING,
+    allowNull: false
+  },
+  message: {
+    type: DataTypes.STRING,
+    allowNull: false
+  },
+  ip : {
+    type: DataTypes.STRING
+  },
+});

+ 5 - 9
docker-compose.yaml

@@ -2,9 +2,9 @@ version: "3.9"
 services:
   dweebui:
     container_name: dweebui
-    image: lllllllillllllillll/dweebui:v0.09-dev
-    # build:
-    #   context: .
+    # image: lllllllillllllillll/dweebui:v0.20
+    build:
+      context: .
     environment:
       NODE_ENV: production
       PORT: 8000
@@ -14,17 +14,13 @@ services:
       - 8000:8000
     volumes:
       - dweebui:/app
-      - caddyfiles:/app/caddyfiles
       - /var/run/docker.sock:/var/run/docker.sock
-      #- ./custom-templates.json:/app/custom-templates.json
-      #- ./composefiles:/app/composefiles
     networks:
-      - dweeb_network
+      - dweebui_net
 
 volumes:
   dweebui:
-  caddyfiles:
 
 networks:
-  dweeb_network:
+  dweebui_net:
     driver: bridge

+ 0 - 205
functions/compose.js

@@ -1,205 +0,0 @@
-const { writeFileSync, mkdirSync, readFileSync } = require("fs");
-const yaml = require('js-yaml');
-
-const { exec, execSync } = require("child_process");
-
-const { docker } = require('./system');
-
-var DockerodeCompose = require('dockerode-compose');
-
-
-module.exports.install = async function (data) {
-    
-        console.log(`[Start of install function]`);
-
-        let { service_name, name, image, command_check, command, net_mode, restart_policy } = data;        
-        let { port0, port1, port2, port3, port4, port5 } = data;
-        let { volume0, volume1, volume2, volume3, volume4, volume5 } = data;
-        let { env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11 } = data;
-        let { label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11 } = data;
-
-
-        if ((service_name.includes('caddy')) || (name.includes('caddy'))) {
-            req.app.locals.caddy = 'enabled';
-        }
-
-        let docker_volumes = [];
-
-        if (image.startsWith('https://')){
-            mkdirSync(`./appdata/${name}`, { recursive: true });
-            execSync(`curl -o ./appdata/${name}/${name}_stack.yml -L ${image}`);
-            console.log(`Downloaded stackfile: ${image}`);
-            let stackfile = yaml.load(readFileSync(`./appdata/${name}/${name}_stack.yml`, 'utf8'));
-            let services = Object.keys(stackfile.services);
-
-            for ( let i = 0; i < services.length; i++ ) {
-                try {
-                    console.log(stackfile.services[Object.keys(stackfile.services)[i]].environment);
-                } catch { console.log('no env') }
-            }
-            
-        } else {
-
-            let compose_file = `version: '3'`;
-                compose_file += `\nservices:`
-                compose_file += `\n  ${service_name}:`
-                compose_file += `\n    container_name: ${name}`;
-                compose_file += `\n    image: ${image}`;
-
-            // Command
-            if (command_check == 'on') {
-                compose_file += `\n    command: ${command}`
-            }
-
-            // Network mode
-            if (net_mode == 'host') {
-                compose_file += `\n    network_mode: 'host'`
-            }
-            else if (net_mode != 'host' && net_mode != 'docker') {
-                compose_file += `\n    network_mode: '${net_mode}'`
-            }
-            
-            // Restart policy
-            if (restart_policy != '') {
-                compose_file += `\n    restart: ${restart_policy}`
-            }
-
-            // Ports
-            if ((port0 == 'on' || port1 == 'on' || port2 == 'on' || port3 == 'on' || port4 == 'on' || port5 == 'on') && (net_mode != 'host')) {
-                compose_file += `\n    ports:`
-
-                    for (let i = 0; i < 6; i++) {
-                        if (data[`port${i}`] == 'on') {
-                            compose_file += `\n      - ${data[`port_${i}_external`]}:${data[`port_${i}_internal`]}/${data[`port_${i}_protocol`]}`
-                        }
-                    }
-            }
-
-            // Volumes
-            if (volume0 == 'on' || volume1 == 'on' || volume2 == 'on' || volume3 == 'on' || volume4 == 'on' || volume5 == 'on') {
-                compose_file += `\n    volumes:`
-
-                for (let i = 0; i < 6; i++) {
-
-                    // if volume is on and neither bind or container is empty, it's a bind mount (ex /mnt/user/appdata/config:/config  )
-                    if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] != '') && (data[`volume_${i}_container`] != '')) {
-                        compose_file += `\n      - ${data[`volume_${i}_bind`]}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
-                    }
-
-                    // if bind is empty create a docker volume (ex container_name_config:/config) convert any '/' in container name to '_'
-                    else if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] == '') && (data[`volume_${i}_container`] != '')) {
-                        let volume_name = data[`volume_${i}_container`].replace(/\//g, '_');
-                        compose_file += `\n      - ${name}_${volume_name}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
-                        docker_volumes.push(`${name}_${volume_name}`);
-                    } 
-                }
-            }
-
-            // Environment variables
-            if (env0 == 'on' || env1 == 'on' || env2 == 'on' || env3 == 'on' || env4 == 'on' || env5 == 'on' || env6 == 'on' || env7 == 'on' || env8 == 'on' || env9 == 'on' || env10 == 'on' || env11 == 'on') {
-                compose_file += `\n    environment:`
-            }
-            for (let i = 0; i < 12; i++) {
-                if (data[`env${i}`] == 'on') {
-                    compose_file += `\n      - ${data[`env_${i}_name`]}=${data[`env_${i}_default`]}`
-
-                }
-            }
-
-            // Add labels
-            if (label0 == 'on' || label1 == 'on' || label2 == 'on' || label3 == 'on' || label4 == 'on' || label5 == 'on' || label6 == 'on' || label7 == 'on' || label8 == 'on' || label9 == 'on' || label10 == 'on' || label11 == 'on') {
-                compose_file += `\n    labels:`
-            }   
-            for (let i = 0; i < 12; i++) {
-                if (data[`label${i}`] == 'on') {
-                    compose_file += `\n      - ${data[`label_${i}_name`]}=${data[`label_${i}_value`]}`
-                }
-            }
-
-            // Add privileged mode 
-
-            if (data.privileged == 'on') {
-                compose_file += `\n    privileged: true`
-            }
-
-
-            // Add hardware acceleration to the docker-compose file if one of the environment variables has the label DRINODE
-            if (env0 == 'on' || env1 == 'on' || env2 == 'on' || env3 == 'on' || env4 == 'on' || env5 == 'on' || env6 == 'on' || env7 == 'on' || env8 == 'on' || env9 == 'on' || env10 == 'on' || env11 == 'on') {
-                for (let i = 0; i < 12; i++) {
-                    if (data[`env${i}`] == 'on') {
-                        if (data[`env_${i}_name`] == 'DRINODE') {
-                            compose_file += `\n    deploy:`
-                            compose_file += `\n      resources:`
-                            compose_file += `\n        reservations:`
-                            compose_file += `\n          devices:`
-                            compose_file += `\n          - driver: nvidia`
-                            compose_file += `\n            count: 1`
-                            compose_file += `\n            capabilities: [gpu]`
-                        }
-                    }
-                }
-            }
-
-    
-            // add any docker volumes to the docker-compose file
-            if ( docker_volumes.length > 0 ) {
-                compose_file += `\n`
-                compose_file += `\nvolumes:`
-
-                // check docker_volumes for duplicates and remove them completely
-                docker_volumes = docker_volumes.filter((item, index) => docker_volumes.indexOf(item) === index)
-
-                for (let i = 0; i < docker_volumes.length; i++) {
-                    if ( docker_volumes[i] != '') {
-                        compose_file += `\n  ${docker_volumes[i]}:`
-                    }
-                }
-            }
-
-            try {   
-                mkdirSync(`./appdata/${name}`, { recursive: true });
-                writeFileSync(`./appdata/${name}/docker-compose.yml`, compose_file, function (err) { console.log(err) });
-
-            } catch { console.log('error creating directory or compose file') }
-
-            try {
-                var compose = new DockerodeCompose(docker, `./appdata/${name}/docker-compose.yml`, `${name}`);
-
-                (async () => {
-                await compose.pull();
-                await compose.up();
-                })();
-
-            } catch { console.log('error running compose file')}
-
-        }
-
-
-}
-
-
-
-module.exports.uninstall = async function (data) {
-    
-
-        if (data.confirm == 'Yes') {
-
-
-            var containerName = docker.getContainer(`${data.service_name}`);
-
-            try {
-                    containerName.stop(function (err, data) {
-                        if (data) {
-                            containerName.remove(function (err, data) {
-                            });
-                        }
-                    });
-                } catch { 
-                    containerName.remove(function (err, data) {
-                    });
-                }
-
-        }
-
-   
-}

+ 0 - 194
functions/package_manager.js

@@ -1,194 +0,0 @@
-const { writeFileSync, mkdirSync, readFileSync } = require("fs");
-const yaml = require('js-yaml');
-
-const { execSync } = require("child_process");
-
-const { docker } = require('./system');
-
-var DockerodeCompose = require('dockerode-compose');
-
-
-module.exports.install = async function (data) {
-    
-        console.log(`[Start of install function]`);
-
-        let { service_name, name, image, command_check, command, net_mode, restart_policy } = data;        
-        let { port0, port1, port2, port3, port4, port5 } = data;
-        let { volume0, volume1, volume2, volume3, volume4, volume5 } = data;
-        let { env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11 } = data;
-        let { label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11 } = data;
-
-        let docker_volumes = [];
-
-        if (image.startsWith('https://')){
-            mkdirSync(`./appdata/${name}`, { recursive: true });
-            execSync(`curl -o ./appdata/${name}/${name}_stack.yml -L ${image}`);
-            console.log(`Downloaded stackfile: ${image}`);
-            let stackfile = yaml.load(readFileSync(`./appdata/${name}/${name}_stack.yml`, 'utf8'));
-            let services = Object.keys(stackfile.services);
-
-            for ( let i = 0; i < services.length; i++ ) {
-                try {
-                    console.log(stackfile.services[Object.keys(stackfile.services)[i]].environment);
-                } catch { console.log('no env') }
-            }
-            
-        } else {
-
-            let compose_file = `version: '3'`;
-                compose_file += `\nservices:`
-                compose_file += `\n  ${service_name}:`
-                compose_file += `\n    container_name: ${name}`;
-                compose_file += `\n    image: ${image}`;
-
-            // Command
-            if (command_check == 'on') {
-                compose_file += `\n    command: ${command}`
-            }
-
-            // Network mode
-            if (net_mode == 'host') {
-                compose_file += `\n    network_mode: 'host'`
-            }
-            else if (net_mode != 'host' && net_mode != 'docker') {
-                compose_file += `\n    network_mode: '${net_mode}'`
-            }
-            
-            // Restart policy
-            if (restart_policy != '') {
-                compose_file += `\n    restart: ${restart_policy}`
-            }
-
-            // Ports
-            if ((port0 == 'on' || port1 == 'on' || port2 == 'on' || port3 == 'on' || port4 == 'on' || port5 == 'on') && (net_mode != 'host')) {
-                compose_file += `\n    ports:`
-
-                    for (let i = 0; i < 6; i++) {
-                        if (data[`port${i}`] == 'on') {
-                            compose_file += `\n      - ${data[`port_${i}_external`]}:${data[`port_${i}_internal`]}/${data[`port_${i}_protocol`]}`
-                        }
-                    }
-            }
-
-            // Volumes
-            if (volume0 == 'on' || volume1 == 'on' || volume2 == 'on' || volume3 == 'on' || volume4 == 'on' || volume5 == 'on') {
-                compose_file += `\n    volumes:`
-
-                for (let i = 0; i < 6; i++) {
-
-                    // if volume is on and neither bind or container is empty, it's a bind mount (ex /mnt/user/appdata/config:/config  )
-                    if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] != '') && (data[`volume_${i}_container`] != '')) {
-                        compose_file += `\n      - ${data[`volume_${i}_bind`]}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
-                    }
-
-                    // if bind is empty create a docker volume (ex container_name_config:/config) convert any '/' in container name to '_'
-                    else if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] == '') && (data[`volume_${i}_container`] != '')) {
-                        let volume_name = data[`volume_${i}_container`].replace(/\//g, '_');
-                        compose_file += `\n      - ${name}_${volume_name}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
-                        docker_volumes.push(`${name}_${volume_name}`);
-                    } 
-                }
-            }
-
-            // Environment variables
-            if (env0 == 'on' || env1 == 'on' || env2 == 'on' || env3 == 'on' || env4 == 'on' || env5 == 'on' || env6 == 'on' || env7 == 'on' || env8 == 'on' || env9 == 'on' || env10 == 'on' || env11 == 'on') {
-                compose_file += `\n    environment:`
-            }
-            for (let i = 0; i < 12; i++) {
-                if (data[`env${i}`] == 'on') {
-                    compose_file += `\n      - ${data[`env_${i}_name`]}=${data[`env_${i}_default`]}`
-
-                }
-            }
-
-            // Add labels
-            if (label0 == 'on' || label1 == 'on' || label2 == 'on' || label3 == 'on' || label4 == 'on' || label5 == 'on' || label6 == 'on' || label7 == 'on' || label8 == 'on' || label9 == 'on' || label10 == 'on' || label11 == 'on') {
-                compose_file += `\n    labels:`
-            }   
-            for (let i = 0; i < 12; i++) {
-                if (data[`label${i}`] == 'on') {
-                    compose_file += `\n      - ${data[`label_${i}_name`]}=${data[`label_${i}_value`]}`
-                }
-            }
-
-            // Add privileged mode 
-
-            if (data.privileged == 'on') {
-                compose_file += `\n    privileged: true`
-            }
-
-
-            // Add hardware acceleration to the docker-compose file if one of the environment variables has the label DRINODE
-            if (env0 == 'on' || env1 == 'on' || env2 == 'on' || env3 == 'on' || env4 == 'on' || env5 == 'on' || env6 == 'on' || env7 == 'on' || env8 == 'on' || env9 == 'on' || env10 == 'on' || env11 == 'on') {
-                for (let i = 0; i < 12; i++) {
-                    if (data[`env${i}`] == 'on') {
-                        if (data[`env_${i}_name`] == 'DRINODE') {
-                            compose_file += `\n    deploy:`
-                            compose_file += `\n      resources:`
-                            compose_file += `\n        reservations:`
-                            compose_file += `\n          devices:`
-                            compose_file += `\n          - driver: nvidia`
-                            compose_file += `\n            count: 1`
-                            compose_file += `\n            capabilities: [gpu]`
-                        }
-                    }
-                }
-            }
-
-    
-            // add any docker volumes to the docker-compose file
-            if ( docker_volumes.length > 0 ) {
-                compose_file += `\n`
-                compose_file += `\nvolumes:`
-
-                // check docker_volumes for duplicates and remove them completely
-                docker_volumes = docker_volumes.filter((item, index) => docker_volumes.indexOf(item) === index)
-
-                for (let i = 0; i < docker_volumes.length; i++) {
-                    if ( docker_volumes[i] != '') {
-                        compose_file += `\n  ${docker_volumes[i]}:`
-                    }
-                }
-            }
-
-            try {   
-                mkdirSync(`./appdata/${name}`, { recursive: true });
-                writeFileSync(`./appdata/${name}/docker-compose.yml`, compose_file, function (err) { console.log(err) });
-
-            } catch { console.log('error creating directory or compose file') }
-
-            try {
-                var compose = new DockerodeCompose(docker, `./appdata/${name}/docker-compose.yml`, `${name}`);
-
-                (async () => {
-                await compose.pull();
-                await compose.up();
-                })();
-
-            } catch { console.log('error running compose file')}
-
-        }
-
-
-}
-
-
-
-module.exports.uninstall = async function (data) {
-    if (data.confirm == 'Yes') {
-        console.log(`Uninstalling ${data.service_name}: ${data}`);
-        var containerName = docker.getContainer(`${data.service_name}`);
-        try {
-            await containerName.stop();
-            console.log(`Stopped ${data.service_name} container`);
-        } catch {
-            console.log(`Error stopping ${data.service_name} container`);
-        }
-        try {
-            await containerName.remove();
-            console.log(`Removed ${data.service_name} container`);
-        } catch {
-            console.log(`Error removing ${data.service_name} container`);
-        }
-    }
-}

+ 0 - 382
functions/system.js

@@ -1,382 +0,0 @@
-const { currentLoad, mem, networkStats, fsSize, dockerContainerStats, dockerImages, networkInterfaces, dockerInfo } = require('systeminformation');
-var Docker = require('dockerode');
-var docker = new Docker({ socketPath: '/var/run/docker.sock' });
-const { dashCard } = require('../components/dashCard');
-const { Readable } = require('stream');
-
-const Containers = require('../database/ContainerModel');
-
-// export docker
-module.exports.docker = docker;
-
-
-let IPv4 = '';
-networkInterfaces().then(data => {
-    IPv4 = data[0].ip4;
-});
-
-let hidden = '';
-module.exports.hiddenContainers = async function () {
-    hidden = await Containers.findAll({ where: {visibility:false}});
-    hidden = hidden.map(a => a.name);
-}
-
-module.exports.serverStats = async function () {
-    const cpuUsage = await currentLoad();
-    const ramUsage = await mem();
-    const netUsage = await networkStats();
-    const diskUsage = await fsSize();
-
-    const info = {
-        cpu: Math.round(cpuUsage.currentLoad),
-        ram: Math.round((ramUsage.active / ramUsage.total) * 100),
-        tx: netUsage[0].tx_bytes,
-        rx: netUsage[0].rx_bytes,
-        disk: diskUsage[0].use,
-    };
-
-    return info;
-}
-
-
-
-module.exports.containerList = async function () {
-    let card_list = '';
-
-    const data = await docker.listContainers({ all: true });
-    for (const container of data) {
-
-
-        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();
-
-            // Get ports //////////////////////////
-            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 { 
-                // console.log('no ports') 
-                }
-
-            for (let i = 0; i < 12; i++) {
-                if (ports_list[i] == undefined) {
-                    let ports = {
-                        check : '',
-                        external: '',
-                        internal: '',
-                        protocol: ''
-                    }
-                    ports_list[i] = ports;
-                }
-            } /////////////////////////////////////
-
-
-            // Get volumes ////////////////////////
-            let volumes_list = [];
-            try { for (const [key, value] of Object.entries(containerInfo.HostConfig.Binds)) {
-                    let volumes = {
-                        check : 'checked',
-                        bind: value.split(':')[0],
-                        container: value.split(':')[1],
-                        readwrite: value.split(':')[2]
-                    }
-                    volumes_list.push(volumes);
-                }} catch { 
-                    // console.log('no volumes') 
-                    }
-            for (let i = 0; i < 12; i++) {
-                if (volumes_list[i] == undefined) {
-                    let volumes = {
-                        check : '',
-                        bind: '',
-                        container: '',
-                        readwrite: ''
-                    }
-                    volumes_list[i] = volumes;
-                }
-            } /////////////////////////////////////
-
-
-            // Get environment variables.
-            let environment_variables = [];
-            try { for (const [key, value] of Object.entries(containerInfo.Config.Env)) {
-                let env = {
-                    check : 'checked',
-                    name: value.split('=')[0],
-                    default: value.split('=')[1]
-                }
-                environment_variables.push(env);
-            }} catch { console.log('no env') }
-            for (let i = 0; i < 12; i++) {
-                if (environment_variables[i] == undefined) {
-                    let env = {
-                        check : '',
-                        name: '',
-                        default: ''
-                    }
-                    environment_variables[i] = env;
-                }
-            }
-
-            // Get labels.
-            let labels = [];
-            for (const [key, value] of Object.entries(containerInfo.Config.Labels)) {
-                let label = {
-                    check : 'checked',
-                    name: key,
-                    value: value
-                }
-                labels.push(label);
-            }
-            for (let i = 0; i < 12; i++) {
-                if (labels[i] == undefined) {
-                    let label = {
-                        check : '',
-                        name: '',
-                        value: ''
-                    }
-                    labels[i] = label;
-                }
-            }
-
-
-            let container_info = {
-                name: container.Names[0].slice(1),
-                service: service,
-                id: container.Id,
-                state: container.State,
-                image: container.Image,
-                external_port: ports_list[0].external || 0,
-                internal_port: ports_list[0].internal || 0, 
-                ports: ports_list,
-                volumes: volumes_list,
-                environment_variables: environment_variables,
-                labels: labels,
-                IPv4: IPv4,
-                style: "Compact"
-            }
-
-            let dockerCard = dashCard(container_info);
-
-            card_list += dockerCard;
-        }
-        
-    }
-
-    return card_list;
-}
-
-
-
-
-
-
-
-module.exports.containerStats = async function () {
-
-    let container_stats = [];
-    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);
-            
-            let container_stat = {
-                name: container.Names[0].slice(1),
-                cpu: Math.round(stats[0].cpuPercent),
-                ram: Math.round(stats[0].memPercent)
-            }
-            
-            //push stats to an array
-            container_stats.push(container_stat);
-        }
-    }
-    return container_stats;
-}
-
-
-
-
-
-
-module.exports.containerAction = async function (data) {
-
-    let { user, role, action, container, state } = data;
-
-    console.log(`${user} wants to: ${action} ${container}`);
-    
-    if (role == 'admin') {
-        var containerName = docker.getContainer(container);
-
-        if ((action == 'start') && (state == 'stopped')) {
-            containerName.start();
-        } else if ((action == 'start') && (state == 'paused')) {
-            containerName.unpause();
-        } else if ((action == 'stop') && (state != 'stopped')) {
-            containerName.stop();
-        } else if ((action == 'pause') && (state == 'running')) {
-            containerName.pause();
-        } else if ((action == 'pause') && (state == 'paused')) {
-            containerName.unpause();
-        } else if (action == 'restart') {
-            containerName.restart();
-        }
-    } else {
-        console.log('User is not an admin');
-    }
-}
-
-
-
-module.exports.containerExec = async function (data) {
-
-    let { container, command } = data;
-
-    var containerName = docker.getContainer(container);
-
-    var options = {
-        Cmd: ['/bin/sh', '-c', command],
-        AttachStdout: true,
-        AttachStderr: true,
-        Tty: true
-    };
-
-    containerName.exec(options, function (err, exec) {
-        if (err) return;
-
-        exec.start(function (err, stream) {
-            if (err) return;
-
-            containerName.modem.demuxStream(stream, process.stdout, process.stderr);
-
-            exec.inspect(function (err, data) {
-                if (err) return;
-
-              
-            });
-        });
-    });
-    
-}
-
-
-
-
-
-
-
-
-
-
-module.exports.containerLogs = function (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);
-            });
-        });
-    });
-};
-
-
-
-module.exports.dockerImages = async function () {
-
-    // get the names, tags, status, created date, and size of all images
- 
-    const data1 = await dockerImages({ all: true });
-
-    const data2 = await docker.listImages({ all: true });
-
-    // for ( i = 0; i < data.length; i++) {
-    //     console.log(`Image ${i}:`)
-    //     console.log(`repoTags: ${data[i].repoTags}`)
-    // }
-    
-    // console.log(data1);
-
-    console.log(data2);
-
-}
-
-
-module.exports.dockerVolumes = async function () {
-    let volume_list = '';
-
-    const data = await docker.listVolumes();
-
-    return data;
-
-    // for (const volume of data.Volumes) {
-
-    //     let volume_info = {
-    //         name: volume.Name,
-    //         style: "Compact"
-    //     }
-
-    //     let dockerCard = dashCard(volume_info);
-
-    //     volume_list += dockerCard;
-    // }
-
-    // return volume_list;
-}
-
-
-module.exports.dockerNetworks = async function () {
-    let network_list = '';
-
-    const data = await docker.listNetworks();
-
-    return data;
-
-    // for (const network of data) {
-
-    //     let network_info = {
-    //         name: network.Name,
-    //         style: "Compact"
-    //     }
-
-    //     let dockerCard = dashCard(network_info);
-
-    //     network_list += dockerCard;
-    // }
-
-    // return network_list;
-}

文件差异内容过多而无法显示
+ 875 - 17
package-lock.json


+ 19 - 9
package.json

@@ -1,25 +1,35 @@
 {
   "name": "dweebui",
   "version": "1.0.0",
+  "description": "A web UI for Docker",
   "main": "app.js",
+  "type": "module",
+  "scripts": {
+    "test": "mocha --require @babel/register --exit"
+  },
   "keywords": [],
   "author": "",
   "license": "ISC",
   "dependencies": {
-    "bcrypt": "^5.1.0",
-    "child_process": "^1.0.2",
+    "@babel/register": "^7.23.7",
+    "@journeyapps/sqlcipher": "^5.3.1",
+    "@socket.io/admin-ui": "^0.5.1",
+    "bcrypt": "^5.1.1",
+    "chai": "^5.0.0",
     "compression": "^1.7.4",
-    "dockerode": "^4.0.0",
-    "dockerode-compose": "^1.4.0",
+    "cors": "^2.8.5",
+    "dockerode": "^4.0.1",
     "ejs": "^3.1.9",
     "express": "^4.18.2",
     "express-session": "^1.17.3",
     "helmet": "^7.1.0",
-    "js-yaml": "^4.1.0",
+    "mocha": "^10.2.0",
     "sequelize": "^6.35.2",
-    "socket.io": "^4.6.1",
+    "sinon": "^17.0.1",
+    "socket.io": "^4.7.2",
     "sqlite3": "^5.1.6",
-    "systeminformation": "^5.21.20"
-  },
-  "description": ""
+    "stream": "^0.0.2",
+    "supertest": "^6.3.3",
+    "systeminformation": "^5.21.22"
+  }
 }

+ 1 - 1
public/css/meters.css

@@ -2,7 +2,7 @@
 
 .meter {
     box-sizing: content-box;
-    height: 15px; /* Can be anything */
+    height: 15px;
     margin-left: auto;
     margin-right: auto;
     position: relative;

+ 0 - 72
public/css/tabler.min.css

@@ -11462,47 +11462,38 @@ fieldset:disabled .btn {
 }
 
 .column-gap-0 {
-  -moz-column-gap: 0 !important;
   column-gap: 0 !important
 }
 
 .column-gap-1 {
-  -moz-column-gap: .25rem !important;
   column-gap: .25rem !important
 }
 
 .column-gap-2 {
-  -moz-column-gap: .5rem !important;
   column-gap: .5rem !important
 }
 
 .column-gap-3 {
-  -moz-column-gap: 1rem !important;
   column-gap: 1rem !important
 }
 
 .column-gap-4 {
-  -moz-column-gap: 1.5rem !important;
   column-gap: 1.5rem !important
 }
 
 .column-gap-5 {
-  -moz-column-gap: 2rem !important;
   column-gap: 2rem !important
 }
 
 .column-gap-6 {
-  -moz-column-gap: 3rem !important;
   column-gap: 3rem !important
 }
 
 .column-gap-7 {
-  -moz-column-gap: 5rem !important;
   column-gap: 5rem !important
 }
 
 .column-gap-8 {
-  -moz-column-gap: 8rem !important;
   column-gap: 8rem !important
 }
 
@@ -12910,17 +12901,14 @@ fieldset:disabled .btn {
 }
 
 .columns-2 {
-  -moz-columns: 2 !important;
   columns: 2 !important
 }
 
 .columns-3 {
-  -moz-columns: 3 !important;
   columns: 3 !important
 }
 
 .columns-4 {
-  -moz-columns: 4 !important;
   columns: 4 !important
 }
 
@@ -13821,47 +13809,38 @@ fieldset:disabled .btn {
   }
 
   .column-gap-sm-0 {
-    -moz-column-gap: 0 !important;
     column-gap: 0 !important
   }
 
   .column-gap-sm-1 {
-    -moz-column-gap: .25rem !important;
     column-gap: .25rem !important
   }
 
   .column-gap-sm-2 {
-    -moz-column-gap: .5rem !important;
     column-gap: .5rem !important
   }
 
   .column-gap-sm-3 {
-    -moz-column-gap: 1rem !important;
     column-gap: 1rem !important
   }
 
   .column-gap-sm-4 {
-    -moz-column-gap: 1.5rem !important;
     column-gap: 1.5rem !important
   }
 
   .column-gap-sm-5 {
-    -moz-column-gap: 2rem !important;
     column-gap: 2rem !important
   }
 
   .column-gap-sm-6 {
-    -moz-column-gap: 3rem !important;
     column-gap: 3rem !important
   }
 
   .column-gap-sm-7 {
-    -moz-column-gap: 5rem !important;
     column-gap: 5rem !important
   }
 
   .column-gap-sm-8 {
-    -moz-column-gap: 8rem !important;
     column-gap: 8rem !important
   }
 
@@ -13878,17 +13857,14 @@ fieldset:disabled .btn {
   }
 
   .columns-sm-2 {
-    -moz-columns: 2 !important;
     columns: 2 !important
   }
 
   .columns-sm-3 {
-    -moz-columns: 3 !important;
     columns: 3 !important
   }
 
   .columns-sm-4 {
-    -moz-columns: 4 !important;
     columns: 4 !important
   }
 }
@@ -14790,47 +14766,38 @@ fieldset:disabled .btn {
   }
 
   .column-gap-md-0 {
-    -moz-column-gap: 0 !important;
     column-gap: 0 !important
   }
 
   .column-gap-md-1 {
-    -moz-column-gap: .25rem !important;
     column-gap: .25rem !important
   }
 
   .column-gap-md-2 {
-    -moz-column-gap: .5rem !important;
     column-gap: .5rem !important
   }
 
   .column-gap-md-3 {
-    -moz-column-gap: 1rem !important;
     column-gap: 1rem !important
   }
 
   .column-gap-md-4 {
-    -moz-column-gap: 1.5rem !important;
     column-gap: 1.5rem !important
   }
 
   .column-gap-md-5 {
-    -moz-column-gap: 2rem !important;
     column-gap: 2rem !important
   }
 
   .column-gap-md-6 {
-    -moz-column-gap: 3rem !important;
     column-gap: 3rem !important
   }
 
   .column-gap-md-7 {
-    -moz-column-gap: 5rem !important;
     column-gap: 5rem !important
   }
 
   .column-gap-md-8 {
-    -moz-column-gap: 8rem !important;
     column-gap: 8rem !important
   }
 
@@ -14847,17 +14814,14 @@ fieldset:disabled .btn {
   }
 
   .columns-md-2 {
-    -moz-columns: 2 !important;
     columns: 2 !important
   }
 
   .columns-md-3 {
-    -moz-columns: 3 !important;
     columns: 3 !important
   }
 
   .columns-md-4 {
-    -moz-columns: 4 !important;
     columns: 4 !important
   }
 }
@@ -15759,47 +15723,38 @@ fieldset:disabled .btn {
   }
 
   .column-gap-lg-0 {
-    -moz-column-gap: 0 !important;
     column-gap: 0 !important
   }
 
   .column-gap-lg-1 {
-    -moz-column-gap: .25rem !important;
     column-gap: .25rem !important
   }
 
   .column-gap-lg-2 {
-    -moz-column-gap: .5rem !important;
     column-gap: .5rem !important
   }
 
   .column-gap-lg-3 {
-    -moz-column-gap: 1rem !important;
     column-gap: 1rem !important
   }
 
   .column-gap-lg-4 {
-    -moz-column-gap: 1.5rem !important;
     column-gap: 1.5rem !important
   }
 
   .column-gap-lg-5 {
-    -moz-column-gap: 2rem !important;
     column-gap: 2rem !important
   }
 
   .column-gap-lg-6 {
-    -moz-column-gap: 3rem !important;
     column-gap: 3rem !important
   }
 
   .column-gap-lg-7 {
-    -moz-column-gap: 5rem !important;
     column-gap: 5rem !important
   }
 
   .column-gap-lg-8 {
-    -moz-column-gap: 8rem !important;
     column-gap: 8rem !important
   }
 
@@ -15816,17 +15771,14 @@ fieldset:disabled .btn {
   }
 
   .columns-lg-2 {
-    -moz-columns: 2 !important;
     columns: 2 !important
   }
 
   .columns-lg-3 {
-    -moz-columns: 3 !important;
     columns: 3 !important
   }
 
   .columns-lg-4 {
-    -moz-columns: 4 !important;
     columns: 4 !important
   }
 }
@@ -16728,47 +16680,38 @@ fieldset:disabled .btn {
   }
 
   .column-gap-xl-0 {
-    -moz-column-gap: 0 !important;
     column-gap: 0 !important
   }
 
   .column-gap-xl-1 {
-    -moz-column-gap: .25rem !important;
     column-gap: .25rem !important
   }
 
   .column-gap-xl-2 {
-    -moz-column-gap: .5rem !important;
     column-gap: .5rem !important
   }
 
   .column-gap-xl-3 {
-    -moz-column-gap: 1rem !important;
     column-gap: 1rem !important
   }
 
   .column-gap-xl-4 {
-    -moz-column-gap: 1.5rem !important;
     column-gap: 1.5rem !important
   }
 
   .column-gap-xl-5 {
-    -moz-column-gap: 2rem !important;
     column-gap: 2rem !important
   }
 
   .column-gap-xl-6 {
-    -moz-column-gap: 3rem !important;
     column-gap: 3rem !important
   }
 
   .column-gap-xl-7 {
-    -moz-column-gap: 5rem !important;
     column-gap: 5rem !important
   }
 
   .column-gap-xl-8 {
-    -moz-column-gap: 8rem !important;
     column-gap: 8rem !important
   }
 
@@ -16785,17 +16728,14 @@ fieldset:disabled .btn {
   }
 
   .columns-xl-2 {
-    -moz-columns: 2 !important;
     columns: 2 !important
   }
 
   .columns-xl-3 {
-    -moz-columns: 3 !important;
     columns: 3 !important
   }
 
   .columns-xl-4 {
-    -moz-columns: 4 !important;
     columns: 4 !important
   }
 }
@@ -17697,47 +17637,38 @@ fieldset:disabled .btn {
   }
 
   .column-gap-xxl-0 {
-    -moz-column-gap: 0 !important;
     column-gap: 0 !important
   }
 
   .column-gap-xxl-1 {
-    -moz-column-gap: .25rem !important;
     column-gap: .25rem !important
   }
 
   .column-gap-xxl-2 {
-    -moz-column-gap: .5rem !important;
     column-gap: .5rem !important
   }
 
   .column-gap-xxl-3 {
-    -moz-column-gap: 1rem !important;
     column-gap: 1rem !important
   }
 
   .column-gap-xxl-4 {
-    -moz-column-gap: 1.5rem !important;
     column-gap: 1.5rem !important
   }
 
   .column-gap-xxl-5 {
-    -moz-column-gap: 2rem !important;
     column-gap: 2rem !important
   }
 
   .column-gap-xxl-6 {
-    -moz-column-gap: 3rem !important;
     column-gap: 3rem !important
   }
 
   .column-gap-xxl-7 {
-    -moz-column-gap: 5rem !important;
     column-gap: 5rem !important
   }
 
   .column-gap-xxl-8 {
-    -moz-column-gap: 8rem !important;
     column-gap: 8rem !important
   }
 
@@ -17754,17 +17685,14 @@ fieldset:disabled .btn {
   }
 
   .columns-xxl-2 {
-    -moz-columns: 2 !important;
     columns: 2 !important
   }
 
   .columns-xxl-3 {
-    -moz-columns: 3 !important;
     columns: 3 !important
   }
 
   .columns-xxl-4 {
-    -moz-columns: 4 !important;
     columns: 4 !important
   }
 }

二进制
public/fonts/Inter-Black.woff2


二进制
public/fonts/Inter-BlackItalic.woff2


二进制
public/fonts/Inter-Bold.woff2


二进制
public/fonts/Inter-BoldItalic.woff2


二进制
public/fonts/Inter-ExtraBold.woff2


二进制
public/fonts/Inter-ExtraBoldItalic.woff2


二进制
public/fonts/Inter-ExtraLight.woff2


二进制
public/fonts/Inter-ExtraLightItalic.woff2


二进制
public/fonts/Inter-Italic.woff2


二进制
public/fonts/Inter-Light.woff2


二进制
public/fonts/Inter-LightItalic.woff2


二进制
public/fonts/Inter-Medium.woff2


二进制
public/fonts/Inter-MediumItalic.woff2


二进制
public/fonts/Inter-Regular.woff2


二进制
public/fonts/Inter-SemiBold.woff2


二进制
public/fonts/Inter-SemiBoldItalic.woff2


二进制
public/fonts/Inter-Thin.woff2


二进制
public/fonts/Inter-ThinItalic.woff2


二进制
public/fonts/InterDisplay-Black.woff2


二进制
public/fonts/InterDisplay-BlackItalic.woff2


二进制
public/fonts/InterDisplay-Bold.woff2


二进制
public/fonts/InterDisplay-BoldItalic.woff2


二进制
public/fonts/InterDisplay-ExtraBold.woff2


二进制
public/fonts/InterDisplay-ExtraBoldItalic.woff2


二进制
public/fonts/InterDisplay-ExtraLight.woff2


二进制
public/fonts/InterDisplay-ExtraLightItalic.woff2


二进制
public/fonts/InterDisplay-Italic.woff2


二进制
public/fonts/InterDisplay-Light.woff2


二进制
public/fonts/InterDisplay-LightItalic.woff2


二进制
public/fonts/InterDisplay-Medium.woff2


二进制
public/fonts/InterDisplay-MediumItalic.woff2


二进制
public/fonts/InterDisplay-Regular.woff2


二进制
public/fonts/InterDisplay-SemiBold.woff2


二进制
public/fonts/InterDisplay-SemiBoldItalic.woff2


二进制
public/fonts/InterDisplay-Thin.woff2


二进制
public/fonts/InterDisplay-ThinItalic.woff2


二进制
public/fonts/InterVariable-Italic.woff2


二进制
public/fonts/InterVariable.woff2


+ 59 - 0
public/fonts/inter.css

@@ -0,0 +1,59 @@
+/* Variable fonts usage:
+:root { font-family: "Inter", sans-serif; }
+@supports (font-variation-settings: normal) {
+  :root { font-family: "InterVariable", sans-serif; font-optical-sizing: auto; }
+} */
+@font-face {
+  font-family: InterVariable;
+  font-style: normal;
+  font-weight: 100 900;
+  font-display: swap;
+  src: url('InterVariable.woff2?v=4.0') format('woff2');
+}
+@font-face {
+  font-family: InterVariable;
+  font-style: italic;
+  font-weight: 100 900;
+  font-display: swap;
+  src: url('InterVariable-Italic.woff2?v=4.0') format('woff2');
+}
+/* legacy name "Inter var" (Oct 2023) */
+@font-face { font-family:'Inter var'; font-style:normal; font-weight:100 900; font-display:swap; src: url('InterVariable.woff2?v=4.0') format('woff2'); }
+@font-face { font-family:'Inter var'; font-style:italic; font-weight:100 900; font-display:swap; src: url('InterVariable-Italic.woff2?v=4.0') format('woff2'); }
+/* static fonts */
+@font-face { font-family:Inter; font-style:normal; font-weight:100; font-display:swap; src:url("Inter-Thin.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:italic; font-weight:100; font-display:swap; src:url("Inter-ThinItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:normal; font-weight:200; font-display:swap; src:url("Inter-ExtraLight.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:italic; font-weight:200; font-display:swap; src:url("Inter-ExtraLightItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:normal; font-weight:300; font-display:swap; src:url("Inter-Light.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:italic; font-weight:300; font-display:swap; src:url("Inter-LightItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:normal; font-weight:400; font-display:swap; src:url("Inter-Regular.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:italic; font-weight:400; font-display:swap; src:url("Inter-Italic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:normal; font-weight:500; font-display:swap; src:url("Inter-Medium.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:italic; font-weight:500; font-display:swap; src:url("Inter-MediumItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:normal; font-weight:600; font-display:swap; src:url("Inter-SemiBold.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:italic; font-weight:600; font-display:swap; src:url("Inter-SemiBoldItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:normal; font-weight:700; font-display:swap; src:url("Inter-Bold.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:italic; font-weight:700; font-display:swap; src:url("Inter-BoldItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:normal; font-weight:800; font-display:swap; src:url("Inter-ExtraBold.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:italic; font-weight:800; font-display:swap; src:url("Inter-ExtraBoldItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:normal; font-weight:900; font-display:swap; src:url("Inter-Black.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:Inter; font-style:italic; font-weight:900; font-display:swap; src:url("Inter-BlackItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:normal; font-weight:100; font-display:swap; src:url("InterDisplay-Thin.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:italic; font-weight:100; font-display:swap; src:url("InterDisplay-ThinItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:normal; font-weight:200; font-display:swap; src:url("InterDisplay-ExtraLight.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:italic; font-weight:200; font-display:swap; src:url("InterDisplay-ExtraLightItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:normal; font-weight:300; font-display:swap; src:url("InterDisplay-Light.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:italic; font-weight:300; font-display:swap; src:url("InterDisplay-LightItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:normal; font-weight:400; font-display:swap; src:url("InterDisplay-Regular.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:italic; font-weight:400; font-display:swap; src:url("InterDisplay-Italic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:normal; font-weight:500; font-display:swap; src:url("InterDisplay-Medium.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:italic; font-weight:500; font-display:swap; src:url("InterDisplay-MediumItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:normal; font-weight:600; font-display:swap; src:url("InterDisplay-SemiBold.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:italic; font-weight:600; font-display:swap; src:url("InterDisplay-SemiBoldItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:normal; font-weight:700; font-display:swap; src:url("InterDisplay-Bold.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:italic; font-weight:700; font-display:swap; src:url("InterDisplay-BoldItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:normal; font-weight:800; font-display:swap; src:url("InterDisplay-ExtraBold.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:italic; font-weight:800; font-display:swap; src:url("InterDisplay-ExtraBoldItalic.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:normal; font-weight:900; font-display:swap; src:url("InterDisplay-Black.woff2?v=4.0") format("woff2"); }
+@font-face { font-family:InterDisplay; font-style:italic; font-weight:900; font-display:swap; src:url("InterDisplay-BlackItalic.woff2?v=4.0") format("woff2"); }

+ 0 - 0
public/static/avatars/burns.jpg → public/img/avatars/burns.jpg


二进制
public/img/avatars/duffman.png


+ 0 - 0
public/static/avatars/frank.jpg → public/img/avatars/frank.jpg


+ 0 - 0
public/static/avatars/moe.jpg → public/img/avatars/moe.jpg


二进制
public/img/avatars/moleman.png


+ 0 - 0
public/static/avatars/poochie.jpg → public/img/avatars/poochie.jpg


+ 0 - 0
public/static/avatars/rus.jpg → public/img/avatars/rus.jpg


+ 0 - 0
public/static/avatars/skinner.jpg → public/img/avatars/skinner.jpg


+ 1 - 1
public/js/demo-theme.js

@@ -12,7 +12,7 @@
 })((function () { 'use strict';
 
 	var themeStorageKey = "tablerTheme";
-	var defaultTheme = "light";
+	var defaultTheme = "dark";
 	var selectedTheme;
 	var params = new Proxy(new URLSearchParams(window.location.search), {
 	  get: function get(searchParams, prop) {

+ 100 - 155
public/js/main.js

@@ -1,16 +1,6 @@
-// SOCKET IO
-const socket = io({
-  auth: {
-    token: "abc"
-  }
-});
+socket.on('connect', () => { console.log('connected'); });
 
-// ON CONNECT EVENT
-socket.on('connect', () => {
-    console.log('Connected');
-});
-
-// SELECT METRICS ELEMENTS
+// Server metrics
 const cpuText = document.getElementById('cpu-text');
 const cpuBar = document.getElementById('cpu-bar');
 const ramText = document.getElementById('ram-text');
@@ -20,19 +10,22 @@ const netBar = document.getElementById('net-bar');
 const diskText = document.getElementById('disk-text');
 const diskBar = document.getElementById('disk-bar');
 
+// Container cards
 const dockerCards = document.getElementById('cards');
 
+// Container logs
 const logViewer = document.getElementById('logView');
 
-//Update usage bars
+// Server metrics
 socket.on('metrics', (data) => {
-
-    let {cpu, ram, tx, rx, disk} = data;
+    let [cpu, ram, tx, rx, disk] = data;
 
     cpuText.innerHTML = `<span>CPU ${cpu} %</span>`;
+    if (cpu < 7 ) { cpu = 7; }
     cpuBar.innerHTML = `<span style="width: ${cpu}%"><span></span></span>`;
     
     ramText.innerHTML = `<span>RAM ${ram} %</span>`;
+    if (ram < 7 ) { ram = 7; }
     ramBar.innerHTML = `<span style="width: ${ram}%"><span></span></span>`;
 
     tx = Math.round(tx / 1024 / 1024);
@@ -42,165 +35,117 @@ socket.on('metrics', (data) => {
     netBar.innerHTML = `<span style="width: 50%"><span></span></span>`;
 
     diskText.innerHTML = `<span>DISK ${disk} %</span>`;
+    if (disk < 7 ) { disk = 7; }
     diskBar.innerHTML = `<span style="width: ${disk}%"><span></span></span>`;
 });
 
-function drawCharts(name, cpu_array, ram_array) {
-  var elements = document.querySelectorAll(`${name}`);
-
-  Array.from(elements).forEach(function(element) {
-    if (window.ApexCharts) {
-      new ApexCharts(element, {
-        chart: {
-          type: "line",
-          fontFamily: 'inherit',
-          height: 40.0,
-          sparkline: {
-            enabled: true
-          },
-          animations: {
-            enabled: false
-          }
-        },
-        fill: {
-          opacity: 1
-        },
-        stroke: {
-          width: [2, 1],
-          dashArray: [0, 3],
-          lineCap: "round",
-          curve: "smooth"
-        },
-        series: [{
-          name: "CPU",
-          data: cpu_array
-        }, {
-          name: "RAM",
-          data: ram_array
-        }],
-        tooltip: {
-          theme: 'dark'
-        },
-        grid: {
-          strokeDashArray: 4
-        },
-        xaxis: {
-          labels: {
-            padding: 0
-          },
-          tooltip: {
-            enabled: false
-          },
-          type: 'datetime'
-        },
-        yaxis: {
-          labels: {
-            padding: 4
-          }
-        },
-        labels: [
-          '2020-06-20', '2020-06-21', '2020-06-22', '2020-06-23', '2020-06-24', '2020-06-25', '2020-06-26', '2020-06-27', '2020-06-28', '2020-06-29', '2020-06-30', '2020-07-01', '2020-07-02', '2020-07-03', '2020-07-04', '2020-07-05', '2020-07-06', '2020-07-07', '2020-07-08', '2020-07-09', '2020-07-10', '2020-07-11', '2020-07-12', '2020-07-13', '2020-07-14', '2020-07-15', '2020-07-16', '2020-07-17', '2020-07-18', '2020-07-19'
-        ],
-        colors: [tabler.getColor("primary"), tabler.getColor("gray-600")],
-        legend: {
-          show: false
-        }
-      }).render();
-    }
-  });
-}
-
-// container button actions
-function buttonAction(button) {
-  socket.emit('clicked', {container: button.name, state: button.id, action: button.value});
-}
+// Container cards
+socket.on('containers', (data) => {
+    let deleteMeElements = document.querySelectorAll('.deleteme');
+    deleteMeElements.forEach((element) => {
+      element.parentNode.removeChild(element);
+    });
+    dockerCards.insertAdjacentHTML("afterend", data);
+});
 
 
-function hideContainer(button) {
-  socket.emit('hide', {container: button.name});
+function drawCharts(name, cpuArray, ramArray) {
+  let element = document.querySelector(`${name}`);
+
+  let chart = new ApexCharts(element, {
+    chart: {
+      type: "line",
+      height: 40.0,
+      sparkline: {
+        enabled: true
+      },
+      animations: {
+        enabled: false
+      }
+    },
+    fill: {
+      opacity: 1
+    },
+    stroke: {
+      width: [2, 1],
+      dashArray: [0, 3],
+      lineCap: "round",
+      curve: "smooth"
+    },
+    series: [{
+      name: "CPU",
+      data: cpuArray
+    }, {
+      name: "RAM",
+      data: ramArray
+    }],
+    tooltip: {
+      enabled: false
+    },
+    grid: {
+      strokeDashArray: 4
+    },
+    xaxis: {
+      labels: {
+        padding: 0
+      },
+      tooltip: {
+        enabled: false
+      }
+    },
+    yaxis: {
+      labels: {
+        padding: 4
+      }
+    },
+    colors: [tabler.getColor("primary"), tabler.getColor("gray-600")],
+    legend: {
+      show: false
+    }
+  })
+  chart.render();
 }
 
-function resetView() {
-  socket.emit('reset');
+// Buttons functions
+function clicked(button) {
+  socket.emit('clicked', {name: button.name, id: button.id, value: button.value});
 }
 
-let containerLogs;
-
-function viewLogs(button) {
-
-  if (button.name != 'refresh') {
-    containerLogs = button.name;
-  }
 
+socket.on('containerStats', (data) => {
+  let containerStats = data;
+  
+  for (const [name, statsArray] of Object.entries(containerStats)) {
 
-  socket.emit('logs', {container: containerLogs});
-}
-
-socket.on('logString', (data) => {
-  logViewer.innerHTML = `<pre>${data}</pre>`;
-});
-
-
-
-socket.on('cards', (data) => {
-
-  console.log('cards deleted');
-  let deleteMeElements = document.querySelectorAll('.deleteme');
-  deleteMeElements.forEach((element) => {
-    element.parentNode.removeChild(element);
-  });
- 
-  dockerCards.insertAdjacentHTML("afterend", data);
+    let cpuArray = statsArray.cpuArray;
+    let ramArray = statsArray.ramArray;
 
-  // check localStorage for items ending with _cpu and redraw the charts
-  for (let i = 0; i < localStorage.length; i++) {
-    if (localStorage.key(i).endsWith('_cpu')) {
-      let name = localStorage.key(i).split('_')[0];
-      let cpu_array = JSON.parse(localStorage.getItem(`${name}_cpu`));
-      let ram_array = JSON.parse(localStorage.getItem(`${name}_ram`));
-      drawCharts(`#${name}_chart`, cpu_array, ram_array);
+    let chart = document.getElementById(`${name}_chart`);
+    if (chart) {
+      chart.innerHTML = '';
+      drawCharts(`#${name}_chart`, cpuArray, ramArray);
+    } else {
+      console.log(`Chart element with id ${name}_chart not found in the DOM`);
     }
   }
-  
-  
-});
 
+});
 
-socket.on('containerStats', (data) => {
-
-  let {name, cpu, ram} = data;
 
-  console.log(`drawing chart for ${name}`)
 
-  var cpu_array = JSON.parse(localStorage.getItem(`${name}_cpu`));
-  var ram_array = JSON.parse(localStorage.getItem(`${name}_ram`));
+// socket.on('install', (data) => {
+//   dockerCards.insertAdjacentHTML("afterend", data);
+// });
 
-  if (cpu_array == null) { cpu_array = Array(30).fill(0); }
-  if (ram_array == null) { ram_array = Array(30).fill(0); }
+// let containerLogs;
 
-  cpu_array.push(cpu);
-  ram_array.push(ram);
-  
-  cpu_array = cpu_array.slice(-30);
-  ram_array = ram_array.slice(-30);
-
-  localStorage.setItem(`${name}_cpu`, JSON.stringify(cpu_array));
-  localStorage.setItem(`${name}_ram`, JSON.stringify(ram_array));
-
-  // replace the old chart with the new one
-  let chart = document.getElementById(`${name}_chart`);
-  if (chart) {
-    let newChart = document.createElement('div');
-    newChart.id = `${name}_chart`;
-    chart.parentNode.replaceChild(newChart, chart);
-    drawCharts(`#${name}_chart`, cpu_array, ram_array);
-  } else {
-    console.log(`Chart element with id ${name}_chart not found in the DOM`);
-  }
-});
+// function viewLogs(button) {
+//   if (button.name != 'refresh') {
+//     containerLogs = button.name;
+//   }
+//   socket.emit('logs', {container: containerLogs});
+// }
 
-socket.on('install', (data) => {
-  
-  console.log('added install card');
-  dockerCards.insertAdjacentHTML("afterend", data);
+socket.on('logs', (data) => {
+  logViewer.innerHTML = `<pre>${data}</pre>`;
 });

文件差异内容过多而无法显示
+ 0 - 0
public/static/logo-dark.svg


文件差异内容过多而无法显示
+ 0 - 0
public/static/logo-sm-black.svg


文件差异内容过多而无法显示
+ 0 - 0
public/static/logo-sm-white.svg


+ 0 - 3
public/static/logo-small-white.svg

@@ -1,3 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 68">
-  <path d="M64.6 16.2C63 9.9 58.1 5 51.8 3.4 40 1.5 28 1.5 16.2 3.4 9.9 5 5 9.9 3.4 16.2 1.5 28 1.5 40 3.4 51.8 5 58.1 9.9 63 16.2 64.6c11.8 1.9 23.8 1.9 35.6 0C58.1 63 63 58.1 64.6 51.8c1.9-11.8 1.9-23.8 0-35.6zM33.3 36.3c-2.8 4.4-6.6 8.2-11.1 11-1.5.9-3.3.9-4.8.1s-2.4-2.3-2.5-4c0-1.7.9-3.3 2.4-4.1 2.3-1.4 4.4-3.2 6.1-5.3-1.8-2.1-3.8-3.8-6.1-5.3-2.3-1.3-3-4.2-1.7-6.4s4.3-2.9 6.5-1.6c4.5 2.8 8.2 6.5 11.1 10.9 1 1.4 1 3.3.1 4.7zM49.2 46H37.8c-2.1 0-3.8-1-3.8-3s1.7-3 3.8-3h11.4c2.1 0 3.8 1 3.8 3s-1.7 3-3.8 3z" fill="#ffffff"/>
-</svg>

文件差异内容过多而无法显示
+ 0 - 0
public/static/logo-small.svg


文件差异内容过多而无法显示
+ 0 - 2
public/static/logo-white.svg


文件差异内容过多而无法显示
+ 0 - 0
public/static/logo.svg


+ 60 - 0
router/index.js

@@ -0,0 +1,60 @@
+import express from "express";
+import { io } from "../app.js";
+
+import { Login, submitLogin } from "../controllers/login.js";
+import { Register, submitRegister } from "../controllers/register.js";
+import { Dashboard, searchDashboard } from "../controllers/dashboard.js";
+import { Apps } from "../controllers/apps.js";
+import { Users } from "../controllers/users.js";
+import { Images } from "../controllers/images.js";
+import { Account } from "../controllers/account.js";
+import { Settings } from "../controllers/settings.js";
+import { Networks } from "../controllers/networks.js";
+import { Volumes } from "../controllers/volumes.js";
+import { Syslogs } from "../controllers/syslogs.js"; 
+
+export const router = express.Router();
+
+const auth = (req, res, next) => {
+    if (req.session.role == "admin") {
+        next();
+    } else {
+        res.redirect("/login");
+    }
+};
+
+
+router.get("/login", Login);
+router.post("/login", submitLogin);
+
+router.get("/register", Register);
+router.post("/register", submitRegister);  
+
+router.get("/", auth, Dashboard);
+router.post("/", auth, searchDashboard);
+router.post("/:search", auth, searchDashboard);
+
+router.get("/apps", auth, Apps);
+router.get("/apps/:page", auth, Apps);
+
+router.get("/users", auth, Users);
+
+router.get("/images", auth, Images);
+
+router.get("/networks", auth, Networks);
+
+router.get("/volumes", auth, Volumes);
+
+router.get("/syslogs", auth, Syslogs);
+
+router.get("/account", Account);
+
+router.get("/settings", auth, Settings);
+  
+router.get("/logout", (req, res) => {
+    const sessionId = req.session.id;
+    req.session.destroy(() => {
+        io.to(sessionId).disconnectSockets();
+        res.redirect("/login");
+    });
+});

+ 0 - 59
routes/index.js

@@ -1,59 +0,0 @@
-const express = require("express");
-const router = express.Router();
-
-const { Dashboard, AddSite, RemoveSite, RefreshSites, DisableSite, EnableSite } = require("../controllers/dashboard");
-const { Login, processLogin, Logout, Register, processRegister } = require("../controllers/auth");
-const { Apps, searchApps, Install, Uninstall } = require("../controllers/apps");
-
-const { Images } = require("../controllers/images");
-const { Volumes } = require("../controllers/volumes");
-const { Networks } = require("../controllers/networks");
-
-const { Users } = require("../controllers/users");
-const { Account } = require("../controllers/account");
-const { Settings } = require("../controllers/settings");
-
-// Authentication middleware
-const authenticate = (req, res, next) => {
-    if (req.session && req.session.role == "admin") {
-        console.log(`User ${req.session.user} [${req.session.role}] accessed ${req.originalUrl}`)
-        next();
-    } else {
-        console.log('Not admin')
-        res.redirect("/login");
-    }
-};
-
-// Dashboard
-router.get("/", authenticate, Dashboard);
-router.post("/addsite", authenticate, AddSite);
-router.post("/removesite", authenticate, RemoveSite);
-router.get("/refreshsites", authenticate, RefreshSites);
-router.post("/disablesite", authenticate, DisableSite);
-router.post("/enablesite", authenticate, EnableSite);
-
-router.get("/images", authenticate, Images);
-
-// Auth
-router.get("/login", Login);
-router.post("/login", processLogin);
-router.get("/register", Register);
-router.post("/register", processRegister);
-router.get("/logout", Logout);
-
-// Apps page
-router.get("/apps", authenticate, Apps);
-router.get("/apps/:page", authenticate, Apps);
-router.get("/apps/:template/:page", authenticate, Apps);
-router.post("/apps", authenticate, searchApps);
-
-// Settings page
-router.get("/settings", authenticate, Settings);
-router.get("/account", authenticate, Account);
-
-router.post("/install", authenticate, Install);
-router.post("/uninstall", authenticate, Uninstall);
-
-router.get("/users", authenticate, Users);
-
-module.exports = router;

二进制
screenshots/account.png


二进制
screenshots/apps.png


二进制
screenshots/dashboard.png


二进制
screenshots/logs.png


二进制
screenshots/register.png


+ 0 - 70
setup.sh

@@ -1,70 +0,0 @@
-#!/bin/bash
-
-# To demo DweebUI, run this script on a fresh Debian 12.2 install. This script will open port 443/tcp for Reverse Proxy and 22/tcp for SSH.
-
-# Manual Install:
-# cd DweebUI
-# chmod +x setup.sh
-# sudo ./setup.sh
-
-# Install dependencies
-apt-get install -y curl unzip ufw gnupg ca-certificates lsb-release gpg
-
-# Enable firewall
-ufw allow ssh && ufw --force enable
-
-# Opens port 443/tcp for Reverse Proxy
-ufw allow https
-
-# Install Docker
-install -m 0755 -d /etc/apt/keyrings
-curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
-chmod a+r /etc/apt/keyrings/docker.gpg
-
-echo \
-  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
-  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
-  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
-sudo apt-get update -y
-sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
-
-# Create docker network
-docker network create -d bridge AppBridge 
-
-# Create a redis docker container with persistent storage and a password
-docker run -d --name DweebCache --restart unless-stopped -v /home/docker/redis:/data -p 6379:6379 redis redis-server --requirepass "somesupersecretpassword"
-
-
-# Install redis
-# curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
-# echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
-# apt-get update -y
-# apt-get install -y redis
-# systemctl enable --now redis-server
-
-# Install nodejs
-mkdir -p /etc/apt/keyrings
-curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
-NODE_MAJOR=20
-echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
-sudo apt-get update
-sudo apt-get install nodejs -y
-
-npm i
-
-# Prep for caddy
-mkdir -p /home/docker/caddy/sites
-echo "import sites/*" > /home/docker/caddy/Caddyfile.tmp
-mv /home/docker/caddy/Caddyfile.tmp /home/docker/caddy/Caddyfile
-
-
-# Install pm2 and start DweebUI
-npm install pm2 -g
-pm2 start app.js --name "dweebui"
-pm2 log
-
-
-# Creates a 'docker-compose' alias, since the command changed to 'docker compose' in Debian 11.
-echo '#!/bin/sh
-docker compose "$@"' > /usr/local/bin/docker-compose
-chmod +x /usr/local/bin/docker-compose

部分文件因为文件数量过多而无法显示