refactoring dashboard.js / updated compose file
This commit is contained in:
parent
c9da3bd30b
commit
e78afb90ca
6 changed files with 103 additions and 190 deletions
|
@ -7,7 +7,6 @@ import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
|
|||
|
||||
const permissionsModal = readFileSync('./views/modals/permissions.html', 'utf8');
|
||||
const uninstallModal = readFileSync('./views/modals/uninstall.html', 'utf8');
|
||||
const detailsModal = readFileSync('./views/modals/details.html', 'utf8');
|
||||
|
||||
|
||||
// The actual page
|
||||
|
@ -21,7 +20,6 @@ export const Dashboard = (req, res) => {
|
|||
|
||||
// Server metrics (CPU, RAM, TX, RX, DISK)
|
||||
let [ cpu, ram, tx, rx, disk, stats ] = [0, 0, 0, 0, 0, {}];
|
||||
|
||||
let serverMetrics = setInterval(async () => {
|
||||
currentLoad().then(data => {
|
||||
cpu = Math.round(data.currentLoad);
|
||||
|
@ -38,7 +36,6 @@ let serverMetrics = setInterval(async () => {
|
|||
});
|
||||
}, 1000);
|
||||
|
||||
|
||||
export const Stats = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let color = req.header('hx-trigger');
|
||||
|
@ -64,13 +61,73 @@ export const Stats = async (req, res) => {
|
|||
res.send(info);
|
||||
}
|
||||
|
||||
async function containerInfo (containerName) {
|
||||
let container = docker.getContainer(containerName);
|
||||
let info = await container.inspect();
|
||||
let image = info.Config.Image.split('/');
|
||||
let ports_list = [];
|
||||
try {
|
||||
for (const [key, value] of Object.entries(info.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {
|
||||
// no exposed ports
|
||||
}
|
||||
let details = {
|
||||
name: containerName,
|
||||
image: image,
|
||||
service: image[image.length - 1].split(':')[0],
|
||||
state: info.State.Status,
|
||||
external_port: ports_list[0]?.external || 0,
|
||||
internal_port: ports_list[0]?.internal || 0,
|
||||
ports: ports_list,
|
||||
link: 'localhost',
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
let [ hidden, cardList, eventType, containersArray, sentArray ] = [ '', '', '', [], [] ];
|
||||
async function createCard (details) {
|
||||
if (hidden.includes(details.name)) { return;}
|
||||
let shortname = details.name.slice(0, 10) + '...';
|
||||
let trigger = 'data-hx-trigger="load, every 3s"';
|
||||
let state = details.state;
|
||||
let state_color = '';
|
||||
switch (state) {
|
||||
case 'running':
|
||||
state_color = 'green';
|
||||
break;
|
||||
case 'exited':
|
||||
state = 'stopped';
|
||||
state_color = 'red';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
case 'paused':
|
||||
state_color = 'orange';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
}
|
||||
// if (name.startsWith('dweebui')) { disable = 'disabled=""'; }
|
||||
let card = readFileSync('./views/partials/containerCard.html', 'utf8');
|
||||
card = card.replace(/AppName/g, details.name);
|
||||
card = card.replace(/AppShortName/g, shortname);
|
||||
card = card.replace(/AppIcon/g, details.service);
|
||||
card = card.replace(/AppState/g, state);
|
||||
card = card.replace(/StateColor/g, state_color);
|
||||
card = card.replace(/ChartName/g, details.name.replace(/-/g, ''));
|
||||
card = card.replace(/AppNameState/g, `${details.name}State`);
|
||||
card = card.replace(/data-trigger=""/, trigger);
|
||||
return card;
|
||||
}
|
||||
|
||||
// HTMX server-side events
|
||||
let [ hidden, cardList, newCards, containersArray, sentArray, updatesArray ] = [ '', '', '', [], [], [] ];
|
||||
|
||||
|
||||
let cardUpdates = [];
|
||||
let newCards = '';
|
||||
export const SSE = (req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
|
||||
|
||||
|
@ -80,45 +137,49 @@ export const SSE = (req, res) => {
|
|||
await docker.listContainers({ all: true }).then(containers => {
|
||||
containers.forEach(container => {
|
||||
let name = container.Names[0].replace('/', '');
|
||||
if (hidden.includes(name)) {
|
||||
// do nothing
|
||||
} else {
|
||||
if (!hidden.includes(name)) { // if not hidden
|
||||
containersArray.push({ container: name, state: container.State });
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if ((JSON.stringify(containersArray) !== JSON.stringify(sentArray))) {
|
||||
cardList = '';
|
||||
newCards = '';
|
||||
cardUpdates = [];
|
||||
containersArray.forEach(container => {
|
||||
const { container: containerName, state } = container;
|
||||
const existingContainer = sentArray.find(c => c.container === containerName);
|
||||
if (!existingContainer) {
|
||||
addCard(containerName, 'newCards');
|
||||
containerInfo(containerName).then(details => {
|
||||
createCard(details).then(card => {
|
||||
newCards += card;
|
||||
});
|
||||
});
|
||||
res.write(`event: update\n`);
|
||||
res.write(`data: 'update cards'\n\n`);
|
||||
} else if (existingContainer.state !== state) {
|
||||
cardUpdates.push(containerName);
|
||||
updatesArray.push(containerName);
|
||||
}
|
||||
addCard(containerName, 'cardList');
|
||||
containerInfo(containerName).then(details => {
|
||||
createCard(details).then(card => {
|
||||
cardList += card;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sentArray.forEach(container => {
|
||||
const { container: containerName } = container;
|
||||
const existingContainer = containersArray.find(c => c.container === containerName);
|
||||
if (!existingContainer) {
|
||||
cardUpdates.push(containerName);
|
||||
updatesArray.push(containerName);
|
||||
}
|
||||
});
|
||||
|
||||
for (let i = 0; i < cardUpdates.length; i++) {
|
||||
res.write(`event: ${cardUpdates[i]}\n`);
|
||||
for (let i = 0; i < updatesArray.length; i++) {
|
||||
res.write(`event: ${updatesArray[i]}\n`);
|
||||
res.write(`data: 'update cards'\n\n`);
|
||||
}
|
||||
|
||||
updatesArray = [];
|
||||
sentArray = containersArray.slice();
|
||||
}
|
||||
|
||||
|
@ -161,143 +222,21 @@ export const Installs = async (req, res) => {
|
|||
}
|
||||
|
||||
|
||||
async function containerInfo (containerName) {
|
||||
|
||||
let card = readFileSync('./views/partials/containerCard.html', 'utf8');
|
||||
let container = docker.getContainer(containerName);
|
||||
let info = await container.inspect();
|
||||
|
||||
let name = containerName;
|
||||
let state = info.State.Status;
|
||||
let state_color = '';
|
||||
switch (state) {
|
||||
case 'running':
|
||||
state_color = 'green';
|
||||
break;
|
||||
case 'exited':
|
||||
state_color = 'red';
|
||||
state = 'stopped';
|
||||
break;
|
||||
case 'paused':
|
||||
state_color = 'orange';
|
||||
break;
|
||||
}
|
||||
|
||||
let wrapped = name;
|
||||
if (name.length > 13) { wrapped = name.slice(0, 10) + '...'; }
|
||||
if (name.startsWith('dweebui')) { disable = 'disabled=""'; }
|
||||
let disable = "";
|
||||
let chartName = name.replace(/-/g, '');
|
||||
|
||||
let trigger = 'data-hx-trigger="load, every 3s"';
|
||||
if (state != 'running') { trigger = 'data-hx-trigger="load"'; }
|
||||
|
||||
let imageVersion = info.Config.Image.split('/');
|
||||
let service = imageVersion[imageVersion.length - 1].split(':')[0];
|
||||
|
||||
let ports_list = [];
|
||||
try {
|
||||
for (const [key, value] of Object.entries(info.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {}
|
||||
let external_port = ports_list[0]?.external || 0;
|
||||
let internal_port = ports_list[0]?.internal || 0;
|
||||
|
||||
card = card.replace(/AppName/g, name);
|
||||
card = card.replace(/AppShortName/g, wrapped);
|
||||
card = card.replace(/AppIcon/g, service);
|
||||
card = card.replace(/AppState/g, state);
|
||||
card = card.replace(/StateColor/g, state_color);
|
||||
card = card.replace(/ChartName/g, chartName);
|
||||
card = card.replace(/data-trigger=""/, trigger);
|
||||
if (hidden.includes(name)) { card = ''; }
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
|
||||
async function addCard(container, list) {
|
||||
let card = readFileSync('./views/partials/containerCard.html', 'utf8');
|
||||
let containerId = docker.getContainer(container);
|
||||
containerId.inspect().then(data => {
|
||||
|
||||
let state = data.State.Status;
|
||||
let wrapped = container;
|
||||
let disable = "";
|
||||
let chartName = container.replace(/-/g, '');
|
||||
|
||||
// shorten long names
|
||||
if (container.length > 13) { wrapped = container.slice(0, 10) + '...'; }
|
||||
// disable buttons for dweebui
|
||||
if (container.startsWith('dweebui')) { disable = 'disabled=""'; }
|
||||
|
||||
// if ( external_port == undefined ) { external_port = 0; }
|
||||
// if ( internal_port == undefined ) { internal_port = 0; }
|
||||
|
||||
let state_color = 'green';
|
||||
if (state == 'exited') {
|
||||
state = 'stopped';
|
||||
state_color = 'red';
|
||||
} else if (state == 'paused') {
|
||||
state_color = 'orange';
|
||||
}
|
||||
|
||||
let trigger = 'data-hx-trigger="load, every 3s"';
|
||||
if (state != 'running') { trigger = 'data-hx-trigger="load"'; }
|
||||
|
||||
let imageVersion = data.Config.Image.split('/');
|
||||
let service = imageVersion[imageVersion.length - 1].split(':')[0];
|
||||
let ports_list = [];
|
||||
try {
|
||||
for (const [key, value] of Object.entries(data.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {}
|
||||
let external_port = ports_list[0]?.external || 0;
|
||||
let internal_port = ports_list[0]?.internal || 0;
|
||||
card = card.replace(/AppName/g, container);
|
||||
card = card.replace(/AppShortName/g, wrapped);
|
||||
card = card.replace(/AppIcon/g, service);
|
||||
card = card.replace(/AppState/g, state);
|
||||
card = card.replace(/StateColor/g, state_color);
|
||||
card = card.replace(/ChartName/g, chartName);
|
||||
card = card.replace(/AppNameState/g, `${container}State`);
|
||||
card = card.replace(/data-trigger=""/, trigger);
|
||||
if (list == 'newCards') {
|
||||
newCards += card;
|
||||
} else if (list == 'cardList'){
|
||||
cardList += card;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const updateCards = async (req, res) => {
|
||||
console.log('updateCards called');
|
||||
res.send(newCards);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const Containers = async (req, res) => {
|
||||
res.send(cardList);
|
||||
}
|
||||
|
||||
export const Card = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
console.log(`Updated card for ${name}`);
|
||||
let card = await containerInfo(name);
|
||||
let details = await containerInfo(name);
|
||||
let card = await createCard(details);
|
||||
res.send(card);
|
||||
}
|
||||
|
||||
|
@ -398,7 +337,7 @@ export const Reset = async (req, res) => {
|
|||
res.send("ok");
|
||||
}
|
||||
|
||||
export const Modal = async (req, res) => {
|
||||
export const Modals = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let id = req.header('hx-trigger');
|
||||
|
||||
|
@ -414,35 +353,10 @@ export const Modal = async (req, res) => {
|
|||
return;
|
||||
}
|
||||
|
||||
let containerId = docker.getContainer(name);
|
||||
let containerInfo = await containerId.inspect();
|
||||
let ports_list = [];
|
||||
try {
|
||||
for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {}
|
||||
let external_port = ports_list[0]?.external || 0;
|
||||
let internal_port = ports_list[0]?.internal || 0;
|
||||
let modal = readFileSync('./views/modals/details.html', 'utf8');
|
||||
let details = await containerInfo(name);
|
||||
|
||||
let container_info = {
|
||||
name: containerInfo.Name.slice(1),
|
||||
state: containerInfo.State.Status,
|
||||
image: containerInfo.Config.Image,
|
||||
external_port: external_port,
|
||||
internal_port: internal_port,
|
||||
ports: ports_list,
|
||||
link: 'localhost',
|
||||
}
|
||||
|
||||
let details = detailsModal;
|
||||
details = details.replace(/AppName/g, containerInfo.Name.slice(1));
|
||||
details = details.replace(/AppImage/g, containerInfo.Config.Image);
|
||||
res.send(details);
|
||||
modal = modal.replace(/AppName/g, details.name);
|
||||
modal = modal.replace(/AppImage/g, details.image);
|
||||
res.send(modal);
|
||||
}
|
|
@ -7,7 +7,7 @@ services:
|
|||
PORT: 8000
|
||||
SECRET: MrWiskers
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
expose:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- dweebui:/app
|
||||
|
|
|
@ -4,7 +4,7 @@ export const router = express.Router();
|
|||
// Controllers
|
||||
import { Login, submitLogin, Logout } from "../controllers/login.js";
|
||||
import { Register, submitRegister } from "../controllers/register.js";
|
||||
import { Dashboard, Start, Stop, Pause, Restart, Logs, Modal, Stats, Hide, Reset, Chart, Installs, SSE, Card, updateCards } from "../controllers/dashboard.js";
|
||||
import { Dashboard, Start, Stop, Pause, Restart, Logs, Modals, Stats, Hide, Reset, Chart, Installs, SSE, Card, updateCards, Containers } from "../controllers/dashboard.js";
|
||||
import { Apps, appSearch } from "../controllers/apps.js";
|
||||
import { Users } from "../controllers/users.js";
|
||||
import { Images, removeImage } from "../controllers/images.js";
|
||||
|
@ -33,13 +33,14 @@ router.post("/stop", auth, Stop);
|
|||
router.post("/pause", auth, Pause);
|
||||
router.post("/restart", auth, Restart);
|
||||
router.get("/logs", auth, Logs);
|
||||
router.get ("/modal", auth, Modal);
|
||||
router.get ("/modals", auth, Modals);
|
||||
router.get("/stats", auth, Stats);
|
||||
router.post("/hide", auth, Hide);
|
||||
router.post("/reset", auth, Reset);
|
||||
router.get("/chart", auth, Chart);
|
||||
router.get("/installs", auth, Installs);
|
||||
router.get("/sse_event", auth, SSE);
|
||||
router.get("/containers", auth, Containers);
|
||||
router.get("/card", auth, Card);
|
||||
router.get("/new_cards", auth, updateCards);
|
||||
|
||||
|
|
|
@ -30,9 +30,7 @@
|
|||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Apps
|
||||
</h2>
|
||||
|
||||
<div class="text-secondary mt-1"><%= list_start %> - <%= list_end %> of <%= app_count %> Apps.</div>
|
||||
</div>
|
||||
<!-- Page title actions -->
|
||||
|
|
|
@ -142,13 +142,13 @@
|
|||
|
||||
<!-- HTMX -->
|
||||
<div class="col-12">
|
||||
<div class="row row-cards" id="containers">
|
||||
<div class="row row-cards" id="containers" data-hx-get="/containers" data-hx-trigger="load" data-hx-swap="innerHTML">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HTMX -->
|
||||
<div class="col-12">
|
||||
<div class="row row-cards" data-hx-get="/new_cards" data-hx-trigger="load, sse:update" data-hx-swap="beforeend" hx-target="#containers">
|
||||
<div class="row row-cards" data-hx-get="/new_cards" data-hx-trigger="sse:update" data-hx-swap="beforeend" hx-target="#containers">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -26,11 +26,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-get="/modal" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-get="/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="logs" data-hx-get="/logs" hx-swap="innerHTML" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="edit">Edit</button>
|
||||
<button class="dropdown-item text-primary" name="AppName" id="update" disabled="">Update</button>
|
||||
<button class="dropdown-item text-danger" name="AppName" id="uninstall" hx-trigger="click" data-hx-get="/modal" hx-swap="innerHTML" data-bs-toggle="modal" data-hx-target="#modals-here" data-bs-target="#modals-here">Uninstall</button>
|
||||
<button class="dropdown-item text-danger" name="AppName" id="uninstall" hx-trigger="click" data-hx-get="/modals" hx-swap="innerHTML" data-bs-toggle="modal" data-hx-target="#modals-here" data-bs-target="#modals-here">Uninstall</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
|
@ -40,7 +40,7 @@
|
|||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item text-secondary" data-hx-post="/hide" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="hide" value="hide">Hide</button>
|
||||
<button class="dropdown-item text-secondary" data-hx-post="/reset" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="reset" value="reset">Reset View</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="permissions" data-hx-get="/modal" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="permissions" data-hx-get="/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,7 +48,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-baseline">
|
||||
<div class="h1 me-2" title="AppName" style="margin-bottom: 0;">
|
||||
<div class="h1 me-2" title="AppName">
|
||||
<a href="http://${link}:${external_port}" target="_blank">
|
||||
AppShortName
|
||||
</a>
|
||||
|
|
Loading…
Add table
Reference in a new issue