First draft of permissions system

This commit is contained in:
lllllllillllllillll 2024-04-22 19:20:13 -07:00
parent 62b7e73aac
commit 15722b1687
31 changed files with 433 additions and 451 deletions

View File

@ -12,7 +12,7 @@ export const Account = async (req, res) => {
id: user.id,
email: user.email,
role: user.role,
avatar: user.avatar,
avatar: req.session.user.charAt(0).toUpperCase(),
alert: '',
});

View File

@ -1,16 +1,18 @@
import { readFileSync } from 'fs';
import { readFileSync, readdirSync, renameSync, mkdirSync, unlinkSync } from 'fs';
import multer from 'multer';
const upload = multer({storage: multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'templates/')
cb(null, 'templates/tmp/')
},
filename: function (req, file, cb) {
cb(null, file.originalname)
},
filename: function (req, file, cb) {
cb(null, file.originalname)
}
})
})
// load the default template then sort the templates by name
let templatesJSON = readFileSync('./templates/templates.json');
let templates = JSON.parse(templatesJSON).templates;
@ -22,8 +24,9 @@ templates = templates.sort((a, b) => {
let alert = '';
export const Apps = (req, res) => {
export const Apps = (req, res) => {
let page = Number(req.params.page) || 1;
let list_start = (page-1)*28;
let list_end = (page*28);
@ -71,7 +74,64 @@ export const Apps = (req, res) => {
prev: prev,
next: next,
apps_list: apps_list,
alert: alert || ''
alert: alert,
template_list: '',
});
alert = '';
}
export const AppTemplate = (req, res) => {
let templateTest = Number(req.params.template) || 'template.json';
let page = Number(req.params.page) || 1;
let list_start = (page-1)*28;
let list_end = (page*28);
let last_page = Math.ceil(templates.length/28);
let prev = '/apps/' + (page-1);
let next = '/apps/' + (page+1);
if (page == 1) { prev = '/apps/' + (page); }
if (page == last_page) { next = '/apps/' + (page); }
let apps_list = '';
for (let i = list_start; i < list_end && i < templates.length; i++) {
let appCard = readFileSync('./views/partials/appCard.html', 'utf8');
let name = templates[i].name || templates[i].title.toLowerCase();
let desc = templates[i].description.slice(0, 60) + "...";
let description = templates[i].description.replaceAll(". ", ".\n") || "no description available";
let note = templates[i].note ? templates[i].note.replaceAll(". ", ".\n") : "no notes available";
let image = templates[i].image;
let logo = templates[i].logo;
let categories = '';
// set data.catagories to 'other' if data.catagories is empty or undefined
if (templates[i].categories == null || templates[i].categories == undefined || templates[i].categories == '') {
templates[i].categories = ['Other'];
}
// loop through the categories and add the badge to the card
for (let j = 0; j < templates[i].categories.length; j++) {
categories += CatagoryColor(templates[i].categories[j]);
}
appCard = appCard.replace(/AppName/g, name);
appCard = appCard.replace(/AppShortName/g, name);
appCard = appCard.replace(/AppDesc/g, desc);
appCard = appCard.replace(/AppLogo/g, logo);
appCard = appCard.replace(/AppCategories/g, categories);
apps_list += appCard;
}
// let templatesJSON = readFileSync('./templates/templates.json');
// let templates = JSON.parse(templatesJSON).templates;
res.render("apps", {
name: req.session.user,
role: req.session.role,
avatar: req.session.user.charAt(0).toUpperCase(),
list_start: list_start + 1,
list_end: list_end,
app_count: templates.length,
prev: prev,
next: next,
apps_list: apps_list,
alert: alert,
template_list: '',
});
alert = '';
}
@ -133,7 +193,8 @@ export const appSearch = async (req, res) => {
prev: prev,
next: next,
apps_list: apps_list,
alert: res.locals.alert || ''
alert: alert,
template_list: '',
});
}
@ -399,6 +460,19 @@ export const Upload = (req, res) => {
<a class="btn-close" data-bs-dismiss="alert" aria-label="close" style="padding-top: 0.5rem;"></a>
</div>`;
let files = readdirSync('templates/tmp/');
for (let i = 0; i < files.length; i++) {
if (files[i].endsWith('.json')) {
renameSync(`templates/tmp/${files[i]}`, `templates/json/${files[i]}`);
} else if (files[i].endsWith('.yml') || files[i].endsWith('.yaml')) {
mkdirSync(`templates/compose/${files[i].slice(0, -4)}`);
renameSync(`templates/tmp/${files[i]}`, `templates/compose/${files[i].slice(0, -4)}/${files[i]}`);
} else {
unlinkSync(`templates/tmp/${files[i]}`);
}
}
res.redirect('/apps');
});
};

View File

@ -7,21 +7,166 @@ import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
let hidden = '';
// The actual page
// The page
export const Dashboard = (req, res) => {
let name = req.session.user;
let role = req.session.role;
let avatar = name.charAt(0).toUpperCase();
res.render("dashboard", {
name: name,
avatar: avatar,
avatar: name.charAt(0).toUpperCase(),
role: role,
alert: ''
});
}
// The page actions
export const DashboardAction = async (req, res) => {
let name = req.header('hx-trigger-name');
let value = req.header('hx-trigger');
let action = req.params.action;
let modal = '';
switch (action) {
case 'permissions':
let title = name.charAt(0).toUpperCase() + name.slice(1);
let permissions_list = '';
let permissions_modal = readFileSync('./views/modals/permissions.html', 'utf8');
permissions_modal = permissions_modal.replace(/PermissionsTitle/g, title);
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'); }
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);
return;
case 'uninstall':
modal = readFileSync('./views/modals/uninstall.html', 'utf8');
modal = modal.replace(/AppName/g, name);
// let containerPermissions = await Permission.findAll({ where: {containerName: name}});
res.send(modal);
return;
case 'details':
modal = readFileSync('./views/modals/details.html', 'utf8');
let details = await containerInfo(name);
modal = modal.replace(/AppName/g, details.name);
modal = modal.replace(/AppImage/g, details.image);
res.send(modal);
return;
case 'containers':
res.send(cardList);
return;
case 'updates':
res.send(newCards);
newCards = '';
return;
case 'card':
if (hidden.includes(name) || !containersArray.find(c => c.container === name)) {
res.send('');
return;
} else {
let details = await containerInfo(name);
let card = await createCard(details);
res.send(card);
return;
}
case 'logs':
let logString = '';
let options = { follow: true, stdout: true, stderr: false, timestamps: false };
docker.getContainer(name).logs(options, function (err, stream) {
if (err) { console.log(err); return; }
const readableStream = Readable.from(stream);
readableStream.on('data', function (chunk) {
logString += chunk.toString('utf8');
});
readableStream.on('end', function () {
res.send(`<pre>${logString}</pre> `);
});
});
return;
case 'hide':
let exists = await Container.findOne({ where: {name: name}});
if (!exists) {
const newContainer = await Container.create({ name: name, visibility: false, });
} else {
exists.update({ visibility: false });
}
hidden = await Container.findAll({ where: {visibility:false}});
hidden = hidden.map((container) => container.name);
res.send("ok");
return;
case 'reset':
await Container.update({ visibility: true }, { where: {} });
hidden = await Container.findAll({ where: {visibility:false}});
hidden = hidden.map((container) => container.name);
res.send("ok");
return;
}
function status (state) {
let status = `<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>`;
return status;
}
// Start
if ((action == 'start') && (value == 'stopped')) {
docker.getContainer(name).start();
res.send(status('starting'));
} else if ((action == 'start') && (value == 'paused')) {
docker.getContainer(name).unpause();
res.send(status('starting'));
// Stop
} else if ((action == 'stop') && (value != 'stopped')) {
docker.getContainer(name).stop();
res.send(status('stopping'));
// Pause
} else if ((action == 'pause') && (value == 'paused')) {
docker.getContainer(name).unpause();
res.send(status('starting'));
} else if ((action == 'pause') && (value == 'running')) {
docker.getContainer(name).pause();
res.send(status('pausing'));
// Restart
} else if (action == 'restart') {
docker.getContainer(name).restart();
res.send(status('restarting'));
}
}
// Server metrics (CPU, RAM, TX, RX, DISK)
export const Stats = async (req, res) => {
let name = req.header('hx-trigger-name');
@ -155,28 +300,6 @@ async function createCard (details) {
let [ cardList, newCards, containersArray, sentArray, updatesArray ] = [ '', '', [], [], [] ];
export async function addCard (name, state) {
console.log(`Adding card for ${name}: ${state}`);
let details = {
name: name,
image: name,
service: name,
state: 'installing',
external_port: 0,
internal_port: 0,
ports: [],
link: 'localhost',
}
createCard(details).then(card => {
cardList += card;
});
}
// HTMX server-side events
export const SSE = async (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
@ -264,242 +387,40 @@ export const Chart = async (req, res) => {
res.send(chart);
}
export const updateCards = async (req, res) => {
console.log('updateCards called');
res.send(newCards);
newCards = '';
}
export const Containers = async (req, res) => {
res.send(cardList);
}
export const Card = async (req, res) => {
let name = req.header('hx-trigger-name');
console.log(`${name} requesting updated card`);
// return nothing if in hidden or not found in containersArray
if (hidden.includes(name) || !containersArray.find(c => c.container === name)) {
res.send('');
return;
} else {
let details = await containerInfo(name);
let card = await createCard(details);
res.send(card);
}
}
function status (state) {
let status = `<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>`;
return status;
}
export const Logs = (req, res) => {
let name = req.header('hx-trigger-name');
function containerLogs (data) {
return new Promise((resolve, reject) => {
let logString = '';
var options = { follow: false, stdout: true, stderr: false, timestamps: false };
var containerName = docker.getContainer(data);
containerName.logs(options, function (err, stream) {
if (err) { reject(err); return; }
const readableStream = Readable.from(stream);
readableStream.on('data', function (chunk) {
logString += chunk.toString('utf8');
});
readableStream.on('end', function () {
resolve(logString);
});
});
});
};
containerLogs(name).then((data) => {
res.send(`<pre>${data}</pre> `)
});
}
export const Action = async (req, res) => {
let name = req.header('hx-trigger-name');
let state = req.header('hx-trigger');
let action = req.params.action;
// Start
if ((action == 'start') && (state == 'stopped')) {
var containerName = docker.getContainer(name);
containerName.start();
res.send(status('starting'));
} else if ((action == 'start') && (state == 'paused')) {
var containerName = docker.getContainer(name);
containerName.unpause();
res.send(status('starting'));
// Stop
} else if ((action == 'stop') && (state != 'stopped')) {
var containerName = docker.getContainer(name);
containerName.stop();
res.send(status('stopping'));
// Pause
} else if ((action == 'pause') && (state == 'paused')) {
var containerName = docker.getContainer(name);
containerName.unpause();
res.send(status('starting'));
} else if ((action == 'pause') && (state == 'running')) {
var containerName = docker.getContainer(name);
containerName.pause();
res.send(status('pausing'));
// Restart
} else if (action == 'restart') {
var containerName = docker.getContainer(name);
containerName.restart();
res.send(status('restarting'));
// Hide
} else if (action == 'hide') {
let exists = await Container.findOne({ where: {name: name}});
if (!exists) {
const newContainer = await Container.create({ name: name, visibility: false, });
} else {
exists.update({ visibility: false });
}
hidden = await Container.findAll({ where: {visibility:false}});
hidden = hidden.map((container) => container.name);
res.send("ok");
// Reset View
} else if (action == 'reset') {
await Container.update({ visibility: true }, { where: {} });
hidden = await Container.findAll({ where: {visibility:false}});
hidden = hidden.map((container) => container.name);
res.send("ok");
}
}
export const Modals = async (req, res) => {
let name = req.header('hx-trigger-name');
let id = req.header('hx-trigger');
let title = name.charAt(0).toUpperCase() + name.slice(1);
if (id == 'permissions') {
let permissions_list = '';
let permissions_modal = readFileSync('./views/modals/permissions.html', 'utf8');
permissions_modal = permissions_modal.replace(/PermissionsTitle/g, title);
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'); }
user_permissions = user_permissions.replace(/EntryNumber/g, i);
user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
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);
return;
}
if (id == 'uninstall') {
let modal = readFileSync('./views/modals/uninstall.html', 'utf8');
modal = modal.replace(/AppName/g, name);
// let containerPermissions = await Permission.findAll({ where: {containerName: name}});
res.send(modal);
return;
}
let modal = readFileSync('./views/modals/details.html', 'utf8');
let details = await containerInfo(name);
modal = modal.replace(/AppName/g, details.name);
modal = modal.replace(/AppImage/g, details.image);
res.send(modal);
}
export const UpdatePermissions = async (req, res) => {
let user = req.body.username;
let container = req.body.container;
let { user, container } = req.body;
let id = req.header('hx-trigger');
console.log(`${req.session.user} is updating permissions for: ${user} on ${container}`);
await Permission.update({ uninstall: false, edit: false, upgrade: false, start: false, stop: false, pause: false, restart: false, logs: false }, { where: { containerName: container, user: user } });
Object.keys(req.body).forEach(async function(key) {
if (key != 'username' && key != 'container') {
if (key != 'user' && key != 'container') {
let permissions = req.body[key];
if (permissions.includes('uninstall')){
if (permissions.includes('uninstall')) {
await Permission.update({ uninstall: true }, { where: {containerName: container, user: user}});
}
else {
await Permission.update({ uninstall: false }, { where: {containerName: container, user: user}});
}
if (permissions.includes('edit')){
}
if (permissions.includes('edit')) {
await Permission.update({ edit: true }, { where: {containerName: container, user: user}});
}
else {
await Permission.update({ edit: false }, { where: {containerName: container, user: user}});
}
if (permissions.includes('upgrade')){
if (permissions.includes('upgrade')) {
await Permission.update({ upgrade: true }, { where: {containerName: container, user: user}});
}
else {
await Permission.update({ upgrade: false }, { where: {containerName: container, user: user}});
}
if (permissions.includes('start')){
if (permissions.includes('start')) {
await Permission.update({ start: true }, { where: {containerName: container, user: user}});
} else {
await Permission.update({ start: false }, { where: {containerName: container, user: user}});
}
if (permissions.includes('stop')){
}
if (permissions.includes('stop')) {
await Permission.update({ stop: true }, { where: {containerName: container, user: user}});
} else {
await Permission.update({ stop: false }, { where: {containerName: container, user: user}});
}
if (permissions.includes('pause')){
}
if (permissions.includes('pause')) {
await Permission.update({ pause: true }, { where: {containerName: container, user: user}});
} else {
await Permission.update({ pause: false }, { where: {containerName: container, user: user}});
}
if (permissions.includes('restart')){
}
if (permissions.includes('restart')) {
await Permission.update({ restart: true }, { where: {containerName: container, user: user}});
} else {
await Permission.update({ restart: false }, { where: {containerName: container, user: user}});
}
if (permissions.includes('logs')){
}
if (permissions.includes('logs')) {
await Permission.update({ logs: true }, { where: {containerName: container, user: user}});
}
else {
await Permission.update({ logs: false }, { where: {containerName: container, user: user}});
}
}
});
@ -509,8 +430,27 @@ export const UpdatePermissions = async (req, res) => {
res.send(submit);
return;
} else if (id == 'confirmed') {
submit = `<button class="btn" type="button" id="submit" hx-post="/updatePermissions" hx-vals="#updatePermissions" hx-swap="outerHTML">Update  </button>`;
submit = `<button class="btn" type="button" id="submit" hx-post="/updatePermissions" hx-vals="#updatePermissions" hx-swap="outerHTML">Update </button>`;
res.send(submit);
return;
}
}
// Gets imported by install.js
export async function addCard (name, state) {
console.log(`Adding card for ${name}: ${state}`);
let details = {
name: name,
image: name,
service: name,
state: 'installing',
external_port: 0,
internal_port: 0,
ports: [],
link: 'localhost',
}
createCard(details).then(card => {
cardList += card;
});
}

View File

@ -2,6 +2,31 @@ import { docker } from '../server.js';
export const Images = async function(req, res) {
let action = req.params.action;
if (action == "remove") {
console.log("Removing images");
let images = req.body.select;
if (typeof(images) == 'string') {
images = [images];
}
for (let i = 0; i < images.length; i++) {
if (images[i] != 'on') {
try {
console.log(`Removing image: ${images[i]}`);
let image = docker.getImage(images[i]);
await image.remove();
} catch (error) {
console.log(`Unable to remove image: ${images[i]}`);
}
}
}
res.redirect("/images");
return;
}
let images = await docker.listImages({ all: true });
let image_list = `
@ -48,34 +73,10 @@ export const Images = async function(req, res) {
res.render("images", {
name: req.session.user,
role: req.session.role,
avatar: req.session.avatar,
avatar: req.session.user.charAt(0).toUpperCase(),
image_list: image_list,
image_count: images.length,
alert: '',
});
}
export const removeImage = async function(req, res) {
let images = req.body.select;
if (typeof(images) == 'string') {
images = [images];
}
for (let i = 0; i < images.length; i++) {
if (images[i] != 'on') {
try {
console.log(`Removing image: ${images[i]}`);
let image = docker.getImage(images[i]);
await image.remove();
} catch (error) {
console.log(`Unable to remove image: ${images[i]}`);
}
}
}
res.redirect("/images");
}

View File

@ -43,13 +43,7 @@ export const submitLogin = async function(req,res){
message: "User logged in successfully",
ip: req.socket.remoteAddress
});
if (req.session.role == "admin") {
res.redirect("/");
}
else {
res.redirect("/portal");
}
res.redirect("/dashboard");
}else{
const syslog = await Syslog.create({

View File

@ -41,7 +41,7 @@ export const Networks = async function(req, res) {
res.render("networks", {
name: req.session.user,
role: req.session.role,
avatar: req.session.avatar,
avatar: req.session.user.charAt(0).toUpperCase(),
network_list: network_list,
network_count: networks.length,
alert: '',

View File

@ -3,12 +3,10 @@ import { Permission, Container, User } from '../database/models.js';
import { docker } from '../server.js';
import { readFileSync } from 'fs';
let hidden = '';
// The actual page
export const Portal = (req, res) => {
let name = req.session.user;
let role = req.session.role;
let avatar = name.charAt(0).toUpperCase();
@ -16,7 +14,8 @@ export const Portal = (req, res) => {
res.render("portal", {
name: name,
avatar: avatar,
role: role
role: role,
alert: '',
});
}
@ -29,12 +28,6 @@ async function CardList () {
let card = await createCard(details);
cardList += card;
}
// for (let i = 0; i < containers.length; i++) {
// console.log(containers[i].containerName);
// }
}
export const UserContainers = async (req, res) => {

View File

@ -81,7 +81,7 @@ export const submitRegister = async function(req,res){
ip: req.socket.remoteAddress
});
res.redirect("/");
res.redirect("/dashboard");
}
} catch(err) {
res.render("register",{

View File

@ -4,7 +4,7 @@ export const Settings = (req, res) => {
res.render("settings", {
name: req.session.user,
role: req.session.role,
avatar: req.session.avatar,
avatar: req.session.user.charAt(0).toUpperCase(),
alert: '',
});
}

View File

@ -12,7 +12,7 @@ export const Supporters = async (req, res) => {
id: user.id,
email: user.email,
role: user.role,
avatar: user.avatar,
avatar: req.session.user.charAt(0).toUpperCase(),
alert: '',
});

View File

@ -29,7 +29,7 @@ export const Syslogs = async function(req, res) {
res.render("syslogs", {
name: req.session.user || 'Dev',
role: req.session.role || 'Dev',
avatar: req.session.avatar || '<img src="/img/avatars/rus.jpg">',
avatar: req.session.user.charAt(0).toUpperCase(),
logs: logs,
alert: '',
});

View File

@ -54,8 +54,9 @@ export const Users = async (req, res) => {
res.render("users", {
name: req.session.user,
role: req.session.role,
avatar: req.session.avatar,
user_list: user_list
avatar: req.session.user.charAt(0).toUpperCase(),
user_list: user_list,
alert: ''
});
}

View File

@ -62,7 +62,7 @@ export const Volumes = async function(req, res) {
res.render("volumes", {
name: req.session.user,
role: req.session.role,
avatar: req.session.avatar,
avatar: req.session.user.charAt(0).toUpperCase(),
volume_list: volume_list,
volume_count: volumes.length,
alert: '',

View File

Before

Width:  |  Height:  |  Size: 413 B

After

Width:  |  Height:  |  Size: 413 B

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,15 +1,14 @@
import express from "express";
import { Permission } from '../database/models.js';
export const router = express.Router();
// Controllers
import { Login, submitLogin, Logout } from "../controllers/login.js";
import { Register, submitRegister } from "../controllers/register.js";
import { Dashboard, Logs, Modals, Stats, Chart, SSE, Card, updateCards, Containers, Action, UpdatePermissions } from "../controllers/dashboard.js";
import { Apps, appSearch, InstallModal, ImportModal, LearnMore, Upload } from "../controllers/apps.js";
import { Dashboard, DashboardAction, Stats, Chart, SSE, UpdatePermissions } from "../controllers/dashboard.js";
import { Apps, appSearch, AppTemplate, InstallModal, ImportModal, LearnMore, Upload } from "../controllers/apps.js";
import { Users } from "../controllers/users.js";
import { Images, removeImage } from "../controllers/images.js";
import { Images } from "../controllers/images.js";
import { Networks, removeNetwork } from "../controllers/networks.js";
import { Volumes, removeVolume } from "../controllers/volumes.js";
import { Account } from "../controllers/account.js";
@ -17,105 +16,91 @@ import { Variables } from "../controllers/variables.js";
import { Settings } from "../controllers/settings.js";
import { Supporters, Thanks } from "../controllers/supporters.js";
import { Syslogs } from "../controllers/syslogs.js";
import { Portal, UserContainers } from "../controllers/portal.js"
import { Install } from "../utils/install.js"
import { Uninstall } from "../utils/uninstall.js"
// Auth middleware
const auth = async (req, res, next) => {
// Permission Middleware
const adminOnly = async (req, res, next) => {
if (req.session.role == 'admin') { next(); }
else { res.redirect('/dashboard'); }
}
const sessionCheck = async (req, res, next) => {
if (req.session.user) { next(); }
else { res.redirect('/login'); }
}
const permissionCheck = async (req, res, next) => {
if (!req.session.user) { res.redirect('/login'); return; }
else if (req.session.role == 'admin') { next(); return; }
let user = req.session.user;
let role = req.session.role;
let path = req.path;
let trigger = req.header('hx-trigger-name');
// console.log("Auth: ", user, role, path, trigger, req.path);
if (!user) { res.redirect('/login'); return; }
else if (role == 'admin' || path == "/portal" || path == "/account" || path == "/supporters" || path == "/thank" || path == "/user_containers") { next(); return; }
// else { res.redirect('/portal'); return; }
let action = req.path.split("/")[2];
if (action == "start" || action == "stop" || action == "pause" || action == "restart") {
let trigger = req.header('hx-trigger-name');
const userAction = ['start', 'stop', 'restart', 'pause', 'uninstall', 'upgrade', 'edit', 'logs', 'hide', 'reset_view'];
const userPaths = ['card', 'containers', 'updates'];
if (userAction.includes(action)) {
let permission = await Permission.findOne({ where: { containerName: trigger, user: user }, attributes: [`${action}`] });
if (permission) {
if (permission) {
if (permission[action] == true) {
console.log(`User ${user} has permission to ${action} ${trigger}`);
next();
return;
}
else {
console.log(`User ${user} does not have permission to ${action} ${trigger}`);
}
} else {
console.log(`No entry found for ${user} in ${trigger} permissions`);
}
}
else {
res.redirect('/portal');
} else if (userPaths.includes(action)) {
next();
return;
}
}
// Utils
router.post("/install", adminOnly, Install);
router.post("/uninstall", adminOnly, Uninstall);
// Admin routes
router.get("/", auth, Dashboard);
router.post("/action/:action", auth, Action);
router.post("/updatePermissions", auth, UpdatePermissions);
router.get("/logs", auth, Logs);
router.get("/modals", auth, Modals);
router.get("/stats", auth, Stats);
router.get("/chart", auth, Chart);
router.get("/sse_event", auth, SSE);
router.get("/containers", auth, Containers);
router.get("/card", auth, Card);
router.get("/new_cards", auth, updateCards);
router.get("/images", auth, Images);
router.post("/removeImage", auth, removeImage);
router.get("/volumes", auth, Volumes);
router.post("/removeVolume", auth, removeVolume);
router.get("/networks", auth, Networks);
router.post("/removeNetwork", auth, removeNetwork);
router.get("/apps", auth, Apps);
router.get("/apps/:page", auth, Apps);
router.post("/apps", auth, appSearch);
router.get("/install_modal", auth, InstallModal)
router.get("/import_modal", auth, ImportModal)
router.get("/learn_more", auth, LearnMore)
router.post("/upload", auth, Upload);
router.get("/users", auth, Users);
router.get("/syslogs", auth, Syslogs);
router.get("/variables", auth, Variables);
router.get("/settings", auth, Settings);
// User routes
router.get("/portal", auth, Portal);
router.get("/user_containers", auth, UserContainers);
router.get("/account", auth, Account);
router.get("/supporters", auth, Supporters);
router.post("/thank", auth, Thanks);
// Routes
router.get("/login", Login);
router.post("/login", submitLogin);
router.get("/logout", Logout);
router.get("/register", Register);
router.post("/register", submitRegister);
router.get("/logout", Logout);
router.get("/", sessionCheck, Dashboard);
router.get("/dashboard", sessionCheck, Dashboard);
router.post("/dashboard/:action", permissionCheck, DashboardAction);
router.get("/sse", sessionCheck, SSE);
router.post("/updatePermissions", adminOnly, UpdatePermissions);
router.get("/stats", sessionCheck, Stats);
router.get("/chart", sessionCheck, Chart);
router.get("/images", adminOnly, Images);
router.post("/images/:action", adminOnly, Images);
router.get("/volumes", adminOnly, Volumes);
router.post("/removeVolume", adminOnly, removeVolume);
router.get("/networks", adminOnly, Networks);
router.post("/removeNetwork", adminOnly, removeNetwork);
router.get("/apps", adminOnly, Apps);
router.get("/apps/:page", adminOnly, Apps);
router.get("/apps/template/:template", adminOnly, AppTemplate);
router.post("/apps", adminOnly, appSearch);
router.get("/install_modal", adminOnly, InstallModal)
router.get("/import_modal", adminOnly, ImportModal)
router.get("/learn_more", adminOnly, LearnMore)
router.post("/upload", adminOnly, Upload);
router.get("/users", adminOnly, Users);
router.get("/syslogs", adminOnly, Syslogs);
router.get("/variables", adminOnly, Variables);
router.get("/settings", adminOnly, Settings);
// Functions
import { Install } from "../functions/install.js"
import { Uninstall } from "../functions/uninstall.js"
router.post("/install", Install);
router.post("/uninstall", Uninstall);
router.get("/account", sessionCheck, Account);
router.get("/supporters", sessionCheck, Supporters);
router.post("/thank", sessionCheck, Thanks);

View File

@ -5,7 +5,7 @@ import ejs from 'ejs';
import Docker from 'dockerode';
import { router } from './router/index.js';
import { sequelize } from './database/models.js';
export var docker = new Docker();
export const docker = new Docker();
const app = express();
const MemoryStore = memorystore(session);

View File

@ -35,8 +35,7 @@
<div class="card">
<div class="card-body text-center">
<div class="d-flex align-items-center">
<div class="me-auto"><%= list_start %> - <%= list_end %> of <%= app_count %> Apps</div>
<button class="btn btn-primary" name="Import" id="Import" data-hx-get="/import_modal" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Import</button>
<div class="me-auto btn"><%= list_start %> - <%= list_end %> of <%= app_count %> Apps</div>
</div>
</div>
</div>
@ -46,27 +45,34 @@
<div class="card">
<div class="card-body text-center">
<div class="d-flex align-items-center">
<select class="form-select">
<option>All</option>
<option>Media</option>
<option>Tools</option>
<option>Gaming</option>
<option>FOSS</option>
<option>Database</option>
</select>
<div class="btn me-2">Category:</div>
<div class="dropdown">
<button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">All</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Media</a></li>
<li><a class="dropdown-item" href="#">Tools</a></li>
<li><a class="dropdown-item" href="#">Gaming</a></li>
<li><a class="dropdown-item" href="#">FOSS</a></li>
<li><a class="dropdown-item" href="#">Database</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card">
<div class="card-body text-center">
<div class="d-flex align-items-center">
<select class="form-select">
<option>Templates.json (default)</option>
<option>Compose</option>
</select>
<dropdown class="me-2">
<button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Templates.json</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Templates.json</a></li>
<li><a class="dropdown-item" href="#">Compose</a></li>
</ul>
</dropdown>
<button class="btn" name="Import" id="Import" data-hx-get="/import_modal" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Import</button>
</div>
</div>
</div>
@ -78,7 +84,7 @@
<div class="text-secondary d-flex align-items-center">
<form action="/apps" id="search" name="search" method="POST" class="d-flex">
<input type="search" class="form-control me-2" name="search" placeholder="Search apps…" >
<input type="submit" form="search" class="btn btn-primary" value="Search">
<input type="submit" form="search" class="btn" value="Search">
</form>
</div>
</div>

View File

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<title>DweebUI - Dashboard</title>
<!-- CSS files -->
<link href="/css/tabler.min.css" rel="stylesheet"/>
<link href="/css/meters.css" rel="stylesheet"/>
<script src="/js/htmx.min.js"></script>
@ -30,7 +29,7 @@
<div class="page-body">
<div class="container-xl">
<div class="row row-deck row-cards" hx-ext="sse" sse-connect="/sse_event">
<div class="row row-deck row-cards" hx-ext="sse" sse-connect="/sse">
<div class="col-12">
<div class="row row-cards">
@ -142,13 +141,13 @@
<!-- HTMX -->
<div class="col-12">
<div class="row row-cards" id="containers" data-hx-get="/containers" data-hx-trigger="load" data-hx-swap="innerHTML">
<div class="row row-cards" id="containers" data-hx-post="/dashboard/containers" data-hx-trigger="load" data-hx-swap="innerHTML">
</div>
</div>
<!-- HTMX -->
<div class="col-12">
<div class="row row-cards" data-hx-get="/new_cards" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
<div class="row row-cards" data-hx-post="/dashboard/updates" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
</div>
</div>

View File

@ -57,7 +57,7 @@
<div class="card-footer d-flex align-items-center">
<button class="btn" type="submit" formaction="/removeImage">Remove</button>
<button class="btn" type="submit" formaction="/images/remove">Remove</button>
</form>

View File

@ -24,12 +24,12 @@
<div class="container container-tight py-4">
<div class="text-center">
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
<img src="/images/logo.png" alt="DweebUI" title="DweebUI" height="100px">
<img src="/img/logo.png" alt="DweebUI" title="DweebUI" height="100px">
</h1>
</div>
<div class="text-center mb-4">
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
<img src="/images/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
<img src="/img/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
</h1>
</div>

View File

@ -4,16 +4,13 @@
<div class="modal-body">
<div class="modal-title">Import Template(s)</div>
<div class="text-muted">Template(s) can be *.json, *.yml, or *.yaml</div>
<div class="mt-3">
<div class="form-label">Choose file(s):</div>
<form method="post" action="/upload" enctype="multipart/form-data" id="upload">
<input type="file" name="files" multiple />
</form>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link link-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary" data-bs-dismiss="modal" form="upload">Upload</button>

View File

@ -17,7 +17,7 @@
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" disabled="">Reset</button>
</div>
<div class="col">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Update</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" disabled="">Update</button>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
<div class="col-sm-6 col-lg-3 pt-1" hx-get="/card" hx-trigger="sse:AppName" hx-swap="outerHTML" name="AppName">
<div class="col-sm-6 col-lg-3 pt-1" hx-post="/dashboard/card" hx-trigger="sse:AppName" hx-swap="outerHTML" name="AppName">
<div class="card">
<div class="card-body">
<div class="card-stamp card-stamp-sm">
@ -9,16 +9,16 @@
<div class="ms-auto lh-1">
<div class="card-actions btn-actions">
<div class="card-actions btn-actions">
<button class="btn-action" title="Start" data-hx-post="/action/start" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<button class="btn-action" title="Start" data-hx-post="/dashboard/start" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-play" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 4v16l13 -8z"></path></svg>
</button>
<button class="btn-action" title="Stop" data-hx-post="/action/stop" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<button class="btn-action" title="Stop" data-hx-post="/dashboard/stop" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-stop" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 5m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"></path></svg>
</button>
<button class="btn-action" title="Pause" data-hx-post="/action/pause" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<button class="btn-action" title="Pause" data-hx-post="/dashboard/pause" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-pause" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path><path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path></svg>
</button>
<button class="btn-action" title="Restart" data-hx-post="/action/restart" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<button class="btn-action" title="Restart" data-hx-post="/dashboard/restart" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<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>
<div class="dropdown">
@ -26,11 +26,11 @@
<svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
</a>
<div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-get="/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
<button class="dropdown-item text-secondary" name="AppName" id="logs" data-hx-get="/logs" hx-swap="innerHTML" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
<button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-get="/dashboard/details" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
<button class="dropdown-item text-secondary" name="AppName" id="logs" data-hx-post="/dashboard/logs" hx-swap="innerHTML" hx-trigger="click" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
<button class="dropdown-item text-secondary" name="AppName" id="edit">Edit</button>
<button class="dropdown-item text-primary" name="AppName" id="update" disabled="">Update</button>
<button class="dropdown-item text-danger" name="AppName" id="uninstall" hx-trigger="click" data-hx-get="/modals" hx-swap="innerHTML" data-bs-toggle="modal" data-hx-target="#modals-here" data-bs-target="#modals-here">Uninstall</button>
<button class="dropdown-item text-danger" name="AppName" id="uninstall" hx-trigger="click" data-hx-post="/dashboard/uninstall" hx-swap="innerHTML" data-bs-toggle="modal" data-hx-target="#modals-here" data-bs-target="#modals-here">Uninstall</button>
</div>
</div>
<div class="dropdown">
@ -38,9 +38,9 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /> </svg>
</a>
<div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item text-secondary" data-hx-post="/action/hide" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="hide" value="hide">Hide</button>
<button class="dropdown-item text-secondary" data-hx-post="/action/reset" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="reset" value="reset">Reset View</button>
<button class="dropdown-item text-secondary" name="AppName" id="permissions" data-hx-get="/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
<button class="dropdown-item text-secondary" data-hx-post="/dashboard/hide" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="hide" value="hide">Hide</button>
<button class="dropdown-item text-secondary" data-hx-post="/dashboard/reset" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="reset" value="reset">Reset View</button>
<button class="dropdown-item text-secondary" data-hx-post="/dashboard/permissions" name="AppName" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
</div>
</div>
</div>

View File

@ -9,16 +9,16 @@
<div class="ms-auto lh-1">
<div class="card-actions btn-actions">
<div class="card-actions btn-actions">
<button class="btn-action" title="Start" data-hx-post="/action/start" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<button class="btn-action" title="Start" data-hx-post="/dashboard/start" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-play" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 4v16l13 -8z"></path></svg>
</button>
<button class="btn-action" title="Stop" data-hx-post="/action/stop" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<button class="btn-action" title="Stop" data-hx-post="/dashboard/stop" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-stop" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 5m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"></path></svg>
</button>
<button class="btn-action" title="Pause" data-hx-post="/action/pause" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<button class="btn-action" title="Pause" data-hx-post="/dashboard/pause" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-pause" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path><path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path></svg>
</button>
<button class="btn-action" title="Restart" data-hx-post="/action/restart" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<button class="btn-action" title="Restart" data-hx-post="/dashboard/restart" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
<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>
<div class="dropdown">
@ -26,8 +26,8 @@
<svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
</a>
<div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-get="/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
<button class="dropdown-item text-secondary" name="AppName" id="logs" data-hx-get="/logs" hx-swap="innerHTML" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
<button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-get="/dashboard/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
<button class="dropdown-item text-secondary" name="AppName" id="logs" data-hx-get="/dashboard/logs" hx-swap="innerHTML" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
<button class="dropdown-item text-secondary" name="AppName" id="edit">Edit</button>
<button class="dropdown-item text-primary" name="AppName" id="update" disabled="">Update</button>
<button class="dropdown-item text-danger" name="AppName" id="uninstall" hx-trigger="click" data-hx-get="/modals" hx-swap="innerHTML" data-bs-toggle="modal" data-hx-target="#modals-here" data-bs-target="#modals-here">Uninstall</button>
@ -38,8 +38,8 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /> </svg>
</a>
<div class="dropdown-menu dropdown-menu-end">
<button class="dropdown-item text-secondary" data-hx-post="/action/hide" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="hide" value="hide">Hide</button>
<button class="dropdown-item text-secondary" data-hx-post="/action/reset" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="reset" value="reset">Reset View</button>
<button class="dropdown-item text-secondary" data-hx-post="/dashboard/hide" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="hide" value="hide">Hide</button>
<button class="dropdown-item text-secondary" data-hx-post="/dashboard/reset" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="reset" value="reset">Reset View</button>
<button class="dropdown-item text-secondary" name="AppName" id="permissions" data-hx-get="/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
</div>
</div>

View File

@ -35,10 +35,10 @@
</button>
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0">
<a href="#">
<img src="/images/logo.png" alt="DweebUI" title="DweebUI" height="40px">
<img src="/img/logo.png" alt="DweebUI" title="DweebUI" height="40px">
</a>
<a href="#">
<img src="/images/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
<img src="/img/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
</a>
</h1>
@ -179,7 +179,7 @@
<div class="container-xl">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">
<a class="nav-link" href="/dashboard">
<span
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/dashboard -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-dashboard" 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 13m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path> <path d="M13.45 11.55l2.05 -2.05"></path> <path d="M6.4 20a9 9 0 1 1 11.2 0z"></path> </svg>

View File

@ -27,7 +27,7 @@
</div>
</div>
<input type="hidden" name="username" value="PermissionsUsername">
<input type="hidden" name="user" value="PermissionsUsername">
<input type="hidden" name="container" value="PermissionsContainer">
<div class="row mb-2">

View File

@ -44,7 +44,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-cpu" 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="M5 5m0 1a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1z"></path><path d="M9 9h6v6h-6z"></path><path d="M3 10h2"></path><path d="M3 14h2"></path><path d="M10 3v2"></path><path d="M14 3v2"></path><path d="M21 10h-2"></path><path d="M21 14h-2"></path><path d="M14 21v-2"></path><path d="M10 21v-2"></path></svg>
</span>
</div>
<!-- HTMX -->
<div class="col" name="CPU" id="green">
<div class="font-weight-medium">
@ -54,7 +53,6 @@
<span style="width:20%"><span></span></span>
</div>
</div>
</div>
</div>
</div>
@ -69,7 +67,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-container" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M20 4v.01"></path> <path d="M20 20v.01"></path> <path d="M20 16v.01"></path> <path d="M20 12v.01"></path> <path d="M20 8v.01"></path> <path d="M8 4m0 1a1 1 0 0 1 1 -1h6a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-6a1 1 0 0 1 -1 -1z"></path> <path d="M4 4v.01"></path> <path d="M4 20v.01"></path> <path d="M4 16v.01"></path> <path d="M4 12v.01"></path> <path d="M4 8v.01"></path> </svg>
</span>
</div>
<!-- HTMX -->
<div class="col" name="RAM" id="blue">
<div class="font-weight-medium">
@ -79,7 +76,6 @@
<span style="width:20%"><span></span></span>
</div>
</div>
</div>
</div>
</div>
@ -94,7 +90,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrows-left-right" 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="M21 17l-18 0"></path> <path d="M6 10l-3 -3l3 -3"></path> <path d="M3 7l18 0"></path> <path d="M18 20l3 -3l-3 -3"></path> </svg>
</span>
</div>
<!-- HTMX -->
<div class="col" name="NET" id="purple">
<div class="font-weight-medium">
@ -104,7 +99,6 @@
<span style="width:20%"><span></span></span>
</div>
</div>
</div>
</div>
</div>
@ -119,7 +113,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-database" 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 6m-8 0a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"></path> <path d="M4 6v6a8 3 0 0 0 16 0v-6"></path> <path d="M4 12v6a8 3 0 0 0 16 0v-6"></path></svg>
</span>
</div>
<!-- HTMX -->
<div class="col" name="DISK" id="orange">
<div class="font-weight-medium">
@ -129,7 +122,6 @@
<span style="width:20%"><span></span></span>
</div>
</div>
</div>
</div>
</div>
@ -147,10 +139,10 @@
</div>
<!-- HTMX -->
<!-- <div class="col-12">
<div class="row row-cards" data-hx-get="/new_cards" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
<div class="col-12">
<div class="row row-cards" data-hx-get="/new_user_cards" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
</div>
</div> -->
</div>
<!-- HTMX Target-->
<div id="modals-here" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">

View File

@ -28,12 +28,12 @@
<div class="text-center">
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
<img src="/images/logo.png" alt="DweebUI" title="DweebUI" height="100px">
<img src="/img/logo.png" alt="DweebUI" title="DweebUI" height="100px">
</h1>
</div>
<div class="text-center mb-4">
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
<img src="/images/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
<img src="/img/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
</h1>
</div>