commit
e2463ebc47
15 changed files with 900 additions and 330 deletions
174
app.js
174
app.js
|
@ -1,15 +1,12 @@
|
|||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const redis = require('connect-redis');
|
||||
const { currentLoad, mem, networkStats, fsSize, dockerContainerStats } = require('systeminformation');
|
||||
const app = express();
|
||||
const routes = require("./routes");
|
||||
const PORT = 8000;
|
||||
var Docker = require('dockerode');
|
||||
var docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
||||
const { dashCard } = require('./components/dashCard');
|
||||
|
||||
let DockerContainers, sent_list, clicked, open_ports, ServerMetrics, card_list, external_port, internal_port;
|
||||
const { serverStats, containerList, containerStats, containerAction } = require('./functions/system_information');
|
||||
|
||||
let sent_list, clicked;
|
||||
|
||||
const redisClient = require('redis').createClient({
|
||||
legacyMode:true
|
||||
|
@ -38,143 +35,66 @@ app.use([
|
|||
routes
|
||||
]);
|
||||
|
||||
const server = app.listen(PORT, async () => {
|
||||
console.log(`App listening on port ${PORT}`);
|
||||
const server = app.listen(8000, async () => {
|
||||
console.log(`App listening on port 8000`);
|
||||
});
|
||||
|
||||
const io = require('socket.io')(server);
|
||||
io.engine.use(sessionMiddleware);
|
||||
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
|
||||
// set user session
|
||||
const user_session = socket.request.session;
|
||||
console.log(`${user_session.user} connected from ${socket.handshake.headers.host} ${socket.handshake.address}`);
|
||||
|
||||
// display client connection info
|
||||
console.log(`${user_session.user} connected from ${socket.handshake.headers.host} ${socket.handshake.address} \n Active Sessions: ${io.engine.clientsCount}`);
|
||||
|
||||
// send list of running docker containers if sent_list contains data
|
||||
// check if a list of containers needs to be sent
|
||||
if (sent_list != null) { socket.emit('cards', sent_list); }
|
||||
|
||||
// check if an install is in progress
|
||||
if((app.locals.install != '') && (app.locals.install != null)){
|
||||
socket.emit('install', app.locals.install);
|
||||
}
|
||||
// check if an install card has to be sent
|
||||
if((app.locals.install != '') && (app.locals.install != null)){ socket.emit('install', app.locals.install); }
|
||||
|
||||
// send server metrics to client
|
||||
async function Metrics() {
|
||||
Promise.all([currentLoad(), mem(), networkStats(), fsSize()]).then(([cpuUsage, ramUsage, netUsage, diskUsage]) => {
|
||||
let cpu = Math.round(cpuUsage.currentLoad);
|
||||
let ram = Math.round(((ramUsage.active / ramUsage.total) * 100));
|
||||
let tx = netUsage[0].tx_bytes;
|
||||
let rx = netUsage[0].rx_bytes;
|
||||
let disk = diskUsage[0].use;
|
||||
socket.emit('metrics', { cpu, ram, tx, rx, disk });
|
||||
});
|
||||
}
|
||||
// send server metrics
|
||||
let ServerStats = setInterval(async () => {
|
||||
socket.emit('metrics', await serverStats());
|
||||
}, 1000);
|
||||
|
||||
async function ContainersList() {
|
||||
card_list = '';
|
||||
open_ports = '';
|
||||
external_port;
|
||||
internal_port;
|
||||
|
||||
docker.listContainers({ all: true }, async function (err, data) {
|
||||
for (const container of data) {
|
||||
|
||||
let imageVersion = container.Image.split('/');
|
||||
let dockerService = imageVersion[imageVersion.length - 1].split(":")[0];
|
||||
|
||||
|
||||
let containerId = docker.getContainer(container.Id);
|
||||
let containerInfo = await containerId.inspect();
|
||||
|
||||
// console.log(containerInfo.Name.split('/')[1]);
|
||||
// console.log(container.Image);
|
||||
// console.log(containerInfo.HostConfig.RestartPolicy.Name);
|
||||
|
||||
|
||||
for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) {
|
||||
console.log(`${value[0].HostPort}:${key}`);
|
||||
external_port = value[0].HostPort;
|
||||
internal_port = key;
|
||||
}
|
||||
|
||||
// console.log('Volumes:');
|
||||
// for (const [key, value] of Object.entries(containerInfo.Mounts)) {
|
||||
// console.log(`${value.Source}: ${value.Destination}: ${value.RW}`);
|
||||
// }
|
||||
|
||||
|
||||
// console.log('Environment Variables:');
|
||||
// for (const [key, value] of Object.entries(containerInfo.Config.Env)) {
|
||||
// console.log(`${key}: ${value}`);
|
||||
// }
|
||||
|
||||
// console.log('Labels:');
|
||||
// for (const [key, value] of Object.entries(containerInfo.Config.Labels)) {
|
||||
// console.log(`${key}: ${value}`);
|
||||
// }
|
||||
|
||||
// dockerContainerStats(container.Id).then((data) => {
|
||||
// console.log(`${container.Names[0].slice(1)} // CPU: ${Math.round(data[0].cpuPercent)} // RAM: ${Math.round(data[0].memPercent)}`);
|
||||
// });
|
||||
|
||||
let dockerCard = dashCard(container.Names[0].slice(1), dockerService, container.Id, container.State, container.Image, external_port, internal_port);
|
||||
// open_ports += `-L ${external_port}:localhost:${external_port} `
|
||||
card_list += dockerCard;
|
||||
}
|
||||
|
||||
// emit card list is it's different from what was sent last time, then clear install local
|
||||
if (sent_list !== card_list) {
|
||||
sent_list = card_list;
|
||||
app.locals.install = '';
|
||||
socket.emit('cards', card_list);
|
||||
console.log('Cards updated');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Starting Metrics');
|
||||
ServerMetrics = setInterval(Metrics, 1000);
|
||||
|
||||
console.log('Starting Containers List');
|
||||
DockerContainers = setInterval(ContainersList, 1000);
|
||||
|
||||
|
||||
socket.on('clicked', (data) => {
|
||||
// Prevent multiple clicks
|
||||
if (clicked == true) { return; } clicked = true;
|
||||
|
||||
console.log(`${socket.request.session.user} wants to: ${data.action} ${data.container}`);
|
||||
|
||||
if (socket.request.session.role == 'admin') {
|
||||
var containerName = docker.getContainer(data.container);
|
||||
|
||||
if ((data.action == 'start') && (data.state == 'stopped')) {
|
||||
containerName.start();
|
||||
} else if ((data.action == 'start') && (data.state == 'paused')) {
|
||||
containerName.unpause();
|
||||
} else if ((data.action == 'stop') && (data.state != 'stopped')) {
|
||||
containerName.stop();
|
||||
} else if ((data.action == 'pause') && (data.state == 'running')) {
|
||||
containerName.pause();
|
||||
} else if ((data.action == 'pause') && (data.state == 'paused')) {
|
||||
containerName.unpause();
|
||||
} else if (data.action == 'restart') {
|
||||
containerName.restart();
|
||||
}
|
||||
} else {
|
||||
console.log('User is not an admin');
|
||||
// send container list
|
||||
let ContainerList = setInterval(async () => {
|
||||
let card_list = await containerList();
|
||||
if (sent_list !== card_list) {
|
||||
sent_list = card_list;
|
||||
app.locals.install = '';
|
||||
socket.emit('cards', card_list);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// send container metrics
|
||||
let ContainerStats = setInterval(async () => {
|
||||
let container_stats = await containerStats();
|
||||
for (let i = 0; i < container_stats.length; i++) {
|
||||
socket.emit('container_stats', container_stats[i]);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// play/pause/stop/restart container
|
||||
socket.on('clicked', (data) => {
|
||||
if (clicked == true) { return; } clicked = true;
|
||||
let buttonPress = {
|
||||
user: socket.request.session.user,
|
||||
role: socket.request.session.role,
|
||||
action: data.action,
|
||||
container: data.container,
|
||||
state: data.state
|
||||
}
|
||||
containerAction(buttonPress);
|
||||
clicked = false;
|
||||
});
|
||||
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('Stopping Metrics');
|
||||
clearInterval(ServerMetrics);
|
||||
console.log('Stopping Containers List');
|
||||
clearInterval(DockerContainers);
|
||||
socket.on('disconnect', () => {
|
||||
clearInterval(ServerStats);
|
||||
clearInterval(ContainerList);
|
||||
clearInterval(ContainerStats);
|
||||
});
|
||||
|
||||
});
|
|
@ -12,6 +12,9 @@ function appCard(data) {
|
|||
let command_check = command ? "checked" : "";
|
||||
let privileged = data.privileged || "";
|
||||
let privileged_check = privileged ? "checked" : "";
|
||||
let repository = data.repository || "";
|
||||
let source = data.image || "";
|
||||
|
||||
|
||||
// if data.network is set to host, bridge, or docker set the radio button to checked
|
||||
let net_host, net_bridge, net_docker = '';
|
||||
|
@ -35,6 +38,11 @@ function appCard(data) {
|
|||
}
|
||||
|
||||
|
||||
if (repository != "") {
|
||||
source = (`${repository.url}/raw/master/${repository.stackfile}`);
|
||||
}
|
||||
|
||||
|
||||
function CatagoryColor(category) {
|
||||
switch (category) {
|
||||
case 'Other':
|
||||
|
@ -47,6 +55,8 @@ function appCard(data) {
|
|||
return '<span class="badge bg-blue-lt">Dashboard</span> ';
|
||||
case 'Communication':
|
||||
return '<span class="badge bg-azure-lt">Communication</span> ';
|
||||
case 'Media':
|
||||
return '<span class="badge bg-azure-lt">Media</span> ';
|
||||
case 'CMS':
|
||||
return '<span class="badge bg-azure-lt">CMS</span> ';
|
||||
case 'Monitoring':
|
||||
|
@ -131,7 +141,11 @@ function appCard(data) {
|
|||
let volume_check = volumes ? "checked" : "";
|
||||
let volume_bind = volumes.bind.split(":")[0] ? volumes.bind.split(":")[0] : "";
|
||||
let volume_container = volumes.container.split(":")[0] ? volumes.container.split(":")[0] : "";
|
||||
let volume_readwrite = volumes.container.endsWith(":ro") ? "ro" : "rw";
|
||||
let volume_readwrite = "rw"
|
||||
|
||||
if ((volumes.readonly == true) || (volumes.container.endsWith(":ro"))) {
|
||||
volume_readwrite = "ro";
|
||||
}
|
||||
|
||||
volumes_data.push({
|
||||
check: volume_check,
|
||||
|
@ -263,7 +277,7 @@ function appCard(data) {
|
|||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Image: </label>
|
||||
<input type="text" class="form-control" name="image" value="${data.image}"/>
|
||||
<input type="text" class="form-control" name="image" value="${source}"/>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Restart Policy: </label>
|
||||
|
@ -340,7 +354,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_0_check" type="checkbox" ${ports_data[0].check}>
|
||||
<input class="form-check-input" name="port0" type="checkbox" ${ports_data[0].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">External Port</label>
|
||||
|
@ -362,7 +376,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_1_check" type="checkbox" ${ports_data[1].check}>
|
||||
<input class="form-check-input" name="port1" type="checkbox" ${ports_data[1].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_external" value="${ports_data[1].external}"/>
|
||||
|
@ -381,7 +395,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_2_check" type="checkbox" ${ports_data[2].check}>
|
||||
<input class="form-check-input" name="port2" type="checkbox" ${ports_data[2].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_external" value="${ports_data[2].external}"/>
|
||||
|
@ -400,7 +414,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_3_check" type="checkbox" ${ports_data[3].check}>
|
||||
<input class="form-check-input" name="port3" type="checkbox" ${ports_data[3].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_external" value="${ports_data[3].external}"/>
|
||||
|
@ -419,7 +433,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_4_check" type="checkbox" ${ports_data[4].check}>
|
||||
<input class="form-check-input" name="port4" type="checkbox" ${ports_data[4].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_external" value="${ports_data[4].external}"/>
|
||||
|
@ -438,7 +452,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_5_check" type="checkbox" ${ports_data[5].check}>
|
||||
<input class="form-check-input" name="port5" type="checkbox" ${ports_data[5].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_5_external" value="${ports_data[5].external}"/>
|
||||
|
@ -471,7 +485,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_0_check" type="checkbox" ${volumes_data[0].check}>
|
||||
<input class="form-check-input" name="volume0" type="checkbox" ${volumes_data[0].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_bind" value="${volumes_data[0].bind}"/>
|
||||
|
@ -490,7 +504,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_1_check" type="checkbox" ${volumes_data[1].check}>
|
||||
<input class="form-check-input" name="volume1" type="checkbox" ${volumes_data[1].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_bind" value="${volumes_data[1].bind}"/>
|
||||
|
@ -509,7 +523,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_2_check" type="checkbox" ${volumes_data[2].check}>
|
||||
<input class="form-check-input" name="volume2" type="checkbox" ${volumes_data[2].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_bind" value="${volumes_data[2].bind}"/>
|
||||
|
@ -528,7 +542,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_3_check" type="checkbox" ${volumes_data[3].check}>
|
||||
<input class="form-check-input" name="volume3" type="checkbox" ${volumes_data[3].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_bind" value="${volumes_data[3].bind}"/>
|
||||
|
@ -547,7 +561,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_4_check" type="checkbox" ${volumes_data[4].check}>
|
||||
<input class="form-check-input" name="volume4" type="checkbox" ${volumes_data[4].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_bind" value="${volumes_data[4].bind}"/>
|
||||
|
@ -566,7 +580,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_5_check" type="checkbox" ${volumes_data[5].check}>
|
||||
<input class="form-check-input" name="volume5" type="checkbox" ${volumes_data[5].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_bind" value="${volumes_data[5].bind}"/>
|
||||
|
@ -599,7 +613,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_0_check" ${env_data[0].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env0" ${env_data[0].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Variable</label>
|
||||
|
@ -613,7 +627,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_1_check" ${env_data[1].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env1" ${env_data[1].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_1_name" value="${env_data[1].name}"/>
|
||||
|
@ -625,7 +639,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_2_check" ${env_data[2].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env2" ${env_data[2].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_2_name" value="${env_data[2].name}"/>
|
||||
|
@ -637,7 +651,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_3_check" ${env_data[3].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env3" ${env_data[3].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_3_name" value="${env_data[3].name}"/>
|
||||
|
@ -649,7 +663,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_4_check" ${env_data[4].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env4" ${env_data[4].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_4_name" value="${env_data[4].name}"/>
|
||||
|
@ -661,7 +675,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_5_check" ${env_data[5].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env5" ${env_data[5].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_5_name" value="${env_data[5].name}"/>
|
||||
|
@ -673,7 +687,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_6_check" ${env_data[6].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env6" ${env_data[6].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_6_name" value="${env_data[6].name}"/>
|
||||
|
@ -686,7 +700,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_7_check" ${env_data[7].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env7" ${env_data[7].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_7_name" value="${env_data[7].name}"/>
|
||||
|
@ -699,7 +713,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_8_check" ${env_data[8].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env8" ${env_data[8].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_8_name" value="${env_data[8].name}"/>
|
||||
|
@ -712,7 +726,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_9_check" ${env_data[9].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env9" ${env_data[9].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_9_name" value="${env_data[9].name}"/>
|
||||
|
@ -725,7 +739,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_10_check" ${env_data[10].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env10" ${env_data[10].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_10_name" value="${env_data[10].name}"/>
|
||||
|
@ -738,7 +752,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_11_check" ${env_data[11].check}>
|
||||
<input class="form-check-input" type="checkbox" name="env11" ${env_data[11].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_11_name" value="${env_data[11].name}"/>
|
||||
|
@ -766,7 +780,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_0_check" ${label_data[0].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label0" ${label_data[0].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Variable</label>
|
||||
|
@ -780,7 +794,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_1_check" ${label_data[1].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label1" ${label_data[1].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_name" value="${label_data[1].name}"/>
|
||||
|
@ -793,7 +807,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_2_check" ${label_data[2].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label2" ${label_data[2].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_name" value="${label_data[2].name}"/>
|
||||
|
@ -805,7 +819,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_3_check" ${label_data[3].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label3" ${label_data[3].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_name" value="${label_data[3].name}"/>
|
||||
|
@ -817,7 +831,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_4_check" ${label_data[4].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label4" ${label_data[4].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_name" value="${label_data[4].name}"/>
|
||||
|
@ -829,7 +843,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_5_check" ${label_data[5].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label5" ${label_data[5].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_5_name" value="${label_data[5].name}"/>
|
||||
|
@ -841,7 +855,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_6_check" ${label_data[6].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label6" ${label_data[6].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_6_name" value="${label_data[6].name}"/>
|
||||
|
@ -853,7 +867,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_7_check" ${label_data[7].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label7" ${label_data[7].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_7_name" value="${label_data[7].name}"/>
|
||||
|
@ -865,7 +879,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_8_check" ${label_data[8].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label8" ${label_data[8].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_8_name" value="${label_data[8].name}"/>
|
||||
|
@ -877,7 +891,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_9_check" ${label_data[9].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label9" ${label_data[9].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_9_name" value="${label_data[9].name}"/>
|
||||
|
@ -889,7 +903,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_10_check" ${label_data[10].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label10" ${label_data[10].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_10_name" value="${label_data[10].name}"/>
|
||||
|
@ -901,7 +915,7 @@ function appCard(data) {
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" ${label_data[11].check}>
|
||||
<input class="form-check-input" type="checkbox" name="label11" ${label_data[11].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="${label_data[11].name}"/>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
function dashCard(name, service, id, state, image, external_port, internal_port) {
|
||||
module.exports.dashCard = function dashCard(data) {
|
||||
|
||||
let { name, service, id, state, image, external_port, internal_port } = data;
|
||||
|
||||
//disable controls for a docker container depending on its name
|
||||
let enabled = "";
|
||||
|
@ -100,7 +102,7 @@ function dashCard(name, service, id, state, image, external_port, internal_port)
|
|||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-stamp card-stamp-sm">
|
||||
<img heigh="300px" width="300px" src="https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/png/${service}.png"></img>
|
||||
<img heigh="150px" width="150px" src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/${service}.png"></img>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
|
@ -150,7 +152,7 @@ function dashCard(name, service, id, state, image, external_port, internal_port)
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cardChart" class="chart-sm"></div>
|
||||
<div id="${name}_chart" class="chart-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1062,6 +1064,4 @@ function dashCard(name, service, id, state, image, external_port, internal_port)
|
|||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
module.exports = { dashCard };
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
const { writeFileSync, mkdirSync, appendFileSync } = require("fs");
|
||||
const { exec } = require("child_process");
|
||||
const { writeFileSync, mkdirSync, readFileSync } = require("fs");
|
||||
const { exec, execSync } = require("child_process");
|
||||
const { dashCard } = require('../components/dashCard');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
|
||||
|
||||
|
@ -8,122 +9,147 @@ exports.Install = async function (req, res) {
|
|||
|
||||
if (req.session.role == "admin") {
|
||||
|
||||
console.log(req.body);
|
||||
|
||||
|
||||
let { service_name, name, image, command_check, command, net_mode, restart_policy } = req.body;
|
||||
|
||||
let { port_0_check, port_1_check, port_2_check, port_3_check, port_4_check, port_5_check } = req.body;
|
||||
let { volume_0_check, volume_1_check, volume_2_check, volume_3_check, volume_4_check, volume_5_check } = req.body;
|
||||
let { env_0_check, env_1_check, env_2_check, env_3_check, env_4_check, env_5_check, env_6_check, env_7_check, env_8_check, env_9_check, env_10_check, env_11_check } = req.body;
|
||||
let { label_0_check, label_1_check, label_2_check, label_3_check, label_4_check, label_5_check, label_6_check, label_7_check, label_8_check, label_9_check, label_10_check, label_11_check } = req.body;
|
||||
let { port0, port1, port2, port3, port4, port5 } = req.body;
|
||||
let { volume0, volume1, volume2, volume3, volume4, volume5 } = req.body;
|
||||
let { env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11 } = req.body;
|
||||
let { label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11 } = req.body;
|
||||
|
||||
|
||||
let installCard = dashCard(req.body.name, req.body.service_name, '', 'installing', req.body.image, 0, 0);
|
||||
req.app.locals.install = installCard;
|
||||
if (image.startsWith('https://')){
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
execSync(`curl -o ./appdata/${name}/${name}_stack.yml -L ${image}`);
|
||||
console.log(`Downloaded stackfile: ${image}`);
|
||||
let stackfile = yaml.load(readFileSync(`./appdata/${name}/${name}_stack.yml`, 'utf8'));
|
||||
let services = Object.keys(stackfile.services);
|
||||
|
||||
let compose_file = `version: '3'`;
|
||||
compose_file += `\nservices:`
|
||||
compose_file += `\n ${service_name}:`
|
||||
compose_file += `\n container_name: ${name}`;
|
||||
compose_file += `\n image: ${image}`;
|
||||
for ( let i = 0; i < services.length; i++ ) {
|
||||
try {
|
||||
console.log(stackfile.services[Object.keys(stackfile.services)[i]].environment);
|
||||
} catch { console.log('no env') }
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Command
|
||||
if (command_check == 'on') {
|
||||
compose_file += `\n command: ${command}`
|
||||
}
|
||||
let container_info = {
|
||||
name: name,
|
||||
service: service_name,
|
||||
state: 'installing',
|
||||
image: image,
|
||||
restart_policy: restart_policy
|
||||
}
|
||||
|
||||
|
||||
// Network mode
|
||||
if (net_mode == 'host') {
|
||||
compose_file += `\n network_mode: 'host'`
|
||||
}
|
||||
else if (net_mode != 'host' && net_mode != 'docker') {
|
||||
compose_file += `\n network_mode: '${net_mode}'`
|
||||
}
|
||||
|
||||
// Restart policy
|
||||
if (restart_policy != '') {
|
||||
compose_file += `\n restart: ${restart_policy}`
|
||||
}
|
||||
let installCard = dashCard(container_info);
|
||||
|
||||
// Ports
|
||||
if ((port_0_check == 'on' || port_1_check == 'on' || port_2_check == 'on' || port_3_check == 'on' || port_4_check == 'on' || port_5_check == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n ports:`
|
||||
req.app.locals.install = installCard;
|
||||
|
||||
let compose_file = `version: '3'`;
|
||||
compose_file += `\nservices:`
|
||||
compose_file += `\n ${service_name}:`
|
||||
compose_file += `\n container_name: ${name}`;
|
||||
compose_file += `\n image: ${image}`;
|
||||
|
||||
// Command
|
||||
if (command_check == 'on') {
|
||||
compose_file += `\n command: ${command}`
|
||||
}
|
||||
|
||||
// Network mode
|
||||
if (net_mode == 'host') {
|
||||
compose_file += `\n network_mode: 'host'`
|
||||
}
|
||||
else if (net_mode != 'host' && net_mode != 'docker') {
|
||||
compose_file += `\n network_mode: '${net_mode}'`
|
||||
}
|
||||
|
||||
// Restart policy
|
||||
if (restart_policy != '') {
|
||||
compose_file += `\n restart: ${restart_policy}`
|
||||
}
|
||||
|
||||
// Ports
|
||||
if ((port0 == 'on' || port1 == 'on' || port2 == 'on' || port3 == 'on' || port4 == 'on' || port5 == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n ports:`
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (req.body[`port${i}`] == 'on') {
|
||||
compose_file += `\n - ${req.body[`port_${i}_external`]}:${req.body[`port_${i}_internal`]}/${req.body[`port_${i}_protocol`]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Volumes
|
||||
if (volume0 == 'on' || volume1 == 'on' || volume2 == 'on' || volume3 == 'on' || volume4 == 'on' || volume5 == 'on') {
|
||||
compose_file += `\n volumes:`
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (req.body[`port_${i}_check`] == 'on') {
|
||||
compose_file += `\n - ${req.body[`port_${i}_external`]}:${req.body[`port_${i}_internal`]}/${req.body[`port_${i}_protocol`]}`
|
||||
if (req.body[`volume${i}`] == 'on') {
|
||||
compose_file += `\n - ${req.body[`volume_${i}_bind`]}:${req.body[`volume_${i}_container`]}:${req.body[`volume_${i}_readwrite`]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Volumes
|
||||
if (volume_0_check == 'on' || volume_1_check == 'on' || volume_2_check == 'on' || volume_3_check == 'on' || volume_4_check == 'on' || volume_5_check == 'on') {
|
||||
compose_file += `\n volumes:`
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (req.body[`volume_${i}_check`] == 'on') {
|
||||
compose_file += `\n - ${req.body[`volume_${i}_bind`]}:${req.body[`volume_${i}_container`]}:${req.body[`volume_${i}_readwrite`]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Environment variables
|
||||
if (env_0_check == 'on' || env_1_check == 'on' || env_2_check == 'on' || env_3_check == 'on' || env_4_check == 'on' || env_5_check == 'on' || env_6_check == 'on' || env_7_check == 'on' || env_8_check == 'on' || env_9_check == 'on' || env_10_check == 'on' || env_11_check == 'on') {
|
||||
compose_file += `\n environment:`
|
||||
}
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (req.body[`env_${i}_check`] == 'on') {
|
||||
compose_file += `\n - ${req.body[`env_${i}_name`]}=${req.body[`env_${i}_default`]}`
|
||||
|
||||
// Environment variables
|
||||
if (env0 == 'on' || env1 == 'on' || env2 == 'on' || env3 == 'on' || env4 == 'on' || env5 == 'on' || env6 == 'on' || env7 == 'on' || env8 == 'on' || env9 == 'on' || env10 == 'on' || env11 == 'on') {
|
||||
compose_file += `\n environment:`
|
||||
}
|
||||
}
|
||||
|
||||
// Add labels
|
||||
if (label_0_check == 'on' || label_1_check == 'on' || label_2_check == 'on' || label_3_check == 'on' || label_4_check == 'on' || label_5_check == 'on' || label_6_check == 'on' || label_7_check == 'on' || label_8_check == 'on' || label_9_check == 'on' || label_10_check == 'on' || label_11_check == 'on') {
|
||||
compose_file += `\n labels:`
|
||||
}
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (req.body[`label_${i}_check`] == 'on') {
|
||||
compose_file += `\n - ${req.body[`label_${i}_name`]}=${req.body[`label_${i}_value`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Add privileged mode
|
||||
|
||||
if (req.body.privileged == 'on') {
|
||||
compose_file += `\n privileged: true`
|
||||
}
|
||||
|
||||
|
||||
// Add hardware acceleration to the docker-compose file if one of the environment variables has the label DRINODE
|
||||
if (env_0_check == 'on' || env_1_check == 'on' || env_2_check == 'on' || env_3_check == 'on' || env_4_check == 'on' || env_5_check == 'on' || env_6_check == 'on' || env_7_check == 'on' || env_8_check == 'on' || env_9_check == 'on' || env_10_check == 'on' || env_11_check == 'on') {
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (req.body[`env_${i}_check`] == 'on') {
|
||||
if (req.body[`env_${i}_name`] == 'DRINODE') {
|
||||
compose_file += `\n deploy:`
|
||||
compose_file += `\n resources:`
|
||||
compose_file += `\n reservations:`
|
||||
compose_file += `\n devices:`
|
||||
compose_file += `\n - driver: nvidia`
|
||||
compose_file += `\n count: 1`
|
||||
compose_file += `\n capabilities: [gpu]`
|
||||
if (req.body[`env${i}`] == 'on') {
|
||||
compose_file += `\n - ${req.body[`env_${i}_name`]}=${req.body[`env_${i}_default`]}`
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Add labels
|
||||
if (label0 == 'on' || label1 == 'on' || label2 == 'on' || label3 == 'on' || label4 == 'on' || label5 == 'on' || label6 == 'on' || label7 == 'on' || label8 == 'on' || label9 == 'on' || label10 == 'on' || label11 == 'on') {
|
||||
compose_file += `\n labels:`
|
||||
}
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (req.body[`label${i}`] == 'on') {
|
||||
compose_file += `\n - ${req.body[`label_${i}_name`]}=${req.body[`label_${i}_value`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Add privileged mode
|
||||
|
||||
if (req.body.privileged == 'on') {
|
||||
compose_file += `\n privileged: true`
|
||||
}
|
||||
|
||||
|
||||
// Add hardware acceleration to the docker-compose file if one of the environment variables has the label DRINODE
|
||||
if (env0 == 'on' || env1 == 'on' || env2 == 'on' || env3 == 'on' || env4 == 'on' || env5 == 'on' || env6 == 'on' || env7 == 'on' || env8 == 'on' || env9 == 'on' || env10 == 'on' || env11 == 'on') {
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (req.body[`env${i}`] == 'on') {
|
||||
if (req.body[`env_${i}_name`] == 'DRINODE') {
|
||||
compose_file += `\n deploy:`
|
||||
compose_file += `\n resources:`
|
||||
compose_file += `\n reservations:`
|
||||
compose_file += `\n devices:`
|
||||
compose_file += `\n - driver: nvidia`
|
||||
compose_file += `\n count: 1`
|
||||
compose_file += `\n capabilities: [gpu]`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
writeFileSync(`./appdata/${name}/docker-compose.yml`, compose_file, function (err) { console.log(err) });
|
||||
|
||||
exec(`docker compose -f ./appdata/${name}/docker-compose.yml up -d`, (error, stdout, stderr) => {
|
||||
if (error) { console.error(`error: ${error.message}`); return; }
|
||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
||||
console.log(`stdout:\n${stdout}`);
|
||||
});
|
||||
} catch { console.log('error creating directory or compose file') }
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
writeFileSync(`./appdata/${name}/docker-compose.yml`, compose_file, function (err) { console.log(err) });
|
||||
|
||||
exec(`docker compose -f ./appdata/${name}/docker-compose.yml up -d`, (error, stdout, stderr) => {
|
||||
if (error) { console.error(`error: ${error.message}`); return; }
|
||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
||||
console.log(`stdout:\n${stdout}`);
|
||||
});
|
||||
} catch { console.log('error creating directory or compose file') }
|
||||
|
||||
|
||||
// Redirect to the home page
|
||||
res.redirect("/");
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
const User = require('../database/UserModel');
|
||||
const { appCard } = require('../components/appCard')
|
||||
const { exec, execSync } = require("child_process");
|
||||
const { dashCard } = require('../components/dashCard');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
const { install } = require('../functions/package_manager');
|
||||
|
||||
|
||||
const templates_json = require('../templates.json');
|
||||
let templates = templates_json.templates;
|
||||
|
||||
|
@ -35,6 +42,7 @@ exports.Apps = async function(req, res) {
|
|||
let apps_list = '';
|
||||
for (let i = list_start; i < list_end && i < templates.length; i++) {
|
||||
let app_card = appCard(templates[i]);
|
||||
|
||||
apps_list += app_card;
|
||||
}
|
||||
|
||||
|
@ -59,7 +67,7 @@ exports.Apps = async function(req, res) {
|
|||
|
||||
|
||||
|
||||
exports.processApps = async function(req, res) {
|
||||
exports.searchApps = async function(req, res) {
|
||||
if (req.session.role == "admin") {
|
||||
|
||||
// Get the user.
|
||||
|
@ -126,4 +134,63 @@ exports.processApps = async function(req, res) {
|
|||
// Redirect to the login page
|
||||
res.redirect("/login");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
exports.Install = async function (req, res) {
|
||||
|
||||
if (req.session.role == "admin") {
|
||||
|
||||
install(req.body);
|
||||
|
||||
let container_info = {
|
||||
name: req.body.name,
|
||||
service: req.body.service_name,
|
||||
state: 'installing',
|
||||
image: req.body.image,
|
||||
restart_policy: req.body.restart_policy
|
||||
}
|
||||
|
||||
let installCard = dashCard(container_info);
|
||||
|
||||
req.app.locals.install = installCard;
|
||||
|
||||
|
||||
// Redirect to the home page
|
||||
res.redirect("/");
|
||||
} else {
|
||||
// Redirect to the login page
|
||||
res.redirect("/login");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
exports.Uninstall = async function (req, res) {
|
||||
|
||||
if (req.session.role == "admin") {
|
||||
|
||||
|
||||
if (req.body.confirm == 'Yes') {
|
||||
exec(`docker compose -f ./appdata/${req.body.service_name}/docker-compose.yml down`, (error, stdout, stderr) => {
|
||||
if (error) { console.error(`error: ${error.message}`); return; }
|
||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
||||
console.log(`stdout:\n${stdout}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Redirect to the home page
|
||||
res.redirect("/");
|
||||
} else {
|
||||
// Redirect to the login page
|
||||
res.redirect("/login");
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
const User = require('../database/UserModel.js');
|
||||
const Server = require('../database/ServerSettings.js');
|
||||
|
||||
exports.Settings = async function(req, res) {
|
||||
if (req.session.role == "admin") {
|
||||
|
|
42
database/ServerSettings.js
Normal file
42
database/ServerSettings.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
const { Sequelize, DataTypes } = require('sequelize');
|
||||
|
||||
const sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: 'database/db.sqlite',
|
||||
logging: false
|
||||
});
|
||||
|
||||
|
||||
const Server = sequelize.define('Server', {
|
||||
// Model attributes are defined here
|
||||
timezone: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
hwa: {
|
||||
type: DataTypes.STRING
|
||||
// allowNull defaults to true
|
||||
},
|
||||
media: {
|
||||
type: DataTypes.STRING
|
||||
// allowNull defaults to true
|
||||
},
|
||||
pgid: {
|
||||
type: DataTypes.STRING
|
||||
// allowNull defaults to true
|
||||
},
|
||||
puid: {
|
||||
type: DataTypes.STRING
|
||||
// allowNull defaults to true
|
||||
}
|
||||
});
|
||||
|
||||
async function syncModel() {
|
||||
await sequelize.sync();
|
||||
console.log('Server model synced');
|
||||
}
|
||||
|
||||
syncModel();
|
||||
|
||||
|
||||
module.exports = Server;
|
161
functions/package_manager.js
Normal file
161
functions/package_manager.js
Normal file
|
@ -0,0 +1,161 @@
|
|||
const { writeFileSync, mkdirSync, readFileSync } = require("fs");
|
||||
const { exec, execSync } = require("child_process");
|
||||
const { dashCard } = require('../components/dashCard');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
|
||||
|
||||
module.exports.install = async function (data) {
|
||||
|
||||
|
||||
let { service_name, name, image, command_check, command, net_mode, restart_policy } = data;
|
||||
let { port0, port1, port2, port3, port4, port5 } = data;
|
||||
let { volume0, volume1, volume2, volume3, volume4, volume5 } = data;
|
||||
let { env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11 } = data;
|
||||
let { label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11 } = data;
|
||||
|
||||
if (image.startsWith('https://')){
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
execSync(`curl -o ./appdata/${name}/${name}_stack.yml -L ${image}`);
|
||||
console.log(`Downloaded stackfile: ${image}`);
|
||||
let stackfile = yaml.load(readFileSync(`./appdata/${name}/${name}_stack.yml`, 'utf8'));
|
||||
let services = Object.keys(stackfile.services);
|
||||
|
||||
for ( let i = 0; i < services.length; i++ ) {
|
||||
try {
|
||||
console.log(stackfile.services[Object.keys(stackfile.services)[i]].environment);
|
||||
} catch { console.log('no env') }
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
let compose_file = `version: '3'`;
|
||||
compose_file += `\nservices:`
|
||||
compose_file += `\n ${service_name}:`
|
||||
compose_file += `\n container_name: ${name}`;
|
||||
compose_file += `\n image: ${image}`;
|
||||
|
||||
// Command
|
||||
if (command_check == 'on') {
|
||||
compose_file += `\n command: ${command}`
|
||||
}
|
||||
|
||||
// Network mode
|
||||
if (net_mode == 'host') {
|
||||
compose_file += `\n network_mode: 'host'`
|
||||
}
|
||||
else if (net_mode != 'host' && net_mode != 'docker') {
|
||||
compose_file += `\n network_mode: '${net_mode}'`
|
||||
}
|
||||
|
||||
// Restart policy
|
||||
if (restart_policy != '') {
|
||||
compose_file += `\n restart: ${restart_policy}`
|
||||
}
|
||||
|
||||
// Ports
|
||||
if ((port0 == 'on' || port1 == 'on' || port2 == 'on' || port3 == 'on' || port4 == 'on' || port5 == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n ports:`
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (data[`port${i}`] == 'on') {
|
||||
compose_file += `\n - ${data[`port_${i}_external`]}:${data[`port_${i}_internal`]}/${data[`port_${i}_protocol`]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Volumes
|
||||
if (volume0 == 'on' || volume1 == 'on' || volume2 == 'on' || volume3 == 'on' || volume4 == 'on' || volume5 == 'on') {
|
||||
compose_file += `\n volumes:`
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (data[`volume${i}`] == 'on') {
|
||||
compose_file += `\n - ${data[`volume_${i}_bind`]}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Environment variables
|
||||
if (env0 == 'on' || env1 == 'on' || env2 == 'on' || env3 == 'on' || env4 == 'on' || env5 == 'on' || env6 == 'on' || env7 == 'on' || env8 == 'on' || env9 == 'on' || env10 == 'on' || env11 == 'on') {
|
||||
compose_file += `\n environment:`
|
||||
}
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (data[`env${i}`] == 'on') {
|
||||
compose_file += `\n - ${data[`env_${i}_name`]}=${data[`env_${i}_default`]}`
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Add labels
|
||||
if (label0 == 'on' || label1 == 'on' || label2 == 'on' || label3 == 'on' || label4 == 'on' || label5 == 'on' || label6 == 'on' || label7 == 'on' || label8 == 'on' || label9 == 'on' || label10 == 'on' || label11 == 'on') {
|
||||
compose_file += `\n labels:`
|
||||
}
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (data[`label${i}`] == 'on') {
|
||||
compose_file += `\n - ${data[`label_${i}_name`]}=${data[`label_${i}_value`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Add privileged mode
|
||||
|
||||
if (data.privileged == 'on') {
|
||||
compose_file += `\n privileged: true`
|
||||
}
|
||||
|
||||
|
||||
// Add hardware acceleration to the docker-compose file if one of the environment variables has the label DRINODE
|
||||
if (env0 == 'on' || env1 == 'on' || env2 == 'on' || env3 == 'on' || env4 == 'on' || env5 == 'on' || env6 == 'on' || env7 == 'on' || env8 == 'on' || env9 == 'on' || env10 == 'on' || env11 == 'on') {
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (data[`env${i}`] == 'on') {
|
||||
if (data[`env_${i}_name`] == 'DRINODE') {
|
||||
compose_file += `\n deploy:`
|
||||
compose_file += `\n resources:`
|
||||
compose_file += `\n reservations:`
|
||||
compose_file += `\n devices:`
|
||||
compose_file += `\n - driver: nvidia`
|
||||
compose_file += `\n count: 1`
|
||||
compose_file += `\n capabilities: [gpu]`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
writeFileSync(`./appdata/${name}/docker-compose.yml`, compose_file, function (err) { console.log(err) });
|
||||
|
||||
exec(`docker compose -f ./appdata/${name}/docker-compose.yml up -d`, (error, stdout, stderr) => {
|
||||
if (error) { console.error(`error: ${error.message}`); return; }
|
||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
||||
console.log(`stdout:\n${stdout}`);
|
||||
});
|
||||
} catch { console.log('error creating directory or compose file') }
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports.uninstall = async function (data) {
|
||||
|
||||
if (req.session.role == "admin") {
|
||||
|
||||
|
||||
if (data.confirm == 'Yes') {
|
||||
exec(`docker compose -f ./appdata/${data.service_name}/docker-compose.yml down`, (error, stdout, stderr) => {
|
||||
if (error) { console.error(`error: ${error.message}`); return; }
|
||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
||||
console.log(`stdout:\n${stdout}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Redirect to the home page
|
||||
res.redirect("/");
|
||||
} else {
|
||||
// Redirect to the login page
|
||||
res.redirect("/login");
|
||||
}
|
||||
}
|
150
functions/system_information.js
Normal file
150
functions/system_information.js
Normal file
|
@ -0,0 +1,150 @@
|
|||
const { currentLoad, mem, networkStats, fsSize, dockerContainerStats } = require('systeminformation');
|
||||
var Docker = require('dockerode');
|
||||
var docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
||||
const { dashCard } = require('../components/dashCard');
|
||||
|
||||
|
||||
|
||||
module.exports.serverStats = async function () {
|
||||
const cpuUsage = await currentLoad();
|
||||
const ramUsage = await mem();
|
||||
const netUsage = await networkStats();
|
||||
const diskUsage = await fsSize();
|
||||
|
||||
const info = {
|
||||
cpu: Math.round(cpuUsage.currentLoad),
|
||||
ram: Math.round((ramUsage.active / ramUsage.total) * 100),
|
||||
tx: netUsage[0].tx_bytes,
|
||||
rx: netUsage[0].rx_bytes,
|
||||
disk: diskUsage[0].use,
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports.containerList = async function () {
|
||||
let card_list = '';
|
||||
|
||||
const data = await docker.listContainers({ all: true });
|
||||
for (const container of data) {
|
||||
let imageVersion = container.Image.split('/');
|
||||
let service = imageVersion[imageVersion.length - 1].split(':')[0];
|
||||
|
||||
let containerId = docker.getContainer(container.Id);
|
||||
let containerInfo = await containerId.inspect();
|
||||
|
||||
let open_ports = [];
|
||||
let external_port = 0;
|
||||
let internal_port = 0;
|
||||
|
||||
for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) {
|
||||
open_ports.push(`${value[0].HostPort}`);
|
||||
external_port = value[0].HostPort;
|
||||
internal_port = key;
|
||||
|
||||
if ((external_port == undefined) || (internal_port == undefined)) {
|
||||
external_port = 0;
|
||||
internal_port = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let volumes = [];
|
||||
for (const [key, value] of Object.entries(containerInfo.Mounts)) {
|
||||
volumes.push(`${value.Source}: ${value.Destination}: ${value.RW}`);
|
||||
}
|
||||
|
||||
let environment_variables = [];
|
||||
for (const [key, value] of Object.entries(containerInfo.Config.Env)) {
|
||||
environment_variables.push(`${key}: ${value}`);
|
||||
}
|
||||
|
||||
let labels = [];
|
||||
for (const [key, value] of Object.entries(containerInfo.Config.Labels)) {
|
||||
labels.push(`${key}: ${value}`);
|
||||
}
|
||||
|
||||
|
||||
let container_info = {
|
||||
name: container.Names[0].slice(1),
|
||||
service: service,
|
||||
id: container.Id,
|
||||
state: container.State,
|
||||
image: container.Image,
|
||||
external_port: external_port,
|
||||
internal_port: internal_port
|
||||
}
|
||||
|
||||
let dockerCard = dashCard(container_info);
|
||||
|
||||
card_list += dockerCard;
|
||||
|
||||
}
|
||||
|
||||
return card_list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports.containerStats = async function () {
|
||||
|
||||
let container_stats = [];
|
||||
|
||||
const data = await docker.listContainers({ all: true });
|
||||
|
||||
for (const container of data) {
|
||||
|
||||
const stats = await dockerContainerStats(container.Id);
|
||||
let container_stat = {
|
||||
name: container.Names[0].slice(1),
|
||||
cpu: Math.round(stats[0].cpuPercent),
|
||||
ram: Math.round(stats[0].memPercent)
|
||||
}
|
||||
|
||||
//push stats to an array
|
||||
container_stats.push(container_stat);
|
||||
}
|
||||
return container_stats;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports.containerAction = async function (data) {
|
||||
|
||||
let { user, role, action, container, state } = data;
|
||||
|
||||
console.log(`${user} wants to: ${action} ${container}`);
|
||||
|
||||
if (role == 'admin') {
|
||||
var containerName = docker.getContainer(container);
|
||||
|
||||
if ((action == 'start') && (state == 'stopped')) {
|
||||
containerName.start();
|
||||
} else if ((action == 'start') && (state == 'paused')) {
|
||||
containerName.unpause();
|
||||
} else if ((action == 'stop') && (state != 'stopped')) {
|
||||
containerName.stop();
|
||||
} else if ((action == 'pause') && (state == 'running')) {
|
||||
containerName.pause();
|
||||
} else if ((action == 'pause') && (state == 'paused')) {
|
||||
containerName.unpause();
|
||||
} else if (action == 'restart') {
|
||||
containerName.restart();
|
||||
}
|
||||
} else {
|
||||
console.log('User is not an admin');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
150
functions/systeminformation.js
Normal file
150
functions/systeminformation.js
Normal file
|
@ -0,0 +1,150 @@
|
|||
const { currentLoad, mem, networkStats, fsSize, dockerContainerStats } = require('systeminformation');
|
||||
var Docker = require('dockerode');
|
||||
var docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
||||
const { dashCard } = require('../components/dashCard');
|
||||
|
||||
|
||||
|
||||
module.exports.serverStats = async function () {
|
||||
const cpuUsage = await currentLoad();
|
||||
const ramUsage = await mem();
|
||||
const netUsage = await networkStats();
|
||||
const diskUsage = await fsSize();
|
||||
|
||||
const info = {
|
||||
cpu: Math.round(cpuUsage.currentLoad),
|
||||
ram: Math.round((ramUsage.active / ramUsage.total) * 100),
|
||||
tx: netUsage[0].tx_bytes,
|
||||
rx: netUsage[0].rx_bytes,
|
||||
disk: diskUsage[0].use,
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports.containerList = async function () {
|
||||
let card_list = '';
|
||||
|
||||
const data = await docker.listContainers({ all: true });
|
||||
for (const container of data) {
|
||||
let imageVersion = container.Image.split('/');
|
||||
let service = imageVersion[imageVersion.length - 1].split(':')[0];
|
||||
|
||||
let containerId = docker.getContainer(container.Id);
|
||||
let containerInfo = await containerId.inspect();
|
||||
|
||||
let open_ports = [];
|
||||
let external_port = 0;
|
||||
let internal_port = 0;
|
||||
|
||||
for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) {
|
||||
open_ports.push(`${value[0].HostPort}`);
|
||||
external_port = value[0].HostPort;
|
||||
internal_port = key;
|
||||
|
||||
if ((external_port == undefined) || (internal_port == undefined)) {
|
||||
external_port = 0;
|
||||
internal_port = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let volumes = [];
|
||||
for (const [key, value] of Object.entries(containerInfo.Mounts)) {
|
||||
volumes.push(`${value.Source}: ${value.Destination}: ${value.RW}`);
|
||||
}
|
||||
|
||||
let environment_variables = [];
|
||||
for (const [key, value] of Object.entries(containerInfo.Config.Env)) {
|
||||
environment_variables.push(`${key}: ${value}`);
|
||||
}
|
||||
|
||||
let labels = [];
|
||||
for (const [key, value] of Object.entries(containerInfo.Config.Labels)) {
|
||||
labels.push(`${key}: ${value}`);
|
||||
}
|
||||
|
||||
|
||||
let container_info = {
|
||||
name: container.Names[0].slice(1),
|
||||
service: service,
|
||||
id: container.Id,
|
||||
state: container.State,
|
||||
image: container.Image,
|
||||
external_port: external_port,
|
||||
internal_port: internal_port
|
||||
}
|
||||
|
||||
let dockerCard = dashCard(container_info);
|
||||
|
||||
card_list += dockerCard;
|
||||
|
||||
}
|
||||
|
||||
return card_list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports.containerStats = async function () {
|
||||
|
||||
let container_stats = [];
|
||||
|
||||
const data = await docker.listContainers({ all: true });
|
||||
|
||||
for (const container of data) {
|
||||
|
||||
const stats = await dockerContainerStats(container.Id);
|
||||
let container_stat = {
|
||||
name: container.Names[0].slice(1),
|
||||
cpu: Math.round(stats[0].cpuPercent),
|
||||
ram: Math.round(stats[0].memPercent)
|
||||
}
|
||||
|
||||
//push stats to an array
|
||||
container_stats.push(container_stat);
|
||||
}
|
||||
return container_stats;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports.containerAction = async function (data) {
|
||||
|
||||
let { user, role, action, container, state } = data;
|
||||
|
||||
console.log(`${user} wants to: ${action} ${container}`);
|
||||
|
||||
if (role == 'admin') {
|
||||
var containerName = docker.getContainer(container);
|
||||
|
||||
if ((action == 'start') && (state == 'stopped')) {
|
||||
containerName.start();
|
||||
} else if ((action == 'start') && (state == 'paused')) {
|
||||
containerName.unpause();
|
||||
} else if ((action == 'stop') && (state != 'stopped')) {
|
||||
containerName.stop();
|
||||
} else if ((action == 'pause') && (state == 'running')) {
|
||||
containerName.pause();
|
||||
} else if ((action == 'pause') && (state == 'paused')) {
|
||||
containerName.unpause();
|
||||
} else if (action == 'restart') {
|
||||
containerName.restart();
|
||||
}
|
||||
} else {
|
||||
console.log('User is not an admin');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -17,7 +17,8 @@
|
|||
"sequelize": "^6.32.1",
|
||||
"socket.io": "^4.6.1",
|
||||
"sqlite3": "^5.1.6",
|
||||
"systeminformation": "^5.17.12"
|
||||
"systeminformation": "^5.17.12",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
|
|
|
@ -22,8 +22,13 @@ const diskBar = document.getElementById('disk-bar');
|
|||
|
||||
const dockerCards = document.getElementById('cards');
|
||||
|
||||
// create
|
||||
|
||||
//Update usage bars
|
||||
socket.on('metrics', ({ cpu, ram, tx, rx, disk}) => {
|
||||
socket.on('metrics', (data) => {
|
||||
|
||||
let {cpu, ram, tx, rx, disk} = data;
|
||||
|
||||
cpuText.innerHTML = `<span>CPU ${cpu} %</span>`;
|
||||
cpuBar.innerHTML = `<span style="width: ${cpu}%"><span></span></span>`;
|
||||
ramText.innerHTML = `<span>RAM ${ram} %</span>`;
|
||||
|
@ -32,8 +37,17 @@ socket.on('metrics', ({ cpu, ram, tx, rx, disk}) => {
|
|||
diskBar.innerHTML = `<span style="width: ${disk}%"><span></span></span>`;
|
||||
});
|
||||
|
||||
function drawCharts() {
|
||||
var elements = document.querySelectorAll("#cardChart");
|
||||
function drawCharts(name, cpu_array, ram_array) {
|
||||
var elements = document.querySelectorAll(`${name}`);
|
||||
|
||||
if (cpu_array == undefined) {
|
||||
cpu_array = [37, 35, 44, 28, 36, 24, 65, 31, 37, 39, 62, 51, 35, 41, 35, 27, 93, 53, 61, 27, 54, 43, 4, 46, 39, 62, 51, 35, 41, 67];
|
||||
}
|
||||
|
||||
if (ram_array == undefined) {
|
||||
ram_array = [93, 54, 51, 24, 35, 35, 31, 67, 19, 43, 28, 36, 62, 61, 27, 39, 35, 41, 27, 35, 51, 46, 62, 37, 44, 53, 41, 65, 39, 37];
|
||||
}
|
||||
|
||||
|
||||
Array.from(elements).forEach(function(element) {
|
||||
if (window.ApexCharts) {
|
||||
|
@ -60,10 +74,10 @@ function drawCharts() {
|
|||
},
|
||||
series: [{
|
||||
name: "CPU",
|
||||
data: [37, 35, 44, 28, 36, 24, 65, 31, 37, 39, 62, 51, 35, 41, 35, 27, 93, 53, 61, 27, 54, 43, 4, 46, 39, 62, 51, 35, 41, 67]
|
||||
data: cpu_array
|
||||
}, {
|
||||
name: "RAM",
|
||||
data: [93, 54, 51, 24, 35, 35, 31, 67, 19, 43, 28, 36, 62, 61, 27, 39, 35, 41, 27, 35, 51, 46, 62, 37, 44, 53, 41, 65, 39, 37]
|
||||
data: ram_array
|
||||
}],
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
|
@ -97,14 +111,8 @@ function drawCharts() {
|
|||
});
|
||||
}
|
||||
|
||||
// container button actions
|
||||
function buttonAction(button) {
|
||||
|
||||
// if the button name is 'CaddyProxyManager' and the value is 'install' grab the div with the id of 'sites' and remove d-none class. also add the d-none class to the div with the id of 'CaddyInstallCard'
|
||||
if (button.name == 'CaddyProxyManager' && button.value == 'install') {
|
||||
document.getElementById('sites').classList.remove('d-none');
|
||||
document.getElementById('CaddyInstallCard').classList.add('d-none');
|
||||
}
|
||||
|
||||
socket.emit('clicked', {container: button.name, state: button.id, action: button.value});
|
||||
}
|
||||
|
||||
|
@ -117,10 +125,45 @@ socket.on('cards', (data) => {
|
|||
});
|
||||
|
||||
dockerCards.insertAdjacentHTML("afterend", data);
|
||||
drawCharts();
|
||||
|
||||
});
|
||||
|
||||
|
||||
socket.on('container_stats', (data) => {
|
||||
|
||||
let {name, cpu, ram} = data;
|
||||
|
||||
// get cpu and ram array of the container from local storage
|
||||
var cpu_array = JSON.parse(localStorage.getItem(`${name}_cpu`));
|
||||
var ram_array = JSON.parse(localStorage.getItem(`${name}_ram`));
|
||||
console.log(`#1: ${name} cpu: ${cpu_array} ram: ${ram_array}`);
|
||||
|
||||
// if the cpu and ram arrays are null, create both arrays with 30 values of 0
|
||||
if (cpu_array == null) { cpu_array = Array(30).fill(0); }
|
||||
if (ram_array == null) { ram_array = Array(30).fill(0); }
|
||||
console.log(`#2: ${name} cpu: ${cpu_array} ram: ${ram_array}`);
|
||||
|
||||
// add the new cpu and ram values to the arrays, but limit the array to 30 values
|
||||
cpu_array.push(cpu);
|
||||
ram_array.push(ram);
|
||||
console.log(`#3: ${name} cpu: ${cpu_array} ram: ${ram_array}`);
|
||||
|
||||
cpu_array = cpu_array.slice(-30);
|
||||
ram_array = ram_array.slice(-30);
|
||||
console.log(`#4: ${name} cpu: ${cpu_array} ram: ${ram_array}`);
|
||||
|
||||
// save the arrays to local storage
|
||||
localStorage.setItem(`${name}_cpu`, JSON.stringify(cpu_array));
|
||||
localStorage.setItem(`${name}_ram`, JSON.stringify(ram_array));
|
||||
|
||||
// replace the old chart with the new one without breaking the surrounding elements
|
||||
let chart = document.getElementById(`${name}_chart`);
|
||||
let newChart = document.createElement('div');
|
||||
newChart.id = `${name}_chart`;
|
||||
chart.parentNode.replaceChild(newChart, chart);
|
||||
drawCharts(`#${name}_chart`, cpu_array, ram_array);
|
||||
});
|
||||
|
||||
socket.on('install', (data) => {
|
||||
|
||||
console.log('added install card');
|
||||
|
|
|
@ -4,15 +4,14 @@ const router = express.Router();
|
|||
const { Dashboard } = require("../controllers/dashboard");
|
||||
|
||||
const { AddSite, RemoveSite, RefreshSites, DisableSite, EnableSite } = require("../controllers/site_actions");
|
||||
const { Install, Uninstall } = require("../controllers/app_actions");
|
||||
|
||||
const {Apps, processApps} = require("../controllers/apps");
|
||||
const { Apps, searchApps, Install, Uninstall } = require("../controllers/apps");
|
||||
const { Users } = require("../controllers/users");
|
||||
const {Account} = require("../controllers/account");
|
||||
const {Settings} = require("../controllers/settings");
|
||||
const {Logout} = require("../controllers/logout");
|
||||
const {Login, processLogin} = require("../controllers/login");
|
||||
const {Register, processRegister} = require("../controllers/register");
|
||||
const { Account } = require("../controllers/account");
|
||||
const { Settings } = require("../controllers/settings");
|
||||
const { Logout } = require("../controllers/logout");
|
||||
const { Login, processLogin } = require("../controllers/login");
|
||||
const { Register, processRegister } = require("../controllers/register");
|
||||
|
||||
|
||||
|
||||
|
@ -31,7 +30,7 @@ router.post("/enablesite", EnableSite)
|
|||
router.get("/users", Users);
|
||||
|
||||
router.get("/apps", Apps);
|
||||
router.post("/apps", processApps);
|
||||
router.post("/apps", searchApps);
|
||||
|
||||
router.get("/settings", Settings);
|
||||
router.get("/account", Account);
|
||||
|
|
|
@ -337,7 +337,7 @@
|
|||
"image": "ghcr.io/linuxserver/prowlarr:develop",
|
||||
"categories": [
|
||||
"Downloaders",
|
||||
"Tools"
|
||||
"Arr"
|
||||
],
|
||||
"ports": [
|
||||
"9696/tcp"
|
||||
|
@ -1144,7 +1144,7 @@
|
|||
},
|
||||
{
|
||||
"categories": [
|
||||
"Multimedia"
|
||||
"Media"
|
||||
],
|
||||
"description": "Airsonic is a free, web-based media streamer, providing ubiqutious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.",
|
||||
"env": [
|
||||
|
@ -1371,7 +1371,7 @@
|
|||
"description": "Audacity is an easy-to-use, multi-track audio editor and recorder. Developed by a group of volunteers as open source. (https://www.audacityteam.org/)",
|
||||
"platform": "linux",
|
||||
"categories": [
|
||||
"Multimedia"
|
||||
"Media"
|
||||
],
|
||||
"logo": "https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/audacity-logo.png",
|
||||
"image": "linuxserver/audacity:latest",
|
||||
|
@ -2161,7 +2161,7 @@
|
|||
"note": "",
|
||||
"description": "Kasm workspaces is a docker container streaming platform for delivering browser-based access to desktops, applications, and web services. Kasm uses devops-enabled Containerized Desktop Infrastructure (CDI) to create on-demand, disposable, docker containers that are accessible via web browser. Example use-cases include Remote Browser Isolation (RBI), Data Loss Prevention (DLP), Desktop as a Service (DaaS), Secure Remote Access Services (RAS), and Open Source Intelligence (OSINT) collections. The rendering of the graphical-based containers is powered by the open-source project [KasmVNC](https://www.kasmweb.com/kasmvnc.html?utm_campaign=LinuxServer&utm_source=kasmvnc).",
|
||||
"platform": "linux",
|
||||
"logo": "https://kasm-ci.s3.amazonaws.com/kasm_wide.png",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/kasm.png",
|
||||
"image": "linuxserver/kasm:latest",
|
||||
"privileged": true,
|
||||
"env": [
|
||||
|
@ -2196,20 +2196,20 @@
|
|||
],
|
||||
"volumes": [
|
||||
{
|
||||
"container": "/opt",
|
||||
"bind": "/home/docker/kasm/opt"
|
||||
"bind": "/home/docker/kasm/opt",
|
||||
"container": "/opt"
|
||||
},
|
||||
{
|
||||
"container": "/profiles",
|
||||
"bind": "/home/docker/kasm/profiles"
|
||||
"bind": "/home/docker/kasm/profiles",
|
||||
"container": "/profiles"
|
||||
},
|
||||
{
|
||||
"container": "/dev/input",
|
||||
"bind": "/dev/input"
|
||||
"bind": "/dev/input",
|
||||
"container": "/dev/input"
|
||||
},
|
||||
{
|
||||
"container": "/run/udev/data",
|
||||
"bind": "/run/udev/data"
|
||||
"bind": "/run/udev/data",
|
||||
"container": "/run/udev/data"
|
||||
}
|
||||
],
|
||||
"restart_policy": "unless-stopped"
|
||||
|
@ -2225,7 +2225,7 @@
|
|||
"Tools"
|
||||
],
|
||||
"platform": "linux",
|
||||
"logo": "https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/libreoffice-logo.png",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/libreoffice.png",
|
||||
"image": "linuxserver/libreoffice:latest",
|
||||
"env": [
|
||||
{
|
||||
|
@ -2269,7 +2269,7 @@
|
|||
"Communication"
|
||||
],
|
||||
"platform": "linux",
|
||||
"logo": "https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/mastodon-banner.png",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/mastodon.png",
|
||||
"image": "linuxserver/mastodon:latest",
|
||||
"env": [
|
||||
{
|
||||
|
@ -2511,7 +2511,7 @@
|
|||
"Tools"
|
||||
],
|
||||
"platform": "linux",
|
||||
"logo": "https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/phpmyadmin-logo.png",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/phpmyadmin.png",
|
||||
"image": "linuxserver/phpmyadmin:latest",
|
||||
"env": [
|
||||
{
|
||||
|
@ -2561,9 +2561,9 @@
|
|||
"title": "Pidgin",
|
||||
"name": "pidgin",
|
||||
"note": "",
|
||||
"description": "Pidgin (https://pidgin.im/) is a chat program which lets you log into accounts on multiple chat networks simultaneously. This means that you can be chatting with friends on XMPP and sitting in an IRC channel at the same time.",
|
||||
"description": "Pidgin is a chat program which lets you log into accounts on multiple chat networks simultaneously. This means that you can be chatting with friends on XMPP and sitting in an IRC channel at the same time.",
|
||||
"platform": "linux",
|
||||
"logo": "https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/pidgin-logo.png",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/pidgin.png",
|
||||
"image": "linuxserver/pidgin:latest",
|
||||
"env": [
|
||||
{
|
||||
|
@ -2591,8 +2591,8 @@
|
|||
],
|
||||
"volumes": [
|
||||
{
|
||||
"container": "/config",
|
||||
"bind": "/home/docker/pidgin/config"
|
||||
"bind": "/home/docker/pidgin",
|
||||
"container": "/config"
|
||||
}
|
||||
],
|
||||
"restart_policy": "unless-stopped"
|
||||
|
@ -2602,9 +2602,9 @@
|
|||
"title": "Remmina",
|
||||
"name": "remmina",
|
||||
"note": "",
|
||||
"description": "[Remmina](https://remmina.org/) is a remote desktop client written in GTK, aiming to be useful for system administrators and travellers, who need to work with lots of remote computers in front of either large or tiny screens. Remmina supports multiple network protocols, in an integrated and consistent user interface. Currently RDP, VNC, SPICE, NX, XDMCP, SSH and EXEC are supported.",
|
||||
"description": "Remmina is a remote desktop client written in GTK, aiming to be useful for system administrators and travellers, who need to work with lots of remote computers in front of either large or tiny screens. Remmina supports multiple network protocols, in an integrated and consistent user interface. Currently RDP, VNC, SPICE, NX, XDMCP, SSH and EXEC are supported.",
|
||||
"platform": "linux",
|
||||
"logo": "https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/remmina-icon.png",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/remmina.png",
|
||||
"image": "linuxserver/remmina:latest",
|
||||
"env": [
|
||||
{
|
||||
|
@ -3016,7 +3016,7 @@
|
|||
"name": "PORT"
|
||||
}
|
||||
],
|
||||
"logo": "https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/png/authentik.png",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/authentik.png",
|
||||
"name": "authentik",
|
||||
"platform": "linux",
|
||||
"repository": {
|
||||
|
@ -3063,8 +3063,8 @@
|
|||
"categories": [
|
||||
"Communication"
|
||||
],
|
||||
"description": "Rocket.Chat Server",
|
||||
"logo": "https://raw.githubusercontent.com/portapps/rocketchat-portable/master/res/papp.png",
|
||||
"description": "Rocket.Chat is an open-source fully customizable communications platform developed in JavaScript for organizations with high standards of data protection.",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/rocketchat.png",
|
||||
"note": "Rocket.Chat Server Container",
|
||||
"platform": "linux",
|
||||
"repository": {
|
||||
|
@ -3720,8 +3720,8 @@
|
|||
"Tools"
|
||||
],
|
||||
"description": "ClamAV is an open source antivirus engine for detecting trojans, viruses, malware & other malicious threats.",
|
||||
"image": "mkodockx/docker-clamav:alpine",
|
||||
"logo": "http://www.clamav.net/assets/clamav-trademark.png",
|
||||
"image": "clamav/clamav",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/clamav.png",
|
||||
"name": "clamav",
|
||||
"platform": "linux",
|
||||
"ports": [
|
||||
|
@ -3733,19 +3733,15 @@
|
|||
"volumes": [
|
||||
{
|
||||
"bind": "/etc/timezone",
|
||||
"container": "/etc/timezone"
|
||||
},
|
||||
{
|
||||
"bind": "/etc/localtime",
|
||||
"container": "/etc/localtime"
|
||||
},
|
||||
{
|
||||
"bind": "/home/docker/clamav/config",
|
||||
"container": "/etc/clamav"
|
||||
"container": "/etc/timezone:ro"
|
||||
},
|
||||
{
|
||||
"bind": "/home/docker/clamav/virus_definitions",
|
||||
"container": "/var/lib/clamav"
|
||||
},
|
||||
{
|
||||
"bind": "/home/docker",
|
||||
"container": "/scandir"
|
||||
}
|
||||
],
|
||||
"note": ""
|
||||
|
@ -4935,7 +4931,7 @@
|
|||
{
|
||||
"categories": [
|
||||
"Multimedia",
|
||||
"AI"
|
||||
"Productivity"
|
||||
],
|
||||
"description": "PhotoPrism is an AI-powered app for browsing, organizing & sharing your photo collection. It makes use of the latest technologies to tag and find pictures automatically without getting in your way.| Copy as Custom stack and EDIT environment variables.",
|
||||
"logo": "https://photoprism.app/static/img/logo.svg",
|
||||
|
@ -5721,7 +5717,7 @@
|
|||
"CMS"
|
||||
],
|
||||
"platform": "linux",
|
||||
"logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/joomla.png",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/joomla.png",
|
||||
"image": "joomla:latest",
|
||||
"env": [
|
||||
{
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
<div class="navbar-nav flex-row order-md-last">
|
||||
<div class="nav-item d-none d-md-flex me-3">
|
||||
<div class="btn-list">
|
||||
<a href="#" class="btn text-white">
|
||||
<a href="#" class="btn text-black">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/lock -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-lock" 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 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6z"></path> <path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path> <path d="M8 11v-4a4 4 0 1 1 8 0v4"></path> </svg>
|
||||
VPN
|
||||
|
@ -69,7 +69,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-shield" 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 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"></path> </svg>
|
||||
Firewall
|
||||
</a>
|
||||
<a href="#" class="btn text-white">
|
||||
<a href="#" class="btn text-black">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/shield -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-screen-share" 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 12v3a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1v-10a1 1 0 0 1 1 -1h9"></path> <path d="M7 20l10 0"></path> <path d="M9 16l0 4"></path> <path d="M15 16l0 4"></path> <path d="M17 4h4v4"></path> <path d="M16 9l5 -5"></path> </svg>
|
||||
VNC
|
||||
|
|
Loading…
Add table
Reference in a new issue