Browse Source

First draft of permissions system

lllllllillllllillll 1 year ago
parent
commit
15722b1687

+ 1 - 1
controllers/account.js

@@ -12,7 +12,7 @@ export const Account = async (req, res) => {
         id: user.id,
         id: user.id,
         email: user.email,
         email: user.email,
         role: user.role,
         role: user.role,
-        avatar: user.avatar,
+        avatar: req.session.user.charAt(0).toUpperCase(),
         alert: '',
         alert: '',
     });
     });
 
 

+ 81 - 7
controllers/apps.js

@@ -1,16 +1,18 @@
-import { readFileSync } from 'fs';
+import { readFileSync, readdirSync, renameSync, mkdirSync, unlinkSync } from 'fs';
 import multer from 'multer';
 import multer from 'multer';
 
 
+
 const upload = multer({storage: multer.diskStorage({
 const upload = multer({storage: multer.diskStorage({
   destination: function (req, file, cb) {
   destination: function (req, file, cb) {
-    cb(null, 'templates/')
+    cb(null, 'templates/tmp/')
+  },
+  filename: function (req, file, cb) {
+    cb(null, file.originalname)
   },
   },
-    filename: function (req, file, cb) {
-      cb(null, file.originalname)
-    }
   })
   })
 })
 })
 
 
+
 // load the default template then sort the templates by name
 // load the default template then sort the templates by name
 let templatesJSON = readFileSync('./templates/templates.json');
 let templatesJSON = readFileSync('./templates/templates.json');
 let templates = JSON.parse(templatesJSON).templates;
 let templates = JSON.parse(templatesJSON).templates;
@@ -22,8 +24,65 @@ templates = templates.sort((a, b) => {
 
 
 let alert = '';
 let alert = '';
 
 
+
+
 export const Apps = (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 prev = '/apps/' + (page-1);
+  let next = '/apps/' + (page+1);
+  if (page == 1) { prev = '/apps/' + (page); }
+  if (page == last_page) { next = '/apps/' + (page); }
 
 
+  let apps_list = '';
+  for (let i = list_start; i < list_end && i < templates.length; i++) {
+      let appCard = readFileSync('./views/partials/appCard.html', 'utf8');
+      let name = templates[i].name || templates[i].title.toLowerCase();
+      let desc = templates[i].description.slice(0, 60) + "...";
+      let description = templates[i].description.replaceAll(". ", ".\n") || "no description available";
+      let note = templates[i].note ? templates[i].note.replaceAll(". ", ".\n") : "no notes available";
+      let image = templates[i].image;
+      let logo = templates[i].logo;
+      let categories = '';
+      // set data.catagories to 'other' if data.catagories is empty or undefined
+      if (templates[i].categories == null || templates[i].categories == undefined || templates[i].categories == '') {
+          templates[i].categories = ['Other'];
+      }
+      // loop through the categories and add the badge to the card
+      for (let j = 0; j < templates[i].categories.length; j++) {
+        categories += CatagoryColor(templates[i].categories[j]);
+      }
+      appCard = appCard.replace(/AppName/g, name);
+      appCard = appCard.replace(/AppShortName/g, name);
+      appCard = appCard.replace(/AppDesc/g, desc);
+      appCard = appCard.replace(/AppLogo/g, logo);
+      appCard = appCard.replace(/AppCategories/g, categories);
+      apps_list += appCard;
+  }
+  // let templatesJSON = readFileSync('./templates/templates.json');
+  // let templates = JSON.parse(templatesJSON).templates;
+
+  res.render("apps", {
+    name: req.session.user,
+    role: req.session.role,
+    avatar: req.session.user.charAt(0).toUpperCase(),
+    list_start: list_start + 1,
+    list_end: list_end,
+    app_count: templates.length,
+    prev: prev,
+    next: next,
+    apps_list: apps_list,
+    alert: alert,
+    template_list: '',
+  });
+  alert = '';
+}
+
+
+export const AppTemplate = (req, res) => {
+  let templateTest = Number(req.params.template) || 'template.json';
   let page = Number(req.params.page) || 1;
   let page = Number(req.params.page) || 1;
   let list_start = (page-1)*28;
   let list_start = (page-1)*28;
   let list_end = (page*28);
   let list_end = (page*28);
@@ -71,7 +130,8 @@ export const Apps = (req, res) => {
     prev: prev,
     prev: prev,
     next: next,
     next: next,
     apps_list: apps_list,
     apps_list: apps_list,
-    alert: alert || ''
+    alert: alert,
+    template_list: '',
   });
   });
   alert = '';
   alert = '';
 }
 }
@@ -133,7 +193,8 @@ export const appSearch = async (req, res) => {
       prev: prev,
       prev: prev,
       next: next,
       next: next,
       apps_list: apps_list,
       apps_list: apps_list,
-      alert: res.locals.alert || ''
+      alert: alert,
+      template_list: '',
   });
   });
 }
 }
 
 
@@ -399,6 +460,19 @@ export const Upload = (req, res) => {
               <a class="btn-close" data-bs-dismiss="alert" aria-label="close" style="padding-top: 0.5rem;"></a>
               <a class="btn-close" data-bs-dismiss="alert" aria-label="close" style="padding-top: 0.5rem;"></a>
             </div>`;
             </div>`;
     
     
+    let files = readdirSync('templates/tmp/');
+
+    for (let i = 0; i < files.length; i++) {
+      if (files[i].endsWith('.json')) {
+        renameSync(`templates/tmp/${files[i]}`, `templates/json/${files[i]}`);
+      } else if (files[i].endsWith('.yml') || files[i].endsWith('.yaml')) {
+        mkdirSync(`templates/compose/${files[i].slice(0, -4)}`);
+        renameSync(`templates/tmp/${files[i]}`, `templates/compose/${files[i].slice(0, -4)}/${files[i]}`);
+      } else {
+        unlinkSync(`templates/tmp/${files[i]}`);
+      }
+    }
+        
     res.redirect('/apps');
     res.redirect('/apps');
   });
   });
 };
 };

+ 187 - 247
controllers/dashboard.js

@@ -7,21 +7,166 @@ import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
 
 
 let hidden = '';
 let hidden = '';
 
 
-// The actual page
+// The page
 export const Dashboard = (req, res) => {
 export const Dashboard = (req, res) => {
-    
     let name = req.session.user;
     let name = req.session.user;
     let role = req.session.role;
     let role = req.session.role;
-    let avatar = name.charAt(0).toUpperCase();
-
+    
     res.render("dashboard", {
     res.render("dashboard", {
         name: name,
         name: name,
-        avatar: avatar,
+        avatar: name.charAt(0).toUpperCase(),
         role: role,
         role: role,
         alert: ''
         alert: ''
     });
     });
 }
 }
 
 
+// The page actions
+export const DashboardAction = async (req, res) => {
+    let name = req.header('hx-trigger-name');
+    let value = req.header('hx-trigger');
+    let action = req.params.action;
+    let modal = '';
+
+    switch (action) {
+        case 'permissions':
+            let title = name.charAt(0).toUpperCase() + name.slice(1);
+            let permissions_list = '';
+            let permissions_modal = readFileSync('./views/modals/permissions.html', 'utf8');
+            permissions_modal = permissions_modal.replace(/PermissionsTitle/g, title);
+            let users = await User.findAll({ attributes: ['username', 'UUID']});
+
+            for (let i = 0; i < users.length; i++) {
+                let user_permissions = readFileSync('./views/partials/user_permissions.html', 'utf8');
+                let exists = await Permission.findOne({ where: {containerName: name, user: users[i].username}});
+                
+                if (!exists) {
+                    const newPermission = await Permission.create({ containerName: name, user: users[i].username, userID: users[i].UUID});
+                }
+                
+                let permissions = await Permission.findOne({ where: {containerName: name, user: users[i].username}});
+                if (permissions.uninstall == true) { user_permissions = user_permissions.replace(/data-UninstallCheck/g, 'checked'); }
+                if (permissions.edit == true) { user_permissions = user_permissions.replace(/data-EditCheck/g, 'checked'); }
+                if (permissions.upgrade == true) { user_permissions = user_permissions.replace(/data-UpgradeCheck/g, 'checked'); }
+                if (permissions.start == true) { user_permissions = user_permissions.replace(/data-StartCheck/g, 'checked'); }
+                if (permissions.stop == true) { user_permissions = user_permissions.replace(/data-StopCheck/g, 'checked'); }
+                if (permissions.pause == true) { user_permissions = user_permissions.replace(/data-PauseCheck/g, 'checked'); }
+                if (permissions.restart == true) { user_permissions = user_permissions.replace(/data-RestartCheck/g, 'checked'); }
+                if (permissions.logs == true) { user_permissions = user_permissions.replace(/data-LogsCheck/g, 'checked'); }
+
+                user_permissions = user_permissions.replace(/EntryNumber/g, i);
+                user_permissions = user_permissions.replace(/EntryNumber/g, i);
+                user_permissions = user_permissions.replace(/EntryNumber/g, i);
+                user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
+                user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
+                user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
+                user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
+                user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
+                user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
+
+                permissions_list += user_permissions;
+            }
+
+            permissions_modal = permissions_modal.replace(/PermissionsList/g, permissions_list);
+            res.send(permissions_modal);
+            return;
+        case 'uninstall':
+            modal = readFileSync('./views/modals/uninstall.html', 'utf8');
+            modal = modal.replace(/AppName/g, name);
+            // let containerPermissions = await Permission.findAll({ where: {containerName: name}});
+            res.send(modal);
+            return;
+        case 'details':
+            modal = readFileSync('./views/modals/details.html', 'utf8');
+            let details = await containerInfo(name);
+            modal = modal.replace(/AppName/g, details.name);
+            modal = modal.replace(/AppImage/g, details.image);
+            res.send(modal);
+            return;
+        case 'containers':
+            res.send(cardList);
+            return;
+        case 'updates':
+            res.send(newCards);
+            newCards = '';
+            return;
+        case 'card':
+            if (hidden.includes(name) || !containersArray.find(c => c.container === name)) {
+                res.send('');
+                return;
+            } else {
+                let details = await containerInfo(name);
+                let card = await createCard(details);
+                res.send(card);
+                return;
+            }
+        case 'logs':
+            let logString = '';
+            let options = { follow: true, stdout: true, stderr: false, timestamps: false };
+            docker.getContainer(name).logs(options, function (err, stream) {
+                if (err) { console.log(err); return; }
+                const readableStream = Readable.from(stream);
+                readableStream.on('data', function (chunk) {
+                    logString += chunk.toString('utf8');
+                });
+                readableStream.on('end', function () {
+                    res.send(`<pre>${logString}</pre> `);
+                });
+            });
+            return;
+        case 'hide':
+            let exists = await Container.findOne({ where: {name: name}});
+            if (!exists) {
+                const newContainer = await Container.create({ name: name, visibility: false, });
+            } else {
+                exists.update({ visibility: false });
+            }
+            hidden = await Container.findAll({ where: {visibility:false}});
+            hidden = hidden.map((container) => container.name);
+            res.send("ok");
+            return;
+        case 'reset':
+            await Container.update({ visibility: true }, { where: {} });
+            hidden = await Container.findAll({ where: {visibility:false}});
+            hidden = hidden.map((container) => container.name);
+            res.send("ok");
+            return;
+
+
+    }
+
+    function status (state) {
+        let status = `<span class="text-yellow 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>`;
+        return status;
+    }
+
+    // Start
+    if ((action == 'start') && (value == 'stopped')) {
+        docker.getContainer(name).start();
+        res.send(status('starting'));
+    } else if ((action == 'start') && (value == 'paused')) {
+        docker.getContainer(name).unpause();
+        res.send(status('starting'));
+    // Stop
+    } else if ((action == 'stop') && (value != 'stopped')) {
+        docker.getContainer(name).stop();
+        res.send(status('stopping'));
+    // Pause
+    } else if ((action == 'pause') && (value == 'paused')) {
+        docker.getContainer(name).unpause();
+        res.send(status('starting'));
+    }   else if ((action == 'pause') && (value == 'running')) {
+        docker.getContainer(name).pause();
+        res.send(status('pausing'));
+    // Restart
+    } else if (action == 'restart') {
+        docker.getContainer(name).restart();
+        res.send(status('restarting'));
+    } 
+}
+
 // Server metrics (CPU, RAM, TX, RX, DISK)
 // Server metrics (CPU, RAM, TX, RX, DISK)
 export const Stats = async (req, res) => {
 export const Stats = async (req, res) => {
     let name = req.header('hx-trigger-name');
     let name = req.header('hx-trigger-name');
@@ -155,28 +300,6 @@ async function createCard (details) {
 
 
 let [ cardList, newCards, containersArray, sentArray, updatesArray ] = [ '', '', [], [], [] ];
 let [ cardList, newCards, containersArray, sentArray, updatesArray ] = [ '', '', [], [], [] ];
 
 
-export async function addCard (name, state) {
-    console.log(`Adding card for ${name}: ${state}`);
-
-    let details = {
-        name: name,
-        image: name,
-        service: name,
-        state: 'installing',
-        external_port: 0,
-        internal_port: 0,
-        ports: [],
-        link: 'localhost',
-    
-    }
-    createCard(details).then(card => {
-        cardList += card;
-    });
-}
-
-
-
-
 // HTMX server-side events
 // HTMX server-side events
 export const SSE = async (req, res) => {
 export const SSE = async (req, res) => {
     res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
     res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
@@ -264,242 +387,40 @@ export const Chart = async (req, res) => {
     res.send(chart);
     res.send(chart);
 }
 }
 
 
-export const updateCards = async (req, res) => {
-    console.log('updateCards called');
-    res.send(newCards);
-    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(`${name} requesting updated card`);
-    // return nothing if in hidden or not found in containersArray
-    if (hidden.includes(name) || !containersArray.find(c => c.container === name)) {
-        res.send('');
-        return;
-    } else {
-        let details = await containerInfo(name);
-        let card = await createCard(details);
-        res.send(card);
-    }
-}
-
-
-
-
-function status (state) {
-    let status = `<span class="text-yellow 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>`;
-    return status;
-}
-
-
-export const Logs = (req, res) => {
-    let name = req.header('hx-trigger-name');
-    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) => {
-        res.send(`<pre>${data}</pre> `)
-    });
-}
-
-export const Action = async (req, res) => {
-    let name = req.header('hx-trigger-name');
-    let state = req.header('hx-trigger');
-    let action = req.params.action;
-    // Start
-    if ((action == 'start') && (state == 'stopped')) {
-        var containerName = docker.getContainer(name);
-        containerName.start();
-        res.send(status('starting'));
-    } else if ((action == 'start') && (state == 'paused')) {
-        var containerName = docker.getContainer(name);
-        containerName.unpause();
-        res.send(status('starting'));
-    // Stop
-    } else if ((action == 'stop') && (state != 'stopped')) {
-        var containerName = docker.getContainer(name);
-        containerName.stop();
-        res.send(status('stopping'));
-    // Pause
-    } else if ((action == 'pause') && (state == 'paused')) {
-        var containerName = docker.getContainer(name);
-        containerName.unpause();
-        res.send(status('starting'));
-    }   else if ((action == 'pause') && (state == 'running')) {
-        var containerName = docker.getContainer(name);
-        containerName.pause();
-        res.send(status('pausing'));
-    // Restart
-    } else if (action == 'restart') {
-        var containerName = docker.getContainer(name);
-        containerName.restart();
-        res.send(status('restarting'));
-    // Hide
-    } else if (action == 'hide') {
-        let exists = await Container.findOne({ where: {name: name}});
-        if (!exists) {
-            const newContainer = await Container.create({ name: name, visibility: false, });
-        } else {
-            exists.update({ visibility: false });
-        }
-        hidden = await Container.findAll({ where: {visibility:false}});
-        hidden = hidden.map((container) => container.name);
-        res.send("ok");
-    // Reset View
-    } else if (action == 'reset') {
-        await Container.update({ visibility: true }, { where: {} });
-        hidden = await Container.findAll({ where: {visibility:false}});
-        hidden = hidden.map((container) => container.name);
-        res.send("ok");
-    }   
-}
-
-
-export const Modals = async (req, res) => {
-    let name = req.header('hx-trigger-name');
-    let id = req.header('hx-trigger');
-    let title = name.charAt(0).toUpperCase() + name.slice(1);
-
-    if (id == 'permissions') {
-        let permissions_list = '';
-        let permissions_modal = readFileSync('./views/modals/permissions.html', 'utf8');
-        permissions_modal = permissions_modal.replace(/PermissionsTitle/g, title);
-        let users = await User.findAll({ attributes: ['username', 'UUID']});
-
-        for (let i = 0; i < users.length; i++) {
-            let user_permissions = readFileSync('./views/partials/user_permissions.html', 'utf8');
-            let exists = await Permission.findOne({ where: {containerName: name, user: users[i].username}});
-            if (!exists) {
-                const newPermission = await Permission.create({ containerName: name, user: users[i].username, userID: users[i].UUID});
-            }
-            
-            let permissions = await Permission.findOne({ where: {containerName: name, user: users[i].username}});
-            if (permissions.uninstall == true) { user_permissions = user_permissions.replace(/data-UninstallCheck/g, 'checked'); }
-            if (permissions.edit == true) { user_permissions = user_permissions.replace(/data-EditCheck/g, 'checked'); }
-            if (permissions.upgrade == true) { user_permissions = user_permissions.replace(/data-UpgradeCheck/g, 'checked'); }
-            if (permissions.start == true) { user_permissions = user_permissions.replace(/data-StartCheck/g, 'checked'); }
-            if (permissions.stop == true) { user_permissions = user_permissions.replace(/data-StopCheck/g, 'checked'); }
-            if (permissions.pause == true) { user_permissions = user_permissions.replace(/data-PauseCheck/g, 'checked'); }
-            if (permissions.restart == true) { user_permissions = user_permissions.replace(/data-RestartCheck/g, 'checked'); }
-            if (permissions.logs == true) { user_permissions = user_permissions.replace(/data-LogsCheck/g, 'checked'); }
-
-            user_permissions = user_permissions.replace(/EntryNumber/g, i);
-            user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
-            user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
-
-            permissions_list += user_permissions;
-        }
-
-        permissions_modal = permissions_modal.replace(/PermissionsList/g, permissions_list);
-        res.send(permissions_modal);
-        return;
-    }
-
-
-
-    if (id == 'uninstall') {
-        let modal = readFileSync('./views/modals/uninstall.html', 'utf8');
-        modal = modal.replace(/AppName/g, name);
-        // let containerPermissions = await Permission.findAll({ where: {containerName: name}});
-        res.send(modal);
-        return;
-    }
-
-    let modal = readFileSync('./views/modals/details.html', 'utf8');
-    let details = await containerInfo(name);
-
-    modal = modal.replace(/AppName/g, details.name);
-    modal = modal.replace(/AppImage/g, details.image);
-    res.send(modal);
-}
-
-
 export const UpdatePermissions = async (req, res) => {
 export const UpdatePermissions = async (req, res) => {
-
-    let user = req.body.username;
-    let container = req.body.container;
+    let { user, container } = req.body;
     let id = req.header('hx-trigger');
     let id = req.header('hx-trigger');
-    
-    console.log(`${req.session.user} is updating permissions for: ${user} on ${container}`);
 
 
+    await Permission.update({ uninstall: false, edit: false, upgrade: false, start: false, stop: false, pause: false, restart: false, logs: false }, { where: { containerName: container, user: user } });
+    
     Object.keys(req.body).forEach(async function(key) {
     Object.keys(req.body).forEach(async function(key) {
-        if (key != 'username' && key != 'container') {
+        if (key != 'user' && key != 'container') {
             let permissions = req.body[key];
             let permissions = req.body[key];
 
 
-            if (permissions.includes('uninstall')){
+            if (permissions.includes('uninstall')) {
                 await Permission.update({ uninstall: true }, { where: {containerName: container, user: user}});
                 await Permission.update({ uninstall: true }, { where: {containerName: container, user: user}});
-            }   
-            else {
-                await Permission.update({ uninstall: false }, { where: {containerName: container, user: user}});
-            }
-
-            if (permissions.includes('edit')){
+            }  
+            if (permissions.includes('edit')) {
                 await Permission.update({ edit: true }, { where: {containerName: container, user: user}});
                 await Permission.update({ edit: true }, { where: {containerName: container, user: user}});
             }   
             }   
-            else {
-                await Permission.update({ edit: false }, { where: {containerName: container, user: user}});
-            }
-
-            if (permissions.includes('upgrade')){
+            if (permissions.includes('upgrade')) {
                 await Permission.update({ upgrade: true }, { where: {containerName: container, user: user}});
                 await Permission.update({ upgrade: true }, { where: {containerName: container, user: user}});
             }   
             }   
-            else {
-                await Permission.update({ upgrade: false }, { where: {containerName: container, user: user}});
-            }
-            
-            if (permissions.includes('start')){
+            if (permissions.includes('start')) {
                 await Permission.update({ start: true }, { where: {containerName: container, user: user}});
                 await Permission.update({ start: true }, { where: {containerName: container, user: user}});
-            }   else {
-                await Permission.update({ start: false }, { where: {containerName: container, user: user}});
-            }
-
-            if (permissions.includes('stop')){
+            }   
+            if (permissions.includes('stop')) {
                 await Permission.update({ stop: true }, { where: {containerName: container, user: user}});
                 await Permission.update({ stop: true }, { where: {containerName: container, user: user}});
-            }   else {
-                await Permission.update({ stop: false }, { where: {containerName: container, user: user}});
-            }
-
-            if (permissions.includes('pause')){
+            }   
+            if (permissions.includes('pause')) {
                 await Permission.update({ pause: true }, { where: {containerName: container, user: user}});
                 await Permission.update({ pause: true }, { where: {containerName: container, user: user}});
-            }   else {
-                await Permission.update({ pause: false }, { where: {containerName: container, user: user}});
-            }
-
-            if (permissions.includes('restart')){
+            }   
+            if (permissions.includes('restart')) {
                 await Permission.update({ restart: true }, { where: {containerName: container, user: user}});
                 await Permission.update({ restart: true }, { where: {containerName: container, user: user}});
-            }   else {
-                await Permission.update({ restart: false }, { where: {containerName: container, user: user}});
-            }
-
-            if (permissions.includes('logs')){
+            }   
+            if (permissions.includes('logs')) {
                 await Permission.update({ logs: true }, { where: {containerName: container, user: user}});
                 await Permission.update({ logs: true }, { where: {containerName: container, user: user}});
             }
             }
-            else {
-                await Permission.update({ logs: false }, { where: {containerName: container, user: user}});
-            }
         }  
         }  
     });
     });
 
 
@@ -509,8 +430,27 @@ export const UpdatePermissions = async (req, res) => {
         res.send(submit);
         res.send(submit);
         return;
         return;
     } else if (id == 'confirmed') {
     } else if (id == 'confirmed') {
-        submit = `<button class="btn" type="button" id="submit" hx-post="/updatePermissions" hx-vals="#updatePermissions" hx-swap="outerHTML">Update  </button>`;
+        submit = `<button class="btn" type="button" id="submit" hx-post="/updatePermissions" hx-vals="#updatePermissions" hx-swap="outerHTML">Update  </button>`;
         res.send(submit);
         res.send(submit);
         return;
         return;
     }
     }
+}
+
+// Gets imported by install.js
+export async function addCard (name, state) {
+    console.log(`Adding card for ${name}: ${state}`);
+
+    let details = {
+        name: name,
+        image: name,
+        service: name,
+        state: 'installing',
+        external_port: 0,
+        internal_port: 0,
+        ports: [],
+        link: 'localhost',
+    }
+    createCard(details).then(card => {
+        cardList += card;
+    });
 }
 }

+ 26 - 25
controllers/images.js

@@ -2,6 +2,31 @@ import { docker } from '../server.js';
 
 
 export const Images = async function(req, res) {
 export const Images = async function(req, res) {
 
 
+    let action = req.params.action;
+    if (action == "remove") {
+        console.log("Removing images");
+        let images = req.body.select;
+
+        if (typeof(images) == 'string') {
+            images = [images];
+        }
+
+        for (let i = 0; i < images.length; i++) {
+            if (images[i] != 'on') {
+                try {
+                    console.log(`Removing image: ${images[i]}`);
+                    let image = docker.getImage(images[i]);
+                    await image.remove();
+                } catch (error) {
+                    console.log(`Unable to remove image: ${images[i]}`);
+                }
+            }
+        }
+    res.redirect("/images");
+    return;
+    }
+    
+
     let images = await docker.listImages({ all: true });
     let images = await docker.listImages({ all: true });
 
 
     let image_list = `
     let image_list = `
@@ -48,34 +73,10 @@ export const Images = async function(req, res) {
     res.render("images", {
     res.render("images", {
         name: req.session.user,
         name: req.session.user,
         role: req.session.role,
         role: req.session.role,
-        avatar: req.session.avatar,
+        avatar: req.session.user.charAt(0).toUpperCase(),
         image_list: image_list,
         image_list: image_list,
         image_count: images.length,
         image_count: images.length,
         alert: '',
         alert: '',
     });
     });
 
 
-}
-
-
-
-export const removeImage = async function(req, res) {
-    let images = req.body.select;
-
-    if (typeof(images) == 'string') {
-        images = [images];
-    }
-
-    for (let i = 0; i < images.length; i++) {
-        
-        if (images[i] != 'on') {
-            try {
-                console.log(`Removing image: ${images[i]}`);
-                let image = docker.getImage(images[i]);
-                await image.remove();
-            } catch (error) {
-                console.log(`Unable to remove image: ${images[i]}`);
-            }
-        }
-    }
-    res.redirect("/images");
 }
 }

+ 1 - 7
controllers/login.js

@@ -43,13 +43,7 @@ export const submitLogin = async function(req,res){
                     message: "User logged in successfully",
                     message: "User logged in successfully",
                     ip: req.socket.remoteAddress
                     ip: req.socket.remoteAddress
                 });
                 });
-
-                if (req.session.role == "admin") {
-                    res.redirect("/");
-                }
-                else {
-                    res.redirect("/portal");
-                }
+                res.redirect("/dashboard");
             }else{
             }else{
 
 
                 const syslog = await Syslog.create({
                 const syslog = await Syslog.create({

+ 1 - 1
controllers/networks.js

@@ -41,7 +41,7 @@ export const Networks = async function(req, res) {
     res.render("networks", {
     res.render("networks", {
         name: req.session.user,
         name: req.session.user,
         role: req.session.role,
         role: req.session.role,
-        avatar: req.session.avatar,
+        avatar: req.session.user.charAt(0).toUpperCase(),
         network_list: network_list,
         network_list: network_list,
         network_count: networks.length,
         network_count: networks.length,
         alert: '',
         alert: '',

+ 2 - 9
controllers/portal.js

@@ -3,12 +3,10 @@ import { Permission, Container, User } from '../database/models.js';
 import { docker } from '../server.js';
 import { docker } from '../server.js';
 import { readFileSync } from 'fs';
 import { readFileSync } from 'fs';
 
 
-
 let hidden = '';
 let hidden = '';
 
 
 // The actual page
 // The actual page
 export const Portal = (req, res) => {
 export const Portal = (req, res) => {
-    
     let name = req.session.user;
     let name = req.session.user;
     let role = req.session.role;
     let role = req.session.role;
     let avatar = name.charAt(0).toUpperCase();
     let avatar = name.charAt(0).toUpperCase();
@@ -16,7 +14,8 @@ export const Portal = (req, res) => {
     res.render("portal", {
     res.render("portal", {
         name: name,
         name: name,
         avatar: avatar,
         avatar: avatar,
-        role: role
+        role: role,
+        alert: '',
     });
     });
 }
 }
 
 
@@ -29,12 +28,6 @@ async function CardList () {
         let card = await createCard(details);
         let card = await createCard(details);
         cardList += card;
         cardList += card;
     }
     }
-
-    // for (let i = 0; i < containers.length; i++) {
-    //     console.log(containers[i].containerName);
-    // }
-    
-
 }
 }
 
 
 export const UserContainers = async (req, res) => {
 export const UserContainers = async (req, res) => {

+ 1 - 1
controllers/register.js

@@ -81,7 +81,7 @@ export const submitRegister = async function(req,res){
                         ip: req.socket.remoteAddress
                         ip: req.socket.remoteAddress
                     });
                     });
 
 
-                    res.redirect("/");
+                    res.redirect("/dashboard");
                 }
                 }
             } catch(err) {
             } catch(err) {
                 res.render("register",{
                 res.render("register",{

+ 1 - 1
controllers/settings.js

@@ -4,7 +4,7 @@ export const Settings = (req, res) => {
     res.render("settings", {
     res.render("settings", {
         name: req.session.user,
         name: req.session.user,
         role: req.session.role,
         role: req.session.role,
-        avatar: req.session.avatar,
+        avatar: req.session.user.charAt(0).toUpperCase(),
         alert: '',
         alert: '',
     });
     });
 }
 }

+ 1 - 1
controllers/supporters.js

@@ -12,7 +12,7 @@ export const Supporters = async (req, res) => {
         id: user.id,
         id: user.id,
         email: user.email,
         email: user.email,
         role: user.role,
         role: user.role,
-        avatar: user.avatar,
+        avatar: req.session.user.charAt(0).toUpperCase(),
         alert: '',
         alert: '',
     });
     });
 
 

+ 1 - 1
controllers/syslogs.js

@@ -29,7 +29,7 @@ export const Syslogs = async function(req, res) {
     res.render("syslogs", {
     res.render("syslogs", {
         name: req.session.user || 'Dev',
         name: req.session.user || 'Dev',
         role: req.session.role || 'Dev',
         role: req.session.role || 'Dev',
-        avatar: req.session.avatar || '<img src="/img/avatars/rus.jpg">',
+        avatar: req.session.user.charAt(0).toUpperCase(),
         logs: logs,
         logs: logs,
         alert: '',
         alert: '',
     });
     });

+ 3 - 2
controllers/users.js

@@ -54,8 +54,9 @@ export const Users = async (req, res) => {
     res.render("users", {
     res.render("users", {
         name: req.session.user,
         name: req.session.user,
         role: req.session.role,
         role: req.session.role,
-        avatar: req.session.avatar,
-        user_list: user_list
+        avatar: req.session.user.charAt(0).toUpperCase(),
+        user_list: user_list,
+        alert: ''
     });
     });
 
 
 }
 }

+ 1 - 1
controllers/volumes.js

@@ -62,7 +62,7 @@ export const Volumes = async function(req, res) {
     res.render("volumes", {
     res.render("volumes", {
         name: req.session.user,
         name: req.session.user,
         role: req.session.role,
         role: req.session.role,
-        avatar: req.session.avatar,
+        avatar: req.session.user.charAt(0).toUpperCase(),
         volume_list: volume_list,
         volume_list: volume_list,
         volume_count: volumes.length,
         volume_count: volumes.length,
         alert: '',
         alert: '',

+ 0 - 0
public/images/dweebui.svg → public/img/dweebui.svg


+ 0 - 0
public/images/logo.png → public/img/logo.png


+ 64 - 79
router/index.js

@@ -1,15 +1,14 @@
 import express from "express";
 import express from "express";
 import { Permission } from '../database/models.js';
 import { Permission } from '../database/models.js';
-
 export const router = express.Router();
 export const router = express.Router();
 
 
 // Controllers
 // Controllers
 import { Login, submitLogin, Logout } from "../controllers/login.js";
 import { Login, submitLogin, Logout } from "../controllers/login.js";
 import { Register, submitRegister } from "../controllers/register.js";
 import { Register, submitRegister } from "../controllers/register.js";
-import { Dashboard, Logs, Modals, Stats, Chart, SSE, Card, updateCards, Containers, Action, UpdatePermissions } from "../controllers/dashboard.js";
-import { Apps, appSearch, InstallModal, ImportModal, LearnMore, Upload } from "../controllers/apps.js";
+import { Dashboard, DashboardAction, Stats, Chart, SSE, UpdatePermissions } from "../controllers/dashboard.js";
+import { Apps, appSearch, AppTemplate, InstallModal, ImportModal, LearnMore, Upload } from "../controllers/apps.js";
 import { Users } from "../controllers/users.js";
 import { Users } from "../controllers/users.js";
-import { Images, removeImage } from "../controllers/images.js";
+import { Images } from "../controllers/images.js";
 import { Networks, removeNetwork } from "../controllers/networks.js";
 import { Networks, removeNetwork } from "../controllers/networks.js";
 import { Volumes, removeVolume } from "../controllers/volumes.js";
 import { Volumes, removeVolume } from "../controllers/volumes.js";
 import { Account } from "../controllers/account.js";
 import { Account } from "../controllers/account.js";
@@ -17,105 +16,91 @@ import { Variables } from "../controllers/variables.js";
 import { Settings } from "../controllers/settings.js";
 import { Settings } from "../controllers/settings.js";
 import { Supporters, Thanks } from "../controllers/supporters.js";
 import { Supporters, Thanks } from "../controllers/supporters.js";
 import { Syslogs } from "../controllers/syslogs.js";
 import { Syslogs } from "../controllers/syslogs.js";
-import { Portal, UserContainers } from "../controllers/portal.js"
-
-// Auth middleware
-const auth = async (req, res, next) => {
+import { Install } from "../utils/install.js"
+import { Uninstall } from "../utils/uninstall.js"
 
 
-    let user = req.session.user;
-    let role = req.session.role;
-    let path = req.path;
-    let trigger = req.header('hx-trigger-name');
-
-    // console.log("Auth: ", user, role, path, trigger, req.path);
+// Permission Middleware
+const adminOnly = async (req, res, next) => {
+    if (req.session.role == 'admin') { next(); } 
+    else { res.redirect('/dashboard'); }
+}
 
 
-    if (!user) { res.redirect('/login'); return; }
-    else if (role == 'admin' || path == "/portal" || path == "/account" || path == "/supporters" || path == "/thank" || path == "/user_containers") { next(); return; }
-    // else { res.redirect('/portal'); return; }
-    
+const sessionCheck = async (req, res, next) => {
+    if (req.session.user) { next(); }
+    else { res.redirect('/login'); }
+}
 
 
+const permissionCheck = async (req, res, next) => {
+    if (!req.session.user) { res.redirect('/login'); return; }
+    else if (req.session.role == 'admin') { next(); return; }
+    let user = req.session.user;
     let action = req.path.split("/")[2];
     let action = req.path.split("/")[2];
-
-
-    if (action == "start" || action == "stop" || action == "pause" || action == "restart") {
+    let trigger = req.header('hx-trigger-name');
+    const userAction = ['start', 'stop', 'restart', 'pause', 'uninstall', 'upgrade', 'edit', 'logs', 'hide', 'reset_view'];
+    const userPaths = ['card', 'containers', 'updates'];
+    if (userAction.includes(action)) {
         let permission = await Permission.findOne({ where: { containerName: trigger, user: user }, attributes: [`${action}`] });
         let permission = await Permission.findOne({ where: { containerName: trigger, user: user }, attributes: [`${action}`] });
-        
-        if (permission) {
+        if (permission) { 
             if (permission[action] == true) {
             if (permission[action] == true) {
                 console.log(`User ${user} has permission to ${action} ${trigger}`);
                 console.log(`User ${user} has permission to ${action} ${trigger}`);
                 next();
                 next();
+                return;
             }
             }
             else {
             else {
                 console.log(`User ${user} does not have permission to ${action} ${trigger}`);
                 console.log(`User ${user} does not have permission to ${action} ${trigger}`);
             }
             }
-        } else {
-            console.log(`No entry found for ${user} in ${trigger} permissions`);
         }
         }
-    }
-    else {
-        res.redirect('/portal');
+    } else if (userPaths.includes(action)) {
+        next();
+        return;
     }
     }
 }
 }
 
 
+// Utils
+router.post("/install", adminOnly, Install);
+router.post("/uninstall", adminOnly, Uninstall);
 
 
+// Routes
+router.get("/login", Login);
+router.post("/login", submitLogin);
+router.get("/logout", Logout);
+router.get("/register", Register);
+router.post("/register", submitRegister);  
 
 
+router.get("/", sessionCheck, Dashboard);
+router.get("/dashboard", sessionCheck, Dashboard);
+router.post("/dashboard/:action", permissionCheck, DashboardAction);
+router.get("/sse", sessionCheck, SSE);
+router.post("/updatePermissions", adminOnly, UpdatePermissions);
+router.get("/stats", sessionCheck, Stats);
+router.get("/chart", sessionCheck, Chart);
 
 
-// Admin routes
-router.get("/", auth, Dashboard);
-router.post("/action/:action", auth, Action);
-router.post("/updatePermissions", auth, UpdatePermissions);
-
-router.get("/logs", auth, Logs);
-router.get("/modals", auth, Modals);
-router.get("/stats", auth, Stats);
-router.get("/chart", auth, Chart);
-router.get("/sse_event", auth, SSE);
-router.get("/containers", auth, Containers);
-router.get("/card", auth, Card);
-router.get("/new_cards", auth, updateCards);
-
-
-router.get("/images", auth, Images);
-router.post("/removeImage", auth, removeImage);
-
-router.get("/volumes", auth, Volumes);
-router.post("/removeVolume", auth, removeVolume);
-
-router.get("/networks", auth, Networks);
-router.post("/removeNetwork", auth, removeNetwork);
-
-router.get("/apps", auth, Apps);
-router.get("/apps/:page", auth, Apps);
-router.post("/apps", auth, appSearch);
-router.get("/install_modal", auth, InstallModal)
-router.get("/import_modal", auth, ImportModal)
-router.get("/learn_more", auth, LearnMore)
-router.post("/upload", auth, Upload);
+router.get("/images", adminOnly, Images);
+router.post("/images/:action", adminOnly, Images);
 
 
-router.get("/users", auth, Users);
-router.get("/syslogs", auth, Syslogs);
+router.get("/volumes", adminOnly, Volumes);
+router.post("/removeVolume", adminOnly, removeVolume);
 
 
-router.get("/variables", auth, Variables);
-router.get("/settings", auth, Settings);
+router.get("/networks", adminOnly, Networks);
+router.post("/removeNetwork", adminOnly, removeNetwork);
 
 
-// User routes
-router.get("/portal", auth, Portal);
-router.get("/user_containers", auth, UserContainers);
-router.get("/account", auth, Account);
-router.get("/supporters", auth, Supporters);
-router.post("/thank", auth, Thanks);
+router.get("/apps", adminOnly, Apps);
+router.get("/apps/:page", adminOnly, Apps);
+router.get("/apps/template/:template", adminOnly, AppTemplate);
+router.post("/apps", adminOnly, appSearch);
+router.get("/install_modal", adminOnly, InstallModal)
+router.get("/import_modal", adminOnly, ImportModal)
+router.get("/learn_more", adminOnly, LearnMore)
+router.post("/upload", adminOnly, Upload);
 
 
-router.get("/login", Login);
-router.post("/login", submitLogin);
-router.get("/register", Register);
-router.post("/register", submitRegister);  
-router.get("/logout", Logout);
+router.get("/users", adminOnly, Users);
+router.get("/syslogs", adminOnly, Syslogs);
 
 
+router.get("/variables", adminOnly, Variables);
+router.get("/settings", adminOnly, Settings);
 
 
-// Functions
-import { Install } from "../functions/install.js"
-import { Uninstall } from "../functions/uninstall.js"
 
 
-router.post("/install", Install);
-router.post("/uninstall", Uninstall);
+router.get("/account", sessionCheck, Account);
+router.get("/supporters", sessionCheck, Supporters);
+router.post("/thank", sessionCheck, Thanks);
 
 

+ 1 - 1
server.js

@@ -5,7 +5,7 @@ import ejs from 'ejs';
 import Docker from 'dockerode';
 import Docker from 'dockerode';
 import { router } from './router/index.js';
 import { router } from './router/index.js';
 import { sequelize } from './database/models.js';
 import { sequelize } from './database/models.js';
-export var docker = new Docker();
+export const docker = new Docker();
 
 
 const app = express();
 const app = express();
 const MemoryStore = memorystore(session);
 const MemoryStore = memorystore(session);

+ 0 - 0
functions/install.js → utils/install.js


+ 0 - 0
functions/uninstall.js → utils/uninstall.js


+ 22 - 16
views/apps.html

@@ -35,8 +35,7 @@
                 <div class="card">
                 <div class="card">
                   <div class="card-body text-center">
                   <div class="card-body text-center">
                     <div class="d-flex align-items-center">
                     <div class="d-flex align-items-center">
-                      <div class="me-auto"><%= list_start %> - <%= list_end %> of <%= app_count %> Apps</div>
-                      <button class="btn btn-primary" name="Import" id="Import" data-hx-get="/import_modal" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Import</button>
+                      <div class="me-auto btn"><%= list_start %> - <%= list_end %> of <%= app_count %> Apps</div>
                     </div>
                     </div>
                   </div>
                   </div>
                 </div>
                 </div>
@@ -46,27 +45,34 @@
                 <div class="card">
                 <div class="card">
                   <div class="card-body text-center">
                   <div class="card-body text-center">
                     <div class="d-flex align-items-center">
                     <div class="d-flex align-items-center">
-                      <select class="form-select">
-                        <option>All</option>
-                        <option>Media</option>
-                        <option>Tools</option>
-                        <option>Gaming</option>
-                        <option>FOSS</option>
-                        <option>Database</option>
-                      </select>
+                      <div class="btn me-2">Category:</div>
+                      <div class="dropdown">
+                        <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">All</button>
+                        <ul class="dropdown-menu">
+                          <li><a class="dropdown-item" href="#">Media</a></li>
+                          <li><a class="dropdown-item" href="#">Tools</a></li>
+                          <li><a class="dropdown-item" href="#">Gaming</a></li>
+                          <li><a class="dropdown-item" href="#">FOSS</a></li>
+                          <li><a class="dropdown-item" href="#">Database</a></li>
+                        </ul>
+                      </div>
                     </div>
                     </div>
                   </div>
                   </div>
                 </div>
                 </div>
               </div>
               </div>
-      
+
               <div class="col-md-6 col-lg-3">
               <div class="col-md-6 col-lg-3">
                 <div class="card">
                 <div class="card">
                   <div class="card-body text-center">
                   <div class="card-body text-center">
                     <div class="d-flex align-items-center">
                     <div class="d-flex align-items-center">
-                      <select class="form-select">
-                        <option>Templates.json (default)</option>
-                        <option>Compose</option>
-                      </select>
+                      <dropdown class="me-2">
+                        <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Templates.json</button>
+                        <ul class="dropdown-menu">
+                          <li><a class="dropdown-item" href="#">Templates.json</a></li>
+                          <li><a class="dropdown-item" href="#">Compose</a></li>
+                        </ul>
+                      </dropdown>
+                      <button class="btn" name="Import" id="Import" data-hx-get="/import_modal" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Import</button>
                     </div>
                     </div>
                   </div>
                   </div>
                 </div>
                 </div>
@@ -78,7 +84,7 @@
                     <div class="text-secondary d-flex align-items-center">
                     <div class="text-secondary d-flex align-items-center">
                       <form action="/apps" id="search" name="search" method="POST" class="d-flex">
                       <form action="/apps" id="search" name="search" method="POST" class="d-flex">
                         <input type="search" class="form-control me-2" name="search" placeholder="Search apps…" >
                         <input type="search" class="form-control me-2" name="search" placeholder="Search apps…" >
-                        <input type="submit" form="search" class="btn btn-primary" value="Search">
+                        <input type="submit" form="search" class="btn" value="Search">
                       </form>
                       </form>
                     </div>
                     </div>
                   </div>
                   </div>

+ 3 - 4
views/dashboard.html

@@ -5,7 +5,6 @@
     <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
     <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
     <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
     <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
     <title>DweebUI - Dashboard</title>
     <title>DweebUI - Dashboard</title>
-    <!-- CSS files -->
     <link href="/css/tabler.min.css" rel="stylesheet"/>
     <link href="/css/tabler.min.css" rel="stylesheet"/>
     <link href="/css/meters.css" rel="stylesheet"/>
     <link href="/css/meters.css" rel="stylesheet"/>
     <script src="/js/htmx.min.js"></script>
     <script src="/js/htmx.min.js"></script>
@@ -30,7 +29,7 @@
 
 
       <div class="page-body">
       <div class="page-body">
         <div class="container-xl">
         <div class="container-xl">
-          <div class="row row-deck row-cards" hx-ext="sse" sse-connect="/sse_event">
+          <div class="row row-deck row-cards" hx-ext="sse" sse-connect="/sse">
             
             
             <div class="col-12">
             <div class="col-12">
               <div class="row row-cards">
               <div class="row row-cards">
@@ -142,13 +141,13 @@
             
             
             <!-- HTMX -->
             <!-- HTMX -->
             <div class="col-12">
             <div class="col-12">
-              <div class="row row-cards" id="containers" data-hx-get="/containers" data-hx-trigger="load" data-hx-swap="innerHTML">
+              <div class="row row-cards" id="containers" data-hx-post="/dashboard/containers" data-hx-trigger="load" data-hx-swap="innerHTML">
               </div>
               </div>
             </div>
             </div>
 
 
             <!-- HTMX -->
             <!-- HTMX -->
             <div class="col-12">
             <div class="col-12">
-              <div class="row row-cards" data-hx-get="/new_cards" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
+              <div class="row row-cards" data-hx-post="/dashboard/updates" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
               </div>
               </div>
             </div>
             </div>
             
             

+ 1 - 1
views/images.html

@@ -57,7 +57,7 @@
 
 
                     <div class="card-footer d-flex align-items-center">
                     <div class="card-footer d-flex align-items-center">
 
 
-                      <button class="btn" type="submit" formaction="/removeImage">Remove</button>
+                      <button class="btn" type="submit" formaction="/images/remove">Remove</button>
 
 
                       </form>
                       </form>
                                           
                                           

+ 2 - 2
views/login.html

@@ -24,12 +24,12 @@
       <div class="container container-tight py-4">
       <div class="container container-tight py-4">
         <div class="text-center">
         <div class="text-center">
           <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
           <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
-              <img src="/images/logo.png" alt="DweebUI" title="DweebUI" height="100px">
+              <img src="/img/logo.png" alt="DweebUI" title="DweebUI" height="100px">
           </h1>
           </h1>
         </div>
         </div>
         <div class="text-center mb-4">
         <div class="text-center mb-4">
           <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
           <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
-            <img src="/images/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
+            <img src="/img/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
           </h1>
           </h1>
         </div>
         </div>
 
 

+ 0 - 3
views/modals/import.html

@@ -4,16 +4,13 @@
         <div class="modal-body">
         <div class="modal-body">
             <div class="modal-title">Import Template(s)</div>
             <div class="modal-title">Import Template(s)</div>
             <div class="text-muted">Template(s) can be *.json, *.yml, or *.yaml</div>
             <div class="text-muted">Template(s) can be *.json, *.yml, or *.yaml</div>
-
             <div class="mt-3">
             <div class="mt-3">
                 <div class="form-label">Choose file(s):</div>
                 <div class="form-label">Choose file(s):</div>
                 <form method="post" action="/upload" enctype="multipart/form-data" id="upload">
                 <form method="post" action="/upload" enctype="multipart/form-data" id="upload">
                     <input type="file" name="files" multiple />
                     <input type="file" name="files" multiple />
                 </form>
                 </form>
             </div>
             </div>
-
         </div>
         </div>
-        
         <div class="modal-footer">
         <div class="modal-footer">
             <button type="button" class="btn btn-link link-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
             <button type="button" class="btn btn-link link-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
             <button type="submit" class="btn btn-primary" data-bs-dismiss="modal" form="upload">Upload</button>
             <button type="submit" class="btn btn-primary" data-bs-dismiss="modal" form="upload">Upload</button>

+ 1 - 1
views/modals/permissions.html

@@ -17,7 +17,7 @@
             <button type="button" class="btn btn-danger" data-bs-dismiss="modal" disabled="">Reset</button>
             <button type="button" class="btn btn-danger" data-bs-dismiss="modal" disabled="">Reset</button>
         </div>
         </div>
         <div class="col">
         <div class="col">
-            <button type="button" class="btn btn-primary" data-bs-dismiss="modal">Update</button>
+            <button type="button" class="btn btn-primary" data-bs-dismiss="modal" disabled="">Update</button>
         </div>
         </div>
         </div>
         </div>
         </div>
         </div>

+ 11 - 11
views/partials/containerFull.html

@@ -1,4 +1,4 @@
-  <div class="col-sm-6 col-lg-3 pt-1" hx-get="/card" hx-trigger="sse:AppName" hx-swap="outerHTML" name="AppName">
+  <div class="col-sm-6 col-lg-3 pt-1" hx-post="/dashboard/card" hx-trigger="sse:AppName" hx-swap="outerHTML" name="AppName">
     <div class="card">
     <div class="card">
       <div class="card-body">
       <div class="card-body">
         <div class="card-stamp card-stamp-sm">
         <div class="card-stamp card-stamp-sm">
@@ -9,16 +9,16 @@
           <div class="ms-auto lh-1">
           <div class="ms-auto lh-1">
             <div class="card-actions btn-actions">
             <div class="card-actions btn-actions">
               <div class="card-actions btn-actions">
               <div class="card-actions btn-actions">
-                <button class="btn-action" title="Start" data-hx-post="/action/start" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
+                <button class="btn-action" title="Start" data-hx-post="/dashboard/start" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
                   <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>
                   <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>
-                <button class="btn-action" title="Stop" data-hx-post="/action/stop" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
+                <button class="btn-action" title="Stop" data-hx-post="/dashboard/stop" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
                   <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>
                   <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>
-                <button class="btn-action" title="Pause" data-hx-post="/action/pause" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
+                <button class="btn-action" title="Pause" data-hx-post="/dashboard/pause" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
                   <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>
                   <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>
-                <button class="btn-action" title="Restart" data-hx-post="/action/restart" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
+                <button class="btn-action" title="Restart" data-hx-post="/dashboard/restart" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
                   <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>                          
                   <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>
                 </button>
                 <div class="dropdown">
                 <div class="dropdown">
@@ -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>
                     <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>
                   </a>
                   <div class="dropdown-menu dropdown-menu-end">
                   <div class="dropdown-menu dropdown-menu-end">
-                    <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="details" data-hx-get="/dashboard/details" 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-post="/dashboard/logs" hx-swap="innerHTML" hx-trigger="click" 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-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-primary" name="AppName" id="update" disabled="">Update</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>
+                    <button class="dropdown-item text-danger" name="AppName" id="uninstall" hx-trigger="click" data-hx-post="/dashboard/uninstall" hx-swap="innerHTML" data-bs-toggle="modal" data-hx-target="#modals-here" data-bs-target="#modals-here">Uninstall</button>
                   </div>
                   </div>
                 </div>
                 </div>
                 <div class="dropdown">
                 <div class="dropdown">
@@ -38,9 +38,9 @@
                     <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>
                     <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>
                   </a>
                   <div class="dropdown-menu dropdown-menu-end">
                   <div class="dropdown-menu dropdown-menu-end">
-                    <button class="dropdown-item text-secondary" data-hx-post="/action/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="/action/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="/modals" 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" data-hx-post="/dashboard/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="/dashboard/reset" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="reset" value="reset">Reset View</button>
+                    <button class="dropdown-item text-secondary" data-hx-post="/dashboard/permissions" name="AppName" 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>
                 </div>
               </div>
               </div>

+ 8 - 8
views/partials/containerSimple.html

@@ -9,16 +9,16 @@
           <div class="ms-auto lh-1">
           <div class="ms-auto lh-1">
             <div class="card-actions btn-actions">
             <div class="card-actions btn-actions">
               <div class="card-actions btn-actions">
               <div class="card-actions btn-actions">
-                <button class="btn-action" title="Start" data-hx-post="/action/start" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
+                <button class="btn-action" title="Start" data-hx-post="/dashboard/start" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
                   <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>
                   <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>
-                <button class="btn-action" title="Stop" data-hx-post="/action/stop" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
+                <button class="btn-action" title="Stop" data-hx-post="/dashboard/stop" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
                   <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>
                   <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>
-                <button class="btn-action" title="Pause" data-hx-post="/action/pause" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
+                <button class="btn-action" title="Pause" data-hx-post="/dashboard/pause" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
                   <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>
                   <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>
-                <button class="btn-action" title="Restart" data-hx-post="/action/restart" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
+                <button class="btn-action" title="Restart" data-hx-post="/dashboard/restart" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
                   <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>                          
                   <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>
                 </button>
                 <div class="dropdown">
                 <div class="dropdown">
@@ -26,8 +26,8 @@
                     <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>
                     <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>
                   </a>
                   <div class="dropdown-menu dropdown-menu-end">
                   <div class="dropdown-menu dropdown-menu-end">
-                    <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="details" data-hx-get="/dashboard/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="/dashboard/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-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-primary" name="AppName" id="update" disabled="">Update</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>
                     <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>
@@ -38,8 +38,8 @@
                     <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>
                     <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>
                   </a>
                   <div class="dropdown-menu dropdown-menu-end">
                   <div class="dropdown-menu dropdown-menu-end">
-                    <button class="dropdown-item text-secondary" data-hx-post="/action/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="/action/reset" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="reset" value="reset">Reset View</button>
+                    <button class="dropdown-item text-secondary" data-hx-post="/dashboard/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="/dashboard/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="/modals" 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>
                 </div>

+ 3 - 3
views/partials/navbar.html

@@ -35,10 +35,10 @@
     </button>
     </button>
     <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0">
     <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0">
       <a href="#">
       <a href="#">
-        <img src="/images/logo.png" alt="DweebUI" title="DweebUI" height="40px">
+        <img src="/img/logo.png" alt="DweebUI" title="DweebUI" height="40px">
       </a>
       </a>
       <a href="#">
       <a href="#">
-        <img src="/images/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">     
+        <img src="/img/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">     
       </a>
       </a>
     </h1>
     </h1>
 
 
@@ -179,7 +179,7 @@
       <div class="container-xl">
       <div class="container-xl">
         <ul class="navbar-nav">
         <ul class="navbar-nav">
           <li class="nav-item">
           <li class="nav-item">
-            <a class="nav-link" href="/">
+            <a class="nav-link" href="/dashboard">
               <span
               <span
                 class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/dashboard -->
                 class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/dashboard -->
                 <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-dashboard" 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 13m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path> <path d="M13.45 11.55l2.05 -2.05"></path> <path d="M6.4 20a9 9 0 1 1 11.2 0z"></path> </svg>
                 <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-dashboard" 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 13m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path> <path d="M13.45 11.55l2.05 -2.05"></path> <path d="M6.4 20a9 9 0 1 1 11.2 0z"></path> </svg>

+ 1 - 1
views/partials/user_permissions.html

@@ -27,7 +27,7 @@
                 </div>
                 </div>
               </div>
               </div>
 
 
-              <input type="hidden" name="username" value="PermissionsUsername">
+              <input type="hidden" name="user" value="PermissionsUsername">
               <input type="hidden" name="container" value="PermissionsContainer">
               <input type="hidden" name="container" value="PermissionsContainer">
 
 
               <div class="row mb-2">
               <div class="row mb-2">

+ 3 - 11
views/portal.html

@@ -44,7 +44,6 @@
                             <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-cpu" 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="M5 5m0 1a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1z"></path><path d="M9 9h6v6h-6z"></path><path d="M3 10h2"></path><path d="M3 14h2"></path><path d="M10 3v2"></path><path d="M14 3v2"></path><path d="M21 10h-2"></path><path d="M21 14h-2"></path><path d="M14 21v-2"></path><path d="M10 21v-2"></path></svg>
                             <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-cpu" 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="M5 5m0 1a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1z"></path><path d="M9 9h6v6h-6z"></path><path d="M3 10h2"></path><path d="M3 14h2"></path><path d="M10 3v2"></path><path d="M14 3v2"></path><path d="M21 10h-2"></path><path d="M21 14h-2"></path><path d="M14 21v-2"></path><path d="M10 21v-2"></path></svg>
                           </span>
                           </span>
                         </div>
                         </div>
-
                         <!-- HTMX -->
                         <!-- HTMX -->
                         <div class="col" name="CPU" id="green">
                         <div class="col" name="CPU" id="green">
                           <div class="font-weight-medium">
                           <div class="font-weight-medium">
@@ -54,7 +53,6 @@
                             <span style="width:20%"><span></span></span>
                             <span style="width:20%"><span></span></span>
                           </div>
                           </div>
                         </div>
                         </div>
-
                       </div>
                       </div>
                     </div>
                     </div>
                   </div>
                   </div>
@@ -69,7 +67,6 @@
                             <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-container" 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="M20 4v.01"></path> <path d="M20 20v.01"></path> <path d="M20 16v.01"></path> <path d="M20 12v.01"></path> <path d="M20 8v.01"></path> <path d="M8 4m0 1a1 1 0 0 1 1 -1h6a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-6a1 1 0 0 1 -1 -1z"></path> <path d="M4 4v.01"></path> <path d="M4 20v.01"></path> <path d="M4 16v.01"></path> <path d="M4 12v.01"></path> <path d="M4 8v.01"></path> </svg>                            
                             <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-container" 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="M20 4v.01"></path> <path d="M20 20v.01"></path> <path d="M20 16v.01"></path> <path d="M20 12v.01"></path> <path d="M20 8v.01"></path> <path d="M8 4m0 1a1 1 0 0 1 1 -1h6a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-6a1 1 0 0 1 -1 -1z"></path> <path d="M4 4v.01"></path> <path d="M4 20v.01"></path> <path d="M4 16v.01"></path> <path d="M4 12v.01"></path> <path d="M4 8v.01"></path> </svg>                            
                           </span>
                           </span>
                         </div>
                         </div>
-
                         <!-- HTMX -->
                         <!-- HTMX -->
                         <div class="col" name="RAM" id="blue">
                         <div class="col" name="RAM" id="blue">
                           <div class="font-weight-medium">
                           <div class="font-weight-medium">
@@ -79,7 +76,6 @@
                             <span style="width:20%"><span></span></span>
                             <span style="width:20%"><span></span></span>
                           </div>
                           </div>
                         </div>
                         </div>
-
                       </div>
                       </div>
                     </div>
                     </div>
                   </div>
                   </div>
@@ -94,7 +90,6 @@
                             <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrows-left-right" 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="M21 17l-18 0"></path> <path d="M6 10l-3 -3l3 -3"></path> <path d="M3 7l18 0"></path> <path d="M18 20l3 -3l-3 -3"></path> </svg>                            
                             <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrows-left-right" 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="M21 17l-18 0"></path> <path d="M6 10l-3 -3l3 -3"></path> <path d="M3 7l18 0"></path> <path d="M18 20l3 -3l-3 -3"></path> </svg>                            
                           </span>
                           </span>
                         </div>
                         </div>
-
                         <!-- HTMX -->
                         <!-- HTMX -->
                         <div class="col" name="NET" id="purple">
                         <div class="col" name="NET" id="purple">
                           <div class="font-weight-medium">
                           <div class="font-weight-medium">
@@ -104,7 +99,6 @@
                             <span style="width:20%"><span></span></span>
                             <span style="width:20%"><span></span></span>
                           </div>
                           </div>
                         </div>
                         </div>
-
                       </div>
                       </div>
                     </div>
                     </div>
                   </div>
                   </div>
@@ -119,7 +113,6 @@
                             <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-database" 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 6m-8 0a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"></path> <path d="M4 6v6a8 3 0 0 0 16 0v-6"></path> <path d="M4 12v6a8 3 0 0 0 16 0v-6"></path></svg>
                             <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-database" 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 6m-8 0a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"></path> <path d="M4 6v6a8 3 0 0 0 16 0v-6"></path> <path d="M4 12v6a8 3 0 0 0 16 0v-6"></path></svg>
                           </span>
                           </span>
                         </div>
                         </div>
-
                         <!-- HTMX -->
                         <!-- HTMX -->
                         <div class="col" name="DISK" id="orange">
                         <div class="col" name="DISK" id="orange">
                           <div class="font-weight-medium">
                           <div class="font-weight-medium">
@@ -129,7 +122,6 @@
                             <span style="width:20%"><span></span></span>
                             <span style="width:20%"><span></span></span>
                           </div>
                           </div>
                         </div>
                         </div>
-
                       </div>
                       </div>
                     </div>
                     </div>
                   </div>
                   </div>
@@ -147,10 +139,10 @@
             </div>
             </div>
 
 
             <!-- HTMX -->
             <!-- HTMX -->
-            <!-- <div class="col-12">
-              <div class="row row-cards" data-hx-get="/new_cards" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
+            <div class="col-12">
+              <div class="row row-cards" data-hx-get="/new_user_cards" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
               </div>
               </div>
-            </div> -->
+            </div>
             
             
             <!-- HTMX Target-->
             <!-- HTMX Target-->
             <div id="modals-here" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">
             <div id="modals-here" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">

+ 2 - 2
views/register.html

@@ -28,12 +28,12 @@
         
         
         <div class="text-center">
         <div class="text-center">
           <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
           <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
-              <img src="/images/logo.png" alt="DweebUI" title="DweebUI" height="100px">
+              <img src="/img/logo.png" alt="DweebUI" title="DweebUI" height="100px">
           </h1>
           </h1>
         </div>
         </div>
         <div class="text-center mb-4">
         <div class="text-center mb-4">
           <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
           <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
-            <img src="/images/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
+            <img src="/img/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
           </h1>
           </h1>
         </div>
         </div>