浏览代码

Portal page for regular users. Fix apps.js submit.

lllllllillllllillll 1 年之前
父节点
当前提交
6d8a919d18
共有 13 个文件被更改,包括 392 次插入14 次删除
  1. 3 0
      app.js
  2. 1 1
      components/appCard.js
  3. 7 3
      controllers/apps.js
  4. 12 0
      controllers/portal.js
  5. 27 0
      database/models.js
  6. 213 0
      functions/install.js
  7. 28 0
      functions/uninstall.js
  8. 28 0
      package-lock.json
  9. 2 0
      package.json
  10. 16 5
      router/index.js
  11. 1 1
      views/apps.ejs
  12. 4 4
      views/navbar.ejs
  13. 50 0
      views/portal.ejs

+ 3 - 0
app.js

@@ -212,14 +212,17 @@ io.on('connection', (socket) => {
         
         // Start intervals if not already started
         if (!metricsInterval) {
+            serverMetrics();
             metricsInterval = setInterval(serverMetrics, 1000);
             console.log('Metrics interval started');
         }
         if (!cardsInterval) {
+            containerCards();
             cardsInterval = setInterval(containerCards, 1000);
             console.log('Cards interval started');
         }
         if (!graphsInterval) {
+            containerStats();
             graphsInterval = setInterval(containerStats, 1000);
             console.log('Graphs interval started');
         }

+ 1 - 1
components/appCard.js

@@ -219,7 +219,7 @@ export const appCard = (data) => {
   <div class="col-md-6 col-lg-3">
     <div class="card">
       <div class="card-body p-4 text-center">
-        <span class="avatar avatar-xlplus mb-3 rounded"><img src='${data.logo}' width="144px" height="144px" loading="lazy"></img></span>
+        <span class="avatar avatar-xlplus mb-3 rounded"><img src='${data.logo}' width="144px" height="144px" loading="lazy"/></span>
         <h3 class="m-0 mb-1"><a href="#">${shortened_name}</a></h3>
         <div class="text-secondary">${shortened_desc}</div>
         <div class="mt-3">

+ 7 - 3
controllers/apps.js

@@ -11,6 +11,8 @@ templates = templates.sort((a, b) => {
 });
 
 export const Apps = (req, res) => {
+    // console.log(req.body);
+    
     let page = Number(req.params.page) || 1;
     let list_start = (page-1)*28;
     let list_end = (page*28);
@@ -50,6 +52,7 @@ export const Apps = (req, res) => {
 
 export const searchApps = async (req, res) => {
 
+    // console.log(req.body);
     let page = Number(req.query.page) || 1;
     let list_start = (page - 1) * 28;
     let list_end = (page * 28);
@@ -67,13 +70,14 @@ export const searchApps = async (req, res) => {
     let apps_list = '';
     let search_results = [];
 
+    console.log(req.body);
     let search = req.body.search;
 
     // split value of search into an array of words
     search = search.split(' ');
-    try {console.log(search[0]);} catch (error) {}
-    try {console.log(search[1]);} catch (error) {}
-    try {console.log(search[2]);} catch (error) {}
+    // try {console.log(search[0]);} catch (error) {}
+    // try {console.log(search[1]);} catch (error) {}
+    // try {console.log(search[2]);} catch (error) {}
 
     function searchTemplates(word) {
 

+ 12 - 0
controllers/portal.js

@@ -0,0 +1,12 @@
+
+export const Portal = (req, res) => {
+
+
+    res.render("portal", {
+        name: req.session.user,
+        role: req.session.role,
+        avatar: req.session.avatar,
+    });
+
+}
+

+ 27 - 0
database/models.js

@@ -159,4 +159,31 @@ export const Syslog = sequelize.define('Syslog', {
   ip : {
     type: DataTypes.STRING
   },
+});
+
+
+export const Notification = sequelize.define('Notification', {
+  id: {
+    type: DataTypes.INTEGER,
+    autoIncrement: true,
+    primaryKey: true
+  },
+  title: {
+    type: DataTypes.STRING
+  },
+  message: {
+    type: DataTypes.STRING
+  },
+  icon: {
+    type: DataTypes.STRING,
+  },
+  color: {
+    type: DataTypes.STRING,
+  },
+  createdAt : {
+    type: DataTypes.STRING
+  },
+  createdBy : {
+    type: DataTypes.STRING
+  },
 });

+ 213 - 0
functions/install.js

@@ -0,0 +1,213 @@
+import { writeFileSync, mkdirSync, readFileSync } from "fs";
+import yaml from 'js-yaml';
+import { execSync } from "child_process";
+import { docker } from "../app.js";
+import DockerodeCompose from "dockerode-compose";
+
+
+
+export const Install = async (req, res) => {
+
+    console.log('getInstall:')
+    console.log(req.body);
+
+    res.render("/apps", {
+        name: req.session.user,
+        role: req.session.role,
+        avatar: req.session.avatar,
+        list_start: 0,
+        list_end: 28,
+        app_count: 0,
+        prev: 0,
+        next: 0,
+        apps_list: 0,
+    });
+
+}
+
+
+
+// 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`);
+//         }
+//     }
+// }

+ 28 - 0
functions/uninstall.js

@@ -0,0 +1,28 @@
+import { writeFileSync, mkdirSync, readFileSync } from "fs";
+import yaml from 'js-yaml';
+import { execSync } from "child_process";
+import { docker } from "../app.js";
+import DockerodeCompose from "dockerode-compose";
+
+
+
+export const Uninstall = async (req, res) => {
+
+    console.log('Uninstall')
+    console.log(req.body);
+
+    res.render("/apps", {
+        name: req.session.user,
+        role: req.session.role,
+        avatar: req.session.avatar,
+        list_start: 0,
+        list_end: 28,
+        app_count: 0,
+        prev: 0,
+        next: 0,
+        apps_list: 0,
+    });
+
+}
+
+

+ 28 - 0
package-lock.json

@@ -17,11 +17,13 @@
         "compression": "^1.7.4",
         "cors": "^2.8.5",
         "dockerode": "^4.0.1",
+        "dockerode-compose": "^1.4.0",
         "ejs": "^3.1.9",
         "express": "^4.18.2",
         "express-rate-limit": "^7.1.5",
         "express-session": "^1.17.3",
         "helmet": "^7.1.0",
+        "js-yaml": "^4.1.0",
         "mocha": "^10.2.0",
         "sequelize": "^6.35.2",
         "sinon": "^17.0.1",
@@ -1672,6 +1674,32 @@
         "node": ">= 8.0"
       }
     },
+    "node_modules/dockerode-compose": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/dockerode-compose/-/dockerode-compose-1.4.0.tgz",
+      "integrity": "sha512-6x5ZlK06H+cgoTR4ffucqN5kWVvxNvxwTLcHQUZcegCJBEDGrdzXMOEGDMsxbHwiLtLo2dNwG0eZK7B2RfEWSw==",
+      "dependencies": {
+        "dockerode": "^4.0.0",
+        "js-yaml": "^4.0.0",
+        "tar-fs": "^2.1.1"
+      }
+    },
+    "node_modules/dockerode-compose/node_modules/chownr": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
+    },
+    "node_modules/dockerode-compose/node_modules/tar-fs": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
+      "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
+      "dependencies": {
+        "chownr": "^1.1.1",
+        "mkdirp-classic": "^0.5.2",
+        "pump": "^3.0.0",
+        "tar-stream": "^2.1.4"
+      }
+    },
     "node_modules/dottie": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz",

+ 2 - 0
package.json

@@ -19,11 +19,13 @@
     "compression": "^1.7.4",
     "cors": "^2.8.5",
     "dockerode": "^4.0.1",
+    "dockerode-compose": "^1.4.0",
     "ejs": "^3.1.9",
     "express": "^4.18.2",
     "express-rate-limit": "^7.1.5",
     "express-session": "^1.17.3",
     "helmet": "^7.1.0",
+    "js-yaml": "^4.1.0",
     "mocha": "^10.2.0",
     "sequelize": "^6.35.2",
     "sinon": "^17.0.1",

+ 16 - 5
router/index.js

@@ -1,6 +1,7 @@
 import express from "express";
 import { io } from "../app.js";
 
+// Controllers
 import { Login, submitLogin } from "../controllers/login.js";
 import { Register, submitRegister } from "../controllers/register.js";
 import { Dashboard, searchDashboard } from "../controllers/dashboard.js";
@@ -11,19 +12,24 @@ 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"; 
+import { Syslogs } from "../controllers/syslogs.js";
+import { Portal } from "../controllers/portal.js"
 
 export const router = express.Router();
 
+/// Functions
+import { Install } from "../functions/install.js"
+import { Uninstall } from "../functions/uninstall.js"
+
+// Auth middleware
 const auth = (req, res, next) => {
     if (req.session.role == "admin") {
         next();
     } else {
-        res.redirect("/login");
+        res.redirect("/portal");
     }
 };
 
-
 router.get("/login", Login);
 router.post("/login", submitLogin);
 
@@ -32,7 +38,8 @@ router.post("/register", submitRegister);
 
 router.get("/", auth, Dashboard);
 router.post("/", auth, searchDashboard);
-router.post("/:search", auth, searchDashboard);
+
+router.get("/portal", auth, Portal)
 
 router.get("/apps", auth, Apps);
 router.get("/apps/:page", auth, Apps);
@@ -57,4 +64,8 @@ router.get("/logout", (req, res) => {
         io.to(sessionId).disconnectSockets();
         res.redirect("/login");
     });
-});
+});
+
+
+router.post("/install", auth, Install);
+router.post("/uninstall", auth, Uninstall);

+ 1 - 1
views/apps.ejs

@@ -43,7 +43,7 @@
                   <form action="/apps" id="search" name="search" method="POST">
                     <input type="search" class="form-control" name="search" placeholder="Search apps…">
                   </form>
-                  &nbsp;<input type="submit" form="search" class="btn btn-outline-success h-50" value="search">
+                  <input type="submit" form="search" class="btn btn-outline-success h-50" value="search">
 
                   <div class="card-actions btn-actions">
                     <div class="card-actions btn-actions">

+ 4 - 4
views/navbar.ejs

@@ -255,17 +255,17 @@
           </li>
 
           <li class="nav-item">
-            <a class="nav-link" href="#">
+            <a class="nav-link" href="/portal">
               <span
                 class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
-                <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-tool" 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="M7 10h3v-3l-3.5 -3.5a6 6 0 0 1 8 8l6 6a2 2 0 0 1 -3 3l-6 -6a6 6 0 0 1 -8 -8l3.5 3.5"></path> </svg>              </span>
+                <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-star" 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 d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>              </span>
               <span class="nav-link-title">
-                Tools
+                Portal
               </span>
             </a>
           </li>
-          
 
+          
         </ul>
 
         <div class="my-2 my-md-0 flex-grow-1 flex-md-grow-0 order-first order-md-last">

+ 50 - 0
views/portal.ejs

@@ -0,0 +1,50 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
+    <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
+    <title>DweebUI - Dashboard</title>
+    <!-- CSS files -->
+    <link href="css/tabler.min.css" rel="stylesheet"/>
+    <link href="css/meters.css" rel="stylesheet"/>
+    <style>
+      @import url('fonts/inter.css');
+      :root {
+        --tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
+      }
+      body {
+        font-feature-settings: "cv03", "cv04", "cv11";
+      }
+    </style>
+  </head>
+  <body >
+  <div class="page">
+
+    <%- include('navbar.ejs') %>
+    <div class="page-wrapper">
+
+      <div class="page-body">
+        <div class="container-xl">
+          <div class="row row-deck row-cards">
+            
+            
+            
+            
+          </div>
+        </div>
+      </div>
+      
+      <%- include('footer.ejs') %>
+      
+    </div>
+  </div>
+    
+
+  <!-- Libs JS -->
+  <script src="libs/apexcharts/dist/apexcharts.min.js" defer></script>
+  <!-- Tabler Core -->
+  <script src="js/tabler.min.js"></script>
+  </body>
+</html>
+