mostly working permission system.
This commit is contained in:
parent
365cdde0cc
commit
a73a89b250
8 changed files with 606 additions and 139 deletions
|
@ -9,13 +9,15 @@ let hidden = '';
|
|||
|
||||
// The actual page
|
||||
export const Dashboard = (req, res) => {
|
||||
|
||||
if (!req.session.user) { res.redirect("/logout"); return; }
|
||||
|
||||
let name = req.session.user;
|
||||
let role = req.session.role;
|
||||
let avatar = name.charAt(0).toUpperCase();
|
||||
|
||||
res.render("dashboard", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.avatar,
|
||||
name: name,
|
||||
avatar: avatar,
|
||||
role: role
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -393,6 +395,16 @@ export const Modals = async (req, res) => {
|
|||
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);
|
||||
|
|
|
@ -1,16 +1,299 @@
|
|||
import { Readable } from 'stream';
|
||||
import { Permission, Container, User } from '../database/models.js';
|
||||
import { docker } from '../server.js';
|
||||
import { dockerContainerStats } from 'systeminformation';
|
||||
import { readFileSync } from 'fs';
|
||||
import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
|
||||
|
||||
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();
|
||||
|
||||
if (!req.session.user) {
|
||||
res.redirect("/login");
|
||||
res.render("portal", {
|
||||
name: name,
|
||||
avatar: avatar,
|
||||
role: role
|
||||
});
|
||||
}
|
||||
|
||||
async function containerInfo (containerName) {
|
||||
let container = docker.getContainer(containerName);
|
||||
let info = await container.inspect();
|
||||
let image = info.Config.Image.split('/');
|
||||
let ports_list = [];
|
||||
try {
|
||||
for (const [key, value] of Object.entries(info.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {
|
||||
// no exposed ports
|
||||
}
|
||||
|
||||
let external = 0;
|
||||
let internal = 0;
|
||||
try {
|
||||
external = ports_list[0].external;
|
||||
internal = ports_list[0].internal;
|
||||
} catch {
|
||||
// no exposed ports
|
||||
}
|
||||
|
||||
|
||||
let details = {
|
||||
name: containerName,
|
||||
image: image,
|
||||
service: image[image.length - 1].split(':')[0],
|
||||
state: info.State.Status,
|
||||
external_port: external,
|
||||
internal_port: internal,
|
||||
ports: ports_list,
|
||||
link: 'localhost',
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
async function createCard (details) {
|
||||
if (hidden.includes(details.name)) { return;}
|
||||
let shortname = details.name.slice(0, 10) + '...';
|
||||
let trigger = 'data-hx-trigger="load, every 3s"';
|
||||
let state = details.state;
|
||||
let state_color = '';
|
||||
switch (state) {
|
||||
case 'running':
|
||||
state_color = 'green';
|
||||
break;
|
||||
case 'exited':
|
||||
state = 'stopped';
|
||||
state_color = 'red';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
case 'paused':
|
||||
state_color = 'orange';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
case 'installing':
|
||||
state_color = 'blue';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
}
|
||||
// if (name.startsWith('dweebui')) { disable = 'disabled=""'; }
|
||||
let card = readFileSync('./views/partials/containerCard.html', 'utf8');
|
||||
card = card.replace(/AppName/g, details.name);
|
||||
card = card.replace(/AppShortName/g, shortname);
|
||||
card = card.replace(/AppIcon/g, details.service);
|
||||
card = card.replace(/AppState/g, state);
|
||||
card = card.replace(/StateColor/g, state_color);
|
||||
card = card.replace(/ExternalPort/g, details.external_port);
|
||||
card = card.replace(/InternalPort/g, details.internal_port);
|
||||
card = card.replace(/ChartName/g, details.name.replace(/-/g, ''));
|
||||
card = card.replace(/AppNameState/g, `${details.name}State`);
|
||||
card = card.replace(/data-trigger=""/, trigger);
|
||||
return card;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
res.render("portal", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.avatar,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
|
@ -2,8 +2,6 @@ import { User } from "../database/models.js";
|
|||
|
||||
export const Supporters = async (req, res) => {
|
||||
|
||||
if (!req.session.UUID) return res.redirect("/login");
|
||||
|
||||
let user = await User.findOne({ where: { UUID: req.session.UUID }});
|
||||
|
||||
|
||||
|
|
|
@ -124,39 +124,51 @@ export const Permission = sequelize.define('Permission', {
|
|||
},
|
||||
install: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
uninstall: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
edit: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
upgrade: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
start: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
stop: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
restart: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
pause: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
logs: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
hide: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
reset_view: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
view: {
|
||||
type: DataTypes.STRING
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import express from "express";
|
||||
import { Permission, User } from '../database/models.js';
|
||||
import { Permission } from '../database/models.js';
|
||||
|
||||
export const router = express.Router();
|
||||
|
||||
|
@ -20,28 +20,54 @@ import { Syslogs } from "../controllers/syslogs.js";
|
|||
import { Portal } from "../controllers/portal.js"
|
||||
|
||||
// Auth middleware
|
||||
const auth = (req, res, next) => {
|
||||
if (req.session.role == "admin") {
|
||||
const auth = async (req, res, next) => {
|
||||
if (!req.session.user) { res.redirect('/login'); return; }
|
||||
|
||||
let user = req.session.user;
|
||||
let role = req.session.role;
|
||||
let action = req.path.split("/")[2];
|
||||
let trigger = req.header('hx-trigger-name');
|
||||
// console.log("Auth: ", user, role, action, trigger);
|
||||
|
||||
if (role == "admin") {
|
||||
next();
|
||||
} else {
|
||||
res.redirect("/portal");
|
||||
}
|
||||
};
|
||||
else if (action == "start" || action == "stop" || action == "pause" || action == "restart") {
|
||||
let permission = await Permission.findOne({ where: { containerName: trigger, user: user }, attributes: [`${action}`] });
|
||||
|
||||
if (permission) {
|
||||
if (permission[action] == true) {
|
||||
console.log(`User ${user} has permission to ${action} ${trigger}`);
|
||||
next();
|
||||
}
|
||||
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');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Admin routes
|
||||
router.get("/", auth, Dashboard);
|
||||
router.post("/action/:action", auth, Action);
|
||||
|
||||
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.post("/updatePermissions", auth, UpdatePermissions);
|
||||
|
||||
router.get("/logs", Logs);
|
||||
router.get("/modals", Modals);
|
||||
router.get("/stats", Stats);
|
||||
router.get("/chart", Chart);
|
||||
router.get("/sse_event", SSE);
|
||||
router.get("/containers", Containers);
|
||||
router.get("/card", Card);
|
||||
router.get("/new_cards", updateCards);
|
||||
|
||||
|
||||
router.get("/images", auth, Images);
|
||||
router.post("/removeImage", auth, removeImage);
|
||||
|
||||
|
@ -81,6 +107,6 @@ router.get("/logout", Logout);
|
|||
import { Install } from "../functions/install.js"
|
||||
import { Uninstall } from "../functions/uninstall.js"
|
||||
|
||||
router.post("/install", auth, Install);
|
||||
router.post("/uninstall", auth, Uninstall);
|
||||
router.post("/install", Install);
|
||||
router.post("/uninstall", Uninstall);
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@
|
|||
</div>
|
||||
<div class="nav-item dropdown">
|
||||
<a href="#" class="nav-link d-flex lh-1 text-reset p-0" data-bs-toggle="dropdown" aria-label="Open user menu">
|
||||
<span class="avatar avatar-sm bg-green-lt"><%= name.charAt(0) %></span></span>
|
||||
<span class="avatar avatar-sm bg-green-lt"><%= avatar %></span></span>
|
||||
<div class="d-none d-xl-block ps-2">
|
||||
<div>
|
||||
<%= name %>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="uninstall">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="uninstall" data-UninstallCheck>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -55,7 +55,7 @@
|
|||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="edit">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="edit" data-EditCheck>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -70,7 +70,7 @@
|
|||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="upgrade">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="upgrade" data-UpgradeCheck>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -85,7 +85,7 @@
|
|||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="start">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="start" data-StartCheck>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -100,7 +100,7 @@
|
|||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="stop">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="stop" data-StopCheck>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -115,7 +115,7 @@
|
|||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="pause">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="pause" data-PauseCheck>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -130,7 +130,7 @@
|
|||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="restart">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="restart" data-RestartCheck>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -146,7 +146,7 @@
|
|||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="logs">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntryNumber" value="logs" data-LogsCheck>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/meters.css" rel="stylesheet"/>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
|
@ -19,128 +20,263 @@
|
|||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
|
||||
<div class="page">
|
||||
|
||||
<%- include('partials/navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
|
||||
<div class="col-12" id="cards">
|
||||
<div class="row row-cards">
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-primary text-white avatar">
|
||||
<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>
|
||||
<%- include('partials/navbar.html') %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="row row-cards">
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-green text-white avatar">
|
||||
<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" data-hx-get="" data-hx-trigger="">
|
||||
<div class="font-weight-medium">
|
||||
<label class="cpu-text mb-1" for="cpu">CPU 0%</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="font-weight-medium">
|
||||
<label id="cpu-text" class="cpu-text mb-1" for="cpu">I had to put something here.</label>
|
||||
</div>
|
||||
<div id="cpu-bar" class="cpu-bar meter animate">
|
||||
<span style="width: 25%"><span></span></span>
|
||||
</div>
|
||||
<div class="cpu-bar meter animate green">
|
||||
<span style="width:20%"><span></span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-green text-white avatar">
|
||||
<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>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-blue text-white avatar">
|
||||
<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" data-hx-get="" data-hx-trigger="">
|
||||
<div class="font-weight-medium">
|
||||
<label class="ram-text mb-1" for="ram">RAM 0%</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="font-weight-medium">
|
||||
<label id="ram-text" class="ram-text mb-1" for="ram">RAM 0%</label>
|
||||
</div>
|
||||
<div id="ram-bar" class="ram-bar meter animate orange">
|
||||
<span style="width: 25%"><span></span></span>
|
||||
</div>
|
||||
<div class="ram-bar meter animate blue">
|
||||
<span style="width:20%"><span></span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-twitter text-white avatar">
|
||||
<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>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-purple text-white avatar">
|
||||
<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" data-hx-get="" data-hx-trigger="">
|
||||
<div class="font-weight-medium">
|
||||
<label id="net-text" class="net-text mb-1" for="network">Down: 0MB Up: 0MB</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="font-weight-medium">
|
||||
<label id="net-text" class="net-text mb-1" for="network">Down: 0MB Up: 0MB</label>
|
||||
</div>
|
||||
<div id="net-bar" class="meter animate blue">
|
||||
<span style="width: 25%"><span></span></span>
|
||||
</div>
|
||||
<div class="ram-bar meter animate purple">
|
||||
<span style="width:20%"><span></span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-facebook text-white avatar">
|
||||
<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>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-orange text-white avatar">
|
||||
<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" data-hx-get="" data-hx-trigger="">
|
||||
<div class="font-weight-medium">
|
||||
<label class="disk-text mb-1" for="disk">DISK 0%</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="font-weight-medium">
|
||||
<label id="disk-text" class="disk-text mb-1" for="disk">DISK 0%</label>
|
||||
</div>
|
||||
<div id="disk-bar" class="meter animate red">
|
||||
<span style="width: 25%"><span></span></span>
|
||||
</div>
|
||||
<div class="meter animate orange">
|
||||
<span style="width:20%"><span></span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<!-- HTMX Target-->
|
||||
<div id="modals-here" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered modal-dialog-scrollables">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Loading</h5>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<div class="spinner-border"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- inserted here from -->
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal modal-blur fade" id="log_view" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Logs</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="card-body">
|
||||
<h4>Logs:</h4>
|
||||
<div id="logView">
|
||||
<pre>No logs available</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-info" onclick="viewLogs(this)" name="refresh">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path> <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path> </svg>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('partials/footer.html') %>
|
||||
|
||||
</div>
|
||||
|
||||
<%- include('partials/footer.html') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/apexcharts/dist/apexcharts.min.js" defer></script>
|
||||
<!-- Tabler Core -->
|
||||
<script src="/libs/apexcharts/dist/apexcharts.min.js"></script>
|
||||
<script src="/js/tabler.min.js"></script>
|
||||
<script>
|
||||
var options = {
|
||||
chart: {
|
||||
type: "line",
|
||||
height: 40.0,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
animations: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
opacity: 1
|
||||
},
|
||||
stroke: {
|
||||
width: [3, 1],
|
||||
dashArray: [0, 3],
|
||||
lineCap: "round",
|
||||
curve: "smooth"
|
||||
},
|
||||
series: [{
|
||||
name: "CPU",
|
||||
data: []
|
||||
}, {
|
||||
name: "RAM",
|
||||
data: []
|
||||
}],
|
||||
tooltip: {
|
||||
enabled: false
|
||||
},
|
||||
grid: {
|
||||
strokeDashArray: 4
|
||||
},
|
||||
xaxis: {
|
||||
labels: {
|
||||
padding: 0
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
labels: {
|
||||
padding: 4
|
||||
}
|
||||
},
|
||||
colors: [tabler.getColor("primary"), tabler.getColor("gray-600")],
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- SelectAll for the permissions modal -->
|
||||
<script>
|
||||
function selectAll(group) {
|
||||
|
||||
let checkboxes = document.getElementsByName(group);
|
||||
if (checkboxes[0].checked == true) {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = true;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue