Almost a complete rewrite, part 5. Almost ready.
This commit is contained in:
parent
ec4746ebd3
commit
4bdf1e3148
44 changed files with 2066 additions and 930 deletions
|
@ -35,11 +35,11 @@
|
|||
* CSS and pages tweaks to make the style more consistent.
|
||||
* Improved container cards to be more compact.
|
||||
* Improved sponsors and credits pages.
|
||||
* New - Secret supporter code.
|
||||
* New - Secret code for sponsors.
|
||||
* Fixed installs not appearing or appearing multiple times.
|
||||
* Improved log view and fixed refresh button.
|
||||
* Made app cards more compact.
|
||||
|
||||
* Updated container_card to only show exposed ports.
|
||||
|
||||
|
||||
## v0.60 (June 9th 2024) - Permissions system and import templates
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
FROM node:22-alpine
|
||||
FROM node:23-alpine
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
COPY package.json /app
|
||||
WORKDIR /dweebui
|
||||
COPY package.json /dweebui
|
||||
RUN npm install
|
||||
RUN npm install pm2 -g
|
||||
COPY . /app
|
||||
COPY . /dweebui
|
||||
EXPOSE 8000
|
||||
CMD ["pm2-runtime", "server.js"]
|
|
@ -1,12 +1,13 @@
|
|||
import { User, ServerSettings } from '../database/config.js';
|
||||
import { User, ServerSettings } from '../db/config.js';
|
||||
import { Alert, getLanguage, Navbar, Sidebar, Footer } from '../utils/system.js';
|
||||
|
||||
export const Account = async function(req,res){
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let container_links = await ServerSettings.findOne({ where: {key: 'container_links'}});
|
||||
let user_registration = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||
|
||||
|
||||
let user = await User.findOne({ where: {userID: req.session.userID}});
|
||||
|
||||
res.render("account",{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Alert, getLanguage, Navbar, Footer } from '../utils/system.js';
|
||||
import { Alert, getLanguage, Navbar, Footer, Capitalize } from '../utils/system.js';
|
||||
import { readFileSync, readdirSync, renameSync, mkdirSync, unlinkSync, existsSync } from 'fs';
|
||||
import { parse } from 'yaml';
|
||||
import multer from 'multer';
|
||||
|
@ -26,6 +26,8 @@ export const searchApps = async function (req, res) {
|
|||
|
||||
export const Apps = async function(req,res){
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let [apps_list, app_count] = ['', ''];
|
||||
let page = Number(req.params.page) || 1;
|
||||
let template = req.params.template || 'default';
|
||||
|
@ -154,14 +156,68 @@ export const Apps = async function(req,res){
|
|||
|
||||
export const submitApps = async function (req, res) {
|
||||
|
||||
let app_name = req.header('hx-trigger-name');
|
||||
let app_type = req.header('hx-trigger');
|
||||
let action = req.params.action;
|
||||
|
||||
// console.log(`[submitApps] app_name: ${app_name} app_type: ${app_type} action: ${action}`);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function CatagoryColor(category) {
|
||||
switch (category) {
|
||||
case 'Other':
|
||||
return '<span class="badge bg-blue-lt">Other</span> ';
|
||||
case 'Productivity':
|
||||
return '<span class="badge bg-blue-lt">Productivity</span> ';
|
||||
case 'Tools':
|
||||
return '<span class="badge bg-blue-lt">Tools</span> ';
|
||||
case 'Dashboard':
|
||||
return '<span class="badge bg-blue-lt">Dashboard</span> ';
|
||||
case 'Communication':
|
||||
return '<span class="badge bg-azure-lt">Communication</span> ';
|
||||
case 'Media':
|
||||
return '<span class="badge bg-azure-lt">Media</span> ';
|
||||
case 'CMS':
|
||||
return '<span class="badge bg-azure-lt">CMS</span> ';
|
||||
case 'Monitoring':
|
||||
return '<span class="badge bg-indigo-lt">Monitoring</span> ';
|
||||
case 'LDAP':
|
||||
return '<span class="badge bg-purple-lt">LDAP</span> ';
|
||||
case 'Arr':
|
||||
return '<span class="badge bg-purple-lt">Arr</span> ';
|
||||
case 'Database':
|
||||
return '<span class="badge bg-red-lt">Database</span> ';
|
||||
case 'Paid':
|
||||
return '<span class="badge bg-red-lt" title="This is a paid product or contains paid features.">Paid</span> ';
|
||||
case 'Gaming':
|
||||
return '<span class="badge bg-pink-lt">Gaming</span> ';
|
||||
case 'Finance':
|
||||
return '<span class="badge bg-orange-lt">Finance</span> ';
|
||||
case 'Networking':
|
||||
return '<span class="badge bg-yellow-lt">Networking</span> ';
|
||||
case 'Authentication':
|
||||
return '<span class="badge bg-lime-lt">Authentication</span> ';
|
||||
case 'Development':
|
||||
return '<span class="badge bg-green-lt">Development</span> ';
|
||||
case 'Media Server':
|
||||
return '<span class="badge bg-teal-lt">Media Server</span> ';
|
||||
case 'Downloaders':
|
||||
return '<span class="badge bg-cyan-lt">Downloaders</span> ';
|
||||
default:
|
||||
return ''; // default to other if the category is not recognized
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const appsModals = async function (req, res) {
|
||||
let app_name = req.header('hx-trigger-name');
|
||||
let app_type = req.header('hx-trigger');
|
||||
let modal = req.params.modal;
|
||||
|
||||
// console.log(`[submitApps] app_name: ${app_name} app_type: ${app_type} modal: ${modal}`);
|
||||
|
||||
// Modal for compose files
|
||||
if (action == 'view_install' && app_type == 'compose') {
|
||||
if (modal == 'view_install' && app_type == 'compose') {
|
||||
let compose = readFileSync(`appdata/compose/${app_name}/compose.yaml`, 'utf8');
|
||||
let modal = readFileSync('views/partials/compose.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, app_name);
|
||||
|
@ -170,8 +226,28 @@ export const submitApps = async function (req, res) {
|
|||
return;
|
||||
}
|
||||
|
||||
// More Info modal
|
||||
if (modal == 'info' && app_type == 'json') {
|
||||
|
||||
let modal = readFileSync('views/partials/info.html', 'utf8');
|
||||
|
||||
let app_title = Capitalize(app_name);
|
||||
modal = modal.replace(/AppTitle/g, app_title);
|
||||
|
||||
let result = templates_global.find(t => t.name == app_name);
|
||||
|
||||
modal = modal.replace(/AppDescription/g, result.description);
|
||||
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Modal for json templates
|
||||
if (action == 'view_install' && app_type == 'json') {
|
||||
if (modal == 'view_install' && app_type == 'json') {
|
||||
|
||||
|
||||
let result = templates_global.find(t => t.name == app_name);
|
||||
let name = result.name || result.title.toLowerCase();
|
||||
let short_name = name.slice(0, 25) + "...";
|
||||
|
@ -317,7 +393,7 @@ export const submitApps = async function (req, res) {
|
|||
|
||||
}
|
||||
|
||||
let modal = readFileSync('views/partials/details.html', 'utf8');
|
||||
let modal = readFileSync('views/partials/install.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, name);
|
||||
modal = modal.replace(/AppNote/g, note);
|
||||
modal = modal.replace(/AppImage/g, image);
|
||||
|
@ -359,51 +435,3 @@ export const submitApps = async function (req, res) {
|
|||
res.send(modal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function CatagoryColor(category) {
|
||||
switch (category) {
|
||||
case 'Other':
|
||||
return '<span class="badge bg-blue-lt">Other</span> ';
|
||||
case 'Productivity':
|
||||
return '<span class="badge bg-blue-lt">Productivity</span> ';
|
||||
case 'Tools':
|
||||
return '<span class="badge bg-blue-lt">Tools</span> ';
|
||||
case 'Dashboard':
|
||||
return '<span class="badge bg-blue-lt">Dashboard</span> ';
|
||||
case 'Communication':
|
||||
return '<span class="badge bg-azure-lt">Communication</span> ';
|
||||
case 'Media':
|
||||
return '<span class="badge bg-azure-lt">Media</span> ';
|
||||
case 'CMS':
|
||||
return '<span class="badge bg-azure-lt">CMS</span> ';
|
||||
case 'Monitoring':
|
||||
return '<span class="badge bg-indigo-lt">Monitoring</span> ';
|
||||
case 'LDAP':
|
||||
return '<span class="badge bg-purple-lt">LDAP</span> ';
|
||||
case 'Arr':
|
||||
return '<span class="badge bg-purple-lt">Arr</span> ';
|
||||
case 'Database':
|
||||
return '<span class="badge bg-red-lt">Database</span> ';
|
||||
case 'Paid':
|
||||
return '<span class="badge bg-red-lt" title="This is a paid product or contains paid features.">Paid</span> ';
|
||||
case 'Gaming':
|
||||
return '<span class="badge bg-pink-lt">Gaming</span> ';
|
||||
case 'Finance':
|
||||
return '<span class="badge bg-orange-lt">Finance</span> ';
|
||||
case 'Networking':
|
||||
return '<span class="badge bg-yellow-lt">Networking</span> ';
|
||||
case 'Authentication':
|
||||
return '<span class="badge bg-lime-lt">Authentication</span> ';
|
||||
case 'Development':
|
||||
return '<span class="badge bg-green-lt">Development</span> ';
|
||||
case 'Media Server':
|
||||
return '<span class="badge bg-teal-lt">Media Server</span> ';
|
||||
case 'Downloaders':
|
||||
return '<span class="badge bg-cyan-lt">Downloaders</span> ';
|
||||
default:
|
||||
return ''; // default to other if the category is not recognized
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import { ServerSettings, User } from '../database/config.js';
|
||||
import { ServerSettings, User } from '../db/config.js';
|
||||
import { Alert, getLanguage, Navbar, Sidebar, Footer, Capitalize } from '../utils/system.js';
|
||||
import { readdirSync, readFileSync } from 'fs';
|
||||
|
||||
export const Credits = async function (req, res) {
|
||||
|
||||
let language = await getLanguage(req);
|
||||
let language = await getLanguage(req.session.userID);
|
||||
let Language = Capitalize(language);
|
||||
let selected = `<option value="${language}" selected hidden>${Language}</option>`;
|
||||
|
||||
|
@ -12,10 +12,7 @@ export const Credits = async function (req, res) {
|
|||
let preferences = JSON.parse(user.preferences);
|
||||
let hide_profile = preferences.hide_profile;
|
||||
|
||||
let checked = '';
|
||||
if (hide_profile == true) { checked = 'checked'; }
|
||||
|
||||
|
||||
let checked = ''; if (hide_profile == true) { checked = 'checked'; }
|
||||
|
||||
res.render("credits",{
|
||||
alert: '',
|
||||
|
|
|
@ -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 container_card = readFileSync('./views/partials/container_card.html', 'utf8');
|
||||
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 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');
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ import { imageList, GetContainerLists } from '../utils/docker.js';
|
|||
|
||||
export const Images = async function(req,res){
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let container_images = [];
|
||||
let image_list = '';
|
||||
|
||||
|
@ -21,6 +23,9 @@ export const Images = async function(req,res){
|
|||
try { name = images[i].RepoTags[0].split(':')[0]; } catch {}
|
||||
try { tag = images[i].RepoTags[0].split(':')[1]; } catch {}
|
||||
|
||||
// let image_id = images[i].Id.split(':')[1].substring(0, 12);
|
||||
let image_id = images[i].Id.split(':')[1];
|
||||
|
||||
let date = new Date(images[i].Created * 1000);
|
||||
let created = date.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
|
||||
|
||||
|
@ -39,11 +44,11 @@ export const Images = async function(req,res){
|
|||
<td><input class="form-check-input m-0 align-middle" name="select" value="${images[i].Id}" type="checkbox" aria-label="Select"></td>
|
||||
<td class="sort-name">${name}</td>
|
||||
<td class="sort-type">${tag}</td>
|
||||
<td class="sort-city">${images[i].Id}</td>
|
||||
<td class="sort-city">${image_id}</td>
|
||||
<td class="sort-score text-green">${status}</td>
|
||||
<td class="sort-date" data-date="1628122643">${created}</td>
|
||||
<td class="sort-quantity">${size} MB</td>
|
||||
<td class="text-end"><a class="btn" href="#"><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></a></td>
|
||||
<td class="sort-date" data-date="1628122643">${created}</td>
|
||||
<td class=""><a class="container-action" href="#"><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></a></td>
|
||||
</tr>`
|
||||
image_list += details;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import bcrypt from 'bcrypt';
|
||||
import { User, Syslog, ServerSettings } from '../database/config.js';
|
||||
import { User, Syslog, ServerSettings } from '../db/config.js';
|
||||
|
||||
|
||||
export const Login = async function (req, res) {
|
||||
|
@ -16,14 +16,14 @@ export const Login = async function (req, res) {
|
|||
req.session.username = 'Localhost';
|
||||
req.session.userID = '00000000-0000-0000-0000-000000000000';
|
||||
req.session.role = 'admin';
|
||||
await Syslog.create({ username: 'Localhost', uniqueID: 'localhost', event: "Login", message: "User logged in", ip: req.socket.remoteAddress });
|
||||
await Syslog.create({ username: 'Localhost', uniqueID: 'localhost', event: "Login", message: "User logged in", ip: req.ip });
|
||||
res.redirect("/dashboard");
|
||||
return;
|
||||
} else if (authentication.value == 'no_auth') {
|
||||
req.session.username = 'No Auth';
|
||||
req.session.userID = '00000000-0000-0000-0000-000000000000';
|
||||
req.session.role = 'admin';
|
||||
await Syslog.create({ username: 'No Auth', uniqueID: 'no_auth', event: "Login", message: "User logged in", ip: req.socket.remoteAddress });
|
||||
await Syslog.create({ username: 'No Auth', uniqueID: 'no_auth', event: "Login", message: "User logged in", ip: req.ip });
|
||||
res.redirect("/dashboard");
|
||||
return;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ export const submitLogin = async function (req, res) {
|
|||
|
||||
// If there is no users with that email or the password is incorrect.
|
||||
if (!user || !await bcrypt.compare(password, user.password)) {
|
||||
await Syslog.create({ username: '', uniqueID: email, event: "Login Attempt", message: "User login failed", ip: req.socket.remoteAddress });
|
||||
await Syslog.create({ username: '', uniqueID: email, event: "Login Attempt", message: "User login failed", ip: req.ip });
|
||||
res.render("login",{ "error": "Invalid credentials." });
|
||||
return;
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ export const submitLogin = async function (req, res) {
|
|||
|
||||
console.log(`${req.session.username} logged in`);
|
||||
|
||||
await Syslog.create({ username: user.username, uniqueID: email, event: "Login", message: "User logged in", ip: req.socket.remoteAddress });
|
||||
await Syslog.create({ username: user.username, uniqueID: email, event: "Login", message: "User logged in", ip: req.ip });
|
||||
res.redirect("/dashboard");
|
||||
return;
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ export const submitLogin = async function (req, res) {
|
|||
|
||||
export const Logout = async function(req,res){
|
||||
console.log(`User ${req.session.username} logged out \n`);
|
||||
await Syslog.create({ username: req.session.username, uniqueID: req.session.userID, event: "Logout", message: "User logged out", ip: req.socket.remoteAddress });
|
||||
await Syslog.create({ username: req.session.username, uniqueID: req.session.userID, event: "Logout", message: "User logged out", ip: req.ip });
|
||||
req.session.destroy(() => {
|
||||
res.redirect("/login");
|
||||
});
|
||||
|
|
|
@ -3,6 +3,8 @@ import { networkList, GetContainerLists, removeNetwork } from '../utils/docker.j
|
|||
|
||||
export const Networks = async function(req, res) {
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let container_networks = [];
|
||||
let network_name = '';
|
||||
|
||||
|
@ -30,7 +32,7 @@ export const Networks = async function(req, res) {
|
|||
<td class="sort-city">${networks[i].Id}</td>
|
||||
<td class="sort-score text-green">${status}</td>
|
||||
<td class="sort-date" data-date="1628122643">${networks[i].Created}</td>
|
||||
<td class="text-end"><a class="btn" href="#">Details</a></td>
|
||||
<td class=""><button class="badge badge-outline text-grey" id="" data-hx-get="/users/usersModals/user/" hx-target="#modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Details</button></td>
|
||||
</tr>`
|
||||
// Add the row to the network list
|
||||
network_list += details;
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
import { ServerSettings, User } from '../database/config.js';
|
||||
import { ServerSettings, User } from '../db/config.js';
|
||||
import { Alert, getLanguage, Navbar, Sidebar, Footer, Capitalize } from '../utils/system.js';
|
||||
import { readdirSync, readFileSync } from 'fs';
|
||||
|
||||
export const Preferences = async function(req,res){
|
||||
|
||||
let language = await getLanguage(req);
|
||||
let language = await getLanguage(req.session.userID);
|
||||
let Language = Capitalize(language);
|
||||
let selected = `<option value="${language}" selected hidden>${Language}</option>`;
|
||||
|
||||
let user = await User.findOne({ where: { userID: req.session.userID }});
|
||||
let preferences = JSON.parse(user.preferences);
|
||||
let hide_profile = preferences.hide_profile;
|
||||
|
||||
let checked = '';
|
||||
if (hide_profile == true) { checked = 'checked'; }
|
||||
|
||||
|
||||
let checked = ''; if (hide_profile == true) { checked = 'checked'; }
|
||||
|
||||
res.render("preferences",{
|
||||
alert: '',
|
||||
|
@ -36,46 +31,16 @@ export const submitPreferences = async function(req,res){
|
|||
|
||||
let { language_input, hidden_input, check_languages } = req.body;
|
||||
|
||||
let trigger_name = req.header('hx-trigger-name');
|
||||
let trigger_id = req.header('hx-trigger');
|
||||
|
||||
// console.log(`trigger_name: ${trigger_name} - trigger_id: ${trigger_id}`);
|
||||
// console.log(req.body);
|
||||
|
||||
if (hidden_input == 'on') { hidden_input = true; } else { hidden_input = false; }
|
||||
|
||||
let user_preferences = {
|
||||
language: language_input,
|
||||
hide_profile: hidden_input,
|
||||
};
|
||||
|
||||
if (language_input != undefined && hidden_input != undefined) {
|
||||
await User.update({ preferences: JSON.stringify(user_preferences) }, { where: { userID: req.session.userID }});
|
||||
await User.update({ preferences: JSON.stringify(user_preferences), language: language_input }, { where: { userID: req.session.userID }});
|
||||
}
|
||||
|
||||
// [HTMX Triggered] Changes the update button.
|
||||
if(trigger_id == 'preferences'){
|
||||
res.send(`<button class="btn btn-success" hx-post="/preferences" hx-trigger="load delay:2s" hx-swap="outerHTML" id="submit" hx-target="#submit">Updated</button>`);
|
||||
return;
|
||||
} else if (trigger_id == 'submit'){
|
||||
res.send(`<button class="btn btn-primary" id="submit" form="preferences">Update</button>`);
|
||||
return;
|
||||
}
|
||||
|
||||
let language = await getLanguage(req);
|
||||
let Language = Capitalize(language);
|
||||
let selected = `<option value="${language}" selected hidden>${Language}</option>`;
|
||||
|
||||
res.render("preferences",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
sidebar: await Sidebar(req),
|
||||
footer: await Footer(req),
|
||||
selected: selected,
|
||||
});
|
||||
|
||||
res.redirect('/preferences');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import bcrypt from "bcrypt";
|
||||
import { Op } from "sequelize";
|
||||
import { User, ServerSettings, Permission, Syslog } from "../database/config.js";
|
||||
import { User, ServerSettings, Permission, Syslog } from "../db/config.js";
|
||||
|
||||
|
||||
export const Register = async function(req,res){
|
||||
|
@ -45,12 +45,12 @@ export const submitRegister = async function(req,res){
|
|||
|
||||
else if (registration_secret && secret !== registration_secret) {
|
||||
error = "Invalid secret";
|
||||
await Syslog.create({ username: user.username, uniqueID: email, event: "Failed Registration", message: "Invalid Secret", ip: req.socket.remoteAddress });
|
||||
await Syslog.create({ username: user.username, uniqueID: email, event: "Failed Registration", message: "Invalid Secret", ip: req.ip });
|
||||
}
|
||||
|
||||
else if (await User.findOne({ where: { [Op.or]: [{ username: username }, { email: email }] }})) {
|
||||
error = "Username or email already exists";
|
||||
await Syslog.create({ username: username, uniqueID: email, event: "Failed Registration", message: "Username or email already exists", ip: req.socket.remoteAddress });
|
||||
await Syslog.create({ username: username, uniqueID: email, event: "Failed Registration", message: "Username or email already exists", ip: req.ip });
|
||||
}
|
||||
|
||||
if (error != '') {
|
||||
|
@ -88,7 +88,7 @@ export const submitRegister = async function(req,res){
|
|||
email: email,
|
||||
password: bcrypt.hashSync(password, 10),
|
||||
role: await Role(),
|
||||
preferences: JSON.stringify({ language: "english", hidden_profile: false }),
|
||||
preferences: JSON.stringify({ hidden_profile: false }),
|
||||
lastLogin: new Date().toLocaleString(),
|
||||
});
|
||||
|
||||
|
@ -100,13 +100,13 @@ export const submitRegister = async function(req,res){
|
|||
req.session.userID = user.userID;
|
||||
req.session.role = user.role;
|
||||
|
||||
await Syslog.create({ username: user.username, uniqueID: user.email, event: "Registration", message: "User created", ip: req.socket.remoteAddress });
|
||||
await Syslog.create({ username: user.username, uniqueID: user.email, event: "Registration", message: "User created", ip: req.ip });
|
||||
|
||||
console.log(`User ${username} created`);
|
||||
|
||||
res.redirect("/dashboard");
|
||||
} else {
|
||||
await Syslog.create({ username: user.username, uniqueID: user.email, event: "Failed Registration", message: "Error. User not created", ip: req.socket.remoteAddress });
|
||||
await Syslog.create({ username: user.username, uniqueID: user.email, event: "Failed Registration", message: "Error. User not created", ip: req.ip });
|
||||
res.render("register", { "error": "Error. User not created" });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { ServerSettings } from '../database/config.js';
|
||||
import { Alert, getLanguage, Navbar, Sidebar, Footer } from '../utils/system.js';
|
||||
import { read, readdirSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { ServerSettings } from '../db/config.js';
|
||||
import { configureHost } from '../utils/docker.js';
|
||||
import { Alert, Navbar, Sidebar, Footer } from '../utils/system.js';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
|
||||
export const Settings = async function(req,res){
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let user_registration = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||
let registration_secret = await ServerSettings.findOne({ where: {key: 'registration_secret'}});
|
||||
|
||||
|
@ -68,7 +71,10 @@ export const Settings = async function(req,res){
|
|||
|
||||
|
||||
|
||||
export const updateSettings = async function (req, res) {
|
||||
export const SettingsAction = async function (req, res) {
|
||||
|
||||
let action = req.params.action;
|
||||
let id = req.params.id;
|
||||
|
||||
let { user_registration, registration_secret, custom_link, link_url, authentication } = req.body;
|
||||
let { host2, tag2, ip2, port2 } = req.body;
|
||||
|
@ -144,12 +150,12 @@ export const updateSettings = async function (req, res) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
// Host 2
|
||||
if (host2) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host2'}});
|
||||
if (exists) { await ServerSettings.update({value: `${tag2},${ip2},${port2}`}, {where: {key: 'host2'}}); }
|
||||
else { await ServerSettings.create({ key: 'host2', value: `${tag2},${ip2},${port2}`}); }
|
||||
configureHost(2, ip2, port2);
|
||||
} else if (!host2) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host2'}});
|
||||
if (exists) { await ServerSettings.update({value: ''}, {where: {key: 'host2'}}); }
|
||||
|
@ -161,6 +167,7 @@ export const updateSettings = async function (req, res) {
|
|||
let exists = await ServerSettings.findOne({ where: {key: 'host3'}});
|
||||
if (exists) { await ServerSettings.update({value: `${tag3},${ip3},${port3}`}, {where: {key: 'host3'}}); }
|
||||
else { await ServerSettings.create({ key: 'host3', value: `${tag3},${ip3},${port3}`}); }
|
||||
configureHost(3, ip3, port3);
|
||||
} else if (!host3) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host3'}});
|
||||
if (exists) { await ServerSettings.update({value: ''}, {where: {key: 'host3'}}); }
|
||||
|
@ -172,15 +179,15 @@ export const updateSettings = async function (req, res) {
|
|||
let exists = await ServerSettings.findOne({ where: {key: 'host4'}});
|
||||
if (exists) { await ServerSettings.update({value: `${tag4},${ip4},${port4}`}, {where: {key: 'host4'}}); }
|
||||
else { await ServerSettings.create({ key: 'host4', value: `${tag4},${ip4},${port4}`}); }
|
||||
configureHost(4, ip4, port4);
|
||||
} else if (!host4) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host4'}});
|
||||
if (exists) { await ServerSettings.update({value: ''}, {where: {key: 'host4'}}); }
|
||||
else { await ServerSettings.create({ key: 'host4', value: ''}); }
|
||||
}
|
||||
|
||||
|
||||
console.log('Settings updated');
|
||||
res.send(`<button class="btn btn-success" hx-post="/settings" hx-trigger="load delay:2s" hx-swap="outerHTML" id="submit" hx-target="#submit">Updated</button>`);
|
||||
res.send(`<button class="btn btn-success" hx-post="/settings/action/update_settings/0" hx-trigger="load delay:2s" hx-swap="outerHTML" id="submit" hx-target="#submit">Updated</button>`);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { ServerSettings, User } from '../database/config.js';
|
||||
import { ServerSettings, User } from '../db/config.js';
|
||||
import { Alert, getLanguage, Navbar, Sidebar, Footer, Capitalize } from '../utils/system.js';
|
||||
import { readdirSync, readFileSync } from 'fs';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
export const Sponsors = async function (req, res) {
|
||||
|
||||
let language = await getLanguage(req);
|
||||
let language = await getLanguage(req.session.userID);
|
||||
let Language = Capitalize(language);
|
||||
let selected = `<option value="${language}" selected hidden>${Language}</option>`;
|
||||
|
||||
|
@ -13,10 +13,7 @@ export const Sponsors = async function (req, res) {
|
|||
let preferences = JSON.parse(user.preferences);
|
||||
let hide_profile = preferences.hide_profile;
|
||||
|
||||
let checked = '';
|
||||
if (hide_profile == true) { checked = 'checked'; }
|
||||
|
||||
|
||||
let checked = ''; if (hide_profile == true) { checked = 'checked'; }
|
||||
|
||||
res.render("sponsors",{
|
||||
alert: '',
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { Syslog } from '../database/config.js';
|
||||
import { Syslog } from '../db/config.js';
|
||||
import { Alert, getLanguage, Navbar, Footer } from '../utils/system.js';
|
||||
|
||||
export const Syslogs = async function(req, res) {
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let logs = '';
|
||||
|
||||
const syslogs = await Syslog.findAll({
|
||||
|
@ -38,7 +40,7 @@ export const Syslogs = async function(req, res) {
|
|||
<td class="sort-message">${message}</td>
|
||||
<td class="sort-ip">${log.ip}</td>
|
||||
<td class="sort-timestamp">${datetime}</td>
|
||||
<td class="text-end"><a class="" href="#"><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></a></td>
|
||||
<td class=""><a class="" href="#"><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></a></td>
|
||||
|
||||
</tr>`
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { ServerSettings, User } from '../database/config.js';
|
||||
import { Alert, getLanguage, Navbar, Footer } from '../utils/system.js';
|
||||
import { User, Permission, ContainerLists, Container, ServerSettings } from '../db/config.js';
|
||||
import { Alert, Navbar, Footer } from '../utils/system.js';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
export const Users = async function(req,res){
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let user_list = '';
|
||||
|
||||
let allUsers = await User.findAll();
|
||||
|
@ -23,7 +26,7 @@ export const Users = async function(req,res){
|
|||
<td><input class="form-check-input" type="checkbox" name="select"></td>
|
||||
|
||||
<td class="sort-id">${account.id}</td>
|
||||
<td class="sort-avatar"><span class="avatar avatar-sm bg-green-lt">${avatar}</span></span>
|
||||
<td class="sort-avatar p-1"><span class="avatar avatar-sm bg-green-lt">${avatar}</span></span>
|
||||
<td class="sort-name">${account.name}</td>
|
||||
<td class="sort-username">${account.username}</td>
|
||||
<td class="sort-email">${account.email}</td>
|
||||
|
@ -31,7 +34,7 @@ export const Users = async function(req,res){
|
|||
<td class="sort-role">${account.role}</td>
|
||||
<td class="sort-lastlogin">${account.lastLogin}</td>
|
||||
<td class="sort-active">${active}</td>
|
||||
<td class="sort-action"><a href="#" class="btn">View</a></td>
|
||||
<td class="sort-action"><button class="badge badge-outline text-grey" id="${account.username}" data-hx-get="/users/view/user/${account.userID}" hx-target="#modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#scrolling_modal">View</button></td>
|
||||
</tr>`
|
||||
|
||||
user_list += info;
|
||||
|
@ -86,3 +89,60 @@ export const searchUsers = async function (req, res) {
|
|||
res.send('ok');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const UsersView = async (req, res) => {
|
||||
|
||||
let view = req.params.view;
|
||||
let userID = req.params.id;
|
||||
let username = req.header('hx-trigger');
|
||||
|
||||
// console.log(`[view] ${view} - [userID] ${userID} - [username] ${username}`);
|
||||
|
||||
if (view == 'user') {
|
||||
let user = await User.findOne({ where: { userID: userID } });
|
||||
let modal = readFileSync('./views/partials/user.html', 'utf8');
|
||||
modal = modal.replace(/Username/g, username);
|
||||
modal = modal.replace(/USERID/g, user.userID);
|
||||
modal = modal.replace(/FullName/g, user.name);
|
||||
modal = modal.replace(/EmailAddress/g, user.email);
|
||||
modal = modal.replace(/LastLogin/g, user.lastLogin);
|
||||
modal = modal.replace(/CreatedAt/g, user.createdAt);
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
export const UsersAction = async (req, res) => {
|
||||
|
||||
let action = req.params.action;
|
||||
let userID = req.params.id;
|
||||
let change = req.body.change;
|
||||
|
||||
console.log(`[action] ${action} [change] ${change} - [userID] ${userID}`);
|
||||
|
||||
if (change == 'remove') {
|
||||
|
||||
let container_lists = await ContainerLists.findAll({ where: { userID: userID } });
|
||||
container_lists.destroy();
|
||||
|
||||
let permissions = await Permission.findAll({ where: { userID: userID } });
|
||||
permissions.forEach(async (permission) => {
|
||||
await permission.destroy();
|
||||
});
|
||||
|
||||
let user = await User.findOne({ where: { userID: userID } });
|
||||
await user.destroy();
|
||||
|
||||
console.log(`User removed.`);
|
||||
}
|
||||
|
||||
res.redirect('/users');
|
||||
|
||||
};
|
|
@ -2,6 +2,9 @@ import { Alert, getLanguage, Navbar, Footer } from '../utils/system.js';
|
|||
import { volumeList, GetContainerLists } from '../utils/docker.js';
|
||||
|
||||
export const Volumes = async function(req, res) {
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let container_volumes = [];
|
||||
let volume_list = '';
|
||||
|
||||
|
@ -43,7 +46,7 @@ export const Volumes = async function(req, res) {
|
|||
<td class="sort-score text-green">${status}</td>
|
||||
<td class="sort-date" data-date="1628122643">${volume.CreatedAt}</td>
|
||||
<td class="sort-quantity">MB</td>
|
||||
<td class="text-end"><a class="btn" href="#">Details</a></td>
|
||||
<td class=""><button class="badge badge-outline text-grey" id="" data-hx-get="/users/usersModals/user/" hx-target="#modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Details</button></td>
|
||||
</tr>`
|
||||
|
||||
volume_list += row;
|
||||
|
|
|
@ -2,6 +2,7 @@ import session from 'express-session';
|
|||
import SessionSequelize from 'connect-session-sequelize';
|
||||
import { Sequelize, DataTypes} from 'sequelize';
|
||||
import { readFileSync } from 'fs';
|
||||
import { check_configured_hosts } from '../utils/docker.js';
|
||||
|
||||
const SECURE = process.env.HTTPS || false;
|
||||
|
||||
|
@ -9,7 +10,7 @@ const SECURE = process.env.HTTPS || false;
|
|||
const SequelizeStore = SessionSequelize(session.Store);
|
||||
const sessionData = new Sequelize('database', 'username', 'password', {
|
||||
dialect: 'sqlite',
|
||||
storage: 'database/sessions.sqlite',
|
||||
storage: 'data/sessions.sqlite',
|
||||
logging: false,
|
||||
});
|
||||
const SessionStore = new SequelizeStore({ db: sessionData });
|
||||
|
@ -29,7 +30,7 @@ export const sessionMiddleware = session({
|
|||
// Server settings
|
||||
const settings = new Sequelize('database', 'username', 'password', {
|
||||
dialect: 'sqlite',
|
||||
storage: 'database/settings.sqlite',
|
||||
storage: 'data/settings.sqlite',
|
||||
logging: false,
|
||||
});
|
||||
const SettingsDB = new SequelizeStore({ db: settings });
|
||||
|
@ -43,7 +44,6 @@ console.log(`\x1b[33mAuthor: ${package_info.author}\x1b[0m`);
|
|||
console.log(`\x1b[33mLicense: ${package_info.license}\x1b[0m`);
|
||||
console.log(`\x1b[33mDescription: ${package_info.description}\x1b[0m`);
|
||||
console.log('');
|
||||
|
||||
// console.log in red
|
||||
console.log('\x1b[31m * Only Docker volumes are supported. No bind mounts.\n \x1b[0m');
|
||||
console.log('\x1b[31m * Breaking changes may require you to remove the DweebUI volume and start fresh. \n \x1b[0m');
|
||||
|
@ -53,7 +53,9 @@ try {
|
|||
await sessionData.authenticate();
|
||||
await settings.authenticate();
|
||||
sessionData.sync();
|
||||
settings.sync();
|
||||
settings.sync().then(() => {
|
||||
check_configured_hosts();
|
||||
});
|
||||
console.log(`\x1b[32mDatabase connection established.\x1b[0m`);
|
||||
} catch (error) {
|
||||
console.error('\x1b[31mDatabase connection failed:', error, '\x1b[0m');
|
||||
|
@ -85,6 +87,9 @@ export const User = settings.define('User', {
|
|||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
|
@ -97,6 +102,10 @@ export const User = settings.define('User', {
|
|||
lastLogin: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
language: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'english'
|
||||
},
|
||||
preferences : {
|
||||
type: DataTypes.STRING
|
||||
},
|
54
package-lock.json
generated
54
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "dweebui",
|
||||
"version": "0.70.457",
|
||||
"version": "0.70.474",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dweebui",
|
||||
"version": "0.70.457",
|
||||
"version": "0.70.474",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.16",
|
||||
|
@ -15,13 +15,13 @@
|
|||
"dockerode": "^4.0.2",
|
||||
"dockerode-compose": "^1.4.0",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.0",
|
||||
"express-session": "^1.18.0",
|
||||
"express": "^4.21.1",
|
||||
"express-session": "^1.18.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"sequelize": "^6.37.3",
|
||||
"sequelize": "^6.37.5",
|
||||
"sqlite3": "^5.1.7",
|
||||
"systeminformation": "^5.23.5",
|
||||
"yaml": "^2.5.1"
|
||||
"yaml": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@balena/dockerignore": {
|
||||
|
@ -618,9 +618,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
|
@ -908,16 +908,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
|
||||
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
|
||||
"version": "4.21.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.6.0",
|
||||
"cookie": "0.7.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
|
@ -949,11 +949,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/express-session": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
|
||||
"integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==",
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
|
||||
"integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==",
|
||||
"dependencies": {
|
||||
"cookie": "0.6.0",
|
||||
"cookie": "0.7.2",
|
||||
"cookie-signature": "1.0.7",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
|
@ -966,6 +966,14 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie-signature": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||
|
@ -2245,9 +2253,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/sequelize": {
|
||||
"version": "6.37.3",
|
||||
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.3.tgz",
|
||||
"integrity": "sha512-V2FTqYpdZjPy3VQrZvjTPnOoLm0KudCRXfGWp48QwhyPPp2yW8z0p0sCYZd/em847Tl2dVxJJ1DR+hF+O77T7A==",
|
||||
"version": "6.37.5",
|
||||
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.5.tgz",
|
||||
"integrity": "sha512-10WA4poUb3XWnUROThqL2Apq9C2NhyV1xHPMZuybNMCucDsbbFuKg51jhmyvvAUyUqCiimwTZamc3AHhMoBr2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -2876,9 +2884,9 @@
|
|||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
|
||||
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
|
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "dweebui",
|
||||
"version": "0.70.457",
|
||||
"version": "0.70.474",
|
||||
"main": "server.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
@ -18,12 +18,12 @@
|
|||
"dockerode": "^4.0.2",
|
||||
"dockerode-compose": "^1.4.0",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.0",
|
||||
"express-session": "^1.18.0",
|
||||
"express": "^4.21.1",
|
||||
"express-session": "^1.18.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"sequelize": "^6.37.3",
|
||||
"sequelize": "^6.37.5",
|
||||
"sqlite3": "^5.1.7",
|
||||
"systeminformation": "^5.23.5",
|
||||
"yaml": "^2.5.1"
|
||||
"yaml": "^2.6.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,3 +41,13 @@ function selectAll(group) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function topScroll() {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
|
||||
function bottomScroll() {
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
62
router.js
62
router.js
|
@ -3,25 +3,26 @@ export const router = express.Router();
|
|||
|
||||
import { Login, submitLogin, Logout } from './controllers/login.js';
|
||||
import { Register, submitRegister } from './controllers/register.js';
|
||||
import { Dashboard, searchDashboard, ContainerAction, ServerMetrics, SSE, CardList, UpdateCard } from './controllers/dashboard.js';
|
||||
import { Dashboard, searchDashboard, ServerMetrics, SSE, DashboardView, DashboardAction } from './controllers/dashboard.js';
|
||||
import { Images, submitImages, searchImages } from './controllers/images.js';
|
||||
import { Volumes, submitVolumes, searchVolumes } from './controllers/volumes.js';
|
||||
import { Networks, NetworkAction, searchNetworks } from './controllers/networks.js';
|
||||
import { Apps, submitApps, searchApps } from './controllers/apps.js';
|
||||
import { Users, submitUsers, searchUsers } from './controllers/users.js';
|
||||
import { Apps, submitApps, searchApps, appsModals, } from './controllers/apps.js';
|
||||
import { Users, submitUsers, searchUsers, UsersView, UsersAction } from './controllers/users.js';
|
||||
import { Syslogs, searchSyslogs } from './controllers/syslogs.js';
|
||||
import { Account, searchAccount } from './controllers/account.js';
|
||||
import { Preferences, submitPreferences, searchPreferences } from './controllers/preferences.js';
|
||||
import { Settings, updateSettings, updateLanguages, searchSettings } from './controllers/settings.js';
|
||||
import { Settings, SettingsAction, updateLanguages, searchSettings } from './controllers/settings.js';
|
||||
import { Sponsors, searchSponsors } from './controllers/sponsors.js';
|
||||
import { Credits } from './controllers/credits.js';
|
||||
|
||||
import { Install } from './utils/install.js';
|
||||
import { Uninstall } from './utils/uninstall.js';
|
||||
|
||||
import { Sponsors, searchSponsors } from './controllers/sponsors.js';
|
||||
import { sessionCheck, adminOnly, permissionCheck } from './utils/permissions.js';
|
||||
|
||||
import { Credits } from './controllers/credits.js';
|
||||
|
||||
import { sessionCheck, adminOnly, permissionCheck, permissionModal, updatePermissions } from './utils/permissions.js';
|
||||
// router.get('*', (req, res, next) => { console.log(`[GET] ${req.url}`); next(); });
|
||||
// router.post('*', (req, res, next) => { console.log(`[POST] ${req.url}`); next(); });
|
||||
|
||||
router.get('/login', Login);
|
||||
router.post('/login', submitLogin);
|
||||
|
@ -30,40 +31,33 @@ router.get('/register', Register);
|
|||
router.post('/register', submitRegister);
|
||||
|
||||
router.get("/", sessionCheck, Dashboard);
|
||||
router.get("/:host?/dashboard", sessionCheck, Dashboard);
|
||||
|
||||
router.get("/dashboard", sessionCheck, Dashboard);
|
||||
router.get("/dashboard/view/:view/:id?", sessionCheck, DashboardView);
|
||||
router.post("/dashboard/action/:action/:id?", sessionCheck, DashboardAction);
|
||||
router.get("/server_metrics", sessionCheck, ServerMetrics);
|
||||
|
||||
router.get("/permission_modal", adminOnly, permissionModal);
|
||||
router.post("/update_permissions", adminOnly, updatePermissions);
|
||||
|
||||
router.get("/sse", permissionCheck, SSE);
|
||||
router.get("/card_list", permissionCheck, CardList);
|
||||
router.get("/update_card/:containerid", permissionCheck, UpdateCard);
|
||||
|
||||
router.post("/:host?/container/:action/:containerid?", permissionCheck, ContainerAction);
|
||||
|
||||
router.get('/images', adminOnly, Images);
|
||||
router.get("/images", adminOnly, Images);
|
||||
router.post('/images', adminOnly, submitImages);
|
||||
|
||||
router.get('/volumes', adminOnly, Volumes);
|
||||
router.get("/volumes", adminOnly, Volumes);
|
||||
router.post('/volumes', adminOnly, submitVolumes);
|
||||
|
||||
router.get('/networks', adminOnly, Networks);
|
||||
router.post('/:host?/network/:action/:containerid?', adminOnly, NetworkAction);
|
||||
router.get("/networks", adminOnly, Networks);
|
||||
router.post('/network/:action/:containerid?', adminOnly, NetworkAction);
|
||||
|
||||
router.get("/apps/:page?/:template?", adminOnly, Apps);
|
||||
router.post("/apps/:action?", adminOnly, submitApps);
|
||||
router.post("/install", adminOnly, Install);
|
||||
router.post("/uninstall", adminOnly, Uninstall);
|
||||
|
||||
router.get('/users', adminOnly, Users);
|
||||
router.post('/users', adminOnly, submitUsers);
|
||||
router.get("/users", adminOnly, Users);
|
||||
router.get("/users/view/:view/:id?", adminOnly, UsersView);
|
||||
router.post("/users/action/:action/:id?", adminOnly, UsersAction);
|
||||
|
||||
router.get('/syslogs', adminOnly, Syslogs);
|
||||
|
||||
router.get('/settings', adminOnly, Settings);
|
||||
router.post('/settings', adminOnly, updateSettings);
|
||||
router.post('/update_languages', adminOnly, updateLanguages);
|
||||
router.post('/settings/action/:action?/:id?', adminOnly, SettingsAction);
|
||||
|
||||
router.get('/preferences', sessionCheck, Preferences);
|
||||
router.post('/preferences', sessionCheck, submitPreferences);
|
||||
|
@ -77,6 +71,13 @@ router.get('/credits', sessionCheck, Credits);
|
|||
|
||||
|
||||
|
||||
router.get("/appsModals/:modal?", adminOnly, appsModals);
|
||||
|
||||
router.post("/install", adminOnly, Install);
|
||||
router.post("/uninstall", adminOnly, Uninstall);
|
||||
|
||||
router.post('/update_languages', adminOnly, updateLanguages);
|
||||
|
||||
|
||||
router.post("/search", function (req, res) {
|
||||
// req.header('hx-current-url') == http://localhost:8000/dashboard
|
||||
|
@ -119,10 +120,3 @@ router.post("/search", function (req, res) {
|
|||
res.send('ok');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// router.get('*', (req, res) => {
|
||||
// res.redirect('/dashboard');
|
||||
// });
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import express from 'express';
|
||||
import ejs from 'ejs';
|
||||
import { router } from './router.js';
|
||||
import { sessionMiddleware } from './database/config.js';
|
||||
import { sessionMiddleware } from './db/config.js';
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 8000;
|
||||
|
||||
app.set('view engine', 'html');
|
||||
app.set('trust proxy', true);
|
||||
app.engine('html', ejs.renderFile);
|
||||
app.use([
|
||||
express.static('public'),
|
||||
|
|
195
utils/docker.js
195
utils/docker.js
|
@ -1,55 +1,102 @@
|
|||
import Docker from 'dockerode';
|
||||
import { dockerContainerStats } from 'systeminformation';
|
||||
import { Container, ServerSettings } from '../database/config.js'
|
||||
import { Container, ServerSettings } from '../db/config.js'
|
||||
import stream from 'stream';
|
||||
|
||||
// export var docker1 = new Docker();
|
||||
export var docker = new Docker();
|
||||
export var docker;
|
||||
var docker2;
|
||||
var docker3;
|
||||
var docker4;
|
||||
|
||||
export async function GetContainerLists(hostid) {
|
||||
if (process.env.DOCKER_HOST && process.env.DOCKER_PORT) {
|
||||
console.log('Connecting to Docker with environment variables.');
|
||||
docker = new Docker({ host: process.env.DOCKER_HOST, port: process.env.DOCKER_PORT });
|
||||
console.log('Docker host connected.');
|
||||
} else {
|
||||
console.log('Connecting to default Docker host.');
|
||||
docker = new Docker();
|
||||
console.log('Docker host connected.');
|
||||
}
|
||||
|
||||
// key: host, value: `${tag3},${ip3},${port3}`
|
||||
export async function GetContainerLists(hostid) {
|
||||
|
||||
let host = hostid || 1;
|
||||
|
||||
if (host == 1 || host == 0) {
|
||||
let containers = await docker.listContainers({ all: true });
|
||||
return containers;
|
||||
let containers;
|
||||
|
||||
if (host == 0) {
|
||||
containers = await docker.listContainers({ all: true });
|
||||
}
|
||||
|
||||
if (host == 2 && !docker2) {
|
||||
let settings = await ServerSettings.findOne({ where: { key: 'host2' } });
|
||||
let ip = settings.value.split(',')[1];
|
||||
let port = settings.value.split(',')[2];
|
||||
if ((host == 0) && docker2) {
|
||||
let containers2 = await docker2.listContainers({ all: true });
|
||||
containers = containers.concat(containers2);
|
||||
}
|
||||
|
||||
if ((host == 0) && docker3) {
|
||||
let containers3 = await docker3.listContainers({ all: true });
|
||||
containers = containers.concat(containers3);
|
||||
}
|
||||
|
||||
if ((host == 0) && docker4) {
|
||||
let containers4 = await docker4.listContainers({ all: true });
|
||||
containers = containers.concat(containers4);
|
||||
}
|
||||
|
||||
if (host == 1) {
|
||||
containers = await docker.listContainers({ all: true });
|
||||
}
|
||||
|
||||
if (host == 2 && docker2) {
|
||||
containers = await docker2.listContainers({ all: true });
|
||||
}
|
||||
|
||||
if (host == 3 && docker3) {
|
||||
containers = await docker3.listContainers({ all: true });
|
||||
}
|
||||
|
||||
if (host == 4 && docker4) {
|
||||
containers = await docker4.listContainers({ all: true });
|
||||
}
|
||||
|
||||
return containers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function configureHost(hostid, ip, port) {
|
||||
|
||||
if (hostid == 2) {
|
||||
docker2 = new Docker({ host: ip, port: port });
|
||||
} else if (host == 2 && docker2) {
|
||||
let containers = await docker2.listContainers({ all: true });
|
||||
return containers;
|
||||
}
|
||||
|
||||
if (host == 3 && !docker3) {
|
||||
let settings = await ServerSettings.findOne({ where: { key: 'host3' } });
|
||||
let ip = settings.value.split(',')[1];
|
||||
let port = settings.value.split(',')[2];
|
||||
try {
|
||||
let containers = await docker2.listContainers({ all: true });
|
||||
console.log(`Host 2 connected. ${containers.length} containers found.`);
|
||||
}
|
||||
catch {
|
||||
console.log('Host 2 connection failed.');
|
||||
docker2;
|
||||
}
|
||||
} else if (hostid == 3) {
|
||||
docker3 = new Docker({ host: ip, port: port });
|
||||
} else if (host == 3 && docker3) {
|
||||
let containers = await docker3.listContainers({ all: true });
|
||||
return containers;
|
||||
}
|
||||
|
||||
if (host == 4 && !docker4) {
|
||||
let settings = await ServerSettings.findOne({ where: { key: 'host4' } });
|
||||
let ip = settings.value.split(',')[1];
|
||||
let port = settings.value.split(',')[2];
|
||||
try {
|
||||
let containers = await docker3.listContainers({ all: true });
|
||||
console.log(`Host 3 connected. ${containers.length} containers found.`);
|
||||
}
|
||||
catch {
|
||||
console.log('Host 3 connection failed.');
|
||||
docker3;
|
||||
}
|
||||
} else if (hostid == 4) {
|
||||
docker4 = new Docker({ host: ip, port: port });
|
||||
} else if (host == 4 && docker4) {
|
||||
let containers = await docker4.listContainers({ all: true });
|
||||
return containers;
|
||||
try {
|
||||
let containers = await docker4.listContainers({ all: true });
|
||||
console.log(`Host 4 connected. ${containers.length} containers found.`);
|
||||
}
|
||||
catch {
|
||||
console.log('Host 4 connection failed.');
|
||||
docker4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export async function imageList() {
|
||||
|
@ -118,43 +165,31 @@ export async function containerInfo (containerID) {
|
|||
return container_info;
|
||||
}
|
||||
|
||||
|
||||
export async function containerLogs(containerID) {
|
||||
|
||||
let container = docker.getContainer(containerID);
|
||||
|
||||
// Fetch logs from the container
|
||||
const logs = await container.logs({
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
tail: 'all', // or specify a number for the number of lines
|
||||
});
|
||||
|
||||
const logs = await container.logs({ stdout: true, stderr: true, tail: 'all', });
|
||||
const logsString = logs.toString('utf8');
|
||||
|
||||
return logsString;
|
||||
}
|
||||
|
||||
|
||||
let available_versions = '';
|
||||
async function version_check () {
|
||||
// Fetch the data.
|
||||
const resp = await fetch('https://registry.hub.docker.com/v2/namespaces/lllllllillllllillll/repositories/dweebui/tags/?page_size=10000');
|
||||
// Parse the JSON.
|
||||
let hub = await resp.json();
|
||||
console.log('Checking available versions...');
|
||||
// Loop through the results.
|
||||
for (let i = 0; i < hub.results.length; i++) {
|
||||
// Skip version tag if it includes a dash.
|
||||
if (hub.results[i].name.includes('-')) { continue; }
|
||||
console.log(hub.results[i].name);
|
||||
available_versions += '| ' + hub.results[i].name + ' ';
|
||||
}
|
||||
console.log('Available versions:');
|
||||
console.log(available_versions);
|
||||
}
|
||||
version_check();
|
||||
|
||||
|
||||
// Creates then destroys a docker volume to trigger a docker event.
|
||||
export async function trigger_docker_event () {
|
||||
// Create then destroy a docker volume.
|
||||
let volume = await docker.createVolume({ Name: 'test_volume' });
|
||||
console.log('Manually triggered docker event.');
|
||||
let volume = await docker.createVolume({ Name: 'dweebui_test_volume' });
|
||||
setTimeout(async() => {
|
||||
await volume.remove();
|
||||
}, 200);
|
||||
|
@ -162,18 +197,17 @@ export async function trigger_docker_event () {
|
|||
|
||||
|
||||
export async function containerStats (containerID) {
|
||||
|
||||
const stats = await dockerContainerStats(containerID);
|
||||
|
||||
let info = {
|
||||
containerID: containerID,
|
||||
cpu: Math.round(stats[0].cpuPercent),
|
||||
ram: Math.round(stats[0].memPercent)
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function removeNetwork(networkID) {
|
||||
let network = docker.getNetwork(networkID);
|
||||
await network.remove();
|
||||
|
@ -182,35 +216,26 @@ export async function removeNetwork(networkID) {
|
|||
|
||||
|
||||
|
||||
export async function check_configured_hosts () {
|
||||
|
||||
// Loop that runs every 5 seconds to update the container stats.
|
||||
export async function containerStatsLoop () {
|
||||
let [host2, created] = await ServerSettings.findOrCreate({ where: {key: 'host2'}, defaults: { key: 'host2', value: '' } });
|
||||
if (host2.value != '') {
|
||||
let [tag2, ip2, port2] = host2.value.split(',');
|
||||
configureHost(2, ip2, port2);
|
||||
console.log('Host 2 configured.');
|
||||
}
|
||||
|
||||
let containers = await GetContainerLists(1);
|
||||
let [host3, created3] = await ServerSettings.findOrCreate({ where: {key: 'host3'}, defaults: { key: 'host3', value: '' } });
|
||||
if (host3.value != '') {
|
||||
let [tag3, ip3, port3] = host3.value.split(',');
|
||||
configureHost(3, ip3, port3);
|
||||
console.log('Host 3 configured.');
|
||||
}
|
||||
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
let containerID = containers[i].Id;
|
||||
let stats = await containerStats(containerID);
|
||||
|
||||
let container = await Container.findOne({ where: { containerID: containerID } });
|
||||
if (!container) {
|
||||
container = await Container.create({
|
||||
containerName: containers[i].Names[0].slice(1),
|
||||
containerID: containerID,
|
||||
cpu: JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
ram: JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
});
|
||||
}
|
||||
else {
|
||||
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 [host4, created4] = await ServerSettings.findOrCreate({ where: {key: 'host4'}, defaults: { key: 'host4', value: '' } });
|
||||
if (host4.value != '') {
|
||||
let [tag4, ip4, port4] = host4.value.split(',');
|
||||
configureHost(4, ip4, port4);
|
||||
console.log('Host 4 configured.');
|
||||
}
|
||||
}
|
||||
setInterval(containerStatsLoop, 5000);
|
||||
|
||||
|
|
353
utils/install.js
353
utils/install.js
|
@ -1,198 +1,227 @@
|
|||
import { writeFileSync, mkdirSync, readFileSync, readdirSync, writeFile } from "fs";
|
||||
import { execSync } from "child_process";
|
||||
import { Syslog } from "../database/config.js";
|
||||
import { docker } from "../utils/docker.js";
|
||||
import { Syslog } from "../db/config.js";
|
||||
import { docker } from "./docker.js";
|
||||
import DockerodeCompose from "dockerode-compose";
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
import { Alert } from "./system.js";
|
||||
|
||||
|
||||
export const Install = async (req, res) => {
|
||||
|
||||
let data = req.body;
|
||||
let data = req.body;
|
||||
|
||||
let { name, service_name, image, command_check, command, net_mode, restart_policy } = data;
|
||||
let { port0, port1, port2, port3, port4, port5 } = data;
|
||||
let { volume0, volume1, volume2, volume3, volume4, volume5 } = data;
|
||||
let { env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11 } = data;
|
||||
let { label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11 } = data;
|
||||
let { name, service_name, image, command_check, command, net_mode, restart_policy } = data;
|
||||
let { port0, port1, port2, port3, port4, port5 } = data;
|
||||
let { volume0, volume1, volume2, volume3, volume4, volume5 } = data;
|
||||
let { env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11 } = data;
|
||||
let { label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11 } = data;
|
||||
|
||||
let ports = [ port0, port1, port2, port3, port4, port5 ];
|
||||
let volumes = [volume0, volume1, volume2, volume3, volume4, volume5];
|
||||
let env_vars = [env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11];
|
||||
let labels = [label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11];
|
||||
let ports = [ port0, port1, port2, port3, port4, port5 ];
|
||||
let volumes = [volume0, volume1, volume2, volume3, volume4, volume5];
|
||||
let env_vars = [env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11];
|
||||
let labels = [label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11];
|
||||
|
||||
let docker_volumes = [];
|
||||
let docker_volumes = [];
|
||||
|
||||
// Make sure there isn't a container already running that has the same name
|
||||
let containers = await docker.listContainers({ all: true });
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
if (containers[i].Names[0].includes(name)) {
|
||||
// addAlert(req.session, 'danger', `App '${name}' already exists. Please choose a different name.`);
|
||||
console.log(`App '${name}' already exists. Please choose a different name.`);
|
||||
res.redirect('/');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function composeInstall (name, compose, req) {
|
||||
try {
|
||||
await compose.pull().then(() => {
|
||||
compose.up();
|
||||
|
||||
Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${name} installed successfully`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
|
||||
});
|
||||
} catch (err) {
|
||||
await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${name} installation failed: ${err}`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// addAlert(req.session, 'success', `Installing ${name}. It should appear on the dashboard shortly.`);
|
||||
console.log(`Installing ${name}. It should appear on the dashboard shortly.`);
|
||||
|
||||
// Compose file installation
|
||||
if (req.body.compose) {
|
||||
// Create the directory
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
// Write the form data to the compose file
|
||||
writeFileSync(`./templates/compose/${name}/compose.yaml`, req.body.compose, function (err) { console.log(err) });
|
||||
var compose = new DockerodeCompose(docker, `./templates/compose/${name}/compose.yaml`, `${name}`);
|
||||
composeInstall(name, compose, req);
|
||||
res.redirect('/');
|
||||
// Make sure there isn't a container already running that has the same name
|
||||
let containers = await docker.listContainers({ all: true });
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
if (containers[i].Names[0].includes(name)) {
|
||||
console.log(`App '${name}' already exists. Please choose a different name.`);
|
||||
let alert = Alert('danger', `App '${name}' already exists. Please choose a different name.`);
|
||||
res.send(alert);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a JSON template into a compose file
|
||||
let compose_file = `version: '3'`;
|
||||
compose_file += `\nservices:`
|
||||
compose_file += `\n ${service_name}:`
|
||||
compose_file += `\n container_name: ${name}`;
|
||||
compose_file += `\n image: ${image}`;
|
||||
|
||||
// Command
|
||||
if (command_check == 'on') { compose_file += `\n command: ${command}` }
|
||||
// async function composeInstall (name, compose, req) {
|
||||
|
||||
// Network mode
|
||||
if (net_mode == 'host') { compose_file += `\n network_mode: 'host'` }
|
||||
else if (net_mode != 'host' && net_mode != 'docker') { compose_file += `\n network_mode: '${net_mode}'` }
|
||||
// console.log('[composeInstall]');
|
||||
|
||||
// Restart policy
|
||||
if (restart_policy != '') { compose_file += `\n restart: ${restart_policy}` }
|
||||
// // try {
|
||||
// // await compose.pull().then(() => {
|
||||
|
||||
// Ports
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
if ((ports[i] == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n ports:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
// // compose.up();
|
||||
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
if ((ports[i] == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n - ${data[`port_${i}_external`]}:${data[`port_${i}_internal`]}/${data[`port_${i}_protocol`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Volumes
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
if (volumes[i] == 'on') {
|
||||
compose_file += `\n volumes:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
// // Syslog.create({
|
||||
// // user: req.session.user,
|
||||
// // email: null,
|
||||
// // event: "App Installation",
|
||||
// // message: `${name} installed successfully`,
|
||||
// // ip: req.socket.remoteAddress
|
||||
// // });
|
||||
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
// if volume is on and neither bind or container is empty, it's a bind mount (ex /mnt/user/appdata/config:/config )
|
||||
if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] != '') && (data[`volume_${i}_container`] != '')) {
|
||||
compose_file += `\n - ${data[`volume_${i}_bind`]}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
||||
}
|
||||
// if bind is empty create a docker volume (ex container_name_config:/config) convert any '/' in container name to '_'
|
||||
else if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] == '') && (data[`volume_${i}_container`] != '')) {
|
||||
let volume_name = data[`volume_${i}_container`].replace(/\//g, '_');
|
||||
compose_file += `\n - ${name}_${volume_name}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
||||
docker_volumes.push(`${name}_${volume_name}`);
|
||||
}
|
||||
}
|
||||
// // });
|
||||
// // } catch (err) {
|
||||
// // await Syslog.create({
|
||||
// // user: req.session.user,
|
||||
// // email: null,
|
||||
// // event: "App Installation",
|
||||
// // message: `${name} installation failed: ${err}`,
|
||||
// // ip: req.socket.remoteAddress
|
||||
// // });
|
||||
// // }
|
||||
|
||||
// Environment variables
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if (env_vars[i] == 'on') {
|
||||
compose_file += `\n environment:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if (env_vars[i] == 'on') {
|
||||
compose_file += `\n - ${data[`env_${i}_name`]}=${data[`env_${i}_default`]}`
|
||||
}
|
||||
}
|
||||
// await compose.pull();
|
||||
// await compose.up();
|
||||
// console.log('compose.up');
|
||||
// }
|
||||
|
||||
// Labels
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
if (labels[i] == 'on') {
|
||||
compose_file += `\n labels:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (data[`label${i}`] == 'on') {
|
||||
compose_file += `\n - ${data[`label_${i}_name`]}=${data[`label_${i}_value`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Privileged mode
|
||||
if (data.privileged == 'on') { compose_file += `\n privileged: true` }
|
||||
|
||||
// Hardware acceleration
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if ((env_vars[i] == 'on') && (data[`env_${i}_name`] == 'DRINODE')) {
|
||||
compose_file += `\n deploy:`
|
||||
compose_file += `\n resources:`
|
||||
compose_file += `\n reservations:`
|
||||
compose_file += `\n devices:`
|
||||
compose_file += `\n - driver: nvidia`
|
||||
compose_file += `\n count: 1`
|
||||
compose_file += `\n capabilities: [gpu]`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add volumes to the compose file
|
||||
if ( docker_volumes.length > 0 ) {
|
||||
compose_file += `\n`
|
||||
compose_file += `\nvolumes:`
|
||||
// Removed any duplicates from docker_volumes
|
||||
docker_volumes = docker_volumes.filter((item, index) => docker_volumes.indexOf(item) === index)
|
||||
for (let i = 0; i < docker_volumes.length; i++) {
|
||||
if ( docker_volumes[i] != '') {
|
||||
compose_file += `\n ${docker_volumes[i]}:`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compose file installation
|
||||
if (req.body.compose) {
|
||||
// Create the directory
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
writeFileSync(`./appdata/${name}/compose.yaml`, compose_file, function (err) { console.log(err) });
|
||||
var compose = new DockerodeCompose(docker, `./appdata/${name}/compose.yaml`, `${name}`);
|
||||
// Write the form data to the compose file
|
||||
writeFileSync(`./templates/compose/${name}/compose.yaml`, req.body.compose, function (err) { console.log(err) });
|
||||
var compose = new DockerodeCompose(docker, `./templates/compose/${name}/compose.yaml`, `${name}`);
|
||||
composeInstall(name, compose, req);
|
||||
res.redirect('/');
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert a JSON template into a compose file
|
||||
let compose_file = `version: '3'`;
|
||||
compose_file += `\nservices:`
|
||||
compose_file += `\n ${service_name}:`
|
||||
compose_file += `\n container_name: ${name}`;
|
||||
compose_file += `\n image: ${image}`;
|
||||
|
||||
// Command
|
||||
if (command_check == 'on') { compose_file += `\n command: ${command}` }
|
||||
|
||||
// Network mode
|
||||
if (net_mode == 'host') { compose_file += `\n network_mode: 'host'` }
|
||||
else if (net_mode != 'host' && net_mode != 'docker') { compose_file += `\n network_mode: '${net_mode}'` }
|
||||
|
||||
// Restart policy
|
||||
if (restart_policy != '') { compose_file += `\n restart: ${restart_policy}` }
|
||||
|
||||
// Ports
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
if ((ports[i] == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n ports:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
if ((ports[i] == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n - ${data[`port_${i}_external`]}:${data[`port_${i}_internal`]}/${data[`port_${i}_protocol`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Volumes
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
if (volumes[i] == 'on') {
|
||||
compose_file += `\n volumes:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
res.redirect('/');
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
// if volume is on and neither bind or container is empty, it's a bind mount (ex /mnt/user/appdata/config:/config )
|
||||
if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] != '') && (data[`volume_${i}_container`] != '')) {
|
||||
compose_file += `\n - ${data[`volume_${i}_bind`]}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
||||
}
|
||||
// if bind is empty create a docker volume (ex container_name_config:/config) convert any '/' in container name to '_'
|
||||
else if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] == '') && (data[`volume_${i}_container`] != '')) {
|
||||
let volume_name = data[`volume_${i}_container`].replace(/\//g, '_');
|
||||
compose_file += `\n - ${name}_${volume_name}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
||||
docker_volumes.push(`${name}_${volume_name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Environment variables
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if (env_vars[i] == 'on') {
|
||||
compose_file += `\n environment:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if (env_vars[i] == 'on') {
|
||||
compose_file += `\n - ${data[`env_${i}_name`]}=${data[`env_${i}_default`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Labels
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
if (labels[i] == 'on') {
|
||||
compose_file += `\n labels:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (data[`label${i}`] == 'on') {
|
||||
compose_file += `\n - ${data[`label_${i}_name`]}=${data[`label_${i}_value`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Privileged mode
|
||||
if (data.privileged == 'on') { compose_file += `\n privileged: true` }
|
||||
|
||||
// Hardware acceleration
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if ((env_vars[i] == 'on') && (data[`env_${i}_name`] == 'DRINODE')) {
|
||||
compose_file += `\n deploy:`
|
||||
compose_file += `\n resources:`
|
||||
compose_file += `\n reservations:`
|
||||
compose_file += `\n devices:`
|
||||
compose_file += `\n - driver: nvidia`
|
||||
compose_file += `\n count: 1`
|
||||
compose_file += `\n capabilities: [gpu]`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add volumes to the compose file
|
||||
if ( docker_volumes.length > 0 ) {
|
||||
compose_file += `\n`
|
||||
compose_file += `\nvolumes:`
|
||||
// Removed any duplicates from docker_volumes
|
||||
docker_volumes = docker_volumes.filter((item, index) => docker_volumes.indexOf(item) === index)
|
||||
for (let i = 0; i < docker_volumes.length; i++) {
|
||||
if ( docker_volumes[i] != '') {
|
||||
compose_file += `\n ${docker_volumes[i]}:`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
|
||||
writeFileSync(`./appdata/${name}/compose.yaml`, compose_file, function (err) { console.log(err) });
|
||||
|
||||
console.log(`Installing ${name}. It should appear on the dashboard shortly.`);
|
||||
|
||||
// composeInstall(name, compose, req);
|
||||
|
||||
var compose = new DockerodeCompose(docker, `./appdata/${name}/compose.yaml`, `${name}`);
|
||||
|
||||
(async () => {
|
||||
console.log('Pulling image');
|
||||
await compose.pull();
|
||||
console.log('Starting container');
|
||||
await compose.up();
|
||||
})();
|
||||
|
||||
let alert = Alert('success', `Installing ${name}. It should appear on the dashboard shortly.`);
|
||||
|
||||
res.send(alert);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// im just going to leave this old stackfile snippet here for now
|
||||
|
||||
// if (image.startsWith('https://')){
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Permission, User, Syslog } from "../database/config.js";
|
||||
import { Permission, User, Syslog } from "../db/config.js";
|
||||
import { readFileSync } from 'fs';
|
||||
import { Capitalize } from '../utils/system.js';
|
||||
import { trigger_docker_event } from "./docker.js";
|
||||
|
||||
|
||||
export const adminOnly = async (req, res, next) => {
|
||||
|
@ -15,8 +14,6 @@ export const adminOnly = async (req, res, next) => {
|
|||
|
||||
|
||||
export const sessionCheck = async (req, res, next) => {
|
||||
let path = req.path;
|
||||
// if (path != '/server_metrics') { console.log(`\x1b[90m ${req.session.username} ${path} \x1b[0m`); }
|
||||
if (req.session.userID) { next(); }
|
||||
else { res.redirect('/login'); }
|
||||
}
|
||||
|
@ -61,87 +58,3 @@ export const permissionCheck = async (req, res, next) => {
|
|||
console.log(`User ${req.session.username} does NOT have permission for ${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const permissionModal = async (req, res) => {
|
||||
let app_name = req.header('hx-trigger-name');
|
||||
let app_id = req.header('hx-trigger');
|
||||
let title = Capitalize(app_name);
|
||||
|
||||
let users = await User.findAll({ attributes: ['username', 'userID'] });
|
||||
|
||||
let modal_content =`<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_content += 'No other users.'; break; }
|
||||
// Skip the admin user.
|
||||
else if (i == 0) { continue; }
|
||||
let exists = await Permission.findOne({ where: {containerID: app_id, userID: users[i].userID}});
|
||||
if (!exists) { await Permission.create({ containerName: app_name, containerID: app_id, userID: users[i].userID, username: users[i].username}); }
|
||||
let permissions = await Permission.findOne({ where: {containerID: app_id, 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, app_id);
|
||||
user_permissions = user_permissions.replace(/container_name/g, app_name);
|
||||
user_permissions = user_permissions.replace(/user_id/g, users[i].userID);
|
||||
user_permissions = user_permissions.replace(/Username/g, users[i].username);
|
||||
modal_content += user_permissions;
|
||||
}
|
||||
modal_content += `</div></div>
|
||||
<div class="modal-footer">
|
||||
|
||||
<form id="reset_permissions" class="me-auto">
|
||||
<input type="hidden" name="containerID" value="${app_id}">
|
||||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" name="reset_permissions" id="submit" hx-post="/update_permissions" 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_content);
|
||||
}
|
||||
|
||||
|
||||
export const updatePermissions = async (req, res) => {
|
||||
let { containerID, containerName, 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="/update_permissions" 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="/update_permissions" hx-swap="outerHTML" hx-trigger="load delay:1s">Update ✔️</button>');
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { User, ServerSettings } from '../database/config.js';
|
||||
import { User, ServerSettings } from '../db/config.js';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
|
||||
|
@ -6,15 +6,16 @@ import { readFileSync } from 'fs';
|
|||
// Navbar
|
||||
export async function Navbar (req) {
|
||||
|
||||
let userID = req.session.userID;
|
||||
let username = req.session.username;
|
||||
let role = req.session.role;
|
||||
let host = req.session.host;
|
||||
|
||||
let host = '' + req.session.host;
|
||||
|
||||
let language = await getLanguage(req);
|
||||
let language = await getLanguage(userID);
|
||||
|
||||
// Check if the user wants to hide their profile name.
|
||||
if (req.session.userID != '00000000-0000-0000-0000-000000000000') {
|
||||
let user = await User.findOne({ where: { userID: req.session.userID }});
|
||||
if (userID != '00000000-0000-0000-0000-000000000000') {
|
||||
let user = await User.findOne({ where: { userID: userID }});
|
||||
let preferences = JSON.parse(user.preferences);
|
||||
if (preferences.hide_profile == true) { username = 'Anon'; }
|
||||
}
|
||||
|
@ -32,30 +33,33 @@ export async function Navbar (req) {
|
|||
const [host3, created3] = await ServerSettings.findOrCreate({ where: { key: 'host3' }, defaults: { key: 'host3', value: '' }});
|
||||
const [host4, created4] = await ServerSettings.findOrCreate({ where: { key: 'host4' }, defaults: { key: 'host4', value: '' }});
|
||||
|
||||
|
||||
if (host2.value) { host2_toggle = 'checked'; [host2_tag, host2_ip, host2_port] = host2.value.split(','); }
|
||||
if (host3.value) { host3_toggle = 'checked'; [host3_tag, host3_ip, host3_port] = host3.value.split(','); }
|
||||
if (host4.value) { host4_toggle = 'checked'; [host4_tag, host4_ip, host4_port] = host4.value.split(','); }
|
||||
|
||||
let host_buttons = '';
|
||||
let host_buttons = '<form action="/dashboard/action/switch_host/hostid" method="post">';
|
||||
let nav_link = '';
|
||||
|
||||
if (host == '0') { host0_active = 'text-yellow'; }
|
||||
if (host == '0') { host0_active = 'text-yellow'; nav_link = '/0'; }
|
||||
if (host == '1') { host1_active = 'text-yellow'; }
|
||||
if (host == '2') { host2_active = 'text-yellow'; }
|
||||
if (host == '3') { host3_active = 'text-yellow'; }
|
||||
if (host == '4') { host4_active = 'text-yellow'; }
|
||||
if (host == '2') { host2_active = 'text-yellow'; nav_link = '/2'; }
|
||||
if (host == '3') { host3_active = 'text-yellow'; nav_link = '/3'; }
|
||||
if (host == '4') { host4_active = 'text-yellow'; nav_link = '/4'; }
|
||||
|
||||
if (host2_toggle || host3_toggle || host4_toggle) { host_buttons += `<a href="/0/dashboard" class="btn ${host0_active}" title="All">All</a> <a href="/1/dashboard" class="btn ${host1_active}" title="Host 1">Host 1</a>`; }
|
||||
if (host2_toggle) { host_buttons += `<a href="/2/dashboard" class="btn ${host2_active}" title="${host2_tag}">${host2_tag}</a>`; }
|
||||
if (host3_toggle) { host_buttons += `<a href="/3/dashboard" class="btn ${host3_active}" title="${host3_tag}">${host3_tag}</a>`; }
|
||||
if (host4_toggle) { host_buttons += `<a href="/4/dashboard" class="btn ${host4_active}" title="${host4_tag}">${host4_tag}</a>`; }
|
||||
if (host2_toggle || host3_toggle || host4_toggle) { host_buttons += `<button type="submit" name="host" value="0" class="btn ${host0_active}" title="All">All</button> <button type="submit" name="host" value="1" hx-swap="none" class="btn ${host1_active}" title="Host 1">Host 1</button>`; }
|
||||
if (host2_toggle) { host_buttons += `<button type="submit" name="host" value="2" class="btn ${host2_active}" title="${host2_tag}">${host2_tag}</button>`; }
|
||||
if (host3_toggle) { host_buttons += `<button type="submit" name="host" value="3" hx-swap="none" class="btn ${host3_active}" title="${host3_tag}">${host3_tag}</button>`; }
|
||||
if (host4_toggle) { host_buttons += `<button type="submit" name="host" value="4" hx-swap="none" class="btn ${host4_active}" title="${host4_tag}">${host4_tag}</button>`; }
|
||||
|
||||
host_buttons += '</form>';
|
||||
|
||||
let navbar = readFileSync('./views/partials/navbar.html', 'utf8');
|
||||
|
||||
if (language == 'english') {
|
||||
navbar = navbar.replace(/Username/g, username);
|
||||
navbar = navbar.replace(/Userrole/g, req.session.role);
|
||||
navbar = navbar.replace(/Userrole/g, role);
|
||||
navbar = navbar.replace(/HostButtons/g, host_buttons);
|
||||
navbar = navbar.replace(/HOSTID/g, nav_link);
|
||||
return navbar;
|
||||
} else {
|
||||
let lang = readFileSync(`./languages/${language}.json`, 'utf8');
|
||||
|
@ -68,6 +72,7 @@ export async function Navbar (req) {
|
|||
navbar = navbar.replace(/Apps/g, lang.Apps);
|
||||
navbar = navbar.replace(/Users/g, lang.Users);
|
||||
navbar = navbar.replace(/Syslogs/g, lang.Syslogs);
|
||||
navbar = navbar.replace(/HOSTID/g, nav_link);
|
||||
|
||||
navbar = navbar.replace(/Search/g, lang.Search);
|
||||
navbar = navbar.replace(/Account/g, lang.Account);
|
||||
|
@ -76,9 +81,8 @@ export async function Navbar (req) {
|
|||
navbar = navbar.replace(/Settings/g, lang.Settings);
|
||||
navbar = navbar.replace(/Logout/g, lang.Logout);
|
||||
|
||||
|
||||
navbar = navbar.replace(/Username/g, username);
|
||||
navbar = navbar.replace(/Userrole/g, req.session.role);
|
||||
navbar = navbar.replace(/Userrole/g, role);
|
||||
navbar = navbar.replace(/HostButtons/g, host_buttons);
|
||||
return navbar;
|
||||
}
|
||||
|
@ -88,7 +92,7 @@ export async function Navbar (req) {
|
|||
// Sidebar
|
||||
export async function Sidebar (req) {
|
||||
|
||||
let language = await getLanguage(req);
|
||||
let language = await getLanguage(req.session.userID);
|
||||
|
||||
let sidebar = readFileSync('./views/partials/sidebar.html', 'utf8');
|
||||
|
||||
|
@ -112,7 +116,7 @@ export async function Sidebar (req) {
|
|||
// Footer
|
||||
export async function Footer (req) {
|
||||
|
||||
let language = await getLanguage(req);
|
||||
let language = await getLanguage(req.session.userID);
|
||||
|
||||
let footer = readFileSync('./views/partials/footer.html', 'utf8');
|
||||
|
||||
|
@ -152,17 +156,15 @@ export function Alert (type, message) {
|
|||
}
|
||||
|
||||
|
||||
export async function getLanguage (req) {
|
||||
export async function getLanguage (userID) {
|
||||
|
||||
// No userID if authentication is disabled.
|
||||
if (req.session.userID == '00000000-0000-0000-0000-000000000000') {
|
||||
// Use the admin's language if authentication is disabled.
|
||||
if (userID == '00000000-0000-0000-0000-000000000000') {
|
||||
let user = await User.findOne({ where: { role: 'admin' }});
|
||||
let preferences = JSON.parse(user.preferences);
|
||||
return preferences.language;
|
||||
return user.language;
|
||||
} else {
|
||||
let user = await User.findOne({ where: { userID: req.session.userID }});
|
||||
let preferences = JSON.parse(user.preferences);
|
||||
return preferences.language;
|
||||
let user = await User.findOne({ where: { userID: userID }});
|
||||
return user.language;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { docker } from "../utils/docker.js";
|
||||
import { Syslog } from "../database/config.js";
|
||||
import { Syslog } from "../db/config.js";
|
||||
|
||||
|
||||
export const Uninstall = async (req, res) => {
|
||||
|
|
|
@ -139,28 +139,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal medium-modal modal-blur fade" id="wide_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="wide_modal_content">
|
||||
<!-- modal content inserted with htmx -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="modal slim-modal modal-blur fade" id="info_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="info_modal_content">
|
||||
<!-- modal content inserted with htmx -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="d-flex mt-4">
|
||||
<ul class="pagination ms-auto">
|
||||
|
@ -190,6 +168,22 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal medium-modal modal-blur fade" id="wide_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="wide_modal_content">
|
||||
<!-- modal content inserted with htmx -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal slim-modal modal-blur fade" id="info_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="info_modal_content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
|
|
|
@ -123,7 +123,9 @@
|
|||
|
||||
<!-- HTMX -->
|
||||
<div class="col-12">
|
||||
<div class="row row-cards" hx-get="/card_list" data-hx-trigger="load, sse:update" data-hx-swap="afterbegin" hx-target="#containers"></div>
|
||||
|
||||
<div class="row row-cards" name="card_list" hx-get="/dashboard/view/card_list" data-hx-trigger="load, sse:update" data-hx-swap="afterbegin" hx-target="#containers"></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -146,6 +148,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal medium-modal modal-blur fade" id="medium_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="medium_modal_content">
|
||||
|
@ -163,9 +166,17 @@
|
|||
</div>
|
||||
|
||||
|
||||
<script src="/libs/apexcharts/dist/apexcharts.min.js?1692870487" defer></script>
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
<script defer>
|
||||
var modalScrollable = document.getElementById('wide_modal');
|
||||
modalScrollable.addEventListener('shown.bs.modal', function () {
|
||||
modalScrollable.querySelector('.modal-body').scrollTop = modalScrollable.querySelector('.modal-body').scrollHeight;
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="/libs/apexcharts/dist/apexcharts.min.js?1692870487"></script>
|
||||
|
||||
<script src="/js/dweebui.js"></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
<script src="/js/htmx-sse.js"></script>
|
||||
|
||||
|
|
|
@ -51,8 +51,8 @@
|
|||
<th><label class="table-sort" data-sort="sort-type">Tag</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-city">ID</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-score">Status</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-date">Created</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-quantity">Size</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-date">Created</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-progress">Action</label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -23,20 +23,23 @@
|
|||
<div class="px-0">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<a href="#" class="btn" name="AppName" id="AppType" hx-post="/apps/info" hx-swap="innerHTML" data-hx-trigger="mousedown" hx-target="#wide_modal_content" data-bs-toggle="modal" data-bs-target="#wide_modal">
|
||||
<a href="" class="btn" name="AppName" id="AppType" hx-get="/appsModals/info" hx-swap="innerHTML" data-hx-trigger="mousedown" hx-target="#info_modal_content" data-bs-toggle="modal" data-bs-target="#info_modal">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-article mx-2" 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="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"></path> <path d="M7 8h10"></path> <path d="M7 12h10"></path> <path d="M7 16h10"></path></svg>
|
||||
More Info
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-auto ms-auto">
|
||||
<a href="" class="btn" name="AppName" id="AppType" hx-post="/apps/view_install" hx-swap="innerHTML" data-hx-trigger="mousedown" hx-target="#wide_modal_content" data-bs-toggle="modal" data-bs-target="#wide_modal">
|
||||
|
||||
<button class="btn" name="AppName" id="AppType" hx-get="/appsModals/view_install" hx-swap="innerHTML" data-hx-trigger="mousedown" hx-target="#wide_modal_content" data-bs-toggle="modal" data-bs-target="#wide_modal">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-bar-to-down mx-2" 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="M4 20l16 0"></path> <path d="M12 14l0 -10"></path> <path d="M12 14l4 -4"></path> <path d="M12 14l-4 -4"></path></svg>
|
||||
Install
|
||||
</a>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="col-sm-6 col-lg-3" hx-get="/update_card/ContainerID" hx-trigger="sse:ContainerID" id="AltID" hx-swap="outerHTML" hx-target="#AltID" name="AppName">
|
||||
<div class="col-sm-6 col-lg-3" hx-get="/dashboard/view/update_card/ContainerID" hx-trigger="sse:ContainerID" id="AltID" hx-swap="outerHTML" hx-target="#AltID" name="AppName">
|
||||
<div class="card p-2">
|
||||
<div class="container-stamp">
|
||||
<img class="rounded-4 pt-1 px-1" width="95px" src="https://raw.githubusercontent.com/lllllllillllllillll/Dashboard-Icons/main/png/AppService.png" onerror="this.onerror=null;this.src='https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/docker.png';"></img>
|
||||
|
@ -6,16 +6,16 @@
|
|||
<div class="d-flex align-items-center">
|
||||
<label style="font-size: smaller; font-weight: bold;" class="text-yellow">AppPorts</label>
|
||||
<div class="ms-auto lh-1">
|
||||
<button class="container-action" title="Start" data-hx-post="/container/start/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Start" hx-swap="outerHTML">
|
||||
<button class="container-action" title="Start" data-hx-post="/dashboard/action/start/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Start" id="AppName" hx-swap="outerHTML">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="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 class="container-action" title="Stop" data-hx-post="/container/stop/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Stop" hx-swap="outerHTML">
|
||||
<button class="container-action" title="Stop" data-hx-post="/dashboard/action/stop/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Stop" id="AppName" hx-swap="outerHTML">
|
||||
<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 class="container-action" title="Pause" data-hx-post="/container/pause/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Pause" hx-swap="outerHTML">
|
||||
<button class="container-action" title="Pause" data-hx-post="/dashboard/action/pause/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Pause" id="AppName" hx-swap="outerHTML">
|
||||
<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 class="container-action" title="Restart" data-hx-post="/container/restart/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Restart" hx-swap="outerHTML">
|
||||
<button class="container-action" title="Restart" data-hx-post="/dashboard/action/restart/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Restart" id="AppName" hx-swap="outerHTML">
|
||||
<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>
|
||||
|
||||
|
@ -23,20 +23,20 @@
|
|||
<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="details" data-hx-post="/container/details/ContainerID" hx-swap="innerHTML" data-hx-trigger="mousedown" hx-target="#medium_modal_content" data-bs-toggle="modal" data-bs-target="#medium_modal">Details</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-post="/container/logs/ContainerID" data-hx-trigger="mousedown" hx-target="#wide_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#wide_modal">Logs</button>
|
||||
<button class="dropdown-item text-secondary" name="edit" data-hx-post="/container/details/ContainerID" data-hx-trigger="mousedown" hx-target="#wide_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#wide_modal">Edit</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-get="/dashboard/view/details/ContainerID" data-hx-trigger="mousedown" hx-target="#medium_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#medium_modal">Details</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-get="/dashboard/view/logs/ContainerID" data-hx-trigger="mousedown" hx-target="#wide_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#wide_modal">Logs</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-get="/dashboard/view/edit/ContainerID" data-hx-trigger="mousedown" hx-target="#wide_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#wide_modal">Edit</button>
|
||||
<button class="dropdown-item text-primary" name="AppName" id="update" disabled="">Update</button>
|
||||
<button class="dropdown-item text-danger" name="AppTitle" hx-trigger="mousedown" data-hx-post="/container/uninstall/ContainerID" hx-swap="innerHTML" data-hx-target="#modal_content" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Uninstall</button>
|
||||
<button class="dropdown-item text-danger" name="AppName" hx-trigger="mousedown" data-hx-get="/dashboard/view/uninstall/ContainerID" hx-swap="innerHTML" data-hx-target="#modal_content" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Uninstall</button>
|
||||
</div>
|
||||
|
||||
<a href="#" class="container-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<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>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item text-secondary" data-hx-post="/container/hide/ContainerID" data-hx-trigger="mousedown" data-hx-swap="delete" data-hx-target="#AltID" name="AppName">Hide</button>
|
||||
<button class="dropdown-item text-secondary" name="AppTitle" hx-trigger="mousedown" data-hx-post="/container/link_modal/ContainerID" hx-swap="innerHTML" data-hx-target="#modal_content" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Edit Link</button>
|
||||
<button class="dropdown-item text-secondary" data-hx-get="/permission_modal" name="AppName" id="ContainerID" hx-target="#modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Permissions</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-post="/dashboard/action/hide/ContainerID" data-hx-trigger="mousedown" data-hx-swap="delete" data-hx-target="#AltID">Hide</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-get="/dashboard/view/link_modal/ContainerID" hx-trigger="mousedown" hx-swap="innerHTML" data-hx-target="#modal_content" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Edit Link</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-get="/dashboard/view/permissions/ContainerID" hx-target="#modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Permissions</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -52,7 +52,7 @@
|
|||
|
||||
</div>
|
||||
|
||||
<div id="chart-AltID" class="chart-sm"></div>
|
||||
<div id="AltIDchart" class="chart-sm"></div>
|
||||
|
||||
<script>
|
||||
var options = {
|
||||
|
@ -100,8 +100,10 @@
|
|||
type: 'datetime',
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
labels: {
|
||||
padding: 4
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
labels: [
|
||||
|
@ -113,11 +115,13 @@
|
|||
},
|
||||
};
|
||||
|
||||
var chart = new ApexCharts(document.querySelector("#chart-AltID"), options);
|
||||
|
||||
chart.render();
|
||||
var AltIDchart = new ApexCharts(document.querySelector("#AltIDchart"), options);
|
||||
AltIDchart.render();
|
||||
|
||||
</script>
|
||||
|
||||
ChartTrigger
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -1,31 +1,37 @@
|
|||
|
||||
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<form action="/install" id="install" method="POST">
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-lg-5">
|
||||
<label class="form-label">Container Name: </label>
|
||||
<input type="text" class="form-control" name="service_name" value="AppName" hidden/>
|
||||
<input type="text" class="form-control" name="name" value="AppName"/>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label class="form-label">Image: </label>
|
||||
<input type="text" class="form-control" name="image" value="AppImage"/>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Restart Policy: </label>
|
||||
<select class="form-select" name="restart_policy">
|
||||
<option value="RestartPolicy" selected hidden>RestartPolicy</option>
|
||||
<option value="unless-stopped">unless-stopped</option>
|
||||
<option value="on-failure">on-failure</option>
|
||||
<option value="no">never</option>
|
||||
<option value="always">always</option>
|
||||
</select>
|
||||
</div>
|
||||
<form id="details">
|
||||
|
||||
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-lg-5">
|
||||
<label class="form-label">Container Name: </label>
|
||||
<input type="text" class="form-control" name="service_name" value="AppName" hidden/>
|
||||
<input type="text" class="form-control" name="name" value="AppName"/>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label class="form-label">Image: </label>
|
||||
<input type="text" class="form-control" name="image" value="AppImage"/>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Restart Policy: </label>
|
||||
<select class="form-select" name="restart_policy">
|
||||
<option value="RestartPolicy" selected hidden>RestartPolicy</option>
|
||||
<option value="unless-stopped">unless-stopped</option>
|
||||
<option value="on-failure">on-failure</option>
|
||||
<option value="no">never</option>
|
||||
<option value="always">always</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="form-label">Network Mode</label>
|
||||
|
@ -219,7 +225,7 @@
|
|||
<div class="accordion-body pt-0">
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_0_check" type="checkbox" Vol0Check>
|
||||
<input class="form-check-input" name="volume0" type="checkbox" Vol0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_bind" value="Vol0Source"/>
|
||||
|
@ -238,7 +244,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_1_check" type="checkbox" Vol1Check>
|
||||
<input class="form-check-input" name="volume1" type="checkbox" Vol1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_bind" value="Vol1Source"/>
|
||||
|
@ -257,7 +263,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_2_check" type="checkbox" Vol2Check>
|
||||
<input class="form-check-input" name="volume2" type="checkbox" Vol2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_bind" value="Vol2Source"/>
|
||||
|
@ -276,7 +282,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_3_check" type="checkbox" Vol3Check>
|
||||
<input class="form-check-input" name="volume3" type="checkbox" Vol3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_bind" value="Vol3Source"/>
|
||||
|
@ -295,7 +301,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_4_check" type="checkbox" Vol4Check>
|
||||
<input class="form-check-input" name="volume4" type="checkbox" Vol4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_bind" value="Vol4Source"/>
|
||||
|
@ -314,7 +320,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_5_check" type="checkbox" Vol5Check>
|
||||
<input class="form-check-input" name="volume5" type="checkbox" Vol5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_bind" value="Vol5Source"/>
|
||||
|
@ -824,10 +830,13 @@
|
|||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary" form="install" value="Install">Install</button>
|
||||
<button type="submit" class="btn btn-primary" form="install_info">Install</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="modal-title">AppName</div>
|
||||
<div>AppDesc</div>
|
||||
</div>
|
||||
<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-primary" data-bs-dismiss="modal">Okay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-title">AppTitle</div>
|
||||
<div>AppDescription</div>
|
||||
</div>
|
||||
<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-primary" data-bs-dismiss="modal">Okay</button>
|
||||
</div>
|
||||
|
|
842
views/partials/install.html
Normal file
842
views/partials/install.html
Normal file
|
@ -0,0 +1,842 @@
|
|||
|
||||
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<form id="install_info" hx-post="/install" hx-swap="innerHTML" hx-target="#alert">
|
||||
|
||||
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-lg-5">
|
||||
<label class="form-label">Container Name: </label>
|
||||
<input type="text" class="form-control" name="service_name" value="AppName" hidden/>
|
||||
<input type="text" class="form-control" name="name" value="AppName"/>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label class="form-label">Image: </label>
|
||||
<input type="text" class="form-control" name="image" value="AppImage"/>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Restart Policy: </label>
|
||||
<select class="form-select" name="restart_policy">
|
||||
<option value="RestartPolicy" selected hidden>RestartPolicy</option>
|
||||
<option value="unless-stopped">unless-stopped</option>
|
||||
<option value="on-failure">on-failure</option>
|
||||
<option value="no">never</option>
|
||||
<option value="always">always</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="form-label">Network Mode</label>
|
||||
<div class="form-selectgroup-boxes row mb-3">
|
||||
<div class="col">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="net_mode" value="host" class="form-selectgroup-input" NetHost>
|
||||
<span class="form-selectgroup-label d-flex align-items-center p-3">
|
||||
<span class="me-3">
|
||||
<span class="form-selectgroup-check"></span>
|
||||
</span>
|
||||
<span class="form-selectgroup-label-content">
|
||||
<span class="form-selectgroup-title strong mb-1">Host Network</span>
|
||||
<span class="d-block text-secondary">Same as host. No isolation. ex.127.0.0.1</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="net_mode" value="NetName" class="form-selectgroup-input" NetBridge>
|
||||
<span class="form-selectgroup-label d-flex align-items-center p-3">
|
||||
<span class="me-3">
|
||||
<span class="form-selectgroup-check"></span>
|
||||
</span>
|
||||
<span class="form-selectgroup-label-content">
|
||||
<span class="form-selectgroup-title strong mb-1">Bridge Network</span>
|
||||
<span class="d-block text-secondary">Containers can communicate using names.</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="net_mode" value="docker" class="form-selectgroup-input" NetDocker>
|
||||
<span class="form-selectgroup-label d-flex align-items-center p-3">
|
||||
<span class="me-3">
|
||||
<span class="form-selectgroup-check"></span>
|
||||
</span>
|
||||
<span class="form-selectgroup-label-content">
|
||||
<span class="form-selectgroup-title strong mb-1">Docker Network</span>
|
||||
<span class="d-block text-secondary">Isolated on the docker network. ex.172.0.34.2</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion" id="modal-accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-1">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-1" aria-expanded="false">
|
||||
Ports
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-1" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port0" type="checkbox" Port0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">External Port</label>
|
||||
<input type="text" class="form-control" name="port_0_external" value="Port0External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Internal Port</label>
|
||||
<input type="text" class="form-control" name="port_0_internal" value="Port0Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<label class="form-label">Protocol</label>
|
||||
<select class="form-select" name="port_0_protocol">
|
||||
<option value="Port0Protocol" selected hidden>Port0Protocol</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port1" type="checkbox" Port1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_external" value="Port1External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_internal" value="Port1Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_1_protocol">
|
||||
<option value="Port1Protocol" selected hidden>Port1Protocol</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port2" type="checkbox" Port2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_external" value="Port2External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_internal" value="Port2Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_2_protocol">
|
||||
<option value="Port2Protocol" selected hidden>Port2Protocol</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port3" type="checkbox" Port3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_external" value="Port3External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_internal" value="Port3Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_3_protocol">
|
||||
<option value="Port3Protocol" selected hidden>Port3Protocol</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port4" type="checkbox" Port4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_external" value="Port4External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_internal" value="Port4Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_4_protocol">
|
||||
<option value="Port4Protocol" selected hidden>Port4Protocol</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port5" type="checkbox" Port5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_5_external" value="Port5External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_5_internal" value="Port5Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_5_protocol">
|
||||
<option value="Port5Protocol" selected hidden>Port5Protocol</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-2">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-2" aria-expanded="false">
|
||||
Volumes
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
<div id="collapse-2" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
|
||||
<div class="accordion-body pt-0">
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume0" type="checkbox" Vol0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_bind" value="Vol0Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_container" value="Vol0Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_0_readwrite">
|
||||
<option value="Vol0RW" selected hidden>Vol0RW</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume1" type="checkbox" Vol1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_bind" value="Vol1Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_container" value="Vol1Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_1_readwrite">
|
||||
<option value="Vol1RW" selected hidden>Vol1RW</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume2" type="checkbox" Vol2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_bind" value="Vol2Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_container" value="Vol2Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_2_readwrite">
|
||||
<option value="Vol2RW" selected hidden>Vol2RW</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume3" type="checkbox" Vol3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_bind" value="Vol3Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_container" value="Vol3Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_3_readwrite">
|
||||
<option value="Vol3RW" selected hidden>Vol3RW</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume4" type="checkbox" Vol4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_bind" value="Vol4Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_container" value="Vol4Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_4_readwrite">
|
||||
<option value="Vol4RW" selected hidden>Vol4RW</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume5" type="checkbox" Vol5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_bind" value="Vol5Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_container" value="Vol5Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_5_readwrite">
|
||||
<option value="Vol5RW" selected hidden>Vol5RW</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-3">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-3" aria-expanded="false">
|
||||
Environment Variables
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-3" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_0_check" Env0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Variable</label>
|
||||
<input type="text" class="form-control" name="env_0_name" value="Env0Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Value</label>
|
||||
<input type="text" class="form-control" name="env_0_default" value="Env0Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_1_check" Env1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_1_name" value="Env1Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_1_default" value="Env1Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_2_check" Env2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_2_name" value="Env2Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_2_default" value="Env2Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_3_check" Env3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_3_name" value="Env3Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_3_default" value="Env3Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_4_check" Env4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_4_name" value="Env4Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_4_default" value="Env4Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_5_check" Env5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_5_name" value="Env5Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_5_default" value="Env5Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_6_check" Env6Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_6_name" value="Env6Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_6_default" value="Env6Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_7_check" Env7Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_7_name" value="Env7Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_7_default" value="Env7Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_8_check" Env8Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_8_name" value="Env8Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_8_default" value="Env8Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_9_check" Env9Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_9_name" value="Env9Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_9_default" value="Env9Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_10_check" Env10Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_10_name" value="Env10Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_10_default" value="Env10Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_11_check" Env11Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_11_name" value="Env11Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_11_default" value="Env11Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_12_check" Env12Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_name" value="Env12Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_default" value="Env12Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_12_check" Env13Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_name" value="Env13Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_default" value="Env13Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_12_check" Env14Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_name" value="Env14Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_default" value="Env14Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_12_check" Env15Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_name" value="Env15Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_default" value="Env15Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-4">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-4" aria-expanded="false">
|
||||
Labels
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-4" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_0_check" Label0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Variable</label>
|
||||
<input type="text" class="form-control" name="label_0_name" value="Label0Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Value</label>
|
||||
<input type="text" class="form-control" name="label_0_value" value="Label0Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_1_check" Label1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_name" value="Label1Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_value" value="Label1Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_2_check" Label2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_name" value="Label2Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_value" value="Label2Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_3_check" Label3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_name" value="Label3Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_value" value="Label3Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_4_check" Label4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_name" value="Label4Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_value" value="Label4Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_5_check" Label5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_5_name" value="Label5Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_5_value" value="Label5Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_6_check" Label6Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_6_name" value="Label6Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_6_value" value="Label6Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_7_check" Label7Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_7_name" value="Label7Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_7_value" value="Label7Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_8_check" Label8Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_8_name" value="Label8Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_8_value" value="Label8Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_9_check" Label9Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_9_name" value="Label9Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_9_value" value="Label9Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_10_check" Label10Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_10_name" value="Label10Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_10_value" value="Label10Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" Label11Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label11Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label11Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" Label12Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label12Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label12Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" Label13Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label13Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label13Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" Label14Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label14Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label14Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" Label15Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label15Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label15Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" Label16Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label16Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label16Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" Label17Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label17Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label17Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" Label18Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label18Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label18Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" Label19Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label19Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label19Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-5">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-5" aria-expanded="false">
|
||||
Extras
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-5" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary" form="install_info" data-bs-dismiss="modal" onclick="topScroll()">Install</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
|
||||
<div class="modal-content" id="modal_content">
|
||||
|
||||
<form hx-post="/container/update_link/ContainerID" hx-swap="none">
|
||||
<form action="/container/update_link/ContainerID" method="post">
|
||||
|
||||
<input type="hidden" name="service_id" value="ContainerID">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
|
||||
<div class="modal-body text-center py-3">
|
||||
<h3 class="mb-3">AppName Link</h3>
|
||||
<div class="text-muted mb-2">Enter URL</div>
|
||||
<div class="text-muted mb-2">URL needs to start with http:// or https://</div>
|
||||
<input type="text" class="form-control mb-2" name="url" value="AppLink">
|
||||
</div>
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
|||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="btn btn-primary w-100" type="submit">Update </button>
|
||||
<input type="submit" value="Update" class="btn btn-primary w-100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<form action="/install" id="install" method="POST">
|
||||
<div class="modal-body" id="modalBody">
|
||||
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-lg-5">
|
||||
|
@ -23,11 +21,8 @@
|
|||
<div class="col-lg-3">
|
||||
<label class="form-label mb-1">Filter: </label>
|
||||
<select class="form-select" name="restart_policy">
|
||||
<option value="RestartPolicy" selected hidden>24 Hours</option>
|
||||
<option value="unless-stopped">unless-stopped</option>
|
||||
<option value="on-failure">on-failure</option>
|
||||
<option value="no">never</option>
|
||||
<option value="always">always</option>
|
||||
<option value="All" selected hidden>All</option>
|
||||
<option value="All">All</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -36,14 +31,13 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-12">
|
||||
<textarea class="form-control" style="height: 65vh; resize: none;" readonly>ContainerLogs</textarea>
|
||||
<textarea class="form-control" id="logs" style="height: 65vh; resize: none;" readonly>ContainerLogs</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-success" name="AppName" data-hx-post="/container/logs/ContainerID" data-hx-trigger="mousedown" hx-target="#wide_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#wide_modal">Refresh</button>
|
||||
<button type="submit" class="btn btn-success" name="AppName" data-hx-get="/dashboard/view/logs/ContainerID" data-hx-trigger="mousedown" hx-target="#wide_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#wide_modal">Refresh</button>
|
||||
</div>
|
|
@ -13,9 +13,7 @@
|
|||
</h1>
|
||||
|
||||
|
||||
<!-- <% if(alert) { %>
|
||||
<%- alert %>
|
||||
<% } %> -->
|
||||
<div id="alert"></div>
|
||||
|
||||
|
||||
<div class="navbar-nav flex-row order-md-last">
|
||||
|
@ -235,8 +233,8 @@
|
|||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<form action="/container/reset/000" method="post">
|
||||
<button class="dropdown-item text-secondary" name="reset" id="reset" value="reset">Reset View</button>
|
||||
<form action="/dashboard/action/reset/000" method="post">
|
||||
<button class="dropdown-item text-secondary" name="reset">Reset View</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -18,10 +18,8 @@
|
|||
<input type="hidden" name="username" value="Username">
|
||||
<input type="hidden" name="select" value="selectEntry">
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="row">
|
||||
<span class="col">All</span>
|
||||
|
@ -39,7 +37,6 @@
|
|||
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col">Uninstall</span>
|
||||
|
@ -147,7 +144,7 @@
|
|||
</div>
|
||||
|
||||
<div class="row mb-2 pt-2">
|
||||
<button class="btn" type="button" id="submit" hx-post="/update_permissions" hx-swap="outerHTML">Update </button>
|
||||
<button class="btn" type="button" id="submit" hx-post="/dashboard/action/update_permissions/container_id" hx-swap="outerHTML">Update </button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
84
views/partials/user.html
Normal file
84
views/partials/user.html
Normal file
|
@ -0,0 +1,84 @@
|
|||
|
||||
<form action="/users/action/change/USERID" method="post">
|
||||
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title px-2">Username</h5>
|
||||
<div class="me-auto badge badge-outline text-green">Active</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body py-2">
|
||||
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-auto">
|
||||
<!-- <a href="#" class="avatar avatar-upload rounded">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M12 5l0 14"></path><path d="M5 12l14 0"></path></svg>
|
||||
<span class="avatar-upload-text">Add</span>
|
||||
</a> -->
|
||||
<span class="avatar avatar-upload rounded bg-green-lt">J</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Full Name</label>
|
||||
<input type="text" class="form-control" name="name" value="FullName">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mb-3">
|
||||
<label class="mb-1">Email: </label>
|
||||
<input type="text" class="form-control" name="email" value="EmailAddress"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mb-3">
|
||||
<label class="mb-1">UserID: </label>
|
||||
<input type="text" class="form-control text-secondary" name="userID" value="USERID" readonly/>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-6">
|
||||
<label class="mb-1">Last Login: </label>
|
||||
<input type="text" class="form-control" name="last_login" value="LastLogin"/>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<label class="mb-1">Created: </label>
|
||||
<input type="text" class="form-control" name="created" value="CreatedAt"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col mb-2">
|
||||
<a href="#" class="btn w-100" data-bs-dismiss="modal">
|
||||
Reset Permissions
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="w-100">
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
<a href="#" class="btn w-100" data-bs-dismiss="modal">
|
||||
Close
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<button type="submit" name="change" value="remove" class="btn btn-danger w-100" hx-confirm="Are you sure you want to remove this account?">Remove</button>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<button type="submit" name="change" value="disable" class="btn btn-secondary w-100" hx-confirm="Are you sure you want to disable this account?">Disable</button>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<button type="submit" name="change" value="update" class="btn btn-primary w-100" hx-confirm="Update account?">Update</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
|
@ -32,7 +32,7 @@
|
|||
<div class="card-body">
|
||||
|
||||
<!-- HTMX - Submits the form and replaces the target with the response. Replaces the submit button with "Updated" -->
|
||||
<form id="preferences" hx-post="/preferences" hx-target="#submit" hx-swap="outerHTML">
|
||||
<form id="preferences" action="/preferences" method="POST">
|
||||
|
||||
<h1 class="">Preferences</h1>
|
||||
<label class="text-muted mb-3">User Preferences.</label>
|
||||
|
@ -73,7 +73,7 @@
|
|||
<a href="#" class="btn">
|
||||
Cancel
|
||||
</a>
|
||||
<button class="btn btn-primary" id="submit">
|
||||
<button class="btn btn-primary" id="submit" type="submit">
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<div class="card-body">
|
||||
|
||||
<!-- HTMX - Submits the form and replaces the target with the response. Replaces the submit button with "Updated" -->
|
||||
<form id="settings" hx-post="/settings" hx-target="#submit" hx-swap="outerHTML">
|
||||
<form id="settings" hx-post="/settings/action/update" hx-target="#submit" hx-swap="outerHTML">
|
||||
|
||||
<h1 class="">Settings</h1>
|
||||
<label class="text-muted mb-3">Configure server settings. Admin only.</label>
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
</div>
|
||||
|
||||
<div class="modal-body text-center">
|
||||
<form method="post" action="/addVolume">
|
||||
<form method="post" action="/">
|
||||
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-9">
|
||||
|
|
|
@ -28,22 +28,21 @@
|
|||
|
||||
<div class="col-12 mt-12">
|
||||
<div class="card">
|
||||
<form method="post">
|
||||
<div class="card-header">
|
||||
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Users</h3>
|
||||
<div class="card-options btn-list">
|
||||
<!-- <a href="#" class="btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" 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 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path> <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path> </svg>
|
||||
Refresh
|
||||
</a> -->
|
||||
<a href="#" class="btn" data-bs-toggle="modal" data-bs-target="#modals-here">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" 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 5l0 14"></path> <path d="M5 12l14 0"></path> </svg>
|
||||
|
||||
<a href="#" class="btn" data-bs-toggle="modal" data-bs-target="#modals-here">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" 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 5l0 14"></path> <path d="M5 12l14 0"></path> </svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
|
@ -63,6 +62,7 @@
|
|||
</thead>
|
||||
<tbody class="table-tbody">
|
||||
|
||||
|
||||
<%- user_list %>
|
||||
|
||||
</tbody>
|
||||
|
@ -71,7 +71,7 @@
|
|||
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
|
||||
<button class="btn" type="submit" formaction="/removeVolume">Remove</button>
|
||||
<button class="btn" type="submit" formaction="/">Remove</button>
|
||||
|
||||
<!-- <span class="dropdown">
|
||||
<button class="btn dropdown-toggle align-text-top" data-bs-toggle="dropdown">Actions</button>
|
||||
|
@ -89,43 +89,44 @@
|
|||
</span> -->
|
||||
|
||||
|
||||
<p class="m-0 text-muted ms-auto">Events</p>
|
||||
<p class="m-0 text-muted ms-auto">Users</p>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<div id="modals-here" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">
|
||||
<!-- New User Modal -->
|
||||
<!-- <div id="modals-here" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered modal-dialog-scrollables">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">New Volume</h5>
|
||||
<h5 class="modal-title">New User</h5>
|
||||
</div>
|
||||
|
||||
<div class="modal-body text-center">
|
||||
<form method="post" action="/addVolume">
|
||||
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-9">
|
||||
<label class="form-label text-muted">Volume Name</label>
|
||||
<input type="text" class="form-control" name="volume">
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<button type="submit" class="btn mt-2">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="mt-3 text-muted"><label class="text-danger">*</label>Name cannot contain spaces or special characters.</label>
|
||||
</form>
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-9">
|
||||
<label class="form-label text-muted">User Name</label>
|
||||
<input type="text" class="form-control" name="name">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<button type="submit" class="btn mt-2">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="mt-3 text-muted"><label class="text-danger">*</label>Name cannot contain spaces or special characters.</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
|
||||
<div class="modal slim-modal modal-blur fade" id="scrolling_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="modal_content">
|
||||
<!-- modal content inserted with htmx -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -140,9 +141,9 @@
|
|||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||
<script src="/libs/list.js/dist/list.min.js"></script>
|
||||
|
||||
<script src="/js/dweebui.js" ></script>
|
||||
<script src="/js/dweebui.js"></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
|
@ -154,7 +155,7 @@
|
|||
const list = new List('table-default', {
|
||||
sortClass: 'table-sort',
|
||||
listClass: 'table-tbody',
|
||||
valueNames: [ 'sort-id', 'sort-avatar', 'sort-name', 'sort-username', 'sort-email', 'sort-userid', 'sort-role', 'sort-lastlogin', 'sort-status', 'sort-action' ]
|
||||
valueNames: [ 'sort-id', 'sort-avatar', 'sort-name', 'sort-username', 'sort-email', 'sort-userid', 'sort-role', 'sort-lastlogin', 'sort-status']
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
|
Loading…
Add table
Reference in a new issue