Almost a complete rewrite, part 2.

This commit is contained in:
lllllllillllllillll 2024-08-10 00:57:22 -07:00
parent f113fa546b
commit 00c31f0fb6
31 changed files with 1091 additions and 773 deletions

View file

@ -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.

View file

@ -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', () => {
});
} }

View file

@ -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);
} }

View file

@ -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");

View file

@ -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;

View file

@ -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>`);
} }

View file

@ -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": ""
} }

View file

@ -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",

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -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);

View file

@ -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');
});
});

View file

@ -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);
} }

View file

@ -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) {

View file

@ -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>

View file

@ -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">

View file

@ -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&nbsp;&nbsp;</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>

View file

@ -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">

View file

@ -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>

View file

@ -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">

View file

@ -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>

View file

@ -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>

View file

@ -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>

View 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&nbsp;&nbsp;</button>
</div>
</div>
</form>
</div>
</div>
</div>

View file

@ -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>

View file

@ -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>

View file

@ -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">

View file

@ -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>

View file

@ -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>

View file

@ -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">

View file

@ -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">