Almost a complete rewrite, part 2.
This commit is contained in:
parent
f113fa546b
commit
00c31f0fb6
31 changed files with 1091 additions and 773 deletions
|
@ -4,7 +4,6 @@
|
||||||
* Updated adm-zip.
|
* Updated adm-zip.
|
||||||
* Updated yaml.
|
* Updated yaml.
|
||||||
* Pushed new docker image with 'latest' tag.
|
* Pushed new docker image with 'latest' tag.
|
||||||
* Updated compose.yaml volume to /app/config.
|
|
||||||
* Fixed container card links.
|
* Fixed container card links.
|
||||||
* Moved 'Reset view' button.
|
* Moved 'Reset view' button.
|
||||||
* New - 'Grid view' and 'List view' button (non-functioning).
|
* New - 'Grid view' and 'List view' button (non-functioning).
|
||||||
|
@ -18,6 +17,7 @@
|
||||||
* Fixed issue updating view permission.
|
* Fixed issue updating view permission.
|
||||||
* Fixed issue viewing container logs.
|
* Fixed issue viewing container logs.
|
||||||
* App icons are now determined by service label instead of image name.
|
* App icons are now determined by service label instead of image name.
|
||||||
|
* App icons sourced from new repo with 1000+ icons.
|
||||||
|
|
||||||
## v0.60 (June 9th 2024) - Permissions system and import templates
|
## v0.60 (June 9th 2024) - Permissions system and import templates
|
||||||
* Converted JS template literals into HTML.
|
* Converted JS template literals into HTML.
|
||||||
|
|
|
@ -1,70 +1,248 @@
|
||||||
import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
|
import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
|
||||||
import { containerList, containerInspect } from '../utils/docker.js';
|
import { docker, getContainer, containerInspect } from '../utils/docker.js';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { User } from '../database/config.js';
|
import { User, Permission } from '../database/config.js';
|
||||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
import { Alert, Navbar, Capitalize } from '../utils/system.js';
|
||||||
|
import { Op } from 'sequelize';
|
||||||
|
|
||||||
export const Dashboard = async function(req,res){
|
let [ hidden, alert, newCards, stats ] = [ '', '', '', {} ];
|
||||||
|
let logString = '';
|
||||||
let container_list = '';
|
|
||||||
|
|
||||||
let containers = await containerList();
|
|
||||||
for (let container of containers) {
|
|
||||||
let details = await containerInspect(container.containerID);
|
|
||||||
let container_card = readFileSync('./views/partials/container_card.html', 'utf8');
|
|
||||||
|
|
||||||
if (details.name.length > 17) {
|
|
||||||
details.name = details.name.substring(0, 17) + '...';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capitalize the first letter of the name
|
|
||||||
details.name = details.name.charAt(0).toUpperCase() + details.name.slice(1);
|
|
||||||
|
|
||||||
|
|
||||||
let state = details.state;
|
|
||||||
let state_color = '';
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case 'running':
|
|
||||||
state_color = 'green';
|
|
||||||
break;
|
|
||||||
case 'exited':
|
|
||||||
state = 'stopped';
|
|
||||||
state_color = 'red';
|
|
||||||
break;
|
|
||||||
case 'paused':
|
|
||||||
state_color = 'orange';
|
|
||||||
break;
|
|
||||||
case 'installing':
|
|
||||||
state_color = 'blue';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
container_card = container_card.replace(/AppName/g, details.name);
|
|
||||||
container_card = container_card.replace(/AppService/g, details.service);
|
|
||||||
container_card = container_card.replace(/AppState/g, state);
|
|
||||||
container_card = container_card.replace(/StateColor/g, state_color);
|
|
||||||
|
|
||||||
if (details.external_port == 0 && details.internal_port == 0) {
|
|
||||||
container_card = container_card.replace(/AppPorts/g, ``);
|
|
||||||
} else {
|
|
||||||
container_card = container_card.replace(/AppPorts/g, `${details.external_port}:${details.internal_port}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
container_list += container_card;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Dashboard
|
||||||
|
export const Dashboard = async function (req, res) {
|
||||||
|
|
||||||
res.render("dashboard",{
|
res.render("dashboard",{
|
||||||
alert: '',
|
alert: '',
|
||||||
username: req.session.username,
|
username: req.session.username,
|
||||||
role: req.session.role,
|
role: req.session.role,
|
||||||
container_list: container_list,
|
|
||||||
navbar: await Navbar(req),
|
navbar: await Navbar(req),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dashboard search
|
||||||
|
export const submitDashboard = async function (req, res) {
|
||||||
|
console.log('[SubmitDashboard]');
|
||||||
|
console.log(req.body);
|
||||||
|
res.send('ok');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const CardList = async function (req, res) {
|
||||||
|
|
||||||
|
res.send(newCards);
|
||||||
|
newCards = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function containerInfo (containerID) {
|
||||||
|
|
||||||
|
// get the container info
|
||||||
|
let info = docker.getContainer(containerID);
|
||||||
|
let container = await info.inspect();
|
||||||
|
|
||||||
|
let container_name = container.Name.slice(1);
|
||||||
|
let container_image = container.Config.Image;
|
||||||
|
let container_service = container.Config.Labels['com.docker.compose.service'];
|
||||||
|
|
||||||
|
let ports_list = [];
|
||||||
|
let external = 0;
|
||||||
|
let internal = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const [key, value] of Object.entries(container.HostConfig.PortBindings)) {
|
||||||
|
let ports = {
|
||||||
|
check: 'checked',
|
||||||
|
external: value[0].HostPort,
|
||||||
|
internal: key.split('/')[0],
|
||||||
|
protocol: key.split('/')[1]
|
||||||
|
}
|
||||||
|
ports_list.push(ports);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
try { external = ports_list[0].external; internal = ports_list[0].internal; } catch { }
|
||||||
|
|
||||||
|
let container_info = {
|
||||||
|
containerName: container_name,
|
||||||
|
containerID: containerID,
|
||||||
|
containerImage: container_image,
|
||||||
|
containerService: container_service,
|
||||||
|
containerState: container.State.Status,
|
||||||
|
external_port: external,
|
||||||
|
internal_port: internal,
|
||||||
|
ports: ports_list,
|
||||||
|
volumes: container.Mounts,
|
||||||
|
env: container.Config.Env,
|
||||||
|
labels: container.Config.Labels,
|
||||||
|
link: 'localhost',
|
||||||
|
}
|
||||||
|
|
||||||
|
return container_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function userCards (session) {
|
||||||
|
session.container_list = [];
|
||||||
|
// check what containers the user wants hidden
|
||||||
|
let hidden = await Permission.findAll({ where: {userID: session.userID, hide: true}}, { attributes: ['containerID'] });
|
||||||
|
hidden = hidden.map((container) => container.containerID);
|
||||||
|
// check what containers the user has permission to view
|
||||||
|
let visable = await Permission.findAll({ where: { userID: session.userID, [Op.or]: [{ uninstall: true }, { edit: true }, { upgrade: true }, { start: true }, { stop: true }, { pause: true }, { restart: true }, { logs: true }, { view: true }] }, attributes: ['containerID'] });
|
||||||
|
visable = visable.map((container) => container.containerID);
|
||||||
|
// get all containers
|
||||||
|
let containers = await docker.listContainers({ all: true });
|
||||||
|
// loop through containers
|
||||||
|
for (let i = 0; i < containers.length; i++) {
|
||||||
|
let container_name = containers[i].Names[0].split('/').pop();
|
||||||
|
// skip hidden containers
|
||||||
|
if (hidden.includes(containers[i].Id)) { continue; }
|
||||||
|
// admin can see all containers that they don't have hidden
|
||||||
|
if (session.role == 'admin') { session.container_list.push({ containerName: container_name, containerID: containers[i].Id, containerState: containers[i].State }); }
|
||||||
|
// user can see any containers that they have any permissions for
|
||||||
|
else if (visable.includes(containers[i].Id)){ session.container_list.push({ containerName: container_name, containerID: containers[i].Id, containerState: containers[i].State }); }
|
||||||
|
}
|
||||||
|
// Create the lists if they don't exist
|
||||||
|
if (!session.sent_list) { session.sent_list = []; }
|
||||||
|
if (!session.update_list) { session.update_list = []; }
|
||||||
|
if (!session.new_cards) { session.new_cards = []; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function updateDashboard (session) {
|
||||||
|
let container_list = session.container_list;
|
||||||
|
let sent_list = session.sent_list;
|
||||||
|
session.new_cards = [];
|
||||||
|
session.update_list = [];
|
||||||
|
// loop through the containers list
|
||||||
|
container_list.forEach(container => {
|
||||||
|
let { containerName, containerID, containerState } = container;
|
||||||
|
let sent = sent_list.find(c => c.containerID === containerID);
|
||||||
|
if (!sent) { session.new_cards.push(containerID);}
|
||||||
|
else if (sent.containerState !== containerState) { session.update_list.push(containerID); }
|
||||||
|
});
|
||||||
|
// loop through the sent list to see if any containers have been removed
|
||||||
|
sent_list.forEach(container => {
|
||||||
|
let { containerName, containerID, containerState } = container;
|
||||||
|
let exists = container_list.find(c => c.containerID === containerID);
|
||||||
|
if (!exists) { session.update_list.push(containerID); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Container actions (start, stop, pause, restart, hide)
|
||||||
|
export const ContainerAction = async (req, res) => {
|
||||||
|
|
||||||
|
// let trigger_id = req.header('hx-trigger');
|
||||||
|
let container_name = req.header('hx-trigger-name');
|
||||||
|
let containerID = req.params.containerid;
|
||||||
|
let action = req.params.action;
|
||||||
|
|
||||||
|
console.log(`Container: ${container_name} ID: ${containerID} Action: ${action}`);
|
||||||
|
|
||||||
|
// Reset the view
|
||||||
|
if (action == 'reset') {
|
||||||
|
console.log('Resetting view');
|
||||||
|
await Permission.update({ hide: false }, { where: { userID: req.session.userID } });
|
||||||
|
res.redirect('/dashboard');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == 'update') {
|
||||||
|
await userCards(req.session);
|
||||||
|
if (!req.session.container_list.find(c => c.containerID === containerID)) {
|
||||||
|
res.send('');
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
let details = await containerInfo(containerID);
|
||||||
|
let card = await createCard(details);
|
||||||
|
res.send(card);
|
||||||
|
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 shortname = details.name.slice(0, 10) + '...';
|
||||||
|
// let trigger = 'data-hx-trigger="load, every 3s"';
|
||||||
|
|
||||||
|
// Capitalize the container name and shorten it if it's too long
|
||||||
|
let containerName = Capitalize(details.containerName);
|
||||||
|
if (containerName.length > 17) { containerName = containerName.substring(0, 17) + '...'; }
|
||||||
|
|
||||||
|
let containerID = details.containerID;
|
||||||
|
let containerState = details.containerState;
|
||||||
|
let containerService = details.containerService;
|
||||||
|
let containerStateColor = '';
|
||||||
|
|
||||||
|
if (containerState == 'running') { containerStateColor = 'green'; }
|
||||||
|
else if (containerState == 'exited') { containerStateColor = 'red'; containerState = 'stopped'; }
|
||||||
|
else if (containerState == 'paused') { containerStateColor = 'orange'; }
|
||||||
|
else { containerStateColor = 'blue'; }
|
||||||
|
|
||||||
|
let container_card = readFileSync('./views/partials/container_card.html', 'utf8');
|
||||||
|
|
||||||
|
// let links = await ServerSettings.findOne({ where: {key: 'links'}});
|
||||||
|
// if (!links) { links = { value: 'localhost' }; }
|
||||||
|
|
||||||
|
container_card = container_card.replace(/ContainerID/g, containerID);
|
||||||
|
container_card = container_card.replace(/AltID/g, 'a' + containerID);
|
||||||
|
container_card = container_card.replace(/AppName/g, containerName);
|
||||||
|
container_card = container_card.replace(/AppService/g, containerService);
|
||||||
|
container_card = container_card.replace(/AppState/g, containerState);
|
||||||
|
container_card = container_card.replace(/StateColor/g, containerStateColor);
|
||||||
|
|
||||||
|
if (details.external_port == 0 && details.internal_port == 0) {
|
||||||
|
container_card = container_card.replace(/AppPorts/g, ``);
|
||||||
|
} else {
|
||||||
|
container_card = container_card.replace(/AppPorts/g, `${details.external_port}:${details.internal_port}`);
|
||||||
|
}
|
||||||
|
// card = card.replace(/data-trigger=""/, trigger);
|
||||||
|
return container_card;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Server metrics (CPU, RAM, TX, RX, DISK)
|
// Server metrics (CPU, RAM, TX, RX, DISK)
|
||||||
|
@ -96,8 +274,50 @@ export const ServerMetrics = async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const submitDashboard = async function(req,res){
|
export const SSE = async (req, res) => {
|
||||||
console.log(req.body);
|
|
||||||
res.send('ok');
|
// Set the response headers
|
||||||
return;
|
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
|
||||||
|
|
||||||
|
async function eventCheck () {
|
||||||
|
await userCards(req.session);
|
||||||
|
await updateDashboard(req.session);
|
||||||
|
|
||||||
|
if (JSON.stringify(req.session.sent_list) === JSON.stringify(req.session.container_list)) { console.log('Event - No Change'); return; }
|
||||||
|
|
||||||
|
console.log('Event - Change Detected');
|
||||||
|
|
||||||
|
for (let i = 0; i < req.session.new_cards.length; i++) {
|
||||||
|
let details = await containerInfo(req.session.new_cards[i]);
|
||||||
|
let card = await createCard(details);
|
||||||
|
newCards += card;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < req.session.update_list.length; i++) {
|
||||||
|
res.write(`event: ${req.session.update_list[i]}\n`);
|
||||||
|
res.write(`data: 'update cards'\n\n`);
|
||||||
|
}
|
||||||
|
res.write(`event: update\n`);
|
||||||
|
res.write(`data: 'update cards'\n\n`);
|
||||||
|
|
||||||
|
req.session.sent_list = req.session.container_list.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
await eventCheck();
|
||||||
|
|
||||||
|
// Listens for docker events. Only triggers every other event.
|
||||||
|
docker.getEvents({}, function (err, data) {
|
||||||
|
let count = 0;
|
||||||
|
data.on('data', async function () {
|
||||||
|
count++;
|
||||||
|
if (count % 2 === 0) {
|
||||||
|
await eventCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
req.on('close', () => {
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||||
import { containerList, imageList } from '../utils/docker.js';
|
import { imageList } from '../utils/docker.js';
|
||||||
|
|
||||||
export const Images = async function(req,res){
|
export const Images = async function(req,res){
|
||||||
|
|
||||||
let container_images = [];
|
let container_images = [];
|
||||||
|
|
||||||
let containers = await containerList();
|
let containers = await containerList(req);
|
||||||
for (let i = 0; i < containers.length; i++) {
|
for (let i = 0; i < containers.length; i++) {
|
||||||
container_images.push(containers[i].Image);
|
container_images.push(containers[i].Image);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,39 @@
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import { User, Syslog } from '../database/config.js';
|
import { User, Syslog, ServerSettings } from '../database/config.js';
|
||||||
|
|
||||||
|
|
||||||
export const Login = function(req,res){
|
|
||||||
if (req.session.userID) { res.redirect("/dashboard"); }
|
// Login page
|
||||||
else { res.render("login",{
|
export const Login = async function(req,res){
|
||||||
|
|
||||||
|
if (req.session.userID) { res.redirect("/dashboard"); return; }
|
||||||
|
|
||||||
|
let authentication = await ServerSettings.findOne({ where: { key: 'authentication' }});
|
||||||
|
if (!authentication) { await ServerSettings.create({ key: 'authentication', value: 'default' }); }
|
||||||
|
authentication = await ServerSettings.findOne({ where: { key: 'authentication' }});
|
||||||
|
|
||||||
|
if (authentication.value == 'localhost' && req.hostname == 'localhost') {
|
||||||
|
req.session.username = 'Localhost';
|
||||||
|
req.session.userID = '00000000-0000-0000-0000-000000000000';
|
||||||
|
req.session.role = 'admin';
|
||||||
|
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';
|
||||||
|
res.redirect("/dashboard");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render("login",{
|
||||||
"error":"",
|
"error":"",
|
||||||
}); }
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Submit login
|
||||||
export const submitLogin = async function(req,res){
|
export const submitLogin = async function(req,res){
|
||||||
const { password } = req.body;
|
const { password } = req.body;
|
||||||
let email = req.body.email.toLowerCase();
|
let email = req.body.email.toLowerCase();
|
||||||
|
@ -27,9 +52,9 @@ export const submitLogin = async function(req,res){
|
||||||
req.session.role = user.role;
|
req.session.role = user.role;
|
||||||
res.redirect("/dashboard");
|
res.redirect("/dashboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logout
|
||||||
export const Logout = function(req,res){
|
export const Logout = function(req,res){
|
||||||
req.session.destroy(() => {
|
req.session.destroy(() => {
|
||||||
res.redirect("/login");
|
res.redirect("/login");
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import { Op } from "sequelize";
|
import { Op } from "sequelize";
|
||||||
import { User, ServerSettings } from "../database/config.js";
|
import { User, ServerSettings, Permission } from "../database/config.js";
|
||||||
|
|
||||||
|
|
||||||
export const Register = async function(req,res){
|
export const Register = async function(req,res){
|
||||||
|
@ -8,19 +8,21 @@ export const Register = async function(req,res){
|
||||||
// Redirect to dashboard if user is already logged in.
|
// Redirect to dashboard if user is already logged in.
|
||||||
if (req.session.username) { res.redirect("/dashboard"); }
|
if (req.session.username) { res.redirect("/dashboard"); }
|
||||||
|
|
||||||
|
|
||||||
|
let user_registration = await ServerSettings.findOne({ where: { key: 'user_registration' }});
|
||||||
|
|
||||||
let secret_input = '';
|
let secret_input = '';
|
||||||
let registration_secret = await ServerSettings.findOne({ where: { key: 'registration' }}).value;
|
|
||||||
|
|
||||||
// Input field for secret if one has been set.
|
// Input field for secret if one has been set.
|
||||||
if (registration_secret) {
|
if (user_registration) {
|
||||||
secret_input = `<div class="mb-3"><label class="form-label">Secret</label>
|
secret_input = `<div class="mb-3"><label class="form-label">Secret</label>
|
||||||
<div class="input-group input-group-flat">
|
<div class="input-group input-group-flat">
|
||||||
<input type="text" class="form-control" autocomplete="off" name="secret">
|
<input type="text" class="form-control" autocomplete="off" name="registration_secret">
|
||||||
</div>
|
</div>
|
||||||
</div>`}
|
</div>`}
|
||||||
|
|
||||||
// If there are no users, or a registration secret has not been set, display the registration page.
|
// If there are no users, or registration has been enabled, display the registration page.
|
||||||
if ((await User.count() == 0) || (registration_secret == '')) {
|
if ((await User.count() == 0) || (user_registration.value == true)) {
|
||||||
res.render("register",{
|
res.render("register",{
|
||||||
"error": "",
|
"error": "",
|
||||||
"reg_secret": secret_input,
|
"reg_secret": secret_input,
|
||||||
|
@ -37,7 +39,7 @@ export const submitRegister = async function(req,res){
|
||||||
const { name, username, password, confirm, secret } = req.body;
|
const { name, username, password, confirm, secret } = req.body;
|
||||||
let email = req.body.email.toLowerCase();
|
let email = req.body.email.toLowerCase();
|
||||||
|
|
||||||
let registration_secret = await ServerSettings.findOne({ where: { key: 'registration' }}).value;
|
let registration_secret = await ServerSettings.findOne({ where: { key: 'registration_secret' }}).value;
|
||||||
|
|
||||||
let error = '';
|
let error = '';
|
||||||
if (!name || !username || !email || !password || !confirm) { error = "All fields are required"; }
|
if (!name || !username || !email || !password || !confirm) { error = "All fields are required"; }
|
||||||
|
@ -72,6 +74,7 @@ export const submitRegister = async function(req,res){
|
||||||
let match = await bcrypt.compare(password, user.password);
|
let match = await bcrypt.compare(password, user.password);
|
||||||
if (match) {
|
if (match) {
|
||||||
console.log(`User ${username} created`);
|
console.log(`User ${username} created`);
|
||||||
|
|
||||||
req.session.username = user.username;
|
req.session.username = user.username;
|
||||||
req.session.userID = user.userID;
|
req.session.userID = user.userID;
|
||||||
req.session.role = user.role;
|
req.session.role = user.role;
|
||||||
|
|
|
@ -3,48 +3,163 @@ import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||||
|
|
||||||
export const Settings = async function(req,res){
|
export const Settings = async function(req,res){
|
||||||
|
|
||||||
let container_links = await ServerSettings.findOne({ where: {key: 'container_links'}});
|
let custom_link = await ServerSettings.findOne({ where: {key: 'custom_link'}});
|
||||||
|
let link_url = await ServerSettings.findOne({ where: {key: 'link_url'}});
|
||||||
|
|
||||||
let user_registration = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
let user_registration = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||||
|
let registration_secret = await ServerSettings.findOne({ where: {key: 'registration_secret'}});
|
||||||
|
|
||||||
|
let authentication = await ServerSettings.findOne({ where: {key: 'authentication'}});
|
||||||
|
|
||||||
|
let custom_link_enabled = '';
|
||||||
|
try { if (custom_link.value == true) { custom_link_enabled = 'checked'; } } catch { console.log('Custom Link: No Value Set'); }
|
||||||
|
|
||||||
|
let user_registration_enabled = '';
|
||||||
|
try { if (user_registration.value == true) { user_registration_enabled = 'checked'; } } catch { console.log('User Registration: No Value Set'); }
|
||||||
|
|
||||||
|
let link_url_value = '';
|
||||||
|
try { link_url_value = link_url.value; } catch { console.log('Link URL: No Value Set'); }
|
||||||
|
|
||||||
|
let registration_secret_value = '';
|
||||||
|
try { registration_secret_value = registration_secret.value; } catch { console.log('Registration Secret: No Value Set'); }
|
||||||
|
|
||||||
|
|
||||||
res.render("settings",{
|
res.render("settings",{
|
||||||
alert: '',
|
alert: '',
|
||||||
username: req.session.username,
|
username: req.session.username,
|
||||||
role: req.session.role,
|
role: req.session.role,
|
||||||
user_registration: 'checked',
|
user_registration: user_registration_enabled,
|
||||||
registration_secret: 'some-long-secret',
|
registration_secret: registration_secret_value,
|
||||||
container_links: 'checked',
|
custom_link: custom_link_enabled,
|
||||||
link_url: 'mydomain.com',
|
link_url: link_url_value,
|
||||||
|
authentication: authentication.value,
|
||||||
navbar: await Navbar(req),
|
navbar: await Navbar(req),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const submitSettings = async function(req,res){
|
export const updateSettings = async function (req, res) {
|
||||||
|
|
||||||
console.log(req.body);
|
let { user_registration, registration_secret, custom_link, link_url, authentication } = req.body;
|
||||||
|
let { host2, tag2, ip2, port2 } = req.body;
|
||||||
|
let { host3, tag3, ip3, port3 } = req.body;
|
||||||
|
let { host4, tag4, ip4, port4 } = req.body;
|
||||||
|
|
||||||
let trigger_name = req.header('hx-trigger-name');
|
let trigger_name = req.header('hx-trigger-name');
|
||||||
let trigger_id = req.header('hx-trigger');
|
let trigger_id = req.header('hx-trigger');
|
||||||
|
|
||||||
console.log(`trigger_name: ${trigger_name} - trigger_id: ${trigger_id}`);
|
// If the trigger is 'submit', return the button
|
||||||
|
if (trigger_id == 'submit'){
|
||||||
|
|
||||||
// [HTMX Triggered] Changes the update button.
|
|
||||||
if(trigger_id == 'settings'){
|
|
||||||
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>`);
|
|
||||||
return;
|
|
||||||
} else if (trigger_id == 'submit'){
|
|
||||||
res.send(`<button class="btn btn-primary" id="submit" form="settings">Update</button>`);
|
res.send(`<button class="btn btn-primary" id="submit" form="settings">Update</button>`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render("settings",{
|
// Continues on if the trigger is 'settings
|
||||||
alert: '',
|
|
||||||
username: req.session.username,
|
|
||||||
role: req.session.role,
|
|
||||||
navbar: await Navbar(req),
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Custom link
|
||||||
|
if (custom_link) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'custom_link'}});
|
||||||
|
if (exists) { await ServerSettings.update({value: true}, {where: {key: 'custom_link'}}); }
|
||||||
|
else { await ServerSettings.create({ key: 'custom_link', value: true}); }
|
||||||
|
|
||||||
|
let exists2 = await ServerSettings.findOne({ where: {key: 'link_url'}});
|
||||||
|
if (exists2) { await ServerSettings.update({value: link_url}, {where: {key: 'link_url'}}); }
|
||||||
|
else { await ServerSettings.create({ key: 'link_url', value: link_url}); }
|
||||||
|
|
||||||
|
console.log('Custom link enabled');
|
||||||
|
|
||||||
|
} else if (!custom_link) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'custom_link'}});
|
||||||
|
if (exists) { await ServerSettings.update({value: false}, {where: {key: 'custom_link'}}); }
|
||||||
|
else { await ServerSettings.create({ key: 'custom_link', value: false}); }
|
||||||
|
|
||||||
|
let exists2 = await ServerSettings.findOne({ where: {key: 'link_url'}});
|
||||||
|
if (exists2) { await ServerSettings.update({value: ''}, {where: {key: 'link_url'}}); }
|
||||||
|
else { await ServerSettings.create({ key: 'link_url', value: ''}); }
|
||||||
|
|
||||||
|
console.log('Custom links off');
|
||||||
|
}
|
||||||
|
|
||||||
|
// User registration
|
||||||
|
if (user_registration) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||||
|
if (exists) { const setting = await ServerSettings.update({value: true}, {where: {key: 'user_registration'}}); }
|
||||||
|
else { const newSetting = await ServerSettings.create({ key: 'user_registration', value: true}); }
|
||||||
|
|
||||||
|
let exists2 = await ServerSettings.findOne({ where: {key: 'registration_secret'}});
|
||||||
|
if (exists2) { await ServerSettings.update({value: registration_secret}, {where: {key: 'registration_secret'}}); }
|
||||||
|
else { await ServerSettings.create({ key: 'registration_secret', value: registration_secret}); }
|
||||||
|
|
||||||
|
console.log('registration on');
|
||||||
|
|
||||||
|
} else if (!user_registration) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||||
|
if (exists) { await ServerSettings.update({value: false}, {where: {key: 'user_registration'}}); }
|
||||||
|
else { await ServerSettings.create({ key: 'user_registration', value: false}); }
|
||||||
|
|
||||||
|
let exists2 = await ServerSettings.findOne({ where: {key: 'registration_secret'}});
|
||||||
|
if (exists2) { await ServerSettings.update({value: ''}, {where: {key: 'registration_secret'}}); }
|
||||||
|
else { await ServerSettings.create({ key: 'registration_secret', value: ''}); }
|
||||||
|
|
||||||
|
console.log('registration off');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
if (authentication) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'authentication'}});
|
||||||
|
if (exists) { await ServerSettings.update({value: authentication}, {where: {key: 'authentication'}}); }
|
||||||
|
else { await ServerSettings.create({ key: 'authentication', value: authentication}); }
|
||||||
|
console.log('Authentication on');
|
||||||
|
} else if (!authentication) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'authentication'}});
|
||||||
|
if (exists) { await ServerSettings.update({value: 'default'}, {where: {key: 'authentication'}}); }
|
||||||
|
else { await ServerSettings.create({ key: 'authentication', value: 'off'}); }
|
||||||
|
console.log('Authentication off');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Host 2
|
||||||
|
if (host2) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'host2'}});
|
||||||
|
if (exists) { const setting = await ServerSettings.update({value: `${tag2},${ip2},${port2}`}, {where: {key: 'host2'}}); }
|
||||||
|
else { const newSetting = await ServerSettings.create({ key: 'host2', value: `${tag2},${ip2},${port2}`}); }
|
||||||
|
console.log('host2 on');
|
||||||
|
} else if (!host2) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'host2'}});
|
||||||
|
if (exists) { const setting = await ServerSettings.update({value: ''}, {where: {key: 'host2'}}); }
|
||||||
|
else { const newSetting = await ServerSettings.create({ key: 'host2', value: ''}); }
|
||||||
|
console.log('host2 off');
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Host 3
|
||||||
|
if (host3) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'host3'}});
|
||||||
|
if (exists) { const setting = await ServerSettings.update({value: `${tag3},${ip3},${port3}`}, {where: {key: 'host3'}}); }
|
||||||
|
else { const newSetting = await ServerSettings.create({ key: 'host3', value: `${tag3},${ip3},${port3}`}); }
|
||||||
|
console.log('host3 on');
|
||||||
|
} else if (!host3) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'host3'}});
|
||||||
|
if (exists) { const setting = await ServerSettings.update({value: ''}, {where: {key: 'host3'}}); }
|
||||||
|
else { const newSetting = await ServerSettings.create({ key: 'host3', value: ''}); }
|
||||||
|
console.log('host3 off');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host 4
|
||||||
|
if (host4) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'host4'}});
|
||||||
|
if (exists) { const setting = await ServerSettings.update({value: `${tag4},${ip4},${port4}`}, {where: {key: 'host4'}}); }
|
||||||
|
else { const newSetting = await ServerSettings.create({ key: 'host4', value: `${tag4},${ip4},${port4}`}); }
|
||||||
|
console.log('host4 on');
|
||||||
|
} else if (!host4) {
|
||||||
|
let exists = await ServerSettings.findOne({ where: {key: 'host4'}});
|
||||||
|
if (exists) { const setting = await ServerSettings.update({value: ''}, {where: {key: 'host4'}}); }
|
||||||
|
else { const newSetting = await ServerSettings.create({ key: 'host4', value: ''}); }
|
||||||
|
console.log('host4 off');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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>`);
|
||||||
}
|
}
|
|
@ -15,19 +15,24 @@
|
||||||
"admin": "",
|
"admin": "",
|
||||||
"user": "",
|
"user": "",
|
||||||
"Start": "",
|
"Start": "",
|
||||||
"Starting": "",
|
|
||||||
"Running": "",
|
|
||||||
"Stop": "",
|
"Stop": "",
|
||||||
"Stopping": "",
|
|
||||||
"Stopped": "",
|
|
||||||
"Pause": "",
|
"Pause": "",
|
||||||
"Pausing": "",
|
|
||||||
"Paused": "",
|
|
||||||
"Restart": "",
|
"Restart": "",
|
||||||
|
"Starting": "",
|
||||||
|
"Stopping": "",
|
||||||
|
"Pausing": "",
|
||||||
"Restarting": "",
|
"Restarting": "",
|
||||||
|
"Running": "",
|
||||||
|
"Stopped": "",
|
||||||
|
"Paused": "",
|
||||||
"Details": "",
|
"Details": "",
|
||||||
"Logs": "",
|
"Logs": "",
|
||||||
"Edit": "",
|
"Edit": "",
|
||||||
"Update": "",
|
"Update": "",
|
||||||
"Uninstall": ""
|
"Uninstall": "",
|
||||||
|
"Hide": "",
|
||||||
|
"Reset View": "",
|
||||||
|
"Permissions": "",
|
||||||
|
"Sponsors": "",
|
||||||
|
"Credits": ""
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "dweebui",
|
"name": "dweebui",
|
||||||
"version": "0.70.402",
|
"version": "0.70.417",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "lllllllillllllillll",
|
"author": "lllllllillllllillll",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "DweebUI is a WebUI for managing your containers. Simple setup, a dynamically updating dashboard, and a multi-user permission system.",
|
"description": "DweebUI is a WebUI for managing your containers. https://dweebui.com",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"connect-session-sequelize": "^7.1.7",
|
"connect-session-sequelize": "^7.1.7",
|
||||||
|
|
|
@ -1,156 +1,163 @@
|
||||||
|
|
||||||
|
@import url('/fonts/inter.css');
|
||||||
|
:root {
|
||||||
|
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-feature-settings: "cv03", "cv04", "cv11";
|
||||||
|
}
|
||||||
|
|
||||||
.meter {
|
.meter {
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #a7a7a752;
|
background: #a7a7a752;
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
box-shadow: inset 0 -1px 1px rgba(255, 255, 255, 0.3);
|
box-shadow: inset 0 -1px 1px rgba(255, 255, 255, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meter > span {
|
.meter > span {
|
||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-top-right-radius: 20px;
|
border-top-right-radius: 20px;
|
||||||
border-bottom-right-radius: 20px;
|
border-bottom-right-radius: 20px;
|
||||||
border-top-left-radius: 20px;
|
border-top-left-radius: 20px;
|
||||||
border-bottom-left-radius: 20px;
|
border-bottom-left-radius: 20px;
|
||||||
background-color: rgb(43, 194, 83);
|
background-color: rgb(43, 194, 83);
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
center bottom,
|
center bottom,
|
||||||
rgb(43, 194, 83) 37%,
|
rgb(43, 194, 83) 37%,
|
||||||
rgb(84, 240, 84) 69%
|
rgb(84, 240, 84) 69%
|
||||||
);
|
);
|
||||||
box-shadow: inset 0 2px 9px rgba(255, 255, 255, 0.3),
|
box-shadow: inset 0 2px 9px rgba(255, 255, 255, 0.3),
|
||||||
inset 0 -2px 6px rgba(0, 0, 0, 0.4);
|
inset 0 -2px 6px rgba(0, 0, 0, 0.4);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meter > span:after,
|
.meter > span:after,
|
||||||
.animate > span > span {
|
.animate > span > span {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
-45deg,
|
-45deg,
|
||||||
rgba(255, 255, 255, 0.2) 25%,
|
rgba(255, 255, 255, 0.2) 25%,
|
||||||
transparent 25%,
|
transparent 25%,
|
||||||
transparent 50%,
|
transparent 50%,
|
||||||
rgba(255, 255, 255, 0.2) 50%,
|
rgba(255, 255, 255, 0.2) 50%,
|
||||||
rgba(255, 255, 255, 0.2) 75%,
|
rgba(255, 255, 255, 0.2) 75%,
|
||||||
transparent 75%,
|
transparent 75%,
|
||||||
transparent
|
transparent
|
||||||
);
|
);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background-size: 50px 50px;
|
background-size: 50px 50px;
|
||||||
animation: move 2s linear infinite;
|
animation: move 2s linear infinite;
|
||||||
border-top-right-radius: 8px;
|
border-top-right-radius: 8px;
|
||||||
border-bottom-right-radius: 8px;
|
border-bottom-right-radius: 8px;
|
||||||
border-top-left-radius: 20px;
|
border-top-left-radius: 20px;
|
||||||
border-bottom-left-radius: 20px;
|
border-bottom-left-radius: 20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate > span:after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes move {
|
|
||||||
0% {
|
|
||||||
background-position: 0 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 50px 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.orange > span {
|
|
||||||
background-image: linear-gradient(#f1a165, #f36d0a);
|
|
||||||
}
|
|
||||||
|
|
||||||
.red > span {
|
|
||||||
background-image: linear-gradient(#f0a3a3, #f42323);
|
|
||||||
}
|
|
||||||
|
|
||||||
.blue > span {
|
.animate > span:after {
|
||||||
background-image: linear-gradient(#2478f5, #22017e);
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purple > span {
|
@keyframes move {
|
||||||
background-image: linear-gradient(#bd14d3, #670370);
|
0% {
|
||||||
|
background-position: 0 0;
|
||||||
}
|
}
|
||||||
|
100% {
|
||||||
.nostripes > span > span,
|
background-position: 50px 50px;
|
||||||
.nostripes > span::after {
|
|
||||||
background-image: none;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange > span {
|
||||||
|
background-image: linear-gradient(#f1a165, #f36d0a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.red > span {
|
||||||
|
background-image: linear-gradient(#f0a3a3, #f42323);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue > span {
|
||||||
|
background-image: linear-gradient(#2478f5, #22017e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple > span {
|
||||||
|
background-image: linear-gradient(#bd14d3, #670370);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nostripes > span > span,
|
||||||
|
.nostripes > span::after {
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.container-stamp {
|
.container-stamp {
|
||||||
--tblr-stamp-size: 8rem;
|
--tblr-stamp-size: 8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: calc(var(--tblr-stamp-size) * 1);
|
width: calc(var(--tblr-stamp-size) * 1);
|
||||||
height: calc(var(--tblr-stamp-size) * 1);
|
height: calc(var(--tblr-stamp-size) * 1);
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-action {
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
color: var(--tblr-secondary);
|
|
||||||
display: inline-flex;
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: var(--tblr-border-radius);
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.container-action:after {
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
.container-action:focus {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
.container-action:hover, .container-action.show {
|
|
||||||
color: var(--tblr-body-color);
|
|
||||||
background: var(--tblr-active-bg);
|
|
||||||
}
|
|
||||||
.container-action.show {
|
|
||||||
color: var(--tblr-primary);
|
|
||||||
}
|
|
||||||
.container-action .icon {
|
|
||||||
margin: 0;
|
|
||||||
width: 1.25rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container-actions {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
.container-action {
|
||||||
border: 1px solid grey;
|
padding: 0;
|
||||||
}
|
border: 0;
|
||||||
|
color: var(--tblr-secondary);
|
||||||
.accordion-item {
|
display: inline-flex;
|
||||||
border: 1px solid grey;
|
width: 1.5rem;
|
||||||
}
|
height: 1.5rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: var(--tblr-border-radius);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.container-action:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
.container-action:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.container-action:hover, .container-action.show {
|
||||||
|
color: var(--tblr-body-color);
|
||||||
|
background: var(--tblr-active-bg);
|
||||||
|
}
|
||||||
|
.container-action.show {
|
||||||
|
color: var(--tblr-primary);
|
||||||
|
}
|
||||||
|
.container-action .icon {
|
||||||
|
margin: 0;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
stroke-width: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-actions {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border: 1px solid grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-user {
|
||||||
|
border: 1px solid grey;
|
||||||
|
}
|
||||||
|
|
|
@ -24,4 +24,20 @@ function toggleTheme(button) {
|
||||||
document.body.removeAttribute("data-bs-theme");
|
document.body.removeAttribute("data-bs-theme");
|
||||||
localStorage.setItem(themeStorageKey, 'light');
|
localStorage.setItem(themeStorageKey, 'light');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function selectAll(group) {
|
||||||
|
|
||||||
|
let checkboxes = document.getElementsByName(group);
|
||||||
|
if (checkboxes[0].checked == true) {
|
||||||
|
for (var i = 0; i < checkboxes.length; i++) {
|
||||||
|
checkboxes[i].checked = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var i = 0; i < checkboxes.length; i++) {
|
||||||
|
checkboxes[i].checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
17
router.js
17
router.js
|
@ -3,19 +3,17 @@ export const router = express.Router();
|
||||||
|
|
||||||
import { Login, submitLogin, Logout } from './controllers/login.js';
|
import { Login, submitLogin, Logout } from './controllers/login.js';
|
||||||
import { Register, submitRegister } from './controllers/register.js';
|
import { Register, submitRegister } from './controllers/register.js';
|
||||||
import { Dashboard, submitDashboard, ServerMetrics } from './controllers/dashboard.js';
|
import { Dashboard, submitDashboard, ContainerAction, ServerMetrics, CardList, SSE } from './controllers/dashboard.js';
|
||||||
import { Settings, submitSettings } from './controllers/settings.js';
|
import { Settings, updateSettings } from './controllers/settings.js';
|
||||||
import { Images, submitImages } from './controllers/images.js';
|
import { Images, submitImages } from './controllers/images.js';
|
||||||
import { Volumes, submitVolumes } from './controllers/volumes.js';
|
import { Volumes, submitVolumes } from './controllers/volumes.js';
|
||||||
import { Networks, submitNetworks } from './controllers/networks.js';
|
import { Networks, submitNetworks } from './controllers/networks.js';
|
||||||
import { Users, submitUsers } from './controllers/users.js';
|
import { Users, submitUsers } from './controllers/users.js';
|
||||||
import { Apps, submitApps } from './controllers/apps.js';
|
import { Apps, submitApps } from './controllers/apps.js';
|
||||||
import { Account } from './controllers/account.js';
|
import { Account } from './controllers/account.js';
|
||||||
import { containerAction } from './utils/docker.js';
|
|
||||||
import { Preferences, submitPreferences } from './controllers/preferences.js';
|
import { Preferences, submitPreferences } from './controllers/preferences.js';
|
||||||
|
|
||||||
|
import { sessionCheck, adminOnly, permissionCheck, permissionModal } from './utils/permissions.js';
|
||||||
import { sessionCheck, adminOnly, permissionCheck } from './utils/permissions.js';
|
|
||||||
|
|
||||||
router.get('/login', Login);
|
router.get('/login', Login);
|
||||||
router.post('/login', submitLogin);
|
router.post('/login', submitLogin);
|
||||||
|
@ -25,8 +23,13 @@ router.post('/register', submitRegister);
|
||||||
|
|
||||||
router.get("/:host?/dashboard", sessionCheck, Dashboard);
|
router.get("/:host?/dashboard", sessionCheck, Dashboard);
|
||||||
router.get("/server_metrics", sessionCheck, ServerMetrics);
|
router.get("/server_metrics", sessionCheck, ServerMetrics);
|
||||||
|
router.get("/permission_modal", adminOnly, permissionModal);
|
||||||
|
|
||||||
router.post("/:host?/container/:action", permissionCheck, containerAction);
|
|
||||||
|
router.get("/sse", sessionCheck, SSE);
|
||||||
|
router.get("/card_list", sessionCheck, CardList);
|
||||||
|
|
||||||
|
router.post("/:host?/container/:action/:containerid?", permissionCheck, ContainerAction);
|
||||||
|
|
||||||
router.get('/images', adminOnly, Images);
|
router.get('/images', adminOnly, Images);
|
||||||
router.post('/images', adminOnly, submitImages);
|
router.post('/images', adminOnly, submitImages);
|
||||||
|
@ -38,7 +41,7 @@ router.get('/networks', adminOnly, Networks);
|
||||||
router.post('/networks', adminOnly, submitNetworks);
|
router.post('/networks', adminOnly, submitNetworks);
|
||||||
|
|
||||||
router.get('/settings', adminOnly, Settings);
|
router.get('/settings', adminOnly, Settings);
|
||||||
router.post('/settings', adminOnly, submitSettings);
|
router.post('/settings', adminOnly, updateSettings);
|
||||||
|
|
||||||
router.get("/apps/:page?/:template?", adminOnly, Apps);
|
router.get("/apps/:page?/:template?", adminOnly, Apps);
|
||||||
router.post('/apps', adminOnly, submitApps);
|
router.post('/apps', adminOnly, submitApps);
|
||||||
|
|
|
@ -1,24 +1,17 @@
|
||||||
import Docker from 'dockerode';
|
import Docker from 'dockerode';
|
||||||
import { Permission } from '../database/config.js';
|
|
||||||
|
|
||||||
export var docker = new Docker();
|
export var docker = new Docker();
|
||||||
|
|
||||||
export async function containerList() {
|
|
||||||
let containers = await docker.listContainers({ all: true });
|
|
||||||
containers = containers.map(container => ({
|
|
||||||
containerName: container.Names[0].split('/').pop(),
|
|
||||||
containerID: container.Id,
|
|
||||||
containerState: container.State,
|
|
||||||
containerImage: container.Image,
|
|
||||||
}));
|
|
||||||
return containers;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function imageList() {
|
export async function imageList() {
|
||||||
let images = await docker.listImages({ all: true });
|
let images = await docker.listImages({ all: true });
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getContainer(containerID) {
|
||||||
|
let container = docker.getContainer(containerID);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
export async function containerInspect (containerID) {
|
export async function containerInspect (containerID) {
|
||||||
// get the container info
|
// get the container info
|
||||||
|
@ -64,72 +57,4 @@ export async function containerInspect (containerID) {
|
||||||
link: 'localhost',
|
link: 'localhost',
|
||||||
}
|
}
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const containerAction = async (req, res) => {
|
|
||||||
|
|
||||||
let container_name = req.header('hx-trigger-name');
|
|
||||||
let container_id = req.header('hx-trigger');
|
|
||||||
let action = req.params.action;
|
|
||||||
|
|
||||||
console.log(`Container: ${container_name} ID: ${container_id} Action: ${action}`);
|
|
||||||
|
|
||||||
// Reset the view
|
|
||||||
if (container_id == 'reset') {
|
|
||||||
console.log('Resetting view');
|
|
||||||
await Permission.update({ hide: false }, { where: { userID: req.session.userID } });
|
|
||||||
res.send('ok');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Inspect the container
|
|
||||||
let container = docker.getContainer(container_id);
|
|
||||||
let containerInfo = await container.inspect();
|
|
||||||
let state = containerInfo.State.Status;
|
|
||||||
// console.log(`Container: ${container_name} ID: ${container_id} State: ${state} Action: ${action}`);
|
|
||||||
// Displays container state (starting, stopping, restarting, pausing)
|
|
||||||
function status (state) {
|
|
||||||
return(`<span class="text-yellow align-items-center lh-1"><svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-point-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 7a5 5 0 1 1 -4.995 5.217l-.005 -.217l.005 -.217a5 5 0 0 1 4.995 -4.783z" stroke-width="0" fill="currentColor"></path></svg>
|
|
||||||
${state}
|
|
||||||
</span>`);
|
|
||||||
}
|
|
||||||
// Perform the action
|
|
||||||
if ((action == 'start') && (state == 'exited')) {
|
|
||||||
await container.start();
|
|
||||||
res.send(status('starting'));
|
|
||||||
} else if ((action == 'start') && (state == 'paused')) {
|
|
||||||
await container.unpause();
|
|
||||||
res.send(status('starting'));
|
|
||||||
} else if ((action == 'stop') && (state != 'exited')) {
|
|
||||||
await container.stop();
|
|
||||||
res.send(status('stopping'));
|
|
||||||
} else if ((action == 'pause') && (state == 'paused')) {
|
|
||||||
await container.unpause();
|
|
||||||
res.send(status('starting'));
|
|
||||||
} else if ((action == 'pause') && (state == 'running')) {
|
|
||||||
await container.pause();
|
|
||||||
res.send(status('pausing'));
|
|
||||||
} else if (action == 'restart') {
|
|
||||||
await container.restart();
|
|
||||||
res.send(status('restarting'));
|
|
||||||
} else if (action == 'hide') {
|
|
||||||
let exists = await Permission.findOne({ where: { containerID: container_id, userID: req.session.userID }});
|
|
||||||
if (!exists) { const newPermission = await Permission.create({ containerName: container_name, containerID: container_id, username: req.session.username, userID: req.session.userID, hide: true }); }
|
|
||||||
else { exists.update({ hide: true }); }
|
|
||||||
// Array of hidden containers
|
|
||||||
hidden = await Permission.findAll({ where: { userID: req.session.userID, hide: true}}, { attributes: ['containerID'] });
|
|
||||||
// Map the container IDs
|
|
||||||
hidden = hidden.map((container) => container.containerID);
|
|
||||||
res.send("ok");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Listens for docker events
|
|
||||||
docker.getEvents({}, function (err, data) {
|
|
||||||
data.on('data', function () {
|
|
||||||
console.log('Docker event');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Permission } from "../database/config.js";
|
import { Permission } from "../database/config.js";
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
export const adminOnly = async (req, res, next) => {
|
export const adminOnly = async (req, res, next) => {
|
||||||
if (req.session.role == 'admin') { next(); }
|
if (req.session.role == 'admin') { next(); }
|
||||||
|
@ -12,5 +13,45 @@ export const sessionCheck = async (req, res, next) => {
|
||||||
|
|
||||||
|
|
||||||
export const permissionCheck = async (req, res, next) => {
|
export const permissionCheck = async (req, res, next) => {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const permissionModal = async (req, res) => {
|
||||||
|
|
||||||
|
// let title = name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
|
// let permissions_list = '';
|
||||||
|
let permissions_modal = readFileSync('./views/partials/permissions.html', 'utf8');
|
||||||
|
// permissions_modal = permissions_modal.replace(/PermissionsTitle/g, title);
|
||||||
|
// permissions_modal = permissions_modal.replace(/PermissionsContainer/g, name);
|
||||||
|
// let users = await User.findAll({ attributes: ['username', 'UUID']});
|
||||||
|
// for (let i = 0; i < users.length; i++) {
|
||||||
|
// let user_permissions = readFileSync('./views/partials/user_permissions.html', 'utf8');
|
||||||
|
// let exists = await Permission.findOne({ where: {containerName: name, user: users[i].username}});
|
||||||
|
// if (!exists) { const newPermission = await Permission.create({ containerName: name, user: users[i].username, userID: users[i].UUID}); }
|
||||||
|
// let permissions = await Permission.findOne({ where: {containerName: name, user: users[i].username}});
|
||||||
|
// if (permissions.uninstall == true) { user_permissions = user_permissions.replace(/data-UninstallCheck/g, 'checked'); }
|
||||||
|
// if (permissions.edit == true) { user_permissions = user_permissions.replace(/data-EditCheck/g, 'checked'); }
|
||||||
|
// if (permissions.upgrade == true) { user_permissions = user_permissions.replace(/data-UpgradeCheck/g, 'checked'); }
|
||||||
|
// if (permissions.start == true) { user_permissions = user_permissions.replace(/data-StartCheck/g, 'checked'); }
|
||||||
|
// if (permissions.stop == true) { user_permissions = user_permissions.replace(/data-StopCheck/g, 'checked'); }
|
||||||
|
// if (permissions.pause == true) { user_permissions = user_permissions.replace(/data-PauseCheck/g, 'checked'); }
|
||||||
|
// if (permissions.restart == true) { user_permissions = user_permissions.replace(/data-RestartCheck/g, 'checked'); }
|
||||||
|
// if (permissions.logs == true) { user_permissions = user_permissions.replace(/data-LogsCheck/g, 'checked'); }
|
||||||
|
// if (permissions.view == true) { user_permissions = user_permissions.replace(/data-ViewCheck/g, 'checked'); }
|
||||||
|
// user_permissions = user_permissions.replace(/EntryNumber/g, i);
|
||||||
|
// user_permissions = user_permissions.replace(/EntryNumber/g, i);
|
||||||
|
// user_permissions = user_permissions.replace(/EntryNumber/g, i);
|
||||||
|
// user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
|
||||||
|
// user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
|
||||||
|
// user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
|
||||||
|
// user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
|
||||||
|
// user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
|
||||||
|
// user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
|
||||||
|
// permissions_list += user_permissions;
|
||||||
|
// }
|
||||||
|
// permissions_modal = permissions_modal.replace(/PermissionsList/g, permissions_list);
|
||||||
|
res.send(permissions_modal);
|
||||||
}
|
}
|
|
@ -2,17 +2,20 @@ import { User } from '../database/config.js';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Navbar
|
||||||
export async function Navbar (req) {
|
export async function Navbar (req) {
|
||||||
|
|
||||||
let username = req.session.username;
|
let username = req.session.username;
|
||||||
|
|
||||||
|
|
||||||
let language = await getLanguage(req);
|
let language = await getLanguage(req);
|
||||||
|
|
||||||
let user = await User.findOne({ where: { userID: req.session.userID }});
|
// Check if the user wants to hide their profile name.
|
||||||
let preferences = JSON.parse(user.preferences);
|
if (req.session.userID != '00000000-0000-0000-0000-000000000000') {
|
||||||
if (preferences.hide_profile == true) {
|
let user = await User.findOne({ where: { userID: req.session.userID }});
|
||||||
username = 'Anonymous';
|
let preferences = JSON.parse(user.preferences);
|
||||||
|
if (preferences.hide_profile == true) { username = 'Anon'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
let navbar = readFileSync('./views/partials/navbar.html', 'utf8');
|
let navbar = readFileSync('./views/partials/navbar.html', 'utf8');
|
||||||
|
@ -39,7 +42,7 @@ export async function Navbar (req) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header Alert
|
||||||
export function Alert (type, message) {
|
export function Alert (type, message) {
|
||||||
return `
|
return `
|
||||||
<div class="alert alert-${type} alert-dismissible" role="alert" style="margin-bottom: 0;">
|
<div class="alert alert-${type} alert-dismissible" role="alert" style="margin-bottom: 0;">
|
||||||
|
@ -55,10 +58,19 @@ export function Alert (type, message) {
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getLanguage (req) {
|
export async function getLanguage (req) {
|
||||||
let user = await User.findOne({ where: { userID: req.session.userID }});
|
|
||||||
let preferences = JSON.parse(user.preferences);
|
// No userID if authentication is disabled.
|
||||||
return preferences.language;
|
if (req.session.userID == '00000000-0000-0000-0000-000000000000') {
|
||||||
|
let user = await User.findOne({ where: { role: 'admin' }});
|
||||||
|
let preferences = JSON.parse(user.preferences);
|
||||||
|
return preferences.language;
|
||||||
|
} else {
|
||||||
|
let user = await User.findOne({ where: { userID: req.session.userID }});
|
||||||
|
let preferences = JSON.parse(user.preferences);
|
||||||
|
return preferences.language;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Capitalize (string) {
|
export function Capitalize (string) {
|
||||||
|
|
|
@ -6,18 +6,9 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||||
<title>Account - DweebUI</title>
|
<title>Account - DweebUI</title>
|
||||||
<!-- CSS files -->
|
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
<style>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body >
|
<body >
|
||||||
<script src="/js/demo-theme.min.js?1692870487"></script>
|
<script src="/js/demo-theme.min.js?1692870487"></script>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
|
@ -8,18 +9,7 @@
|
||||||
<!-- CSS files -->
|
<!-- CSS files -->
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
|
|
||||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
|
|
||||||
<style>
|
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body >
|
<body >
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
|
|
@ -6,21 +6,9 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||||
<title>Dashboard - DweebUI.</title>
|
<title>Dashboard - DweebUI.</title>
|
||||||
<!-- CSS files -->
|
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
|
|
||||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
|
|
||||||
<style>
|
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="/js/demo-theme.min.js?1692870487"></script>
|
<script src="/js/demo-theme.min.js?1692870487"></script>
|
||||||
|
@ -29,12 +17,10 @@
|
||||||
<!-- EJS -->
|
<!-- EJS -->
|
||||||
<%- navbar %>
|
<%- navbar %>
|
||||||
|
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper" hx-ext="sse" sse-connect="/sse">
|
||||||
<div class="page-body">
|
<div class="page-body">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div class="row row-deck row-cards">
|
<div class="row row-deck row-cards">
|
||||||
|
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="row row-cards">
|
<div class="row row-cards">
|
||||||
|
|
||||||
|
@ -73,10 +59,10 @@
|
||||||
<!-- HTMX -->
|
<!-- HTMX -->
|
||||||
<div class="col" name="RAM" id="blue" data-hx-get="/server_metrics" data-hx-trigger="load, every 2s" hx-swap="innerHTML">
|
<div class="col" name="RAM" id="blue" data-hx-get="/server_metrics" data-hx-trigger="load, every 2s" hx-swap="innerHTML">
|
||||||
<div class="font-weight-medium">
|
<div class="font-weight-medium">
|
||||||
<label class="ram-text mb-1" for="ram">RAM 0%</label>
|
<label class="ram-text mb-1" for="ram">RAM 0%</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="ram-bar meter animate blue">
|
<div class="ram-bar meter animate blue">
|
||||||
<span style="width:20%"><span></span></span>
|
<span style="width:20%"><span></span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -129,10 +115,20 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if(container_list) { %>
|
|
||||||
<%- container_list %>
|
<div class="col-12">
|
||||||
<% } %>
|
<div class="row row-cards" id="containers">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- HTMX -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="row row-cards" hx-get="/card_list" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -143,226 +139,39 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal modal-blur fade" id="permissions_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">
|
||||||
<div id="modals-here" class="modal modal-blur fade" tabindex="-1" aria-hidden="true" role="dialog">
|
<div class="modal-header">
|
||||||
<div class="modal-dialog modal-sm modal-dialog-centered modal-dialog-scrollables">
|
<h5 class="modal-title" id="permissions_title">Permissions</h5>
|
||||||
<div class="modal-content">
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Speedtest Permissions</h5>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="accordion" id="modal-accordion">
|
|
||||||
<div class="accordion-item mb-3">
|
|
||||||
<h2 class="accordion-header" id="heading-0">
|
|
||||||
<button class="accordion-button collapsed row" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-0" aria-expanded="false">
|
|
||||||
<span class="avatar avatar-sm bg-green-lt col-3 text-start">JD</span>
|
|
||||||
<div class="col text-end" style="margin-right: 10px;">JohnDoe</div>
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="collapse-0" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
|
||||||
<div class="accordion-body pt-0">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="">
|
|
||||||
<div class="">
|
|
||||||
<form id="updatePermissions0">
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-9">
|
|
||||||
<label class="row text-start">
|
|
||||||
<span class="col">
|
|
||||||
All
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-check form-check-single form-switch text-end">
|
|
||||||
<input class="form-check-input" type="checkbox" name="select0" onclick="selectAll('select0')">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="hidden" name="userID" value="c49673fc-9413-40ae-a500-0f1dd2688fe7">
|
|
||||||
<input type="hidden" name="container" value="speedtest">
|
|
||||||
<input type="hidden" name="containerID" value="b207a9eebba3e678697bc7c224fdcecf005b5ea733d9608a5a59dca15beaaf5b">
|
|
||||||
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-9">
|
|
||||||
<label class="row text-start">
|
|
||||||
<span class="col">
|
|
||||||
Uninstall
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-check form-check-single form-switch text-end">
|
|
||||||
<input class="form-check-input" type="checkbox" name="select0" value="uninstall" data-uninstallcheck="">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-9">
|
|
||||||
<label class="row text-start">
|
|
||||||
<span class="col">
|
|
||||||
Edit
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-check form-check-single form-switch text-end">
|
|
||||||
<input class="form-check-input" type="checkbox" name="select0" value="edit" data-editcheck="">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-9">
|
|
||||||
<label class="row text-start">
|
|
||||||
<span class="col">
|
|
||||||
Upgrade
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-check form-check-single form-switch text-end">
|
|
||||||
<input class="form-check-input" type="checkbox" name="select0" value="upgrade" data-upgradecheck="">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-9">
|
|
||||||
<label class="row text-start">
|
|
||||||
<span class="col">
|
|
||||||
Start
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-check form-check-single form-switch text-end">
|
|
||||||
<input class="form-check-input" type="checkbox" name="select0" value="start" data-startcheck="">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-9">
|
|
||||||
<label class="row text-start">
|
|
||||||
<span class="col">
|
|
||||||
Stop
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-check form-check-single form-switch text-end">
|
|
||||||
<input class="form-check-input" type="checkbox" name="select0" value="stop" data-stopcheck="">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-9">
|
|
||||||
<label class="row text-start">
|
|
||||||
<span class="col">
|
|
||||||
Pause
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-check form-check-single form-switch text-end">
|
|
||||||
<input class="form-check-input" type="checkbox" name="select0" value="pause" data-pausecheck="">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-9">
|
|
||||||
<label class="row text-start">
|
|
||||||
<span class="col">
|
|
||||||
Restart
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-check form-check-single form-switch text-end">
|
|
||||||
<input class="form-check-input" type="checkbox" name="select0" value="restart" data-restartcheck="">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-9">
|
|
||||||
<label class="row text-start">
|
|
||||||
<span class="col">
|
|
||||||
Logs
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-check form-check-single form-switch text-end">
|
|
||||||
<input class="form-check-input" type="checkbox" name="select0" value="logs" data-logscheck="">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row mb-4">
|
|
||||||
<div class="col-9">
|
|
||||||
<label class="row text-start">
|
|
||||||
<span class="col">
|
|
||||||
View
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-check form-check-single form-switch text-end">
|
|
||||||
<input class="form-check-input" type="checkbox" name="select0" value="view" data-viewcheck="">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row mb-2">
|
|
||||||
<button class="btn" type="button" id="submit" hx-post="/updatePermissions" hx-vals="#updatePermissions0" hx-swap="outerHTML">Update </button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="modal-body">
|
||||||
<div class="modal-footer">
|
|
||||||
<div class="row">
|
<div class="accordion" id="user_permissions">
|
||||||
<div class="col">
|
|
||||||
<form id="reset_permissions">
|
PermissionsList
|
||||||
<input type="hidden" name="containerID" value="b207a9eebba3e678697bc7c224fdcecf005b5ea733d9608a5a59dca15beaaf5b">
|
|
||||||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" name="reset_permissions" value="reset_permissions" id="submit" hx-post="/updatePermissions" hx-trigger="click" hx-confirm="Are you sure you want to reset permissions for this container?">Reset</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||||
|
<form id="reset_permissions">
|
||||||
|
<input type="hidden" name="containerID" value="b207a9eebba3e678697bc7c224fdcecf005b5ea733d9608a5a59dca15beaaf5b" id="containerID">
|
||||||
|
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" name="reset_permissions" value="reset_permissions" id="submit" hx-post="/updatePermissions" hx-trigger="click" hx-confirm="Are you sure you want to reset permissions for this container?">Reset</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Libs JS -->
|
|
||||||
<script src="/libs/apexcharts/dist/apexcharts.min.js?1692870487" defer></script>
|
<script src="/libs/apexcharts/dist/apexcharts.min.js?1692870487" defer></script>
|
||||||
|
|
||||||
<script src="/js/dweebui.js" defer></script>
|
<script src="/js/dweebui.js" defer></script>
|
||||||
<script src="/js/htmx.min.js"></script>
|
<script src="/js/htmx.min.js"></script>
|
||||||
<script src="/js/htmx-sse.js"></script>
|
<script src="/js/htmx-sse.js"></script>
|
||||||
|
|
||||||
<!-- Tabler Core -->
|
|
||||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
|
@ -7,17 +8,7 @@
|
||||||
<title>DweebUI - Images</title>
|
<title>DweebUI - Images</title>
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
|
|
||||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
<style>
|
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body >
|
<body >
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
|
|
@ -9,15 +9,7 @@
|
||||||
<!-- CSS files -->
|
<!-- CSS files -->
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
<style>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
@import url('fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body class=" d-flex flex-column">
|
<body class=" d-flex flex-column">
|
||||||
<script src="/js/demo-theme.min.js?1692870487"></script>
|
<script src="/js/demo-theme.min.js?1692870487"></script>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
|
@ -8,18 +9,7 @@
|
||||||
<!-- CSS files -->
|
<!-- CSS files -->
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
|
|
||||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
|
|
||||||
<style>
|
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body >
|
<body >
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<a href="#" class="card-btn"><!-- Download SVG icon from http://tabler-icons.io/i/mail -->
|
<a href="#" class="card-btn"><!-- Download SVG icon from http://tabler-icons.io/i/mail -->
|
||||||
<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 me-2 text-muted"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z"></path><path d="M3 7l9 6l9 -6"></path></svg>
|
<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 me-2 text-muted"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z"></path><path d="M3 7l9 6l9 -6"></path></svg>
|
||||||
Email</a>
|
Info</a>
|
||||||
<a href="#" class="card-btn"><!-- Download SVG icon from http://tabler-icons.io/i/phone -->
|
<a href="#" class="card-btn"><!-- Download SVG icon from http://tabler-icons.io/i/phone -->
|
||||||
<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 me-2 text-muted"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 4h4l2 5l-2.5 1.5a11 11 0 0 0 5 5l1.5 -2.5l5 2v4a2 2 0 0 1 -2 2a16 16 0 0 1 -15 -15a2 2 0 0 1 2 -2"></path></svg>
|
<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 me-2 text-muted"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 4h4l2 5l-2.5 1.5a11 11 0 0 0 5 5l1.5 -2.5l5 2v4a2 2 0 0 1 -2 2a16 16 0 0 1 -15 -15a2 2 0 0 1 2 -2"></path></svg>
|
||||||
Call</a>
|
Install</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,21 +1,21 @@
|
||||||
<div class="col-sm-6 col-lg-3">
|
<div class="col-sm-6 col-lg-3" hx-post="/container/update/ContainerID" hx-trigger="sse:ContainerID" id="AltID" hx-swap="outerHTML" hx-target="#AltID" name="AppName">
|
||||||
<div class="card p-2">
|
<div class="card p-2">
|
||||||
<div class="container-stamp">
|
<div class="container-stamp">
|
||||||
<img width="120px" 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>
|
<img width="110px" 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>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<label style="font-size: smaller; font-weight: bold;" class="text-yellow">AppPorts</label>
|
<label style="font-size: smaller; font-weight: bold;" class="text-yellow">AppPorts</label>
|
||||||
<div class="ms-auto lh-1">
|
<div class="ms-auto lh-1">
|
||||||
<button class="container-action" title="Start" data-hx-post="/container/start" data-hx-trigger="mousedown" data-hx-target="#AppNameState" name="AppName" id="AppID" hx-swap="innerHTML">
|
<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">
|
||||||
<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>
|
<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>
|
||||||
<button class="container-action" title="Stop" data-hx-post="/container/stop" data-hx-trigger="mousedown" data-hx-target="#AppNameState" name="AppName" id="AppID" hx-swap="innerHTML">
|
<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">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-stop" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 5m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-stop" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 5m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"></path></svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="container-action" title="Pause" data-hx-post="/container/pause" data-hx-trigger="mousedown" data-hx-target="#AppNameState" name="AppName" id="AppID" hx-swap="innerHTML">
|
<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">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-pause" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path><path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-pause" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path><path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path></svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="container-action" title="Restart" data-hx-post="/container/restart" data-hx-trigger="mousedown" data-hx-target="#AppNameState" name="AppName" id="AppID" hx-swap="innerHTML">
|
<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">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-reload" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path><path d="M20 4v5h-5"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-reload" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path><path d="M20 4v5h-5"></path></svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-end">
|
<div class="dropdown-menu dropdown-menu-end">
|
||||||
<button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-post="/container/details" hx-swap="innerHTML" data-hx-trigger="mousedown" data-hx-target="#modals-here" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
|
<button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-post="/container/details/" hx-swap="innerHTML" data-hx-trigger="mousedown" data-hx-target="#modals-here" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
|
||||||
<button class="dropdown-item text-secondary" name="AppName" id="logs" data-hx-post="/container/logs" hx-swap="innerHTML" hx-trigger="mousedown" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
|
<button class="dropdown-item text-secondary" name="AppName" id="logs" data-hx-post="/container/logs" hx-swap="innerHTML" hx-trigger="mousedown" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
|
||||||
<button class="dropdown-item text-secondary" name="AppName" id="edit">Edit</button>
|
<button class="dropdown-item text-secondary" name="AppName" id="edit">Edit</button>
|
||||||
<button class="dropdown-item text-primary" name="AppName" id="update" disabled="">Update</button>
|
<button class="dropdown-item text-primary" name="AppName" id="update" disabled="">Update</button>
|
||||||
|
@ -34,19 +34,23 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /> </svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /> </svg>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-end">
|
<div class="dropdown-menu dropdown-menu-end">
|
||||||
<button class="dropdown-item text-secondary" data-hx-post="/container/hide" data-hx-trigger="mousedown" data-hx-swap="none" name="AppName" id="AppID" value="hide">Hide</button>
|
<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" data-hx-post="/dashboard/permissions" name="AppName" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
|
<button class="dropdown-item text-secondary" data-hx-get="/permission_modal" name="AppName" hx-target="#user_permissions" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#permissions_modal">Permissions</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<label style="font-size: x-large; font-weight: bold;">AppName</label>
|
<label style="font-size: x-large; font-weight: bold;">AppName</label>
|
||||||
<div class="text-StateColor d-inline-flex align-items-center lh-1 ms-auto">
|
|
||||||
|
<div class="text-StateColor 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>
|
<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>AppState</strong>
|
<strong>AppState</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="chart-new-clients" class="chart-sm"></div>
|
<div id="chart-new-clients" class="chart-sm"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -237,17 +237,32 @@
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
<div class="my-2 my-md-0 flex-grow-1 flex-md-grow-0 order-first order-md-last">
|
<div class="my-2 my-md-0 flex-grow-1 flex-md-grow-0 order-first order-md-last">
|
||||||
<form action="./" method="get" autocomplete="off" novalidate>
|
<div class="card-actions btn-actions">
|
||||||
<div class="input-icon">
|
<input type="search" class="form-control mx-2" placeholder="Search..." aria-label="search" name="search" hx-post="/search" hx-trigger="input changed delay:500ms, search" hx-swap="none">
|
||||||
<span class="input-icon-addon">
|
<button class="btn-action mx-1" title="Grid View" data-hx-post="/dashboard/view" data-hx-trigger="mousedown" data-hx-target="#" name="grid" id="AppState">
|
||||||
<!-- Download SVG icon from http://tabler-icons.io/i/search -->
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler icons-tabler-outline icon-tabler-layout-grid"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M14 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M14 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /></svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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 d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" /><path d="M21 21l-6 -6" /></svg>
|
</button>
|
||||||
</span>
|
<button class="btn-action mx-1" title="List View" data-hx-post="/dashboard/view" data-hx-trigger="mousedown" data-hx-target="#" name="list" id="AppState">
|
||||||
<input type="text" value="" class="form-control" placeholder="Search…" aria-label="Search in website" name="search" hx-post="/search" hx-trigger="input changed delay:500ms, search">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler icons-tabler-outline icon-tabler-layout-grid"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 6l11 0" /><path d="M9 12l11 0" /><path d="M9 18l11 0" /><path d="M5 6l0 .01" /><path d="M5 12l0 .01" /><path d="M5 18l0 .01" /></svg>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown">
|
||||||
|
<a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<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" data-hx-post="/container/reset/000" data-hx-trigger="mousedown" data-hx-swap="none" name="reset" id="reset" value="reset">Reset View</button> -->
|
||||||
|
<form action="/container/reset/000" method="post">
|
||||||
|
<button class="dropdown-item text-secondary" name="reset" id="reset" value="reset">Reset View</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
180
views/partials/permissions.html
Normal file
180
views/partials/permissions.html
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
<div class="accordion-user mb-3">
|
||||||
|
<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">
|
||||||
|
<span class="avatar avatar-sm bg-green-lt col-3 text-start">JD</span>
|
||||||
|
<div class="col text-end" style="margin-right: 10px;">JohnDoe</div>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div id="collapse-1" class="accordion-collapse collapse" data-bs-parent="#accordion-example">
|
||||||
|
<div class="accordion-body pt-0">
|
||||||
|
|
||||||
|
<form id="updatePermissions0">
|
||||||
|
<div class="col">
|
||||||
|
|
||||||
|
<input type="hidden" name="userID" value="c49673fc-9413-40ae-a500-0f1dd2688fe7">
|
||||||
|
<input type="hidden" name="container" value="speedtest">
|
||||||
|
<input type="hidden" name="containerID" value="b207a9eebba3e678697bc7c224fdcecf005b5ea733d9608a5a59dca15beaaf5b">
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-9">
|
||||||
|
<label class="row text-start">
|
||||||
|
<span class="col">
|
||||||
|
All
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-check form-check-single form-switch text-end">
|
||||||
|
<input class="form-check-input" type="checkbox" name="select0" onclick="selectAll('select0')">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-9">
|
||||||
|
<label class="row text-start">
|
||||||
|
<span class="col">
|
||||||
|
Uninstall
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-check form-check-single form-switch text-end">
|
||||||
|
<input class="form-check-input" type="checkbox" name="select0" value="uninstall" data-uninstallcheck="">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-9">
|
||||||
|
<label class="row text-start">
|
||||||
|
<span class="col">
|
||||||
|
Edit
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-check form-check-single form-switch text-end">
|
||||||
|
<input class="form-check-input" type="checkbox" name="select0" value="edit" data-editcheck="">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-9">
|
||||||
|
<label class="row text-start">
|
||||||
|
<span class="col">
|
||||||
|
Upgrade
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-check form-check-single form-switch text-end">
|
||||||
|
<input class="form-check-input" type="checkbox" name="select0" value="upgrade" data-upgradecheck="">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-9">
|
||||||
|
<label class="row text-start">
|
||||||
|
<span class="col">
|
||||||
|
Start
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-check form-check-single form-switch text-end">
|
||||||
|
<input class="form-check-input" type="checkbox" name="select0" value="start" data-startcheck="">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-9">
|
||||||
|
<label class="row text-start">
|
||||||
|
<span class="col">
|
||||||
|
Stop
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-check form-check-single form-switch text-end">
|
||||||
|
<input class="form-check-input" type="checkbox" name="select0" value="stop" data-stopcheck="">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-9">
|
||||||
|
<label class="row text-start">
|
||||||
|
<span class="col">
|
||||||
|
Pause
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-check form-check-single form-switch text-end">
|
||||||
|
<input class="form-check-input" type="checkbox" name="select0" value="pause" data-pausecheck="">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-9">
|
||||||
|
<label class="row text-start">
|
||||||
|
<span class="col">
|
||||||
|
Restart
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-check form-check-single form-switch text-end">
|
||||||
|
<input class="form-check-input" type="checkbox" name="select0" value="restart" data-restartcheck="">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-9">
|
||||||
|
<label class="row text-start">
|
||||||
|
<span class="col">
|
||||||
|
Logs
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-check form-check-single form-switch text-end">
|
||||||
|
<input class="form-check-input" type="checkbox" name="select0" value="logs" data-logscheck="">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-9">
|
||||||
|
<label class="row text-start">
|
||||||
|
<span class="col">
|
||||||
|
View
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-check form-check-single form-switch text-end">
|
||||||
|
<input class="form-check-input" type="checkbox" name="select0" value="view" data-viewcheck="">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<button class="btn" type="button" id="submit" hx-post="/updatePermissions" hx-vals="#updatePermissions0" hx-swap="outerHTML">Update </button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -9,15 +9,7 @@
|
||||||
<!-- CSS files -->
|
<!-- CSS files -->
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
<style>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body >
|
<body >
|
||||||
<script src="/js/demo-theme.min.js?1692870487"></script>
|
<script src="/js/demo-theme.min.js?1692870487"></script>
|
||||||
|
|
|
@ -9,15 +9,7 @@
|
||||||
<!-- CSS files -->
|
<!-- CSS files -->
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
<style>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
@import url('fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="d-flex flex-column">
|
<body class="d-flex flex-column">
|
||||||
<script src="/js/demo-theme.min.js?1692870487"></script>
|
<script src="/js/demo-theme.min.js?1692870487"></script>
|
||||||
|
|
|
@ -9,15 +9,7 @@
|
||||||
<!-- CSS files -->
|
<!-- CSS files -->
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
<style>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body >
|
<body >
|
||||||
<script src="/js/demo-theme.min.js?1692870487"></script>
|
<script src="/js/demo-theme.min.js?1692870487"></script>
|
||||||
|
@ -48,7 +40,7 @@
|
||||||
|
|
||||||
|
|
||||||
<h3 class="mt-5">User Registration</h3>
|
<h3 class="mt-5">User Registration</h3>
|
||||||
<label class="text-muted mb-2">Allow other users to register.</label>
|
<label class="text-muted mb-2">Enable registration and choose a secret.</label>
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<label class="form-check form-switch form-switch-lg">
|
<label class="form-check form-switch form-switch-lg">
|
||||||
|
@ -71,7 +63,7 @@
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<label class="form-check form-switch form-switch-lg">
|
<label class="form-check form-switch form-switch-lg">
|
||||||
<input class="form-check-input" type="checkbox" name="link_mode" <%= container_links %>>
|
<input class="form-check-input" type="checkbox" name="custom_link" <%= custom_link %>>
|
||||||
<span class="form-check-label form-check-label-on text-warning">
|
<span class="form-check-label form-check-label-on text-warning">
|
||||||
Custom
|
Custom
|
||||||
</span>
|
</span>
|
||||||
|
@ -81,11 +73,49 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<input type="text" class="form-control" name="link" placeholder="IP Address or Domain" value="<%= link_url %>">
|
<input type="text" class="form-control" name="link_url" placeholder="IP Address or Domain" value="<%= link_url %>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="mt-5">Authentication</h3>
|
||||||
|
<label class="text-muted mb-2">Change authentication settings. Only the default, Username and Password, supports multiple users.</label>
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-auto">
|
||||||
|
<select class="form-select" name="authentication">
|
||||||
|
<option value="default">Username and Password - Default</option>
|
||||||
|
<option value="localhost">Localhost</option>
|
||||||
|
<option value="no_auth">Disabled - No Authentication</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="mt-5">Hosts</h3>
|
||||||
|
|
||||||
|
<label class="text-muted mb-2">Host #1</label>
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-auto">
|
||||||
|
<label class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input" type="checkbox" name="host1" checked disabled>
|
||||||
|
<span class="form-check-label form-check-label-on text-success">
|
||||||
|
Enabled
|
||||||
|
</span>
|
||||||
|
<span class="form-check-label form-check-label-off text-danger">
|
||||||
|
Disabled
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<input type="text" class="form-control" placeholder="Host 1" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<input type="text" class="form-control" placeholder="/var/run/docker.sock" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<input type="text" class="form-control" readonly>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-5">Remote Hosts</h3>
|
|
||||||
<label class="text-muted mb-2">Host #2</label>
|
<label class="text-muted mb-2">Host #2</label>
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
|
|
|
@ -1,87 +1,82 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
|
||||||
<title>DweebUI - Settings</title>
|
|
||||||
<!-- CSS files -->
|
|
||||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
|
||||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
|
||||||
<script src="/js/htmx.min.js"></script>
|
|
||||||
<style>
|
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body >
|
|
||||||
<div class="page">
|
|
||||||
|
|
||||||
<!-- EJS -->
|
<html lang="en">
|
||||||
<%- navbar %>
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||||
|
<title>DweebUI - Settings</title>
|
||||||
|
<!-- CSS files -->
|
||||||
|
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||||
|
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||||
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
|
|
||||||
<div class="page-wrapper">
|
</head>
|
||||||
<!-- Page header -->
|
<body >
|
||||||
<div class="page-header d-print-none">
|
<div class="page">
|
||||||
<div class="container-xl">
|
|
||||||
<div class="row g-2 align-items-center">
|
<!-- EJS -->
|
||||||
<div class="col">
|
<%- navbar %>
|
||||||
<h2 class="page-title">
|
|
||||||
Settings
|
<div class="page-wrapper">
|
||||||
</h2>
|
<!-- Page header -->
|
||||||
</div>
|
<div class="page-header d-print-none">
|
||||||
|
<div class="container-xl">
|
||||||
|
<div class="row g-2 align-items-center">
|
||||||
|
<div class="col">
|
||||||
|
<h2 class="page-title">
|
||||||
|
Settings
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Page body -->
|
</div>
|
||||||
<div class="page-body">
|
<!-- Page body -->
|
||||||
<div class="container-xl">
|
<div class="page-body">
|
||||||
<div class="card">
|
<div class="container-xl">
|
||||||
<div class="row g-0">
|
<div class="card">
|
||||||
<%- include('partials/sidebar.html') %>
|
<div class="row g-0">
|
||||||
<div class="col d-flex flex-column">
|
<%- include('partials/sidebar.html') %>
|
||||||
|
<div class="col d-flex flex-column">
|
||||||
<div class="card-body">
|
|
||||||
<h2 class="mb-2">Supporters</h2>
|
<div class="card-body">
|
||||||
<p class="text-muted mb-4">[Click to Thank]</p>
|
<h2 class="mb-2">Supporters</h2>
|
||||||
<div class="row align-items-center">
|
<p class="text-muted mb-4">[Click to Thank]</p>
|
||||||
<div class="col">
|
<div class="row align-items-center">
|
||||||
|
<div class="col">
|
||||||
|
|
||||||
<span type="button" class="avatar avatar-md bg-green-lt" hx-trigger="load, click" hx-post="/thank" hx-target="#count" name="MM" title="MM" style="margin-right: 5px;">mm</span>
|
<span type="button" class="avatar avatar-md bg-green-lt" hx-trigger="load, click" hx-post="/thank" hx-target="#count" name="MM" title="MM" style="margin-right: 5px;">mm</span>
|
||||||
|
|
||||||
<span type="button" class="avatar avatar-md bg-cyan-lt" hx-trigger="click" hx-post="/thank" hx-target="#count" name="PD" title="PD" style="margin-right: 5px;">pd</span>
|
<span type="button" class="avatar avatar-md bg-cyan-lt" hx-trigger="click" hx-post="/thank" hx-target="#count" name="PD" title="PD" style="margin-right: 5px;">pd</span>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
</div>
|
||||||
<p class="text-muted mb-4">Thanks counter:</p>
|
<div class="card-body">
|
||||||
<div class="row align-items-center">
|
<p class="text-muted mb-4">Thanks counter:</p>
|
||||||
<div class="col">
|
<div class="row align-items-center">
|
||||||
|
<div class="col">
|
||||||
|
|
||||||
<span class="avatar avatar-md bg-yellow-lt" id="count" style="margin-right: 5px;">0</span>
|
<span class="avatar avatar-md bg-yellow-lt" id="count" style="margin-right: 5px;">0</span>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%- include('partials/footer.html') %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<%- include('partials/footer.html') %>
|
||||||
</div>
|
</div>
|
||||||
<!-- Libs JS -->
|
</div>
|
||||||
<!-- Tabler Core -->
|
<!-- Libs JS -->
|
||||||
<script src="/js/tabler.min.js" defer></script>
|
<!-- Tabler Core -->
|
||||||
<script src="/js/demo.min.js" defer></script>
|
<script src="/js/htmx.min.js"></script>
|
||||||
</body>
|
<script src="/js/tabler.min.js" defer></script>
|
||||||
</html>
|
<script src="/js/demo.min.js" defer></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,4 +1,5 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
|
@ -7,16 +8,7 @@
|
||||||
<title>DweebUI - Syslogs</title>
|
<title>DweebUI - Syslogs</title>
|
||||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||||
<script src="/js/htmx.min.js"></script>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
<style>
|
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body >
|
<body >
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
@ -74,6 +66,9 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- Libs JS -->
|
<!-- Libs JS -->
|
||||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||||
|
|
||||||
|
<script src="/js/htmx.min.js"></script>
|
||||||
|
|
||||||
<!-- Tabler Core -->
|
<!-- Tabler Core -->
|
||||||
<script src="/js/tabler.min.js" defer></script>
|
<script src="/js/tabler.min.js" defer></script>
|
||||||
<script src="/js/demo.min.js" defer></script>
|
<script src="/js/demo.min.js" defer></script>
|
||||||
|
|
|
@ -1,24 +1,14 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||||
<title>DweebUI - Users</title>
|
<title>DweebUI - Users</title>
|
||||||
<!-- CSS files -->
|
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
|
|
||||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
<style>
|
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body >
|
<body >
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
|
|
@ -1,24 +1,14 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||||
<title>DweebUI - Volumes</title>
|
<title>DweebUI - Volumes</title>
|
||||||
<!-- CSS files -->
|
|
||||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||||
|
|
||||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||||
<style>
|
|
||||||
@import url('/fonts/inter.css');
|
|
||||||
:root {
|
|
||||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-feature-settings: "cv03", "cv04", "cv11";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body >
|
<body >
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
|
Loading…
Add table
Reference in a new issue