Browse Source

refactoring dashboard.js / updated compose file

lllllllillllllillll 1 year ago
parent
commit
e78afb90ca
6 changed files with 103 additions and 190 deletions
  1. 92 178
      controllers/dashboard.js
  2. 1 1
      docker-compose.yaml
  3. 3 2
      router/index.js
  4. 1 3
      views/apps.html
  5. 2 2
      views/dashboard.html
  6. 4 4
      views/partials/containerCard.html

+ 92 - 178
controllers/dashboard.js

@@ -7,7 +7,6 @@ import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
 
 const permissionsModal = readFileSync('./views/modals/permissions.html', 'utf8');
 const uninstallModal = readFileSync('./views/modals/uninstall.html', 'utf8');
-const detailsModal = readFileSync('./views/modals/details.html', 'utf8');
 
 
 // The actual page
@@ -21,7 +20,6 @@ export const Dashboard = (req, res) => {
 
 // Server metrics (CPU, RAM, TX, RX, DISK)
 let [ cpu, ram, tx, rx, disk, stats ] = [0, 0, 0, 0, 0, {}];
-
 let serverMetrics = setInterval(async () => {
     currentLoad().then(data => { 
         cpu = Math.round(data.currentLoad); 
@@ -38,7 +36,6 @@ let serverMetrics = setInterval(async () => {
     });
 }, 1000);
 
-
 export const Stats = async (req, res) => {
     let name = req.header('hx-trigger-name');
     let color = req.header('hx-trigger');
@@ -64,13 +61,73 @@ export const Stats = async (req, res) => {
     res.send(info);
 }
 
+async function containerInfo (containerName) {
+    let container = docker.getContainer(containerName);
+    let info = await container.inspect();
+    let image = info.Config.Image.split('/');
+    let ports_list = [];
+    try {
+        for (const [key, value] of Object.entries(info.HostConfig.PortBindings)) {
+            let ports = {
+                check: 'checked',
+                external: value[0].HostPort,
+                internal: key.split('/')[0],
+                protocol: key.split('/')[1]
+            }
+            ports_list.push(ports);
+        }
+    } catch {
+        // no exposed ports
+    }
+    let details = {
+        name: containerName,
+        image: image,
+        service: image[image.length - 1].split(':')[0],
+        state: info.State.Status,
+        external_port: ports_list[0]?.external || 0,
+        internal_port: ports_list[0]?.internal || 0,
+        ports: ports_list,
+        link: 'localhost',
+    }
+    return details;
+}
 
-let [ hidden, cardList, eventType, containersArray, sentArray ] = [ '', '', '', [], [] ];
-
+async function createCard (details) {
+    if (hidden.includes(details.name)) { return;}
+    let shortname = details.name.slice(0, 10) + '...';
+    let trigger = 'data-hx-trigger="load, every 3s"';
+    let state = details.state;
+    let state_color = '';
+    switch (state) {
+        case 'running':
+            state_color = 'green';
+            break;
+        case 'exited':
+            state = 'stopped';
+            state_color = 'red';
+            trigger = 'data-hx-trigger="load"';
+            break;
+        case 'paused':
+            state_color = 'orange';
+            trigger = 'data-hx-trigger="load"';
+            break;
+    }
+    // if (name.startsWith('dweebui')) { disable = 'disabled=""'; }
+    let card  = readFileSync('./views/partials/containerCard.html', 'utf8');
+    card = card.replace(/AppName/g, details.name);
+    card = card.replace(/AppShortName/g, shortname);
+    card = card.replace(/AppIcon/g, details.service);
+    card = card.replace(/AppState/g, state);
+    card = card.replace(/StateColor/g, state_color);
+    card = card.replace(/ChartName/g, details.name.replace(/-/g, ''));
+    card = card.replace(/AppNameState/g, `${details.name}State`);
+    card = card.replace(/data-trigger=""/, trigger);
+    return card;
+}
 
+// HTMX server-side events
+let [ hidden, cardList, newCards, containersArray, sentArray, updatesArray ] = [ '', '', '', [], [], [] ];
 
-let cardUpdates = [];
-let newCards = '';
 export const SSE = (req, res) => {
     res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
 
@@ -80,45 +137,49 @@ export const SSE = (req, res) => {
         await docker.listContainers({ all: true }).then(containers => {
             containers.forEach(container => {
                 let name = container.Names[0].replace('/', '');
-                if (hidden.includes(name)) {
-                    // do nothing
-                } else {
+                if (!hidden.includes(name)) { // if not hidden
                     containersArray.push({ container: name, state: container.State });
-                }
-
+                } 
             });
         });
 
         if ((JSON.stringify(containersArray) !== JSON.stringify(sentArray))) {
             cardList = '';
             newCards = '';
-            cardUpdates = [];
             containersArray.forEach(container => {
                 const { container: containerName, state } = container;
                 const existingContainer = sentArray.find(c => c.container === containerName);
                 if (!existingContainer) {
-                    addCard(containerName, 'newCards');
+                    containerInfo(containerName).then(details => {
+                        createCard(details).then(card => {
+                            newCards += card;
+                        });
+                    });
                     res.write(`event: update\n`);
                     res.write(`data: 'update cards'\n\n`);
                 } else if (existingContainer.state !== state) {
-                    cardUpdates.push(containerName);
+                    updatesArray.push(containerName);
                 }
-                addCard(containerName, 'cardList');
+                containerInfo(containerName).then(details => {
+                    createCard(details).then(card => {
+                        cardList += card;
+                    });
+                });
             });
 
             sentArray.forEach(container => {
                 const { container: containerName } = container;
                 const existingContainer = containersArray.find(c => c.container === containerName);
                 if (!existingContainer) {
-                    cardUpdates.push(containerName);
+                    updatesArray.push(containerName);
                 }
             });
 
-            for (let i = 0; i < cardUpdates.length; i++) {
-                res.write(`event: ${cardUpdates[i]}\n`);
+            for (let i = 0; i < updatesArray.length; i++) {
+                res.write(`event: ${updatesArray[i]}\n`);
                 res.write(`data: 'update cards'\n\n`);
             }
-
+            updatesArray = [];
             sentArray = containersArray.slice();
         }
 
@@ -161,143 +222,21 @@ export const Installs = async (req, res) => {
 }
 
 
-async function containerInfo (containerName) {
-
-    let card  = readFileSync('./views/partials/containerCard.html', 'utf8');
-    let container = docker.getContainer(containerName);
-    let info = await container.inspect();
-   
-    let name = containerName;
-    let state = info.State.Status;
-    let state_color = '';
-    switch (state) {
-        case 'running':
-            state_color = 'green';
-            break;
-        case 'exited':
-            state_color = 'red';
-            state = 'stopped';
-            break;
-        case 'paused':
-            state_color = 'orange';
-            break;
-    }
-    
-    let wrapped = name;
-    if (name.length > 13) { wrapped = name.slice(0, 10) + '...'; }
-    if (name.startsWith('dweebui')) { disable = 'disabled=""'; }
-    let disable = "";
-    let chartName = name.replace(/-/g, '');
-
-    let trigger = 'data-hx-trigger="load, every 3s"';
-    if (state != 'running') {  trigger = 'data-hx-trigger="load"'; }
-
-    let imageVersion = info.Config.Image.split('/');
-    let service = imageVersion[imageVersion.length - 1].split(':')[0];
-
-    let ports_list = [];
-    try {
-        for (const [key, value] of Object.entries(info.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;
-        
-    card = card.replace(/AppName/g, name);
-    card = card.replace(/AppShortName/g, wrapped);
-    card = card.replace(/AppIcon/g, service);
-    card = card.replace(/AppState/g, state);
-    card = card.replace(/StateColor/g, state_color);
-    card = card.replace(/ChartName/g, chartName);
-    card = card.replace(/data-trigger=""/, trigger);
-    if (hidden.includes(name)) { card = ''; }
-
-    return card;
-}
-
-
-async function addCard(container, list) {
-    let card  = readFileSync('./views/partials/containerCard.html', 'utf8');
-    let containerId = docker.getContainer(container);
-    containerId.inspect().then(data => {
-
-        let state = data.State.Status;
-        let wrapped = container;
-        let disable = "";
-        let chartName = container.replace(/-/g, '');
-        
-        // shorten long names
-        if (container.length > 13) { wrapped = container.slice(0, 10) + '...'; }
-        // disable buttons for dweebui
-        if (container.startsWith('dweebui')) { disable = 'disabled=""'; }
-        
-        // if ( external_port == undefined ) { external_port = 0; }
-        // if ( internal_port == undefined ) { internal_port = 0; }
-        
-        let state_color = 'green';
-        if (state == 'exited') {
-            state = 'stopped';
-            state_color = 'red';
-        } else if (state == 'paused') {
-            state_color = 'orange';
-        }
-        
-        let trigger = 'data-hx-trigger="load, every 3s"';
-        if (state != 'running') {  trigger = 'data-hx-trigger="load"'; }
-
-        let imageVersion = data.Config.Image.split('/');
-        let service = imageVersion[imageVersion.length - 1].split(':')[0];
-        let ports_list = [];
-        try {
-        for (const [key, value] of Object.entries(data.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;
-        card = card.replace(/AppName/g, container);
-        card = card.replace(/AppShortName/g, wrapped);
-        card = card.replace(/AppIcon/g, service);
-        card = card.replace(/AppState/g, state);
-        card = card.replace(/StateColor/g, state_color);
-        card = card.replace(/ChartName/g, chartName);
-        card = card.replace(/AppNameState/g, `${container}State`);
-        card = card.replace(/data-trigger=""/, trigger);
-        if (list == 'newCards') {
-            newCards += card;
-        } else if (list == 'cardList'){
-            cardList += card;
-        }
-    });   
-}
-
 export const updateCards = async (req, res) => {
     console.log('updateCards called');
     res.send(newCards);
 }
 
 
-
-
-
+export const Containers = async (req, res) => {
+    res.send(cardList);
+}
 
 export const Card = async (req, res) => {
     let name = req.header('hx-trigger-name');
     console.log(`Updated card for ${name}`);
-    let card = await containerInfo(name);
+    let details = await containerInfo(name);
+    let card = await createCard(details);
     res.send(card);
 }
 
@@ -398,7 +337,7 @@ export const Reset = async (req, res) => {
     res.send("ok");
 }
 
-export const Modal = async (req, res) => {
+export const Modals = async (req, res) => {
     let name = req.header('hx-trigger-name');
     let id = req.header('hx-trigger');
 
@@ -414,35 +353,10 @@ export const Modal = async (req, res) => {
         return;
     }
 
-    let containerId = docker.getContainer(name);
-    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;
-
-    let container_info = {
-        name: containerInfo.Name.slice(1),
-        state: containerInfo.State.Status,
-        image: containerInfo.Config.Image,
-        external_port: external_port,
-        internal_port: internal_port,
-        ports: ports_list,
-        link: 'localhost',
-    }
+    let modal = readFileSync('./views/modals/details.html', 'utf8');
+    let details = await containerInfo(name);
 
-    let details = detailsModal;
-    details = details.replace(/AppName/g, containerInfo.Name.slice(1));
-    details = details.replace(/AppImage/g, containerInfo.Config.Image);
-    res.send(details);
+    modal = modal.replace(/AppName/g, details.name);
+    modal = modal.replace(/AppImage/g, details.image);
+    res.send(modal);
 }

+ 1 - 1
docker-compose.yaml

@@ -7,7 +7,7 @@ services:
       PORT: 8000
       SECRET: MrWiskers
     restart: unless-stopped
-    ports:
+    expose:
       - 8000:8000
     volumes:
       - dweebui:/app

+ 3 - 2
router/index.js

@@ -4,7 +4,7 @@ export const router = express.Router();
 // Controllers
 import { Login, submitLogin, Logout } from "../controllers/login.js";
 import { Register, submitRegister } from "../controllers/register.js";
-import { Dashboard, Start, Stop, Pause, Restart, Logs, Modal, Stats, Hide, Reset, Chart, Installs, SSE, Card, updateCards } from "../controllers/dashboard.js";
+import { Dashboard, Start, Stop, Pause, Restart, Logs, Modals, Stats, Hide, Reset, Chart, Installs, SSE, Card, updateCards, Containers } from "../controllers/dashboard.js";
 import { Apps, appSearch } from "../controllers/apps.js";
 import { Users } from "../controllers/users.js";
 import { Images, removeImage } from "../controllers/images.js";
@@ -33,13 +33,14 @@ router.post("/stop", auth, Stop);
 router.post("/pause", auth, Pause);
 router.post("/restart", auth, Restart);
 router.get("/logs", auth, Logs);
-router.get ("/modal", auth, Modal);
+router.get ("/modals", auth, Modals);
 router.get("/stats", auth, Stats);
 router.post("/hide", auth, Hide);
 router.post("/reset", auth, Reset);
 router.get("/chart", auth, Chart);
 router.get("/installs", auth, Installs);
 router.get("/sse_event", auth, SSE);
+router.get("/containers", auth, Containers);
 router.get("/card", auth, Card);
 router.get("/new_cards", auth, updateCards);
 

+ 1 - 3
views/apps.html

@@ -30,9 +30,7 @@
           <div class="container-xl">
             <div class="row g-2 align-items-center">
               <div class="col">
-                <h2 class="page-title">
-                  Apps
-                </h2>
+                
                 <div class="text-secondary mt-1"><%= list_start %> - <%= list_end %> of <%= app_count %> Apps.</div>
               </div>
               <!-- Page title actions -->

+ 2 - 2
views/dashboard.html

@@ -142,13 +142,13 @@
             
             <!-- HTMX -->
             <div class="col-12">
-              <div class="row row-cards" id="containers">
+              <div class="row row-cards" id="containers" data-hx-get="/containers" data-hx-trigger="load" data-hx-swap="innerHTML">
               </div>
             </div>
 
             <!-- HTMX -->
             <div class="col-12">
-              <div class="row row-cards" data-hx-get="/new_cards" data-hx-trigger="load, sse:update" data-hx-swap="beforeend" hx-target="#containers">
+              <div class="row row-cards" data-hx-get="/new_cards" data-hx-trigger="sse:update" data-hx-swap="beforeend" hx-target="#containers">
               </div>
             </div>
             

+ 4 - 4
views/partials/containerCard.html

@@ -26,11 +26,11 @@
                     <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" name="AppName" id="details" data-hx-get="/modal" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
+                    <button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-get="/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
                     <button class="dropdown-item text-secondary" name="AppName" id="logs" data-hx-get="/logs" hx-swap="innerHTML" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
                     <button class="dropdown-item text-secondary" name="AppName" id="edit">Edit</button>
                     <button class="dropdown-item text-primary" name="AppName" id="update" disabled="">Update</button>
-                    <button class="dropdown-item text-danger" name="AppName" id="uninstall" hx-trigger="click" data-hx-get="/modal" hx-swap="innerHTML" data-bs-toggle="modal" data-hx-target="#modals-here" data-bs-target="#modals-here">Uninstall</button>
+                    <button class="dropdown-item text-danger" name="AppName" id="uninstall" hx-trigger="click" data-hx-get="/modals" hx-swap="innerHTML" data-bs-toggle="modal" data-hx-target="#modals-here" data-bs-target="#modals-here">Uninstall</button>
                   </div>
                 </div>
                 <div class="dropdown">
@@ -40,7 +40,7 @@
                   <div class="dropdown-menu dropdown-menu-end">
                     <button class="dropdown-item text-secondary" data-hx-post="/hide" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="hide" value="hide">Hide</button>
                     <button class="dropdown-item text-secondary" data-hx-post="/reset" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="reset" value="reset">Reset View</button>
-                    <button class="dropdown-item text-secondary" name="AppName" id="permissions" data-hx-get="/modal" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
+                    <button class="dropdown-item text-secondary" name="AppName" id="permissions" data-hx-get="/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
                   </div>
                 </div>
               </div>
@@ -48,7 +48,7 @@
           </div>
         </div>
         <div class="d-flex align-items-baseline">
-          <div class="h1 me-2" title="AppName" style="margin-bottom: 0;">
+          <div class="h1 me-2" title="AppName">
             <a href="http://${link}:${external_port}" target="_blank">
               AppShortName
             </a>