First draft of permissions system
This commit is contained in:
parent
62b7e73aac
commit
15722b1687
|
@ -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: '',
|
||||
});
|
||||
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
};
|
|
@ -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;
|
||||
});
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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({
|
||||
|
|
|
@ -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: '',
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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",{
|
||||
|
|
|
@ -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: '',
|
||||
});
|
||||
}
|
|
@ -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: '',
|
||||
});
|
||||
|
||||
|
|
|
@ -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: '',
|
||||
});
|
||||
|
|
|
@ -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: ''
|
||||
});
|
||||
|
||||
}
|
|
@ -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: '',
|
||||
|
|
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 413 B |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
149
router/index.js
149
router/index.js
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue