|
@@ -1,57 +1,46 @@
|
|
|
import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
|
|
|
-import { docker, containerInfo, containerLogs, containerStats, GetContainerLists } from '../utils/docker.js';
|
|
|
+import { docker, containerInfo, containerLogs, GetContainerLists, containerStats, trigger_docker_event } from '../utils/docker.js';
|
|
|
import { readFileSync } from 'fs';
|
|
|
-import { User, Permission, ServerSettings, ContainerLists, Container } from '../database/config.js';
|
|
|
+import { User, Permission, ServerSettings, ContainerLists, Container } from '../db/config.js';
|
|
|
import { Alert, Navbar, Footer, Capitalize } from '../utils/system.js';
|
|
|
import { Op } from 'sequelize';
|
|
|
|
|
|
-let [ hidden, alert, stats ] = [ '', '', '', {} ];
|
|
|
-let container_link = 'http://localhost';
|
|
|
-
|
|
|
-
|
|
|
|
|
|
// Dashboard
|
|
|
export const Dashboard = async function (req, res) {
|
|
|
|
|
|
- let host = req.params.host || 1;
|
|
|
- req.session.host = `${host}`;
|
|
|
+ console.log(`[Dashboard] ${req.session.username}`);
|
|
|
|
|
|
+ let username = req.session.username;
|
|
|
+ let userID = req.session.userID;
|
|
|
+ let role = req.session.role;
|
|
|
+ let host = req.session.host;
|
|
|
+
|
|
|
// Create the lists needed for the dashboard
|
|
|
const [list, created] = await ContainerLists.findOrCreate({
|
|
|
- where: { userID: req.session.userID },
|
|
|
- defaults: {
|
|
|
- userID: req.session.userID,
|
|
|
- username: req.session.username,
|
|
|
- containers: '[]',
|
|
|
- new: '[]',
|
|
|
- updates: '[]',
|
|
|
- sent: '[]',
|
|
|
- },
|
|
|
+ where: { userID: userID },
|
|
|
+ defaults: { userID: userID, username: username, containers: '[]', new: '[]', updates: '[]', sent: '[]', },
|
|
|
});
|
|
|
- if (created) { console.log(`New entry created in ContainerLists for ${req.session.username}`); }
|
|
|
+
|
|
|
|
|
|
res.render("dashboard",{
|
|
|
alert: '',
|
|
|
- username: req.session.username,
|
|
|
- role: req.session.role,
|
|
|
+ username: username,
|
|
|
+ role: role,
|
|
|
navbar: await Navbar(req),
|
|
|
footer: await Footer(req),
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
-
|
|
|
-
|
|
|
// Dashboard search
|
|
|
export const searchDashboard = async function (req, res) {
|
|
|
- console.log(`[Search] ${req.body.search}`);
|
|
|
+ // console.log(`[Search] ${req.body.search}`);
|
|
|
res.send('ok');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
-
|
|
|
-
|
|
|
// Server metrics (CPU, RAM, TX, RX, DISK)
|
|
|
export const ServerMetrics = async (req, res) => {
|
|
|
let name = req.header('hx-trigger-name');
|
|
@@ -81,6 +70,7 @@ export const ServerMetrics = async (req, res) => {
|
|
|
}
|
|
|
|
|
|
|
|
|
+
|
|
|
async function userCards (req) {
|
|
|
|
|
|
let container_list = [];
|
|
@@ -108,196 +98,58 @@ async function userCards (req) {
|
|
|
return container_list;
|
|
|
}
|
|
|
|
|
|
-// Container actions (start, stop, pause, restart, hide)
|
|
|
-export const ContainerAction = async (req, res) => {
|
|
|
-
|
|
|
- let container_name = req.header('hx-trigger-name');
|
|
|
- let containerID = req.params.containerid;
|
|
|
- let action = req.params.action;
|
|
|
-
|
|
|
- console.log(`[Action] ${action} ${container_name} ${containerID}`);
|
|
|
-
|
|
|
- if (action == 'reset') {
|
|
|
- console.log('Resetting view');
|
|
|
- await Permission.update({ hide: false }, { where: { userID: req.session.userID } });
|
|
|
- res.redirect('/dashboard');
|
|
|
- return;
|
|
|
- }
|
|
|
- else if (action == 'logs') {
|
|
|
- let logs = await containerLogs(containerID);
|
|
|
- let modal = readFileSync('./views/partials/logs.html', 'utf8');
|
|
|
- modal = modal.replace(/AppName/g, container_name);
|
|
|
- modal = modal.replace(/ContainerID/g, containerID);
|
|
|
- modal = modal.replace(/ContainerLogs/g, logs);
|
|
|
-
|
|
|
- res.send(modal);
|
|
|
- return;
|
|
|
- }
|
|
|
- else if (action == 'details') {
|
|
|
- let container = await containerInfo(containerID);
|
|
|
- let modal = readFileSync('./views/partials/details.html', 'utf8');
|
|
|
- modal = modal.replace(/AppName/g, container.containerName);
|
|
|
- modal = modal.replace(/AppImage/g, container.containerImage);
|
|
|
- res.send(modal);
|
|
|
- return;
|
|
|
- }
|
|
|
- else if (action == 'uninstall') {
|
|
|
- let modal = readFileSync('./views/partials/uninstall.html', 'utf8');
|
|
|
- modal = modal.replace(/AppName/g, container_name);
|
|
|
- modal = modal.replace(/ContainerID/g, containerID);
|
|
|
- res.send(modal);
|
|
|
- return;
|
|
|
- }
|
|
|
- else if (action == 'link_modal') {
|
|
|
- const [container, created] = await Container.findOrCreate({ where: { containerID: containerID }, defaults: { containerName: container_name, containerID: containerID, link: '' } });
|
|
|
- let modal = readFileSync('./views/partials/link.html', 'utf8');
|
|
|
- modal = modal.replace(/AppName/g, container_name);
|
|
|
- modal = modal.replace(/ContainerID/g, containerID);
|
|
|
- modal = modal.replace(/AppLink/g, container.link);
|
|
|
- res.send(modal);
|
|
|
- return;
|
|
|
- } else if (action == 'update_link') {
|
|
|
- let url = req.body.url;
|
|
|
- console.log(url);
|
|
|
- // find the container entry with the containerID and userID
|
|
|
- let container = await Container.findOne({ where: { containerID: containerID } });
|
|
|
- container.update({ link: url });
|
|
|
- res.send('ok');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Inspect the container
|
|
|
- let info = docker.getContainer(containerID);
|
|
|
- let container = await info.inspect();
|
|
|
- let containerState = container.State.Status;
|
|
|
-
|
|
|
- // Displays container state (starting, stopping, restarting, pausing)
|
|
|
- function status (state) {
|
|
|
- return(`<div class="text-yellow d-inline-flex align-items-center lh-1 ms-auto" id="AltIDState">
|
|
|
- <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>
|
|
|
- <strong>${state}</strong>
|
|
|
- </div>`);
|
|
|
- }
|
|
|
-
|
|
|
- // Perform the action
|
|
|
- if ((action == 'start') && (containerState == 'exited')) {
|
|
|
- info.start();
|
|
|
- res.send(status('starting'));
|
|
|
- } else if ((action == 'start') && (containerState == 'paused')) {
|
|
|
- info.unpause();
|
|
|
- res.send(status('starting'));
|
|
|
- } else if ((action == 'stop') && (containerState != 'exited')) {
|
|
|
- info.stop();
|
|
|
- res.send(status('stopping'));
|
|
|
- } else if ((action == 'pause') && (containerState == 'paused')) {
|
|
|
- info.unpause();
|
|
|
- res.send(status('starting'));
|
|
|
- } else if ((action == 'pause') && (containerState == 'running')) {
|
|
|
- info.pause();
|
|
|
- res.send(status('pausing'));
|
|
|
- } else if (action == 'restart') {
|
|
|
- info.restart();
|
|
|
- res.send(status('restarting'));
|
|
|
- } else if (action == 'hide') {
|
|
|
- let exists = await Permission.findOne({ where: { containerID: containerID, userID: req.session.userID }});
|
|
|
- if (!exists) { const newPermission = await Permission.create({ containerName: container_name, containerID: containerID, username: req.session.username, userID: req.session.userID, hide: true }); }
|
|
|
- else { exists.update({ hide: true }); }
|
|
|
- res.send('ok');
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
async function createCard (details) {
|
|
|
|
|
|
- // let trigger = 'data-hx-trigger="load, every 3s"';
|
|
|
+ let container_card = readFileSync('./views/partials/container_card.html', 'utf8');
|
|
|
|
|
|
let containerName = details.containerName;
|
|
|
- if (containerName.length > 13) { containerName = containerName.substring(0, 13) + '...'; }
|
|
|
- let containerTitle = Capitalize(containerName);
|
|
|
-
|
|
|
- let container_link = '';
|
|
|
- let container = await Container.findOne({ where: { containerID: details.containerID } });
|
|
|
- container_link = container.link || '#';
|
|
|
-
|
|
|
- let titleLink = `<a href="${container_link}" class="nav-link" target="_blank">${containerTitle}</a>`;
|
|
|
-
|
|
|
+ let containerTitle = Capitalize(containerName); if (containerTitle.length > 14) { containerTitle = containerTitle.substring(0, 14) + '...'; }
|
|
|
let containerID = details.containerID;
|
|
|
let containerState = details.containerState;
|
|
|
let containerService = details.containerService;
|
|
|
+ let AltID = `a${containerID}`;
|
|
|
+
|
|
|
+ let chart_trigger = `<div name="${containerName}" id="${AltID}info" hx-get="/dashboard/view/chart/${containerID}" hx-swap="outerHTML" hx-trigger="every 3s" hx-target="#${AltID}info">
|
|
|
+ </div>`;
|
|
|
+
|
|
|
let containerStateColor = '';
|
|
|
+ switch (containerState) {
|
|
|
+ case 'running': containerStateColor = 'green'; break;
|
|
|
+ case 'exited': containerStateColor = 'red'; containerState = 'stopped'; chart_trigger = ''; break;
|
|
|
+ case 'paused': containerStateColor = 'orange'; break;
|
|
|
+ default: containerStateColor = 'blue'; break;
|
|
|
+ }
|
|
|
|
|
|
- if (containerState == 'running') { containerStateColor = 'green'; }
|
|
|
- else if (containerState == 'exited') { containerStateColor = 'red'; containerState = 'stopped'; }
|
|
|
- else if (containerState == 'paused') { containerStateColor = 'orange'; }
|
|
|
- else { containerStateColor = 'blue'; }
|
|
|
+ let [title_link, created] = await Container.findOrCreate({ where: { containerID: details.containerID }, defaults: { containerName: containerName, containerID: containerID, link: '', cpu: '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]', ram: '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]' } });
|
|
|
+ if (title_link.link != '') { title_link = `<a href="${title_link.link}" class="nav-link" target="_blank">${containerTitle}</a>`; }
|
|
|
+ else { title_link = containerTitle; }
|
|
|
+
|
|
|
+ let [port_link, created_link] = await ServerSettings.findOrCreate({ where: { key: 'custom_link' }, defaults: { key: 'custom_link', value: 'http://localhost' } });
|
|
|
+ port_link = port_link.value;
|
|
|
|
|
|
- let container_card = readFileSync('./views/partials/container_card.html', 'utf8');
|
|
|
+ let exposed_ports = '';
|
|
|
+ for (let i = 0; i < details.ports.length; i++) {
|
|
|
+ if (details.ports[i].external != '' && details.ports[i].protocol != 'udp') { exposed_ports += `<a href="${port_link}:${details.ports[i].external}" target="_blank" style="color: inherit; text-decoration: none;"> ${details.ports[i].external}</a> `; }
|
|
|
+ }
|
|
|
|
|
|
- container_card = container_card.replace(/ContainerID/g, containerID);
|
|
|
- container_card = container_card.replace(/AltID/g, 'a' + containerID);
|
|
|
- container_card = container_card.replace(/TitleLink/g, titleLink);
|
|
|
container_card = container_card.replace(/AppName/g, containerName);
|
|
|
+ container_card = container_card.replace(/ContainerID/g, containerID);
|
|
|
+ container_card = container_card.replaceAll(/AltID/g, AltID);
|
|
|
+ container_card = container_card.replace(/AppPorts/g, exposed_ports);
|
|
|
+ container_card = container_card.replace(/TitleLink/g, title_link);
|
|
|
container_card = container_card.replace(/AppTitle/g, containerTitle);
|
|
|
container_card = container_card.replace(/AppService/g, containerService);
|
|
|
container_card = container_card.replace(/AppState/g, containerState);
|
|
|
container_card = container_card.replace(/StateColor/g, containerStateColor);
|
|
|
-
|
|
|
+ container_card = container_card.replace(/ChartTrigger/g, chart_trigger);
|
|
|
|
|
|
- if (details.external_port == 0 && details.internal_port == 0) {
|
|
|
- container_card = container_card.replace(/AppPorts/g, ``);
|
|
|
- } else {
|
|
|
- container_card = container_card.replace(/AppPorts/g, `<a href="${container_link}:${details.external_port}" target="_blank" style="color: inherit; text-decoration: none;"> ${details.external_port}:${details.internal_port}</a>`);
|
|
|
- }
|
|
|
return container_card;
|
|
|
}
|
|
|
|
|
|
|
|
|
-
|
|
|
-export const UpdateCard = async function (req, res) {
|
|
|
-
|
|
|
- let containerID = req.params.containerid;
|
|
|
-
|
|
|
- let lists = await ContainerLists.findOne({ where: { userID: req.session.userID }, attributes: ['containers'] });
|
|
|
- let container_list = JSON.parse(lists.containers);
|
|
|
-
|
|
|
- let found = container_list.find(c => c.containerID === containerID);
|
|
|
- if (!found) { res.send(''); return; }
|
|
|
- let details = await containerInfo(containerID);
|
|
|
- let card = await createCard(details);
|
|
|
- res.send(card);
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-export const CardList = async function (req, res) {
|
|
|
- let cards_list = '';
|
|
|
- // Check if there are any new cards in queue.
|
|
|
- let new_cards = await ContainerLists.findOne({ where: { userID: req.session.userID }, attributes: ['new'] });
|
|
|
- let new_list = JSON.parse(new_cards.new);
|
|
|
- // Check what containers the user should see.
|
|
|
- let containers = await userCards(req);
|
|
|
- // Create the cards.
|
|
|
- if (new_list.length > 0) {
|
|
|
- for (let i = 0; i < new_list.length; i++) {
|
|
|
- let details = await containerInfo(new_list[i]);
|
|
|
- let card = await createCard(details);
|
|
|
- cards_list += card;
|
|
|
- }
|
|
|
- } else {
|
|
|
- for (let i = 0; i < containers.length; i++) {
|
|
|
- let details = await containerInfo(containers[i].containerID);
|
|
|
- let card = await createCard(details);
|
|
|
- cards_list += card;
|
|
|
- }
|
|
|
- }
|
|
|
- // Update lists, clear the queue, and send the cards.
|
|
|
- await ContainerLists.update({ containers: JSON.stringify(containers), sent: JSON.stringify(containers), new: '[]' }, { where: { userID: req.session.userID } });
|
|
|
- res.send(cards_list);
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
// HTMX - Server-side events
|
|
|
export const SSE = async (req, res) => {
|
|
|
|
|
@@ -309,7 +161,6 @@ export const SSE = async (req, res) => {
|
|
|
|
|
|
async function eventCheck () {
|
|
|
|
|
|
-
|
|
|
let list = await ContainerLists.findOne({ where: { userID: req.session.userID }, attributes: ['sent'] });
|
|
|
let container_list = await userCards(req);
|
|
|
|
|
@@ -319,7 +170,8 @@ export const SSE = async (req, res) => {
|
|
|
sent_cards = JSON.parse(list.sent);
|
|
|
|
|
|
if (JSON.stringify(container_list) == list.sent) { return; }
|
|
|
- console.log(`Update for ${req.session.username}`);
|
|
|
+
|
|
|
+ // console.log(`Update for ${req.session.username}`);
|
|
|
|
|
|
// loop through the containers list to see if any new containers have been added or changed
|
|
|
container_list.forEach(container => {
|
|
@@ -356,12 +208,305 @@ export const SSE = async (req, res) => {
|
|
|
|
|
|
docker.getEvents({}, async function (err, data) {
|
|
|
data.on('data', async function () {
|
|
|
- console.log(`[Docker Event]`);
|
|
|
await eventCheck();
|
|
|
});
|
|
|
});
|
|
|
|
|
|
req.on('close', async () => {
|
|
|
- // Nothing
|
|
|
});
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+export const DashboardView = async function (req, res) {
|
|
|
+
|
|
|
+ let container_name = req.header('hx-trigger-name');
|
|
|
+ let view = req.params.view;
|
|
|
+ let containerID = req.params.id;
|
|
|
+ let AltID = `a${containerID}`;
|
|
|
+
|
|
|
+ // console.log(`[container_name] ${container_name} [view] ${view} [containerID] ${containerID}`);
|
|
|
+
|
|
|
+ // Container CPU and RAM chart
|
|
|
+
|
|
|
+ if (view == 'chart') {
|
|
|
+ let container = await Container.findOne({ where: { containerID: containerID } });
|
|
|
+ // Get the cpu and ram stats, remove the oldest entry, add the newest stats, then update container info.
|
|
|
+ let stats = await containerStats(containerID);
|
|
|
+ let cpu = JSON.parse(container.cpu); cpu.shift(); cpu.push(stats.cpu);
|
|
|
+ let ram = JSON.parse(container.ram); ram.shift(); ram.push(stats.ram);
|
|
|
+ container.update({ cpu: JSON.stringify(cpu), ram: JSON.stringify(ram) });
|
|
|
+
|
|
|
+ let chartData = `<div name="${container_name}" id="${AltID}info" hx-get="/dashboard/view/chart/${containerID}" hx-swap="outerHTML" hx-trigger="every 3s" hx-target="#${AltID}info">
|
|
|
+ <script>
|
|
|
+ ${AltID}chart.updateSeries([{
|
|
|
+ name: 'CPU',
|
|
|
+ data: ${container.cpu}
|
|
|
+ }, {
|
|
|
+ name: 'RAM',
|
|
|
+ data: ${container.ram}
|
|
|
+ }]);
|
|
|
+ </script>
|
|
|
+ </div>`;
|
|
|
+ res.send(chartData);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Permissions modal
|
|
|
+
|
|
|
+ if (view == 'permissions') {
|
|
|
+ let title = Capitalize(container_name);
|
|
|
+ let users = await User.findAll({ attributes: ['username', 'userID'] });
|
|
|
+
|
|
|
+ let modal =`<div class="modal-header">
|
|
|
+ <h5 class="modal-title">${title} Permissions</h5>
|
|
|
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
|
+ </div>
|
|
|
+ <div class="modal-body"><div class="accordion" id="accordion-example">`;
|
|
|
+
|
|
|
+ for (let i = 0; i < users.length; i++) {
|
|
|
+ if (users.length == 1) { modal += 'No other users.'; break; }
|
|
|
+ // Skip the admin user.
|
|
|
+ else if (i == 0) { continue; }
|
|
|
+ let exists = await Permission.findOne({ where: {containerID: containerID, userID: users[i].userID}});
|
|
|
+ if (!exists) { await Permission.create({ containerName: container_name, containerID: containerID, userID: users[i].userID, username: users[i].username}); }
|
|
|
+ let permissions = await Permission.findOne({ where: {containerID: containerID, userID: users[i].userID}});
|
|
|
+ let user_permissions = readFileSync('./views/partials/permissions.html', 'utf8');
|
|
|
+ if (permissions.uninstall == true && permissions.edit == true && permissions.upgrade == true && permissions.start == true && permissions.stop == true && permissions.pause == true && permissions.restart == true && permissions.logs == true && permissions.view == true) { user_permissions = user_permissions.replace(/data-AllCheck/g, 'checked'); }
|
|
|
+ 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'); }
|
|
|
+ if (permissions.view == true) { user_permissions = user_permissions.replace(/data-ViewCheck/g, 'checked'); }
|
|
|
+ user_permissions = user_permissions.replace(/Entry/g, i);
|
|
|
+ user_permissions = user_permissions.replace(/Entry/g, i);
|
|
|
+ user_permissions = user_permissions.replace(/Entry/g, i);
|
|
|
+ user_permissions = user_permissions.replace(/container_id/g, containerID);
|
|
|
+ user_permissions = user_permissions.replace(/container_name/g, container_name);
|
|
|
+ user_permissions = user_permissions.replace(/user_id/g, users[i].userID);
|
|
|
+ user_permissions = user_permissions.replace(/Username/g, users[i].username);
|
|
|
+ modal += user_permissions;
|
|
|
+ }
|
|
|
+ modal += `</div></div>
|
|
|
+ <div class="modal-footer">
|
|
|
+
|
|
|
+ <form id="reset_permissions" class="me-auto">
|
|
|
+ <input type="hidden" name="containerID" value="${containerID}">
|
|
|
+ <button type="button" class="btn btn-danger" data-bs-dismiss="modal" name="reset_permissions" id="submit" hx-post="/dashboard/action/update_permissions/${containerID}" hx-confirm="Are you sure you want to reset permissions for this container?">Reset</button>
|
|
|
+ </form>
|
|
|
+
|
|
|
+ <button type="button" class="btn" data-bs-dismiss="modal">Close</button>
|
|
|
+ </div>`
|
|
|
+ res.send(modal);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Logs modal
|
|
|
+
|
|
|
+ if (view == 'logs') {
|
|
|
+ let logs = await containerLogs(containerID);
|
|
|
+ let modal = readFileSync('./views/partials/logs.html', 'utf8');
|
|
|
+ modal = modal.replace(/AppName/g, container_name);
|
|
|
+ modal = modal.replace(/ContainerID/g, containerID);
|
|
|
+ modal = modal.replace(/ContainerLogs/g, logs);
|
|
|
+ res.send(modal);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Details modal
|
|
|
+
|
|
|
+ if (view == 'details') {
|
|
|
+ let container = await containerInfo(containerID);
|
|
|
+ let modal = readFileSync('./views/partials/details.html', 'utf8');
|
|
|
+ modal = modal.replace(/AppName/g, container.containerName);
|
|
|
+ modal = modal.replace(/AppImage/g, container.containerImage);
|
|
|
+ for (let i = 0; i <= 6; i++) {
|
|
|
+ modal = modal.replaceAll(`Port${i}Check`, container.ports[i]?.check || '');
|
|
|
+ modal = modal.replaceAll(`Port${i}External`, container.ports[i]?.external || '');
|
|
|
+ modal = modal.replaceAll(`Port${i}Internal`, container.ports[i]?.internal || '');
|
|
|
+ modal = modal.replaceAll(`Port${i}Protocol`, container.ports[i]?.protocol || '');
|
|
|
+ }
|
|
|
+ for (let i = 0; i <= 6; i++) {
|
|
|
+ modal = modal.replaceAll(`Vol${i}Source`, container.volumes[i]?.Source || '');
|
|
|
+ modal = modal.replaceAll(`Vol${i}Destination`, container.volumes[i]?.Destination || '');
|
|
|
+ modal = modal.replaceAll(`Vol${i}RW`, container.volumes[i]?.RW || '');
|
|
|
+ }
|
|
|
+ for (let i = 0; i <= 19; i++) {
|
|
|
+ modal = modal.replaceAll(`Label${i}Key`, Object.keys(container.labels)[i] || '');
|
|
|
+ modal = modal.replaceAll(`Label${i}Value`, Object.values(container.labels)[i] || '');
|
|
|
+ }
|
|
|
+ for (let i = 0; i <= 19; i++) {
|
|
|
+ modal = modal.replaceAll(`Env${i}Key`, container.env[i]?.split('=')[0] || '');
|
|
|
+ modal = modal.replaceAll(`Env${i}Value`, container.env[i]?.split('=')[1] || '');
|
|
|
+ }
|
|
|
+ res.send(modal);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Uninstall modal
|
|
|
+
|
|
|
+ if (view == 'uninstall') {
|
|
|
+ let modal = readFileSync('./views/partials/uninstall.html', 'utf8');
|
|
|
+ modal = modal.replace(/AppName/g, container_name);
|
|
|
+ modal = modal.replace(/ContainerID/g, containerID);
|
|
|
+ res.send(modal);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update link modal
|
|
|
+
|
|
|
+ if (view == 'link_modal') {
|
|
|
+ const [container, created] = await Container.findOrCreate({ where: { containerID: containerID }, defaults: { containerName: container_name, containerID: containerID, link: '' } });
|
|
|
+ let modal = readFileSync('./views/partials/link.html', 'utf8');
|
|
|
+ modal = modal.replace(/AppName/g, container_name);
|
|
|
+ modal = modal.replace(/ContainerID/g, containerID);
|
|
|
+ modal = modal.replace(/AppLink/g, container.link);
|
|
|
+ res.send(modal);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update container_card
|
|
|
+
|
|
|
+ if (view == 'update_card'){
|
|
|
+
|
|
|
+ let lists = await ContainerLists.findOne({ where: { userID: req.session.userID }, attributes: ['containers'] });
|
|
|
+ let container_list = JSON.parse(lists.containers);
|
|
|
+
|
|
|
+ let found = container_list.find(c => c.containerID === containerID);
|
|
|
+ if (!found) { res.send(''); return; }
|
|
|
+ let details = await containerInfo(containerID);
|
|
|
+ let card = await createCard(details);
|
|
|
+ res.send(card);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Generate list of container_cards for the dashboard
|
|
|
+
|
|
|
+ if (view == 'card_list'){
|
|
|
+ let cards_list = '';
|
|
|
+ // Check if there are any new cards in queue.
|
|
|
+ let new_cards = await ContainerLists.findOne({ where: { userID: req.session.userID }, attributes: ['new'] });
|
|
|
+ let new_list = JSON.parse(new_cards.new);
|
|
|
+ // Check what containers the user should see.
|
|
|
+ let containers = await userCards(req);
|
|
|
+ // Create the cards.
|
|
|
+ if (new_list.length > 0) {
|
|
|
+ for (let i = 0; i < new_list.length; i++) {
|
|
|
+ let details = await containerInfo(new_list[i]);
|
|
|
+ let card = await createCard(details);
|
|
|
+ cards_list += card;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ for (let i = 0; i < containers.length; i++) {
|
|
|
+ let details = await containerInfo(containers[i].containerID);
|
|
|
+ let card = await createCard(details);
|
|
|
+ cards_list += card;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Update lists, clear the queue, and send the cards.
|
|
|
+ await ContainerLists.update({ containers: JSON.stringify(containers), sent: JSON.stringify(containers), new: '[]' }, { where: { userID: req.session.userID } });
|
|
|
+ res.send(cards_list);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+// Container actions (start, stop, pause, restart, hide)
|
|
|
+export const DashboardAction = async (req, res) => {
|
|
|
+
|
|
|
+ // let trigger_id = req.header('hx-trigger');
|
|
|
+ let container_name = req.header('hx-trigger-name');
|
|
|
+ let action = req.params.action;
|
|
|
+ let containerID = req.params.id;
|
|
|
+
|
|
|
+ // console.log(`[container_name] ${container_name} [action] ${action} [containerID] ${containerID}`);
|
|
|
+
|
|
|
+ if (action == 'reset') {
|
|
|
+ await Permission.update({ hide: false }, { where: { userID: req.session.userID } });
|
|
|
+ res.redirect('/dashboard');
|
|
|
+ return;
|
|
|
+ } else if (action == 'update_link') {
|
|
|
+ let url = req.body.url;
|
|
|
+ let container = await Container.findOne({ where: { containerID: containerID } });
|
|
|
+ container.update({ link: url });
|
|
|
+ res.redirect('/dashboard');
|
|
|
+ return;
|
|
|
+ } else if (action == 'update_permissions') {
|
|
|
+ let { userID, username, reset_permissions, select } = req.body;
|
|
|
+ let button_id = req.header('hx-trigger');
|
|
|
+ // Replaces the update button if it's been pressed.
|
|
|
+ if (button_id == 'confirmed') { res.send(`<button class="btn" type="button" id="submit" hx-post="/dashboard/action/update_permissions/${containerID}" hx-swap="outerHTML">Update </button>`); return; }
|
|
|
+ // Reset all permissions for the container.
|
|
|
+ if (reset_permissions == '') { await Permission.update({ uninstall: false, edit: false, upgrade: false, start: false, stop: false, pause: false, restart: false, logs: false, view: false }, { where: { containerID: containerID } }); trigger_docker_event(); return; }
|
|
|
+ // Make sure req.body[select] is an array
|
|
|
+ if (typeof req.body[select] == 'string') { req.body[select] = [req.body[select]]; }
|
|
|
+
|
|
|
+ await Permission.update({ uninstall: false, edit: false, upgrade: false, start: false, stop: false, pause: false, restart: false, logs: false, view: false }, { where: { containerID: containerID, userID: userID } });
|
|
|
+ if (req.body[select]) {
|
|
|
+ for (let i = 0; i < req.body[select].length; i++) {
|
|
|
+ let permissions = req.body[select][i];
|
|
|
+ if (permissions == 'uninstall') { await Permission.update({ uninstall: true }, { where: {containerID: containerID, userID: userID}}); }
|
|
|
+ if (permissions == 'edit') { await Permission.update({ edit: true }, { where: {containerID: containerID, userID: userID}}); }
|
|
|
+ if (permissions == 'upgrade') { await Permission.update({ upgrade: true }, { where: {containerID: containerID, userID: userID}}); }
|
|
|
+ if (permissions == 'start') { await Permission.update({ start: true }, { where: {containerID: containerID, userID: userID}}); }
|
|
|
+ if (permissions == 'stop') { await Permission.update({ stop: true }, { where: {containerID: containerID, userID: userID}}); }
|
|
|
+ if (permissions == 'pause') { await Permission.update({ pause: true }, { where: {containerID: containerID, userID: userID}}); }
|
|
|
+ if (permissions == 'restart') { await Permission.update({ restart: true }, { where: {containerID: containerID, userID: userID}}); }
|
|
|
+ if (permissions == 'logs') { await Permission.update({ logs: true }, { where: {containerID: containerID, userID: userID}}); }
|
|
|
+ if (permissions == 'view') { await Permission.update({ view: true }, { where: {containerID: containerID, userID: userID}}); }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ trigger_docker_event();
|
|
|
+ res.send(`<button class="btn" type="button" id="confirmed" hx-post="/dashboard/action/update_permissions/${containerID}" hx-swap="outerHTML" hx-trigger="load delay:1s">Update ✔️</button>`);
|
|
|
+ return;
|
|
|
+ } else if (action == 'switch_host') {
|
|
|
+ req.session.host = req.body.host;
|
|
|
+ console.log(`Switched to host ${req.session.host}`);
|
|
|
+ res.redirect('/dashboard');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // Inspect the container
|
|
|
+ let info = docker.getContainer(containerID);
|
|
|
+ let container = await info.inspect();
|
|
|
+ let containerState = container.State.Status;
|
|
|
+
|
|
|
+ // Displays container state (starting, stopping, restarting, pausing)
|
|
|
+ function status (state) {
|
|
|
+ return(`<div class="text-yellow d-inline-flex align-items-center lh-1 ms-auto" id="AltIDState">
|
|
|
+ <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>
|
|
|
+ <strong>${state}</strong>
|
|
|
+ </div>`);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((action == 'start') && (containerState == 'exited')) {
|
|
|
+ info.start();
|
|
|
+ res.send(status('starting'));
|
|
|
+ } else if ((action == 'start') && (containerState == 'paused')) {
|
|
|
+ info.unpause();
|
|
|
+ res.send(status('starting'));
|
|
|
+ } else if ((action == 'stop') && (containerState != 'exited')) {
|
|
|
+ info.stop();
|
|
|
+ res.send(status('stopping'));
|
|
|
+ } else if ((action == 'pause') && (containerState == 'paused')) {
|
|
|
+ info.unpause();
|
|
|
+ res.send(status('starting'));
|
|
|
+ } else if ((action == 'pause') && (containerState == 'running')) {
|
|
|
+ info.pause();
|
|
|
+ res.send(status('pausing'));
|
|
|
+ } else if (action == 'restart') {
|
|
|
+ info.restart();
|
|
|
+ res.send(status('restarting'));
|
|
|
+ } else if (action == 'hide') {
|
|
|
+ let exists = await Permission.findOne({ where: { containerID: containerID, userID: req.session.userID }});
|
|
|
+ if (!exists) { const newPermission = await Permission.create({ containerName: container_name, containerID: containerID, username: req.session.username, userID: req.session.userID, hide: true }); }
|
|
|
+ else { exists.update({ hide: true }); }
|
|
|
+ res.send('ok');
|
|
|
+ }
|
|
|
}
|