The rewrite. v0.20
1
.github/FUNDING.yml
vendored
|
@ -1,2 +1 @@
|
||||||
github: [lllllllillllllillll]
|
|
||||||
patreon: DweebUI
|
patreon: DweebUI
|
2
.github/dependabot.yml
vendored
|
@ -15,6 +15,6 @@ updates:
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "weekly"
|
||||||
labels:
|
labels:
|
||||||
- "🤖 Dependencies"
|
- "🤖 Dependencies"
|
||||||
|
|
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules
|
||||||
|
database/database.sqlite
|
||||||
|
test
|
||||||
|
.dockerignore
|
18
CHANGELOG.md
|
@ -1,4 +1,20 @@
|
||||||
## v0.09 (dev)
|
## v0.20 (dev) - The rewrite. Jumping all the way to v0.20.
|
||||||
|
* Changed to ES6 imports.
|
||||||
|
* Cleaned up file structure and code layout.
|
||||||
|
* Updated DweebUI logo.
|
||||||
|
* Visual tweaks to login and registration pages.
|
||||||
|
* Added .gitignore and .dockerignore files.
|
||||||
|
* Syslogs - View logs for sign-in and registration attempts. :new:
|
||||||
|
* Docker socket now uses default connection.
|
||||||
|
* Updated Users page displays 'inactive' if no sign-ins within 30 days.
|
||||||
|
* Dashboard updates now triggered by Docker events.
|
||||||
|
* Massive reduction in the amount of HTML, CSS, and JS on client side.
|
||||||
|
* Container graphs are significantly more efficent and no longer use localStorage.
|
||||||
|
* Made dark mode the default theme.
|
||||||
|
* Created intervals to allow application to idle or scale bettery with more users.
|
||||||
|
* Pages for images, volumes, and networks (non-functional at the moment). :new:
|
||||||
|
|
||||||
|
## <del>v0.09 (dev)</del> dead. (It had so many problems that I essentially rewrote everything)
|
||||||
* Added authentication middleware to router.
|
* Added authentication middleware to router.
|
||||||
* Added gzip compression.
|
* Added gzip compression.
|
||||||
* Added PM2.
|
* Added PM2.
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
# syntax=docker/dockerfile:1
|
|
||||||
|
|
||||||
FROM node:21-alpine
|
FROM node:21-alpine
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
ENV LOGGER=true
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
34
README.md
|
@ -1,13 +1,18 @@
|
||||||
# DweebUI
|
# DweebUI
|
||||||
DweebUI is a simple Docker web interface created using Javascript, Node.JS, and Express.
|
DweebUI is a web interface for managing Docker, with a zero-config dashboard for your containers.
|
||||||
|
|
||||||
Alpha v0.09 ( :fire: Experimental :fire: )
|
Alpha v0.20 ( :fire: Experimental :fire: )
|
||||||
|
|
||||||
|
:warning: DweebUI is a management interface and should not be directly exposed to the internet:warning:
|
||||||
|
:warning: External access should be done through a VPN or secure SSH connection :warning:
|
||||||
|
|
||||||
[](https://github.com/lllllllillllllillll)
|
[](https://github.com/lllllllillllllillll)
|
||||||
[](https://github.com/lllllllillllllillll)
|
[](https://github.com/lllllllillllllillll)
|
||||||
[](https://hub.docker.com/repository/docker/lllllllillllllillll/dweebui)
|
[](https://hub.docker.com/repository/docker/lllllllillllllillll/dweebui)
|
||||||
[](https://github.com/lllllllillllllillll/DweebUI/blob/main/LICENSE)
|
[](https://github.com/lllllllillllllillll/DweebUI/blob/main/LICENSE)
|
||||||
|
|
||||||
|
* This is a personal project that I started to get more familiar with Javascript and Node.js.
|
||||||
|
* I probably should have waited a lot longer to share this :|
|
||||||
|
|
||||||
<a href="https://raw.githubusercontent.com//lllllllillllllillll/DweebUI/main/screenshots/dashboard.png"><img src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI/main/screenshots/dashboard.png" width="50%"/></a>
|
<a href="https://raw.githubusercontent.com//lllllllillllllillll/DweebUI/main/screenshots/dashboard.png"><img src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI/main/screenshots/dashboard.png" width="50%"/></a>
|
||||||
|
|
||||||
|
@ -37,12 +42,11 @@ Docker Compose:
|
||||||
```
|
```
|
||||||
version: "3.9"
|
version: "3.9"
|
||||||
services:
|
services:
|
||||||
|
|
||||||
dweebui:
|
dweebui:
|
||||||
container_name: dweebui
|
container_name: dweebui
|
||||||
image: lllllllillllllillll/dweebui:v0.09-dev
|
# image: lllllllillllllillll/dweebui:v0.20
|
||||||
# build:
|
build:
|
||||||
# context: .
|
context: .
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
PORT: 8000
|
PORT: 8000
|
||||||
|
@ -52,21 +56,15 @@ services:
|
||||||
- 8000:8000
|
- 8000:8000
|
||||||
volumes:
|
volumes:
|
||||||
- dweebui:/app
|
- dweebui:/app
|
||||||
- caddyfiles:/app/caddyfiles
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
#- ./custom-templates.json:/app/custom-templates.json
|
|
||||||
#- ./composefiles:/app/composefiles
|
|
||||||
networks:
|
networks:
|
||||||
- dweeb_network
|
- dweebui_net
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
dweebui:
|
dweebui:
|
||||||
caddyfiles:
|
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
dweeb_network:
|
dweebui_net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -77,14 +75,6 @@ Compose setup:
|
||||||
* You may need to use ```docker-compose up -d``` or execute the command as root with either ```sudo docker compose up -d``` or ```sudo docker-compose up -d```.
|
* You may need to use ```docker-compose up -d``` or execute the command as root with either ```sudo docker compose up -d``` or ```sudo docker-compose up -d```.
|
||||||
|
|
||||||
|
|
||||||
Using setup.sh:
|
|
||||||
```
|
|
||||||
Extract DweebUI.zip and navigate to /DweebUI
|
|
||||||
cd DweebUI
|
|
||||||
chmod +x setup.sh
|
|
||||||
sudo ./setup.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
|
437
app.js
|
@ -1,23 +1,43 @@
|
||||||
// Express
|
import express from 'express';
|
||||||
const express = require("express");
|
import session from 'express-session';
|
||||||
const app = express();
|
import compression from 'compression';
|
||||||
const session = require("express-session");
|
import helmet from 'helmet';
|
||||||
const compression = require('compression');
|
import Docker from 'dockerode';
|
||||||
const helmet = require('helmet');
|
import cors from 'cors';
|
||||||
const PORT = process.env.PORT || 8000;
|
import { Readable } from 'stream';
|
||||||
|
import { instrument } from '@socket.io/admin-ui'
|
||||||
|
import { router } from './router/index.js';
|
||||||
|
import { createServer } from 'node:http';
|
||||||
|
import { Server } from 'socket.io';
|
||||||
|
import { sequelize, Container } from './database/models.js';
|
||||||
|
import { currentLoad, mem, networkStats, fsSize, dockerContainerStats, dockerImages, networkInterfaces } from 'systeminformation';
|
||||||
|
import { containerCard } from './components/containerCard.js';
|
||||||
|
|
||||||
// Router
|
export const app = express();
|
||||||
const routes = require("./routes");
|
const server = createServer(app);
|
||||||
|
const port = process.env.PORT || 8000;
|
||||||
|
export var docker = new Docker();
|
||||||
|
let [cpu, ram, tx, rx, disk] = [0, 0, 0, 0, 0];
|
||||||
|
let [hidden, clicked, dockerEvents] = ['', false, ''];
|
||||||
|
let metricsInterval, cardsInterval, graphsInterval;
|
||||||
|
let cardList = '';
|
||||||
|
const statsArray = {};
|
||||||
|
|
||||||
// Functions and variables
|
// socket.io admin ui
|
||||||
const { serverStats, containerList, containerStats, containerAction, containerLogs, hiddenContainers, dockerImages, dockerVolumes, dockerNetworks } = require('./functions/system');
|
export const io = new Server(server, {
|
||||||
let sentList, clicked;
|
connectionStateRecovery: {},
|
||||||
app.locals.site_list = '';
|
cors: {
|
||||||
|
origin: ['http://localhost:8000', 'https://admin.socket.io'],
|
||||||
|
methods: ['GET', 'POST'],
|
||||||
|
credentials: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
instrument(io, {
|
||||||
|
auth: false,
|
||||||
|
readonly: true
|
||||||
|
});
|
||||||
|
|
||||||
const Containers = require('./database/ContainerModel');
|
// Session middleware
|
||||||
|
|
||||||
|
|
||||||
// Configure Session
|
|
||||||
const sessionMiddleware = session({
|
const sessionMiddleware = session({
|
||||||
secret: "keyboard cat",
|
secret: "keyboard cat",
|
||||||
resave: false,
|
resave: false,
|
||||||
|
@ -27,136 +47,279 @@ const sessionMiddleware = session({
|
||||||
httpOnly:false, // Only set to true if you are using HTTPS.
|
httpOnly:false, // Only set to true if you are using HTTPS.
|
||||||
maxAge:3600000 * 8 // Session max age in milliseconds. 3600000 = 1 hour.
|
maxAge:3600000 * 8 // Session max age in milliseconds. 3600000 = 1 hour.
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
io.engine.use(sessionMiddleware);
|
||||||
|
|
||||||
// Middleware
|
// Express middleware
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.use([
|
app.use([
|
||||||
compression(),
|
compression(),
|
||||||
|
cors(),
|
||||||
helmet({contentSecurityPolicy: false}),
|
helmet({contentSecurityPolicy: false}),
|
||||||
express.static("public"),
|
express.static("public"),
|
||||||
express.json(),
|
express.json(),
|
||||||
express.urlencoded({ extended: true }),
|
express.urlencoded({ extended: true }),
|
||||||
sessionMiddleware,
|
sessionMiddleware,
|
||||||
routes
|
router
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Start Express server
|
// Initialize server
|
||||||
const server = app.listen(PORT, async () => {
|
server.listen(port, () => {
|
||||||
console.log(`App listening on port ${PORT}`);
|
async function init() {
|
||||||
});
|
try {
|
||||||
|
await sequelize.authenticate();
|
||||||
// Start Socket.io
|
console.log('[Connected to DB]');
|
||||||
const io = require('socket.io')(server);
|
} catch (error) {
|
||||||
io.engine.use(sessionMiddleware);
|
console.log('[Could not connect to DB]', error);
|
||||||
|
}
|
||||||
io.on('connection', (socket) => {
|
try {
|
||||||
|
await sequelize.sync();
|
||||||
// Set user session
|
console.log('[Models Synced]');
|
||||||
const user_session = socket.request.session;
|
hidden = await Container.findAll({ where: {visibility:false}});
|
||||||
console.log(`${user_session.user} connected from ${socket.handshake.headers.host} ${socket.handshake.address}`);
|
containerCards();
|
||||||
|
} catch (error) {
|
||||||
// Check if a list of containers or an install card needs to be sent
|
console.log('[Could not Sync Models]', error);
|
||||||
if (sentList != null) { socket.emit('cards', sentList); }
|
}
|
||||||
if((app.locals.install != '') && (app.locals.install != null)){ socket.emit('install', app.locals.install); }
|
console.log(`\nServer listening on http://localhost:${port}`);
|
||||||
|
|
||||||
|
|
||||||
async function dockerStuff(){
|
|
||||||
let i = await dockerImages();
|
|
||||||
let v = await dockerVolumes();
|
|
||||||
let n = await dockerNetworks();
|
|
||||||
|
|
||||||
// console.log(i, v, n);
|
|
||||||
}
|
}
|
||||||
|
init();
|
||||||
dockerStuff();
|
|
||||||
|
|
||||||
// Send server metrics
|
|
||||||
let ServerStats = setInterval(async () => {
|
|
||||||
socket.emit('metrics', await serverStats());
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
// Send list of containers
|
|
||||||
let ContainerList = setInterval(async () => {
|
|
||||||
let cardList = await containerList();
|
|
||||||
if (sentList !== cardList) {
|
|
||||||
sentList = cardList;
|
|
||||||
app.locals.install = '';
|
|
||||||
socket.emit('cards', cardList);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
// Send container metrics
|
|
||||||
let ContainerStats = setInterval(async () => {
|
|
||||||
let stats = await containerStats();
|
|
||||||
for (let i = 0; i < stats.length; i++) {
|
|
||||||
socket.emit('containerStats', stats[i]);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
// Container controls
|
|
||||||
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('hide', async (data) => {
|
|
||||||
console.log(`Hide ${data.container}`);
|
|
||||||
|
|
||||||
let containerExists = await Containers.findOne({ where: {name:data.container}});
|
|
||||||
|
|
||||||
if(!containerExists){
|
|
||||||
const container = await Containers.create({
|
|
||||||
name: data.container,
|
|
||||||
visibility: false,
|
|
||||||
});
|
|
||||||
hiddenContainers();
|
|
||||||
console.log(`[Created] Container ${data.container} hidden`)
|
|
||||||
|
|
||||||
let containerData = await Containers.findOne({ where: {name:data.container}});
|
|
||||||
console.log(containerData);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
containerExists.update({ visibility: false });
|
|
||||||
console.log(`[Updated] Container ${data.container} hidden`)
|
|
||||||
hiddenContainers();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('reset', (data) => {
|
|
||||||
// set visibility to true for all containers
|
|
||||||
Containers.update({ visibility: true }, { where: {} });
|
|
||||||
console.log('All containers visible');
|
|
||||||
hiddenContainers();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Container logs
|
|
||||||
socket.on('logs', (data) => {
|
|
||||||
containerLogs(data.container)
|
|
||||||
.then(logs => {
|
|
||||||
console.log(`Refreshed logs for ${data.container}`)
|
|
||||||
socket.emit('logString', logs);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// On disconnect
|
|
||||||
socket.on('disconnect', () => {
|
|
||||||
clearInterval(ServerStats);
|
|
||||||
clearInterval(ContainerList);
|
|
||||||
clearInterval(ContainerStats);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Server metrics
|
||||||
|
let serverMetrics = async () => {
|
||||||
|
currentLoad().then(data => {
|
||||||
|
cpu = Math.round(data.currentLoad);
|
||||||
|
});
|
||||||
|
mem().then(data => {
|
||||||
|
ram = Math.round((data.active / data.total) * 100);
|
||||||
|
});
|
||||||
|
networkStats().then(data => {
|
||||||
|
tx = data[0].tx_bytes / (1024 * 1024);
|
||||||
|
rx = data[0].rx_bytes / (1024 * 1024);
|
||||||
|
});
|
||||||
|
fsSize().then(data => {
|
||||||
|
disk = data[0].use;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// List docker containers
|
||||||
|
let containerCards = async () => {
|
||||||
|
let list = '';
|
||||||
|
const allContainers = await docker.listContainers({ all: true });
|
||||||
|
for (const container of allContainers) {
|
||||||
|
if (!hidden.includes(container.Names[0].slice(1))) {
|
||||||
|
|
||||||
|
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 ports_list = [];
|
||||||
|
try {
|
||||||
|
for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) {
|
||||||
|
let ports = {
|
||||||
|
check: 'checked',
|
||||||
|
external: value[0].HostPort,
|
||||||
|
internal: key.split('/')[0],
|
||||||
|
protocol: key.split('/')[1]
|
||||||
|
}
|
||||||
|
ports_list.push(ports);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
let external_port = ports_list[0]?.external || 0;
|
||||||
|
let internal_port = ports_list[0]?.internal || 0;
|
||||||
|
|
||||||
|
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,
|
||||||
|
ports: ports_list,
|
||||||
|
link: 'localhost',
|
||||||
|
}
|
||||||
|
let card = containerCard(container_info);
|
||||||
|
list += card;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cardList = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container metrics
|
||||||
|
let containerStats = async () => {
|
||||||
|
const data = await docker.listContainers({ all: true });
|
||||||
|
for (const container of data) {
|
||||||
|
if (!hidden.includes(container.Names[0].slice(1))) {
|
||||||
|
const stats = await dockerContainerStats(container.Id);
|
||||||
|
const name = container.Names[0].slice(1);
|
||||||
|
|
||||||
|
if (!statsArray[name]) {
|
||||||
|
statsArray[name] = {
|
||||||
|
cpuArray: Array(15).fill(0),
|
||||||
|
ramArray: Array(15).fill(0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
statsArray[name].cpuArray.push(Math.round(stats[0].cpuPercent));
|
||||||
|
statsArray[name].ramArray.push(Math.round(stats[0].memPercent));
|
||||||
|
|
||||||
|
statsArray[name].cpuArray = statsArray[name].cpuArray.slice(-15);
|
||||||
|
statsArray[name].ramArray = statsArray[name].ramArray.slice(-15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store docker events
|
||||||
|
docker.getEvents((err, stream) => {
|
||||||
|
if (err) throw err;
|
||||||
|
stream.on('data', (chunk) => {
|
||||||
|
dockerEvents += chunk.toString('utf8');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for docker events
|
||||||
|
setInterval( () => {
|
||||||
|
if (dockerEvents != '') {
|
||||||
|
getHidden();
|
||||||
|
containerCards();
|
||||||
|
dockerEvents = '';
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Get hidden containers
|
||||||
|
async function getHidden() {
|
||||||
|
hidden = await Container.findAll({ where: {visibility:false}});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Socket.io
|
||||||
|
io.on('connection', (socket) => {
|
||||||
|
let sessionData = socket.request.session;
|
||||||
|
let sent = '';
|
||||||
|
if (sessionData.user != undefined) {
|
||||||
|
console.log(`${sessionData.user} connected from ${socket.handshake.headers.host}`);
|
||||||
|
|
||||||
|
// Start intervals if not already started
|
||||||
|
if (!metricsInterval) {
|
||||||
|
metricsInterval = setInterval(serverMetrics, 1000);
|
||||||
|
console.log('Metrics interval started');
|
||||||
|
}
|
||||||
|
if (!cardsInterval) {
|
||||||
|
cardsInterval = setInterval(containerCards, 1000);
|
||||||
|
console.log('Cards interval started');
|
||||||
|
}
|
||||||
|
if (!graphsInterval) {
|
||||||
|
graphsInterval = setInterval(containerStats, 1000);
|
||||||
|
console.log('Graphs interval started');
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
socket.emit('metrics', [cpu, ram, tx, rx, disk]);
|
||||||
|
if (sent != cardList) {
|
||||||
|
sent = cardList;
|
||||||
|
socket.emit('containers', cardList);
|
||||||
|
}
|
||||||
|
socket.emit('containerStats', statsArray);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
socket.on('clicked', (data) => {
|
||||||
|
if (clicked == true) { return; } clicked = true;
|
||||||
|
let { name, id, value } = data;
|
||||||
|
console.log(`${sessionData.user} clicked: ${id} ${value} ${name}`);
|
||||||
|
|
||||||
|
|
||||||
|
if (id == 'logs'){
|
||||||
|
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) => {
|
||||||
|
socket.emit('logs', data);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (id == 'start' || id == 'stop' || id == 'pause' || id == 'restart'){
|
||||||
|
var containerName = docker.getContainer(name);
|
||||||
|
|
||||||
|
if ((id == 'start') && (value == 'stopped')) {
|
||||||
|
containerName.start();
|
||||||
|
} else if ((id == 'start') && (value == 'paused')) {
|
||||||
|
containerName.unpause();
|
||||||
|
} else if ((id == 'stop') && (value != 'stopped')) {
|
||||||
|
containerName.stop();
|
||||||
|
} else if ((id == 'pause') && (value == 'running')) {
|
||||||
|
containerName.pause();
|
||||||
|
} else if ((id == 'pause') && (value == 'paused')) {
|
||||||
|
containerName.unpause();
|
||||||
|
} else if (id == 'restart') {
|
||||||
|
containerName.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clicked = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
console.log(`${sessionData.user} disconnected`);
|
||||||
|
socket.disconnect();
|
||||||
|
// clear intervals if no users are connected
|
||||||
|
if (io.engine.clientsCount == 0) {
|
||||||
|
clearInterval(metricsInterval);
|
||||||
|
clearInterval(cardsInterval);
|
||||||
|
clearInterval(graphsInterval);
|
||||||
|
metricsInterval = null;
|
||||||
|
cardsInterval = null;
|
||||||
|
graphsInterval = null;
|
||||||
|
console.log('All intervals cleared');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Missing session data');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// let link = '';
|
||||||
|
// networkInterfaces().then(data => {
|
||||||
|
// link = data[0].ip4;
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
import ./sites/*
|
|
|
@ -1,4 +1,4 @@
|
||||||
function appCard(data) {
|
export const appCard = (data) => {
|
||||||
|
|
||||||
// make data.title lowercase
|
// make data.title lowercase
|
||||||
let app_name = data.name || data.title.toLowerCase();
|
let app_name = data.name || data.title.toLowerCase();
|
||||||
|
@ -227,11 +227,11 @@ function appCard(data) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<a href="#" class="card-btn" data-bs-toggle="modal" data-bs-target="#${modal}-info"><!-- Download SVG icon from http://tabler-icons.io/i/mail -->
|
<a href="#" class="card-btn" data-bs-toggle="modal" data-bs-target="#${modal}-info">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-article" 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="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"></path> <path d="M7 8h10"></path> <path d="M7 12h10"></path> <path d="M7 16h10"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-article" 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="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"></path> <path d="M7 8h10"></path> <path d="M7 12h10"></path> <path d="M7 16h10"></path></svg>
|
||||||
Learn More
|
Learn More
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class="card-btn" data-bs-toggle="modal" data-bs-target="#${modal}-install"><!-- Download SVG icon from http://tabler-icons.io/i/phone -->
|
<a href="#" class="card-btn" data-bs-toggle="modal" data-bs-target="#${modal}-install">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-bar-to-down" 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="M4 20l16 0"></path> <path d="M12 14l0 -10"></path> <path d="M12 14l4 -4"></path> <path d="M12 14l-4 -4"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-bar-to-down" 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="M4 20l16 0"></path> <path d="M12 14l0 -10"></path> <path d="M12 14l4 -4"></path> <path d="M12 14l-4 -4"></path></svg>
|
||||||
Install
|
Install
|
||||||
</a>
|
</a>
|
||||||
|
@ -990,5 +990,3 @@ function appCard(data) {
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { appCard };
|
|
||||||
|
|
118
components/containerCard.js
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
// export for app.js
|
||||||
|
export const containerCard = (data) => {
|
||||||
|
|
||||||
|
let { name, service, state, external_port, internal_port, ports, link } = data;
|
||||||
|
let wrapped = name;
|
||||||
|
let chart = name;
|
||||||
|
|
||||||
|
if (name.length > 13) {
|
||||||
|
wrapped = name.slice(0, 10) + '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
//disable controls for a docker container depending on its name
|
||||||
|
let actions = "";
|
||||||
|
if (name.startsWith('dweebui')) {
|
||||||
|
actions = 'disabled=""';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( external_port == undefined ) { external_port = 0; }
|
||||||
|
if ( internal_port == undefined ) { internal_port = 0; }
|
||||||
|
|
||||||
|
|
||||||
|
let state_indicator = 'green';
|
||||||
|
if (state == 'exited') {
|
||||||
|
state = 'stopped';
|
||||||
|
state_indicator = 'red';
|
||||||
|
} else if (state == 'paused') {
|
||||||
|
state_indicator = 'orange';
|
||||||
|
}
|
||||||
|
|
||||||
|
let ports_data = [];
|
||||||
|
if (ports) {
|
||||||
|
ports_data = ports;
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
|
||||||
|
let port_check = "checked";
|
||||||
|
let external = i;
|
||||||
|
let internal = i;
|
||||||
|
let protocol = "tcp";
|
||||||
|
|
||||||
|
ports_data.push({
|
||||||
|
check: port_check,
|
||||||
|
external: external,
|
||||||
|
internal: internal,
|
||||||
|
protocol: protocol
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="col-sm-6 col-lg-3 deleteme">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-stamp card-stamp-sm">
|
||||||
|
<img width="100px" src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/${service}.png" onerror="this.onerror=null;this.src='https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/docker.png';"></img>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="subheader text-yellow">${external_port}:${internal_port}</div>
|
||||||
|
<div class="ms-auto lh-1">
|
||||||
|
<div class="card-actions btn-actions">
|
||||||
|
<div class="card-actions btn-actions">
|
||||||
|
<button onclick="clicked(this)" name="${name}" value="${state}" id="start" class="btn-action" title="Start" ${actions}><!-- player-play -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-play" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 4v16l13 -8z"></path></svg>
|
||||||
|
</button>
|
||||||
|
<button onclick="clicked(this)" name="${name}" value="${state}" id="stop" class="btn-action" title="Stop" ${actions}><!-- player-stop -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-stop" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 5m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"></path></svg>
|
||||||
|
</button>
|
||||||
|
<button onclick="clicked(this)" name="${name}" value="${state}" id="pause" class="btn-action" title="Pause" ${actions}><!-- player-pause -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-player-pause" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path><path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path></svg>
|
||||||
|
</button>
|
||||||
|
<button onclick="clicked(this)" name="${name}" value="${state}" id="restart" class="btn-action" title="Restart" ${actions}><!-- reload -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-reload" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path><path d="M20 4v5h-5"></path></svg>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown">
|
||||||
|
<a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end">
|
||||||
|
<button class="dropdown-item text-secondary" onclick="clicked(this)" id="details" data-bs-toggle="modal" data-bs-target="#details_modal" href="#">Details</button>
|
||||||
|
<button class="dropdown-item text-secondary" onclick="clicked(this)" name="${name}" id="logs" data-bs-toggle="modal" data-bs-target="#log_view" href="#">Logs</button>
|
||||||
|
<button class="dropdown-item text-secondary" onclick="clicked(this)" name="${name}" id="edit" href="#">Edit</button>
|
||||||
|
<button class="dropdown-item text-primary" onclick="clicked(this)" name="${name}" id="update" href="#">Update</button>
|
||||||
|
<button class="dropdown-item text-danger" onclick="clicked(this)" name="${name}" id="remove" data-bs-toggle="modal" data-bs-target="#remove_modal" href="#">Remove</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /> </svg>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end">
|
||||||
|
<button class="dropdown-item" onclick="clicked(this)" name="${name}" id="hide" value="hide">Hide</button>
|
||||||
|
<button class="dropdown-item" onclick="clicked(this)" name="${name}" id="resetView" value="resetView">Reset View</button>
|
||||||
|
<button class="dropdown-item" onclick="clicked(this)" name="${name}" id="permissions" value="permissions">Permissions</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-baseline">
|
||||||
|
<div class="h1 me-2" title="${name}" style="margin-bottom: 0;">
|
||||||
|
<a href="http://${link}:${external_port}" target="_blank">
|
||||||
|
${wrapped}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="ms-auto">
|
||||||
|
<span class="text-${state_indicator} 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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="${chart}_chart" class="chart-sm"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
|
@ -1,18 +0,0 @@
|
||||||
function siteCard(type, domain, host, port, id) {
|
|
||||||
|
|
||||||
let site = `<tr>`
|
|
||||||
site += `<td><input class="form-check-input m-0 align-middle" name="select${id}" value="${domain}" type="checkbox" aria-label="Select invoice"></td>`
|
|
||||||
site += `<td><span class="text-muted">${id}</span></td>`
|
|
||||||
site += `<td><a href="https://${domain}" class="text-reset" tabindex="-1" target="_blank">${domain}</a></td>`
|
|
||||||
site += `<td>${type}</td>`
|
|
||||||
site += `<td>${host}</td>`
|
|
||||||
site += `<td>${port}</td>`
|
|
||||||
site += `<td><span class="badge bg-success me-1"></span> Enabled</td>`
|
|
||||||
site += `<td><span class="badge bg-success me-1"></span> Enabled</td>`
|
|
||||||
site += `<td class="text-end"><a class="btn" href="#"> Edit </a></td>`
|
|
||||||
site += `</tr>`
|
|
||||||
|
|
||||||
return site;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { siteCard };
|
|
|
@ -1,22 +1,19 @@
|
||||||
const User = require('../database/UserModel');
|
import { User } from "../database/models.js";
|
||||||
|
|
||||||
|
export const Account = async (req, res) => {
|
||||||
|
|
||||||
|
|
||||||
|
let user = await User.findOne({ where: { UUID: req.session.UUID }});
|
||||||
|
|
||||||
|
res.render("account", {
|
||||||
|
first_name: user.name,
|
||||||
|
last_name: user.name,
|
||||||
|
name: user.name,
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
role: user.role,
|
||||||
|
avatar: user.avatar,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
exports.Account = async function(req, res) {
|
|
||||||
if (req.session.user) {
|
|
||||||
// Get the user.
|
|
||||||
let user = await User.findOne({ where: { UUID: req.session.UUID }});
|
|
||||||
// Render the home page
|
|
||||||
res.render("pages/account", {
|
|
||||||
first_name: user.first_name,
|
|
||||||
last_name: user.last_name,
|
|
||||||
name: user.first_name + ' ' + user.last_name,
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
role: user.role,
|
|
||||||
avatar: user.avatar,
|
|
||||||
isLoggedIn: true
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Redirect to the login page
|
|
||||||
res.redirect("/login");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,23 @@
|
||||||
const { appCard } = require('../components/appCard')
|
import { readFileSync } from 'fs';
|
||||||
const { dashCard } = require('../components/dashCard');
|
import { appCard } from '../components/appCard.js';
|
||||||
const { install, uninstall } = require('../functions/package_manager');
|
|
||||||
|
|
||||||
const templates_json = require('../templates.json');
|
let templatesJSON = readFileSync('./templates.json');
|
||||||
let templates = templates_json.templates;
|
let templates = JSON.parse(templatesJSON).templates;
|
||||||
|
|
||||||
// sort templates alphabetically
|
|
||||||
templates = templates.sort((a, b) => {
|
templates = templates.sort((a, b) => {
|
||||||
if (a.name < b.name) {
|
if (a.name < b.name) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
exports.Apps = async function(req, res) {
|
|
||||||
|
|
||||||
|
export const Apps = (req, res) => {
|
||||||
let page = Number(req.params.page) || 1;
|
let page = Number(req.params.page) || 1;
|
||||||
let list_start = (page - 1) * 28;
|
let list_start = (page-1)*28;
|
||||||
let list_end = (page * 28);
|
let list_end = (page*28);
|
||||||
let last_page = Math.ceil(templates.length / 28);
|
let last_page = Math.ceil(templates.length/28);
|
||||||
|
|
||||||
let prev = '/apps/' + (page - 1);
|
let prev = '/apps/' + (page-1);
|
||||||
let next = '/apps/' + (page + 1);
|
let next = '/apps/' + (page+1);
|
||||||
if (page == 1) {
|
if (page == 1) {
|
||||||
prev = '/apps/' + (page);
|
prev = '/apps/' + (page);
|
||||||
}
|
}
|
||||||
|
@ -36,12 +32,10 @@ exports.Apps = async function(req, res) {
|
||||||
apps_list += app_card;
|
apps_list += app_card;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the home page
|
res.render("apps", {
|
||||||
res.render("pages/apps", {
|
|
||||||
name: req.session.user,
|
name: req.session.user,
|
||||||
role: req.session.role,
|
role: req.session.role,
|
||||||
avatar: req.session.avatar,
|
avatar: req.session.avatar,
|
||||||
isLoggedIn: true,
|
|
||||||
list_start: list_start + 1,
|
list_start: list_start + 1,
|
||||||
list_end: list_end,
|
list_end: list_end,
|
||||||
app_count: templates.length,
|
app_count: templates.length,
|
||||||
|
@ -54,7 +48,7 @@ exports.Apps = async function(req, res) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exports.searchApps = async function(req, res) {
|
export const searchApps = async (req, res) => {
|
||||||
|
|
||||||
let page = Number(req.query.page) || 1;
|
let page = Number(req.query.page) || 1;
|
||||||
let list_start = (page - 1) * 28;
|
let list_start = (page - 1) * 28;
|
||||||
|
@ -98,12 +92,10 @@ exports.searchApps = async function(req, res) {
|
||||||
apps_list += app_card;
|
apps_list += app_card;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the home page
|
res.render("apps", {
|
||||||
res.render("pages/apps", {
|
|
||||||
name: req.session.user,
|
name: req.session.user,
|
||||||
role: req.session.role,
|
role: req.session.role,
|
||||||
avatar: req.session.avatar,
|
avatar: req.session.avatar,
|
||||||
isLoggedIn: true,
|
|
||||||
list_start: list_start + 1,
|
list_start: list_start + 1,
|
||||||
list_end: list_end,
|
list_end: list_end,
|
||||||
app_count: templates.length,
|
app_count: templates.length,
|
||||||
|
@ -113,62 +105,3 @@ exports.searchApps = async function(req, res) {
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exports.Install = async function (req, res) {
|
|
||||||
|
|
||||||
if (req.session.role == "admin") {
|
|
||||||
|
|
||||||
console.log(`Starting install for: ${req.body.name}`)
|
|
||||||
|
|
||||||
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') {
|
|
||||||
|
|
||||||
uninstall(req.body);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Redirect to the home page
|
|
||||||
res.redirect("/");
|
|
||||||
} else {
|
|
||||||
// Redirect to the login page
|
|
||||||
res.redirect("/login");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,160 +0,0 @@
|
||||||
const User = require('../database/UserModel');
|
|
||||||
const bcrypt = require('bcrypt');
|
|
||||||
|
|
||||||
|
|
||||||
exports.Login = function(req,res){
|
|
||||||
|
|
||||||
// check whether we have a session
|
|
||||||
if(req.session.user){
|
|
||||||
// Redirect to log out.
|
|
||||||
res.redirect("/logout");
|
|
||||||
}else{
|
|
||||||
// Render the login page.
|
|
||||||
res.render("pages/login",{
|
|
||||||
"error":"",
|
|
||||||
"isLoggedIn": false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.processLogin = async function(req,res){
|
|
||||||
// get the data.
|
|
||||||
let email = req.body.email;
|
|
||||||
let password = req.body.password;
|
|
||||||
// check if we have data.
|
|
||||||
if(email && password){
|
|
||||||
// check if the user exists.
|
|
||||||
let existingUser = await User.findOne({ where: {email:email}});
|
|
||||||
if(existingUser){
|
|
||||||
// compare the password.
|
|
||||||
let match = await bcrypt.compare(password,existingUser.password);
|
|
||||||
if(match){
|
|
||||||
|
|
||||||
// set the session.
|
|
||||||
req.session.user = existingUser.username;
|
|
||||||
req.session.UUID = existingUser.UUID;
|
|
||||||
req.session.role = existingUser.role;
|
|
||||||
req.session.avatar = existingUser.avatar;
|
|
||||||
|
|
||||||
|
|
||||||
// Redirect to the home page.
|
|
||||||
res.redirect("/");
|
|
||||||
}else{
|
|
||||||
// return an error.
|
|
||||||
res.render("pages/login",{
|
|
||||||
"error":"Invalid password",
|
|
||||||
isLoggedIn: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
// return an error.
|
|
||||||
res.render("pages/login",{
|
|
||||||
"error":"User with that email does not exist.",
|
|
||||||
isLoggedIn:false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
res.status(400);
|
|
||||||
res.render("pages/login",{
|
|
||||||
"error":"Please fill in all the fields.",
|
|
||||||
isLoggedIn:false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
exports.Logout = function(req,res){
|
|
||||||
// clear the session.
|
|
||||||
req.session.destroy();
|
|
||||||
// Redirect to the login page.
|
|
||||||
res.redirect("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exports.Register = function(req,res){
|
|
||||||
// Check whether we have a session
|
|
||||||
if(req.session.user){
|
|
||||||
// Redirect to log out.
|
|
||||||
res.redirect("/logout");
|
|
||||||
} else {
|
|
||||||
// Render the signup page.
|
|
||||||
res.render("pages/register",{
|
|
||||||
"error":"",
|
|
||||||
isLoggedIn:false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.processRegister = async function(req,res){
|
|
||||||
|
|
||||||
// Get the data.
|
|
||||||
let { first_name, last_name, username, email, password, avatar, tos, secret } = req.body;
|
|
||||||
let role = "user";
|
|
||||||
|
|
||||||
// Check the data.
|
|
||||||
if((first_name && last_name && email && password && username && tos) && (secret == process.env.SECRET)){
|
|
||||||
|
|
||||||
// Check if there is an existing user with that username.
|
|
||||||
let existingUser = await User.findOne({ where: {username:username}});
|
|
||||||
|
|
||||||
let adminUser = await User.findOne({ where: {role:"admin"}});
|
|
||||||
|
|
||||||
if(!existingUser){
|
|
||||||
// hash the password.
|
|
||||||
let hashedPassword = bcrypt.hashSync(password,10);
|
|
||||||
|
|
||||||
if(!adminUser){
|
|
||||||
console.log('Creating admin User');
|
|
||||||
role = "admin";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const user = await User.create({
|
|
||||||
first_name: first_name,
|
|
||||||
last_name: last_name,
|
|
||||||
username: username,
|
|
||||||
email: email,
|
|
||||||
password: hashedPassword,
|
|
||||||
role: role,
|
|
||||||
group: 'all',
|
|
||||||
avatar: `<img src="./static/avatars/${avatar}">`
|
|
||||||
});
|
|
||||||
|
|
||||||
let newUser = await User.findOne({ where: {email:email}});
|
|
||||||
|
|
||||||
let match = await bcrypt.compare(password,newUser.password);
|
|
||||||
if(match){
|
|
||||||
console.log(`User session created for ${newUser.username}`)
|
|
||||||
req.session.user = newUser.username;
|
|
||||||
req.session.UUID = newUser.UUID;
|
|
||||||
req.session.role = newUser.role;
|
|
||||||
req.session.avatar = newUser.avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to the home page.
|
|
||||||
res.redirect("/");
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
// return an error.
|
|
||||||
res.render("pages/register",{
|
|
||||||
"error":"Something went wrong when creating account.",
|
|
||||||
isLoggedIn:false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}else{
|
|
||||||
// return an error.
|
|
||||||
res.render("pages/register",{
|
|
||||||
"error":"User with that username already exists.",
|
|
||||||
isLoggedIn:false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
// Redirect to the signup page.
|
|
||||||
res.render("pages/register",{
|
|
||||||
"error":"Please fill in all the fields and accept TOS.",
|
|
||||||
isLoggedIn:false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,215 +1,24 @@
|
||||||
const { readFileSync, writeFileSync, appendFileSync, readdirSync } = require('fs');
|
|
||||||
const { execSync } = require("child_process");
|
export const Dashboard = (req, res) => {
|
||||||
const { siteCard } = require('../components/siteCard');
|
|
||||||
const { containerExec } = require('../functions/system')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exports.Dashboard = async function (req, res) {
|
res.render("dashboard", {
|
||||||
|
|
||||||
let caddy = 'd-none';
|
|
||||||
|
|
||||||
if (process.env.Proxy_Manager == 'enabled') {
|
|
||||||
caddy = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the home page
|
|
||||||
res.render("pages/dashboard", {
|
|
||||||
name: req.session.user,
|
name: req.session.user,
|
||||||
role: req.session.role,
|
role: req.session.role,
|
||||||
avatar: req.session.avatar,
|
avatar: req.session.avatar,
|
||||||
isLoggedIn: true,
|
|
||||||
site_list: req.app.locals.site_list,
|
|
||||||
caddy: caddy
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const searchDashboard = (req, res) => {
|
||||||
|
|
||||||
|
console.log(req.params);
|
||||||
|
|
||||||
exports.AddSite = async function (req, res) {
|
res.render("dashboard", {
|
||||||
|
name: req.session.user,
|
||||||
let { domain, type, host, port } = req.body;
|
role: req.session.role,
|
||||||
|
avatar: req.session.avatar,
|
||||||
if ( domain && type && host && port) {
|
|
||||||
|
|
||||||
|
|
||||||
let { domain, type, host, port } = req.body;
|
|
||||||
|
|
||||||
// build caddyfile
|
|
||||||
let caddyfile = `${domain} {`
|
|
||||||
caddyfile += `\n\t${type} ${host}:${port}`
|
|
||||||
caddyfile += `\n\theader {`
|
|
||||||
caddyfile += `\n\t\tStrict-Transport-Security "max-age=31536000; includeSubDomains; preload"`
|
|
||||||
caddyfile += `\n\t}`
|
|
||||||
caddyfile += `\n}`
|
|
||||||
|
|
||||||
|
|
||||||
// save caddyfile
|
|
||||||
writeFileSync(`./caddyfiles/sites/${domain}.Caddyfile`, caddyfile, function (err) { console.log(err) });
|
|
||||||
|
|
||||||
|
|
||||||
// format caddyfile
|
|
||||||
let format = {
|
|
||||||
container: 'DweebProxy',
|
|
||||||
command: `caddy fmt --overwrite /etc/caddy/sites/${domain}.Caddyfile`
|
|
||||||
}
|
|
||||||
await containerExec(format, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(`Formatted ${domain}.Caddyfile`);
|
|
||||||
});
|
|
||||||
|
|
||||||
///////////////// convert caddyfile to json
|
|
||||||
let convert = {
|
|
||||||
container: 'DweebProxy',
|
|
||||||
command: `caddy adapt --config /etc/caddy/sites/${domain}.Caddyfile --pretty >> /etc/caddy/sites/${domain}.json`
|
|
||||||
}
|
|
||||||
await containerExec(convert, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(`Converted ${domain}.Caddyfile to JSON`);
|
|
||||||
});
|
|
||||||
|
|
||||||
////////////// reload caddy
|
|
||||||
let reload = {
|
|
||||||
container: 'DweebProxy',
|
|
||||||
command: `caddy reload --config /etc/caddy/Caddyfile`
|
|
||||||
}
|
|
||||||
await containerExec(reload, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(`Reloaded Caddy Config`);
|
|
||||||
});
|
|
||||||
|
|
||||||
let site = siteCard(type, domain, host, port, 0);
|
|
||||||
|
|
||||||
req.app.locals.site_list += site;
|
|
||||||
|
|
||||||
|
|
||||||
res.redirect("/");
|
|
||||||
} else {
|
|
||||||
// Redirect
|
|
||||||
console.log('missing info')
|
|
||||||
res.redirect("/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
exports.RemoveSite = async function (req, res) {
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(req.body)) {
|
|
||||||
|
|
||||||
execSync(`rm ./caddyfiles/sites/${value}.Caddyfile`, (err, stdout, stderr) => {
|
|
||||||
if (err) { console.error(`error: ${err.message}`); return; }
|
|
||||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
|
||||||
console.log(`removed ${value}.Caddyfile`);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
let reload = {
|
|
||||||
container: 'DweebProxy',
|
|
||||||
command: `caddy reload --config /etc/caddy/Caddyfile`
|
|
||||||
}
|
|
||||||
await containerExec(reload);
|
|
||||||
|
|
||||||
|
|
||||||
console.log('Removed Site(s)')
|
|
||||||
|
|
||||||
res.redirect("/refreshsites");
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
exports.RefreshSites = async function (req, res) {
|
|
||||||
|
|
||||||
let domain, type, host, port;
|
|
||||||
let id = 1;
|
|
||||||
|
|
||||||
// Clear site_list.ejs
|
|
||||||
req.app.locals.site_list = "";
|
|
||||||
|
|
||||||
|
|
||||||
// check if ./caddyfiles/sites contains any .json files, then delete them
|
|
||||||
try {
|
|
||||||
let files = readdirSync('./caddyfiles/sites/');
|
|
||||||
files.forEach(file => {
|
|
||||||
if (file.includes(".json")) {
|
|
||||||
execSync(`rm ./caddyfiles/sites/${file}`, (err, stdout, stderr) => {
|
|
||||||
if (err) { console.error(`error: ${err.message}`); return; }
|
|
||||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
|
||||||
console.log(`removed ${file}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) { console.log("No .json files to delete") }
|
|
||||||
|
|
||||||
// get list of Caddyfiles
|
|
||||||
let sites = readdirSync('./caddyfiles/sites/');
|
|
||||||
|
|
||||||
|
|
||||||
sites.forEach(site_name => {
|
|
||||||
// convert the caddyfile of each site to json
|
|
||||||
let convert = {
|
|
||||||
container: 'DweebProxy',
|
|
||||||
command: `caddy adapt --config ./caddyfiles/sites/${site_name} --pretty >> ./caddyfiles/sites/${site_name}.json`
|
|
||||||
}
|
|
||||||
containerExec(convert);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// read the json file
|
|
||||||
let site_file = readFileSync(`./caddyfiles/sites/${site_name}.json`, 'utf8');
|
|
||||||
// fix whitespace and parse the json file
|
|
||||||
site_file = site_file.replace(/ /g, " ");
|
|
||||||
site_file = JSON.parse(site_file);
|
|
||||||
} catch (error) { console.log("No .json file to read") }
|
|
||||||
|
|
||||||
|
|
||||||
// get the domain, type, host, and port from the json file
|
|
||||||
try { domain = site_file.apps.http.servers.srv0.routes[0].match[0].host[0] } catch (error) { console.log("No Domain") }
|
|
||||||
try { type = site_file.apps.http.servers.srv0.routes[0].handle[0].routes[0].handle[1].handler } catch (error) { console.log("No Type") }
|
|
||||||
try { host = site_file.apps.http.servers.srv0.routes[0].handle[0].routes[0].handle[1].upstreams[0].dial.split(":")[0] } catch (error) { console.log("Not Localhost") }
|
|
||||||
try { port = site_file.apps.http.servers.srv0.routes[0].handle[0].routes[0].handle[1].upstreams[0].dial.split(":")[1] } catch (error) { console.log("No Port") }
|
|
||||||
|
|
||||||
// build the site card
|
|
||||||
let site = siteCard(type, domain, host, port, id);
|
|
||||||
|
|
||||||
// append the site card to site_list
|
|
||||||
req.app.locals.site_list += site;
|
|
||||||
|
|
||||||
id++;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
res.redirect("/");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exports.DisableSite = async function (req, res) {
|
|
||||||
|
|
||||||
console.log(req.body)
|
|
||||||
console.log('Disable Site')
|
|
||||||
|
|
||||||
res.redirect("/");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
exports.EnableSite = async function (req, res) {
|
|
||||||
|
|
||||||
console.log(req.body)
|
|
||||||
console.log('Enable Site')
|
|
||||||
|
|
||||||
res.redirect("/");
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,14 +1,18 @@
|
||||||
|
import { docker } from '../app.js';
|
||||||
|
|
||||||
|
export const Images = async function(req, res) {
|
||||||
|
|
||||||
exports.Images = async function(req, res) {
|
const allImages = await docker.listImages({ all: true });
|
||||||
|
|
||||||
|
for (let i = 0; i < allImages.length; i++) {
|
||||||
|
console.log(`Image ${i}:`)
|
||||||
|
console.log(`repoTags: ${allImages[i].repoTags}`)
|
||||||
|
}
|
||||||
|
|
||||||
// Render the home page
|
res.render("images", {
|
||||||
res.render("pages/images", {
|
|
||||||
name: req.session.user,
|
name: req.session.user,
|
||||||
role: req.session.role,
|
role: req.session.role,
|
||||||
avatar: req.session.avatar,
|
avatar: req.session.avatar,
|
||||||
isLoggedIn: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
72
controllers/login.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { User } from '../database/models.js';
|
||||||
|
import { Syslog } from '../database/models.js';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
|
export const Login = function(req,res){
|
||||||
|
if(req.session.user){
|
||||||
|
res.redirect("/logout");
|
||||||
|
}else{
|
||||||
|
res.render("login",{
|
||||||
|
"error":"",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const submitLogin = async function(req,res){
|
||||||
|
|
||||||
|
let { email, password } = req.body;
|
||||||
|
|
||||||
|
if(email && password){
|
||||||
|
|
||||||
|
let existingUser = await User.findOne({ where: {email:email}});
|
||||||
|
if(existingUser){
|
||||||
|
|
||||||
|
let match = await bcrypt.compare(password,existingUser.password);
|
||||||
|
|
||||||
|
if(match){
|
||||||
|
|
||||||
|
let currentDate = new Date();
|
||||||
|
let newLogin = currentDate.toLocaleString();
|
||||||
|
await User.update({lastLogin: newLogin}, {where: {UUID:existingUser.UUID}});
|
||||||
|
|
||||||
|
req.session.user = existingUser.username;
|
||||||
|
req.session.UUID = existingUser.UUID;
|
||||||
|
req.session.role = existingUser.role;
|
||||||
|
req.session.avatar = existingUser.avatar;
|
||||||
|
|
||||||
|
const syslog = await Syslog.create({
|
||||||
|
user: req.session.user,
|
||||||
|
email: email,
|
||||||
|
event: "Successful Login",
|
||||||
|
message: "User logged in successfully",
|
||||||
|
ip: req.socket.remoteAddress
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
res.redirect("/");
|
||||||
|
}else{
|
||||||
|
|
||||||
|
const syslog = await Syslog.create({
|
||||||
|
user: null,
|
||||||
|
email: email,
|
||||||
|
event: "Bad Login",
|
||||||
|
message: "Invalid password",
|
||||||
|
ip: req.socket.remoteAddress
|
||||||
|
});
|
||||||
|
|
||||||
|
res.render("login",{
|
||||||
|
"error":"Invalid password",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
res.render("login",{
|
||||||
|
"error":"User with that email does not exist.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
res.status(400);
|
||||||
|
res.render("login",{
|
||||||
|
"error":"Please fill in all the fields.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,13 @@
|
||||||
const User = require('../database/UserModel');
|
import { docker } from '../app.js';
|
||||||
|
|
||||||
exports.Networks = async function(req, res) {
|
|
||||||
|
|
||||||
|
|
||||||
// Render the home page
|
export const Networks = async function(req, res) {
|
||||||
res.render("pages/users", {
|
|
||||||
|
|
||||||
|
res.render("networks", {
|
||||||
name: req.session.user,
|
name: req.session.user,
|
||||||
role: req.session.role,
|
role: req.session.role,
|
||||||
avatar: req.session.avatar,
|
avatar: req.session.avatar,
|
||||||
isLoggedIn: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
96
controllers/register.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import { User, Syslog } from '../database/models.js';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
|
let SECRET = process.env.SECRET || "MrWiskers"
|
||||||
|
|
||||||
|
export const Register = function(req,res){
|
||||||
|
if(req.session.user){
|
||||||
|
res.redirect("/logout");
|
||||||
|
} else {
|
||||||
|
res.render("register",{
|
||||||
|
"error":"",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const submitRegister = async function(req,res){
|
||||||
|
|
||||||
|
let { name, username, email, password, confirmPassword, avatar, tos, secret } = req.body;
|
||||||
|
|
||||||
|
|
||||||
|
if (secret != SECRET) {
|
||||||
|
const syslog = await Syslog.create({
|
||||||
|
user: username,
|
||||||
|
email: email,
|
||||||
|
event: "Failed Registration",
|
||||||
|
message: "Invalid secret",
|
||||||
|
ip: req.socket.remoteAddress
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if((name && email && password && confirmPassword && username && tos) && (secret == SECRET) && (password == confirmPassword)){
|
||||||
|
|
||||||
|
async function userRole () {
|
||||||
|
let userCount = await User.count();
|
||||||
|
if(userCount == 0){
|
||||||
|
return "admin";
|
||||||
|
}else{
|
||||||
|
return "user";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let existingUser = await User.findOne({ where: {email:email}});
|
||||||
|
if(!existingUser){
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await User.create({
|
||||||
|
name: name,
|
||||||
|
username: username,
|
||||||
|
email: email,
|
||||||
|
password: bcrypt.hashSync(password,10),
|
||||||
|
role: await userRole(),
|
||||||
|
group: 'all',
|
||||||
|
avatar: `<img src="img/avatars/${avatar}">`
|
||||||
|
});
|
||||||
|
|
||||||
|
// make sure the user was created and get the UUID.
|
||||||
|
let newUser = await User.findOne({ where: {email:email}});
|
||||||
|
let match = await bcrypt.compare(password,newUser.password);
|
||||||
|
|
||||||
|
if(match){
|
||||||
|
req.session.user = newUser.username;
|
||||||
|
req.session.UUID = newUser.UUID;
|
||||||
|
req.session.role = newUser.role;
|
||||||
|
req.session.avatar = newUser.avatar;
|
||||||
|
|
||||||
|
const syslog = await Syslog.create({
|
||||||
|
user: req.session.user,
|
||||||
|
email: email,
|
||||||
|
event: "Successful Registration",
|
||||||
|
message: "User registered successfully",
|
||||||
|
ip: req.socket.remoteAddress
|
||||||
|
});
|
||||||
|
|
||||||
|
res.redirect("/");
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
res.render("register",{
|
||||||
|
"error":"Something went wrong when creating account.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// return an error.
|
||||||
|
res.render("register",{
|
||||||
|
"error":"User with that email already exists.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Redirect to the signup page.
|
||||||
|
res.render("register",{
|
||||||
|
"error":"Please fill in all the fields and accept TOS.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,9 @@
|
||||||
const User = require('../database/UserModel.js');
|
|
||||||
const Server = require('../database/ServerModel.js');
|
|
||||||
|
|
||||||
exports.Settings = async function(req, res) {
|
export const Settings = (req, res) => {
|
||||||
|
|
||||||
// Render the home page
|
res.render("settings", {
|
||||||
res.render("pages/settings", {
|
|
||||||
name: req.session.user,
|
name: req.session.user,
|
||||||
role: req.session.role,
|
role: req.session.role,
|
||||||
avatar: req.session.avatar,
|
avatar: req.session.avatar,
|
||||||
isLoggedIn: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
36
controllers/syslogs.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { Syslog } from '../database/models.js';
|
||||||
|
|
||||||
|
export const Syslogs = async function(req, res) {
|
||||||
|
|
||||||
|
let logs = '';
|
||||||
|
|
||||||
|
const syslogs = await Syslog.findAll({
|
||||||
|
order: [
|
||||||
|
['id', 'DESC']
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const log of syslogs) {
|
||||||
|
let date = (log.createdAt).toDateString();
|
||||||
|
let time = (log.createdAt).toLocaleTimeString();
|
||||||
|
let datetime = `${time} ${date}`;
|
||||||
|
|
||||||
|
logs += `<tr>
|
||||||
|
<td class="sort-id">${log.id}</td>
|
||||||
|
<td class="sort-user">${log.user}</td>
|
||||||
|
<td class="sort-email">${log.email}</td>
|
||||||
|
<td class="sort-event">${log.event}</td>
|
||||||
|
<td class="sort-message">${log.message}</td>
|
||||||
|
<td class="sort-ip">${log.ip}</td>
|
||||||
|
<td class="sort-datetime">${datetime}</td>
|
||||||
|
</tr>`
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render("syslogs", {
|
||||||
|
name: req.session.user || 'Dev',
|
||||||
|
role: req.session.role || 'Dev',
|
||||||
|
avatar: req.session.avatar || '<img src="/img/avatars/rus.jpg">',
|
||||||
|
logs: logs
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
const User = require('../database/UserModel');
|
import { User } from '../database/models.js';
|
||||||
|
|
||||||
exports.Users = async function(req, res) {
|
export const Users = async (req, res) => {
|
||||||
|
|
||||||
let user_list = `
|
let user_list = `
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -12,36 +12,48 @@ exports.Users = async function(req, res) {
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>UUID</th>
|
<th>UUID</th>
|
||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
|
<th>Last Login</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>`
|
</tr>`
|
||||||
|
|
||||||
let users = await User.findAll();
|
let allUsers = await User.findAll();
|
||||||
users.forEach((account) => {
|
allUsers.forEach((account) => {
|
||||||
full_name = account.first_name + ' ' + account.last_name;
|
|
||||||
user_info = `
|
let active = '<span class="badge badge-outline text-green">Active</span>'
|
||||||
|
let lastLogin = new Date(account.lastLogin);
|
||||||
|
let currentDate = new Date();
|
||||||
|
let days = Math.floor((currentDate - lastLogin) / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (days > 30) {
|
||||||
|
active = '<span class="badge badge-outline text-grey">Inactive</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let info = `
|
||||||
<tr>
|
<tr>
|
||||||
<td><input class="form-check-input" type="checkbox"></td>
|
<td><input class="form-check-input" type="checkbox"></td>
|
||||||
<td>${user.id}</td>
|
<td>${account.id}</td>
|
||||||
<td><span class="avatar me-2">${account.avatar}</span></td>
|
<td><span class="avatar me-2">${account.avatar}</span></td>
|
||||||
<td>${full_name}</td>
|
<td>${account.name}</td>
|
||||||
<td>${account.username}</td>
|
<td>${account.username}</td>
|
||||||
<td>${account.email}</td>
|
<td>${account.email}</td>
|
||||||
<td>${account.UUID}</td>
|
<td>${account.UUID}</td>
|
||||||
<td>${account.role}</td>
|
<td>${account.role}</td>
|
||||||
<td><span class="badge badge-outline text-green">Active</span></td>
|
<td>${account.lastLogin}</td>
|
||||||
|
<td>${active}</td>
|
||||||
<td><a href="#" class="btn">Edit</a></td>
|
<td><a href="#" class="btn">Edit</a></td>
|
||||||
</tr>`
|
</tr>`
|
||||||
|
|
||||||
user_list += user_info;
|
user_list += info;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render the home page
|
|
||||||
res.render("pages/users", {
|
res.render("users", {
|
||||||
name: req.session.user,
|
name: req.session.user,
|
||||||
role: req.session.role,
|
role: req.session.role,
|
||||||
avatar: req.session.avatar,
|
avatar: req.session.avatar,
|
||||||
isLoggedIn: true,
|
|
||||||
user_list: user_list
|
user_list: user_list
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
const User = require('../database/UserModel');
|
import { docker } from '../app.js';
|
||||||
|
|
||||||
exports.Volumes = async function(req, res) {
|
export const Volumes = (req, res) => {
|
||||||
|
res.render("volumes", {
|
||||||
|
|
||||||
|
|
||||||
// Render the home page
|
|
||||||
res.render("pages/volumes", {
|
|
||||||
name: req.session.user,
|
name: req.session.user,
|
||||||
role: req.session.role,
|
role: req.session.role,
|
||||||
avatar: req.session.avatar,
|
avatar: req.session.avatar,
|
||||||
isLoggedIn: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,72 +0,0 @@
|
||||||
const { Sequelize, DataTypes } = require('sequelize');
|
|
||||||
|
|
||||||
const sequelize = new Sequelize({
|
|
||||||
dialect: 'sqlite',
|
|
||||||
storage: './database/db.sqlite',
|
|
||||||
logging: false
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const Containers = sequelize.define('Containers', {
|
|
||||||
// Model attributes are defined here
|
|
||||||
id: {
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: DataTypes.STRING,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
visibility: {
|
|
||||||
type: DataTypes.STRING
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: DataTypes.STRING
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
group: {
|
|
||||||
type: DataTypes.STRING
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
start: {
|
|
||||||
type: DataTypes.JSON
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
type: DataTypes.JSON
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
pause: {
|
|
||||||
type: DataTypes.JSON
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
restart: {
|
|
||||||
type: DataTypes.JSON
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
remove: {
|
|
||||||
type: DataTypes.JSON
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
logs: {
|
|
||||||
type: DataTypes.JSON
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
type: DataTypes.JSON
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
async function syncModel() {
|
|
||||||
await sequelize.sync();
|
|
||||||
console.log('Containers model synced');
|
|
||||||
}
|
|
||||||
|
|
||||||
syncModel();
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = Containers;
|
|
|
@ -1,46 +0,0 @@
|
||||||
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
|
|
||||||
},
|
|
||||||
caddy: {
|
|
||||||
type: DataTypes.STRING
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function syncModel() {
|
|
||||||
await sequelize.sync();
|
|
||||||
console.log('Server model synced');
|
|
||||||
}
|
|
||||||
|
|
||||||
syncModel();
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = Server;
|
|
|
@ -1,63 +0,0 @@
|
||||||
const { Sequelize, DataTypes } = require('sequelize');
|
|
||||||
|
|
||||||
const sequelize = new Sequelize({
|
|
||||||
dialect: 'sqlite',
|
|
||||||
storage: './database/db.sqlite',
|
|
||||||
logging: false
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const User = sequelize.define('User', {
|
|
||||||
// Model attributes are defined here
|
|
||||||
id: {
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true
|
|
||||||
},
|
|
||||||
first_name: {
|
|
||||||
type: DataTypes.STRING,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
last_name: {
|
|
||||||
type: DataTypes.STRING
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
username: {
|
|
||||||
type: DataTypes.STRING
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: DataTypes.STRING
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
type: DataTypes.STRING,
|
|
||||||
// allowNull: false
|
|
||||||
},
|
|
||||||
role: {
|
|
||||||
type: DataTypes.STRING
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
group: {
|
|
||||||
type: DataTypes.STRING
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
type: DataTypes.STRING
|
|
||||||
// allowNull defaults to true
|
|
||||||
},
|
|
||||||
UUID: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
defaultValue: DataTypes.UUIDV4
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function syncModel() {
|
|
||||||
await sequelize.sync();
|
|
||||||
console.log('User model synced');
|
|
||||||
}
|
|
||||||
|
|
||||||
syncModel();
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = User;
|
|
162
database/models.js
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
import { Sequelize, DataTypes } from 'sequelize';
|
||||||
|
|
||||||
|
// let SQLITE_PASS = process.env.SQLITE_PASS || 'some_long_elaborate_password';
|
||||||
|
|
||||||
|
// export const sequelize = new Sequelize('dweebui', 'dweebui', SQLITE_PASS, {
|
||||||
|
// dialect: 'sqlite',
|
||||||
|
// dialectModulePath: '@journeyapps/sqlcipher',
|
||||||
|
// storage: './database/database.sqlite',
|
||||||
|
// logging: false,
|
||||||
|
// });
|
||||||
|
|
||||||
|
export const sequelize = new Sequelize({
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: './database/database.sqlite',
|
||||||
|
logging: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const User = sequelize.define('User', {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
lastLogin: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
UUID: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Container = sequelize.define('Container', {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
visibility: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Permission = sequelize.define('Permission', {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
containerName: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
containerID: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
userID: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
install: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
uninstall: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
upgrade: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
stop: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
restart: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
pause: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
logs: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
hide: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
view: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
reset_view: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const Syslog = sequelize.define('Syslog', {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
ip : {
|
||||||
|
type: DataTypes.STRING
|
||||||
|
},
|
||||||
|
});
|
|
@ -2,9 +2,9 @@ version: "3.9"
|
||||||
services:
|
services:
|
||||||
dweebui:
|
dweebui:
|
||||||
container_name: dweebui
|
container_name: dweebui
|
||||||
image: lllllllillllllillll/dweebui:v0.09-dev
|
# image: lllllllillllllillll/dweebui:v0.20
|
||||||
# build:
|
build:
|
||||||
# context: .
|
context: .
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
PORT: 8000
|
PORT: 8000
|
||||||
|
@ -14,17 +14,13 @@ services:
|
||||||
- 8000:8000
|
- 8000:8000
|
||||||
volumes:
|
volumes:
|
||||||
- dweebui:/app
|
- dweebui:/app
|
||||||
- caddyfiles:/app/caddyfiles
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
#- ./custom-templates.json:/app/custom-templates.json
|
|
||||||
#- ./composefiles:/app/composefiles
|
|
||||||
networks:
|
networks:
|
||||||
- dweeb_network
|
- dweebui_net
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
dweebui:
|
dweebui:
|
||||||
caddyfiles:
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
dweeb_network:
|
dweebui_net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
|
@ -1,205 +0,0 @@
|
||||||
const { writeFileSync, mkdirSync, readFileSync } = require("fs");
|
|
||||||
const yaml = require('js-yaml');
|
|
||||||
|
|
||||||
const { exec, execSync } = require("child_process");
|
|
||||||
|
|
||||||
const { docker } = require('./system');
|
|
||||||
|
|
||||||
var DockerodeCompose = require('dockerode-compose');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.install = async function (data) {
|
|
||||||
|
|
||||||
console.log(`[Start of install function]`);
|
|
||||||
|
|
||||||
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 ((service_name.includes('caddy')) || (name.includes('caddy'))) {
|
|
||||||
req.app.locals.caddy = 'enabled';
|
|
||||||
}
|
|
||||||
|
|
||||||
let docker_volumes = [];
|
|
||||||
|
|
||||||
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 volume is on and neither bind or container is empty, it's a bind mount (ex /mnt/user/appdata/config:/config )
|
|
||||||
if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] != '') && (data[`volume_${i}_container`] != '')) {
|
|
||||||
compose_file += `\n - ${data[`volume_${i}_bind`]}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// if bind is empty create a docker volume (ex container_name_config:/config) convert any '/' in container name to '_'
|
|
||||||
else if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] == '') && (data[`volume_${i}_container`] != '')) {
|
|
||||||
let volume_name = data[`volume_${i}_container`].replace(/\//g, '_');
|
|
||||||
compose_file += `\n - ${name}_${volume_name}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
|
||||||
docker_volumes.push(`${name}_${volume_name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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]`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// add any docker volumes to the docker-compose file
|
|
||||||
if ( docker_volumes.length > 0 ) {
|
|
||||||
compose_file += `\n`
|
|
||||||
compose_file += `\nvolumes:`
|
|
||||||
|
|
||||||
// check docker_volumes for duplicates and remove them completely
|
|
||||||
docker_volumes = docker_volumes.filter((item, index) => docker_volumes.indexOf(item) === index)
|
|
||||||
|
|
||||||
for (let i = 0; i < docker_volumes.length; i++) {
|
|
||||||
if ( docker_volumes[i] != '') {
|
|
||||||
compose_file += `\n ${docker_volumes[i]}:`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
|
||||||
writeFileSync(`./appdata/${name}/docker-compose.yml`, compose_file, function (err) { console.log(err) });
|
|
||||||
|
|
||||||
} catch { console.log('error creating directory or compose file') }
|
|
||||||
|
|
||||||
try {
|
|
||||||
var compose = new DockerodeCompose(docker, `./appdata/${name}/docker-compose.yml`, `${name}`);
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
await compose.pull();
|
|
||||||
await compose.up();
|
|
||||||
})();
|
|
||||||
|
|
||||||
} catch { console.log('error running compose file')}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.uninstall = async function (data) {
|
|
||||||
|
|
||||||
|
|
||||||
if (data.confirm == 'Yes') {
|
|
||||||
|
|
||||||
|
|
||||||
var containerName = docker.getContainer(`${data.service_name}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
containerName.stop(function (err, data) {
|
|
||||||
if (data) {
|
|
||||||
containerName.remove(function (err, data) {
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
containerName.remove(function (err, data) {
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
const { writeFileSync, mkdirSync, readFileSync } = require("fs");
|
|
||||||
const yaml = require('js-yaml');
|
|
||||||
|
|
||||||
const { execSync } = require("child_process");
|
|
||||||
|
|
||||||
const { docker } = require('./system');
|
|
||||||
|
|
||||||
var DockerodeCompose = require('dockerode-compose');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.install = async function (data) {
|
|
||||||
|
|
||||||
console.log(`[Start of install function]`);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
let docker_volumes = [];
|
|
||||||
|
|
||||||
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 volume is on and neither bind or container is empty, it's a bind mount (ex /mnt/user/appdata/config:/config )
|
|
||||||
if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] != '') && (data[`volume_${i}_container`] != '')) {
|
|
||||||
compose_file += `\n - ${data[`volume_${i}_bind`]}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// if bind is empty create a docker volume (ex container_name_config:/config) convert any '/' in container name to '_'
|
|
||||||
else if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] == '') && (data[`volume_${i}_container`] != '')) {
|
|
||||||
let volume_name = data[`volume_${i}_container`].replace(/\//g, '_');
|
|
||||||
compose_file += `\n - ${name}_${volume_name}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
|
||||||
docker_volumes.push(`${name}_${volume_name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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]`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// add any docker volumes to the docker-compose file
|
|
||||||
if ( docker_volumes.length > 0 ) {
|
|
||||||
compose_file += `\n`
|
|
||||||
compose_file += `\nvolumes:`
|
|
||||||
|
|
||||||
// check docker_volumes for duplicates and remove them completely
|
|
||||||
docker_volumes = docker_volumes.filter((item, index) => docker_volumes.indexOf(item) === index)
|
|
||||||
|
|
||||||
for (let i = 0; i < docker_volumes.length; i++) {
|
|
||||||
if ( docker_volumes[i] != '') {
|
|
||||||
compose_file += `\n ${docker_volumes[i]}:`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
|
||||||
writeFileSync(`./appdata/${name}/docker-compose.yml`, compose_file, function (err) { console.log(err) });
|
|
||||||
|
|
||||||
} catch { console.log('error creating directory or compose file') }
|
|
||||||
|
|
||||||
try {
|
|
||||||
var compose = new DockerodeCompose(docker, `./appdata/${name}/docker-compose.yml`, `${name}`);
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
await compose.pull();
|
|
||||||
await compose.up();
|
|
||||||
})();
|
|
||||||
|
|
||||||
} catch { console.log('error running compose file')}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.uninstall = async function (data) {
|
|
||||||
if (data.confirm == 'Yes') {
|
|
||||||
console.log(`Uninstalling ${data.service_name}: ${data}`);
|
|
||||||
var containerName = docker.getContainer(`${data.service_name}`);
|
|
||||||
try {
|
|
||||||
await containerName.stop();
|
|
||||||
console.log(`Stopped ${data.service_name} container`);
|
|
||||||
} catch {
|
|
||||||
console.log(`Error stopping ${data.service_name} container`);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await containerName.remove();
|
|
||||||
console.log(`Removed ${data.service_name} container`);
|
|
||||||
} catch {
|
|
||||||
console.log(`Error removing ${data.service_name} container`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,382 +0,0 @@
|
||||||
const { currentLoad, mem, networkStats, fsSize, dockerContainerStats, dockerImages, networkInterfaces, dockerInfo } = require('systeminformation');
|
|
||||||
var Docker = require('dockerode');
|
|
||||||
var docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
|
||||||
const { dashCard } = require('../components/dashCard');
|
|
||||||
const { Readable } = require('stream');
|
|
||||||
|
|
||||||
const Containers = require('../database/ContainerModel');
|
|
||||||
|
|
||||||
// export docker
|
|
||||||
module.exports.docker = docker;
|
|
||||||
|
|
||||||
|
|
||||||
let IPv4 = '';
|
|
||||||
networkInterfaces().then(data => {
|
|
||||||
IPv4 = data[0].ip4;
|
|
||||||
});
|
|
||||||
|
|
||||||
let hidden = '';
|
|
||||||
module.exports.hiddenContainers = async function () {
|
|
||||||
hidden = await Containers.findAll({ where: {visibility:false}});
|
|
||||||
hidden = hidden.map(a => a.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
|
|
||||||
|
|
||||||
if (!hidden.includes(container.Names[0].slice(1))) {
|
|
||||||
|
|
||||||
let imageVersion = container.Image.split('/');
|
|
||||||
let service = imageVersion[imageVersion.length - 1].split(':')[0];
|
|
||||||
|
|
||||||
let containerId = docker.getContainer(container.Id);
|
|
||||||
let containerInfo = await containerId.inspect();
|
|
||||||
|
|
||||||
// Get ports //////////////////////////
|
|
||||||
let ports_list = [];
|
|
||||||
try {
|
|
||||||
for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) {
|
|
||||||
let ports = {
|
|
||||||
check : 'checked',
|
|
||||||
external: value[0].HostPort,
|
|
||||||
internal: key.split('/')[0],
|
|
||||||
protocol: key.split('/')[1]
|
|
||||||
}
|
|
||||||
ports_list.push(ports);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// console.log('no ports')
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < 12; i++) {
|
|
||||||
if (ports_list[i] == undefined) {
|
|
||||||
let ports = {
|
|
||||||
check : '',
|
|
||||||
external: '',
|
|
||||||
internal: '',
|
|
||||||
protocol: ''
|
|
||||||
}
|
|
||||||
ports_list[i] = ports;
|
|
||||||
}
|
|
||||||
} /////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
// Get volumes ////////////////////////
|
|
||||||
let volumes_list = [];
|
|
||||||
try { for (const [key, value] of Object.entries(containerInfo.HostConfig.Binds)) {
|
|
||||||
let volumes = {
|
|
||||||
check : 'checked',
|
|
||||||
bind: value.split(':')[0],
|
|
||||||
container: value.split(':')[1],
|
|
||||||
readwrite: value.split(':')[2]
|
|
||||||
}
|
|
||||||
volumes_list.push(volumes);
|
|
||||||
}} catch {
|
|
||||||
// console.log('no volumes')
|
|
||||||
}
|
|
||||||
for (let i = 0; i < 12; i++) {
|
|
||||||
if (volumes_list[i] == undefined) {
|
|
||||||
let volumes = {
|
|
||||||
check : '',
|
|
||||||
bind: '',
|
|
||||||
container: '',
|
|
||||||
readwrite: ''
|
|
||||||
}
|
|
||||||
volumes_list[i] = volumes;
|
|
||||||
}
|
|
||||||
} /////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
// Get environment variables.
|
|
||||||
let environment_variables = [];
|
|
||||||
try { for (const [key, value] of Object.entries(containerInfo.Config.Env)) {
|
|
||||||
let env = {
|
|
||||||
check : 'checked',
|
|
||||||
name: value.split('=')[0],
|
|
||||||
default: value.split('=')[1]
|
|
||||||
}
|
|
||||||
environment_variables.push(env);
|
|
||||||
}} catch { console.log('no env') }
|
|
||||||
for (let i = 0; i < 12; i++) {
|
|
||||||
if (environment_variables[i] == undefined) {
|
|
||||||
let env = {
|
|
||||||
check : '',
|
|
||||||
name: '',
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
environment_variables[i] = env;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get labels.
|
|
||||||
let labels = [];
|
|
||||||
for (const [key, value] of Object.entries(containerInfo.Config.Labels)) {
|
|
||||||
let label = {
|
|
||||||
check : 'checked',
|
|
||||||
name: key,
|
|
||||||
value: value
|
|
||||||
}
|
|
||||||
labels.push(label);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < 12; i++) {
|
|
||||||
if (labels[i] == undefined) {
|
|
||||||
let label = {
|
|
||||||
check : '',
|
|
||||||
name: '',
|
|
||||||
value: ''
|
|
||||||
}
|
|
||||||
labels[i] = label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let container_info = {
|
|
||||||
name: container.Names[0].slice(1),
|
|
||||||
service: service,
|
|
||||||
id: container.Id,
|
|
||||||
state: container.State,
|
|
||||||
image: container.Image,
|
|
||||||
external_port: ports_list[0].external || 0,
|
|
||||||
internal_port: ports_list[0].internal || 0,
|
|
||||||
ports: ports_list,
|
|
||||||
volumes: volumes_list,
|
|
||||||
environment_variables: environment_variables,
|
|
||||||
labels: labels,
|
|
||||||
IPv4: IPv4,
|
|
||||||
style: "Compact"
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
|
|
||||||
if (!hidden.includes(container.Names[0].slice(1))) {
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.containerExec = async function (data) {
|
|
||||||
|
|
||||||
let { container, command } = data;
|
|
||||||
|
|
||||||
var containerName = docker.getContainer(container);
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
Cmd: ['/bin/sh', '-c', command],
|
|
||||||
AttachStdout: true,
|
|
||||||
AttachStderr: true,
|
|
||||||
Tty: true
|
|
||||||
};
|
|
||||||
|
|
||||||
containerName.exec(options, function (err, exec) {
|
|
||||||
if (err) return;
|
|
||||||
|
|
||||||
exec.start(function (err, stream) {
|
|
||||||
if (err) return;
|
|
||||||
|
|
||||||
containerName.modem.demuxStream(stream, process.stdout, process.stderr);
|
|
||||||
|
|
||||||
exec.inspect(function (err, data) {
|
|
||||||
if (err) return;
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.containerLogs = function (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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.dockerImages = async function () {
|
|
||||||
|
|
||||||
// get the names, tags, status, created date, and size of all images
|
|
||||||
|
|
||||||
const data1 = await dockerImages({ all: true });
|
|
||||||
|
|
||||||
const data2 = await docker.listImages({ all: true });
|
|
||||||
|
|
||||||
// for ( i = 0; i < data.length; i++) {
|
|
||||||
// console.log(`Image ${i}:`)
|
|
||||||
// console.log(`repoTags: ${data[i].repoTags}`)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// console.log(data1);
|
|
||||||
|
|
||||||
console.log(data2);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.dockerVolumes = async function () {
|
|
||||||
let volume_list = '';
|
|
||||||
|
|
||||||
const data = await docker.listVolumes();
|
|
||||||
|
|
||||||
return data;
|
|
||||||
|
|
||||||
// for (const volume of data.Volumes) {
|
|
||||||
|
|
||||||
// let volume_info = {
|
|
||||||
// name: volume.Name,
|
|
||||||
// style: "Compact"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let dockerCard = dashCard(volume_info);
|
|
||||||
|
|
||||||
// volume_list += dockerCard;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return volume_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.dockerNetworks = async function () {
|
|
||||||
let network_list = '';
|
|
||||||
|
|
||||||
const data = await docker.listNetworks();
|
|
||||||
|
|
||||||
return data;
|
|
||||||
|
|
||||||
// for (const network of data) {
|
|
||||||
|
|
||||||
// let network_info = {
|
|
||||||
// name: network.Name,
|
|
||||||
// style: "Compact"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let dockerCard = dashCard(network_info);
|
|
||||||
|
|
||||||
// network_list += dockerCard;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return network_list;
|
|
||||||
}
|
|
2355
package-lock.json
generated
28
package.json
|
@ -1,25 +1,35 @@
|
||||||
{
|
{
|
||||||
"name": "dweebui",
|
"name": "dweebui",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"description": "A web UI for Docker",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "mocha --require @babel/register --exit"
|
||||||
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^5.1.0",
|
"@babel/register": "^7.23.7",
|
||||||
"child_process": "^1.0.2",
|
"@journeyapps/sqlcipher": "^5.3.1",
|
||||||
|
"@socket.io/admin-ui": "^0.5.1",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"chai": "^5.0.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"dockerode": "^4.0.0",
|
"cors": "^2.8.5",
|
||||||
"dockerode-compose": "^1.4.0",
|
"dockerode": "^4.0.1",
|
||||||
"ejs": "^3.1.9",
|
"ejs": "^3.1.9",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
"js-yaml": "^4.1.0",
|
"mocha": "^10.2.0",
|
||||||
"sequelize": "^6.35.2",
|
"sequelize": "^6.35.2",
|
||||||
"socket.io": "^4.6.1",
|
"sinon": "^17.0.1",
|
||||||
|
"socket.io": "^4.7.2",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
"systeminformation": "^5.21.20"
|
"stream": "^0.0.2",
|
||||||
},
|
"supertest": "^6.3.3",
|
||||||
"description": ""
|
"systeminformation": "^5.21.22"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
.meter {
|
.meter {
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
height: 15px; /* Can be anything */
|
height: 15px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
72
public/css/tabler.min.css
vendored
|
@ -11462,47 +11462,38 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-0 {
|
.column-gap-0 {
|
||||||
-moz-column-gap: 0 !important;
|
|
||||||
column-gap: 0 !important
|
column-gap: 0 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-1 {
|
.column-gap-1 {
|
||||||
-moz-column-gap: .25rem !important;
|
|
||||||
column-gap: .25rem !important
|
column-gap: .25rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-2 {
|
.column-gap-2 {
|
||||||
-moz-column-gap: .5rem !important;
|
|
||||||
column-gap: .5rem !important
|
column-gap: .5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-3 {
|
.column-gap-3 {
|
||||||
-moz-column-gap: 1rem !important;
|
|
||||||
column-gap: 1rem !important
|
column-gap: 1rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-4 {
|
.column-gap-4 {
|
||||||
-moz-column-gap: 1.5rem !important;
|
|
||||||
column-gap: 1.5rem !important
|
column-gap: 1.5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-5 {
|
.column-gap-5 {
|
||||||
-moz-column-gap: 2rem !important;
|
|
||||||
column-gap: 2rem !important
|
column-gap: 2rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-6 {
|
.column-gap-6 {
|
||||||
-moz-column-gap: 3rem !important;
|
|
||||||
column-gap: 3rem !important
|
column-gap: 3rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-7 {
|
.column-gap-7 {
|
||||||
-moz-column-gap: 5rem !important;
|
|
||||||
column-gap: 5rem !important
|
column-gap: 5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-8 {
|
.column-gap-8 {
|
||||||
-moz-column-gap: 8rem !important;
|
|
||||||
column-gap: 8rem !important
|
column-gap: 8rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12910,17 +12901,14 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-2 {
|
.columns-2 {
|
||||||
-moz-columns: 2 !important;
|
|
||||||
columns: 2 !important
|
columns: 2 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-3 {
|
.columns-3 {
|
||||||
-moz-columns: 3 !important;
|
|
||||||
columns: 3 !important
|
columns: 3 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-4 {
|
.columns-4 {
|
||||||
-moz-columns: 4 !important;
|
|
||||||
columns: 4 !important
|
columns: 4 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13821,47 +13809,38 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-sm-0 {
|
.column-gap-sm-0 {
|
||||||
-moz-column-gap: 0 !important;
|
|
||||||
column-gap: 0 !important
|
column-gap: 0 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-sm-1 {
|
.column-gap-sm-1 {
|
||||||
-moz-column-gap: .25rem !important;
|
|
||||||
column-gap: .25rem !important
|
column-gap: .25rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-sm-2 {
|
.column-gap-sm-2 {
|
||||||
-moz-column-gap: .5rem !important;
|
|
||||||
column-gap: .5rem !important
|
column-gap: .5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-sm-3 {
|
.column-gap-sm-3 {
|
||||||
-moz-column-gap: 1rem !important;
|
|
||||||
column-gap: 1rem !important
|
column-gap: 1rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-sm-4 {
|
.column-gap-sm-4 {
|
||||||
-moz-column-gap: 1.5rem !important;
|
|
||||||
column-gap: 1.5rem !important
|
column-gap: 1.5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-sm-5 {
|
.column-gap-sm-5 {
|
||||||
-moz-column-gap: 2rem !important;
|
|
||||||
column-gap: 2rem !important
|
column-gap: 2rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-sm-6 {
|
.column-gap-sm-6 {
|
||||||
-moz-column-gap: 3rem !important;
|
|
||||||
column-gap: 3rem !important
|
column-gap: 3rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-sm-7 {
|
.column-gap-sm-7 {
|
||||||
-moz-column-gap: 5rem !important;
|
|
||||||
column-gap: 5rem !important
|
column-gap: 5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-sm-8 {
|
.column-gap-sm-8 {
|
||||||
-moz-column-gap: 8rem !important;
|
|
||||||
column-gap: 8rem !important
|
column-gap: 8rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13878,17 +13857,14 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-sm-2 {
|
.columns-sm-2 {
|
||||||
-moz-columns: 2 !important;
|
|
||||||
columns: 2 !important
|
columns: 2 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-sm-3 {
|
.columns-sm-3 {
|
||||||
-moz-columns: 3 !important;
|
|
||||||
columns: 3 !important
|
columns: 3 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-sm-4 {
|
.columns-sm-4 {
|
||||||
-moz-columns: 4 !important;
|
|
||||||
columns: 4 !important
|
columns: 4 !important
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14790,47 +14766,38 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-md-0 {
|
.column-gap-md-0 {
|
||||||
-moz-column-gap: 0 !important;
|
|
||||||
column-gap: 0 !important
|
column-gap: 0 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-md-1 {
|
.column-gap-md-1 {
|
||||||
-moz-column-gap: .25rem !important;
|
|
||||||
column-gap: .25rem !important
|
column-gap: .25rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-md-2 {
|
.column-gap-md-2 {
|
||||||
-moz-column-gap: .5rem !important;
|
|
||||||
column-gap: .5rem !important
|
column-gap: .5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-md-3 {
|
.column-gap-md-3 {
|
||||||
-moz-column-gap: 1rem !important;
|
|
||||||
column-gap: 1rem !important
|
column-gap: 1rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-md-4 {
|
.column-gap-md-4 {
|
||||||
-moz-column-gap: 1.5rem !important;
|
|
||||||
column-gap: 1.5rem !important
|
column-gap: 1.5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-md-5 {
|
.column-gap-md-5 {
|
||||||
-moz-column-gap: 2rem !important;
|
|
||||||
column-gap: 2rem !important
|
column-gap: 2rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-md-6 {
|
.column-gap-md-6 {
|
||||||
-moz-column-gap: 3rem !important;
|
|
||||||
column-gap: 3rem !important
|
column-gap: 3rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-md-7 {
|
.column-gap-md-7 {
|
||||||
-moz-column-gap: 5rem !important;
|
|
||||||
column-gap: 5rem !important
|
column-gap: 5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-md-8 {
|
.column-gap-md-8 {
|
||||||
-moz-column-gap: 8rem !important;
|
|
||||||
column-gap: 8rem !important
|
column-gap: 8rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14847,17 +14814,14 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-md-2 {
|
.columns-md-2 {
|
||||||
-moz-columns: 2 !important;
|
|
||||||
columns: 2 !important
|
columns: 2 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-md-3 {
|
.columns-md-3 {
|
||||||
-moz-columns: 3 !important;
|
|
||||||
columns: 3 !important
|
columns: 3 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-md-4 {
|
.columns-md-4 {
|
||||||
-moz-columns: 4 !important;
|
|
||||||
columns: 4 !important
|
columns: 4 !important
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15759,47 +15723,38 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-lg-0 {
|
.column-gap-lg-0 {
|
||||||
-moz-column-gap: 0 !important;
|
|
||||||
column-gap: 0 !important
|
column-gap: 0 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-lg-1 {
|
.column-gap-lg-1 {
|
||||||
-moz-column-gap: .25rem !important;
|
|
||||||
column-gap: .25rem !important
|
column-gap: .25rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-lg-2 {
|
.column-gap-lg-2 {
|
||||||
-moz-column-gap: .5rem !important;
|
|
||||||
column-gap: .5rem !important
|
column-gap: .5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-lg-3 {
|
.column-gap-lg-3 {
|
||||||
-moz-column-gap: 1rem !important;
|
|
||||||
column-gap: 1rem !important
|
column-gap: 1rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-lg-4 {
|
.column-gap-lg-4 {
|
||||||
-moz-column-gap: 1.5rem !important;
|
|
||||||
column-gap: 1.5rem !important
|
column-gap: 1.5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-lg-5 {
|
.column-gap-lg-5 {
|
||||||
-moz-column-gap: 2rem !important;
|
|
||||||
column-gap: 2rem !important
|
column-gap: 2rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-lg-6 {
|
.column-gap-lg-6 {
|
||||||
-moz-column-gap: 3rem !important;
|
|
||||||
column-gap: 3rem !important
|
column-gap: 3rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-lg-7 {
|
.column-gap-lg-7 {
|
||||||
-moz-column-gap: 5rem !important;
|
|
||||||
column-gap: 5rem !important
|
column-gap: 5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-lg-8 {
|
.column-gap-lg-8 {
|
||||||
-moz-column-gap: 8rem !important;
|
|
||||||
column-gap: 8rem !important
|
column-gap: 8rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15816,17 +15771,14 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-lg-2 {
|
.columns-lg-2 {
|
||||||
-moz-columns: 2 !important;
|
|
||||||
columns: 2 !important
|
columns: 2 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-lg-3 {
|
.columns-lg-3 {
|
||||||
-moz-columns: 3 !important;
|
|
||||||
columns: 3 !important
|
columns: 3 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-lg-4 {
|
.columns-lg-4 {
|
||||||
-moz-columns: 4 !important;
|
|
||||||
columns: 4 !important
|
columns: 4 !important
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16728,47 +16680,38 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xl-0 {
|
.column-gap-xl-0 {
|
||||||
-moz-column-gap: 0 !important;
|
|
||||||
column-gap: 0 !important
|
column-gap: 0 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xl-1 {
|
.column-gap-xl-1 {
|
||||||
-moz-column-gap: .25rem !important;
|
|
||||||
column-gap: .25rem !important
|
column-gap: .25rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xl-2 {
|
.column-gap-xl-2 {
|
||||||
-moz-column-gap: .5rem !important;
|
|
||||||
column-gap: .5rem !important
|
column-gap: .5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xl-3 {
|
.column-gap-xl-3 {
|
||||||
-moz-column-gap: 1rem !important;
|
|
||||||
column-gap: 1rem !important
|
column-gap: 1rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xl-4 {
|
.column-gap-xl-4 {
|
||||||
-moz-column-gap: 1.5rem !important;
|
|
||||||
column-gap: 1.5rem !important
|
column-gap: 1.5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xl-5 {
|
.column-gap-xl-5 {
|
||||||
-moz-column-gap: 2rem !important;
|
|
||||||
column-gap: 2rem !important
|
column-gap: 2rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xl-6 {
|
.column-gap-xl-6 {
|
||||||
-moz-column-gap: 3rem !important;
|
|
||||||
column-gap: 3rem !important
|
column-gap: 3rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xl-7 {
|
.column-gap-xl-7 {
|
||||||
-moz-column-gap: 5rem !important;
|
|
||||||
column-gap: 5rem !important
|
column-gap: 5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xl-8 {
|
.column-gap-xl-8 {
|
||||||
-moz-column-gap: 8rem !important;
|
|
||||||
column-gap: 8rem !important
|
column-gap: 8rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16785,17 +16728,14 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-xl-2 {
|
.columns-xl-2 {
|
||||||
-moz-columns: 2 !important;
|
|
||||||
columns: 2 !important
|
columns: 2 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-xl-3 {
|
.columns-xl-3 {
|
||||||
-moz-columns: 3 !important;
|
|
||||||
columns: 3 !important
|
columns: 3 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-xl-4 {
|
.columns-xl-4 {
|
||||||
-moz-columns: 4 !important;
|
|
||||||
columns: 4 !important
|
columns: 4 !important
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17697,47 +17637,38 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xxl-0 {
|
.column-gap-xxl-0 {
|
||||||
-moz-column-gap: 0 !important;
|
|
||||||
column-gap: 0 !important
|
column-gap: 0 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xxl-1 {
|
.column-gap-xxl-1 {
|
||||||
-moz-column-gap: .25rem !important;
|
|
||||||
column-gap: .25rem !important
|
column-gap: .25rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xxl-2 {
|
.column-gap-xxl-2 {
|
||||||
-moz-column-gap: .5rem !important;
|
|
||||||
column-gap: .5rem !important
|
column-gap: .5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xxl-3 {
|
.column-gap-xxl-3 {
|
||||||
-moz-column-gap: 1rem !important;
|
|
||||||
column-gap: 1rem !important
|
column-gap: 1rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xxl-4 {
|
.column-gap-xxl-4 {
|
||||||
-moz-column-gap: 1.5rem !important;
|
|
||||||
column-gap: 1.5rem !important
|
column-gap: 1.5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xxl-5 {
|
.column-gap-xxl-5 {
|
||||||
-moz-column-gap: 2rem !important;
|
|
||||||
column-gap: 2rem !important
|
column-gap: 2rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xxl-6 {
|
.column-gap-xxl-6 {
|
||||||
-moz-column-gap: 3rem !important;
|
|
||||||
column-gap: 3rem !important
|
column-gap: 3rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xxl-7 {
|
.column-gap-xxl-7 {
|
||||||
-moz-column-gap: 5rem !important;
|
|
||||||
column-gap: 5rem !important
|
column-gap: 5rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-gap-xxl-8 {
|
.column-gap-xxl-8 {
|
||||||
-moz-column-gap: 8rem !important;
|
|
||||||
column-gap: 8rem !important
|
column-gap: 8rem !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17754,17 +17685,14 @@ fieldset:disabled .btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-xxl-2 {
|
.columns-xxl-2 {
|
||||||
-moz-columns: 2 !important;
|
|
||||||
columns: 2 !important
|
columns: 2 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-xxl-3 {
|
.columns-xxl-3 {
|
||||||
-moz-columns: 3 !important;
|
|
||||||
columns: 3 !important
|
columns: 3 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-xxl-4 {
|
.columns-xxl-4 {
|
||||||
-moz-columns: 4 !important;
|
|
||||||
columns: 4 !important
|
columns: 4 !important
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
public/fonts/Inter-Black.woff2
Normal file
BIN
public/fonts/Inter-BlackItalic.woff2
Normal file
BIN
public/fonts/Inter-Bold.woff2
Normal file
BIN
public/fonts/Inter-BoldItalic.woff2
Normal file
BIN
public/fonts/Inter-ExtraBold.woff2
Normal file
BIN
public/fonts/Inter-ExtraBoldItalic.woff2
Normal file
BIN
public/fonts/Inter-ExtraLight.woff2
Normal file
BIN
public/fonts/Inter-ExtraLightItalic.woff2
Normal file
BIN
public/fonts/Inter-Italic.woff2
Normal file
BIN
public/fonts/Inter-Light.woff2
Normal file
BIN
public/fonts/Inter-LightItalic.woff2
Normal file
BIN
public/fonts/Inter-Medium.woff2
Normal file
BIN
public/fonts/Inter-MediumItalic.woff2
Normal file
BIN
public/fonts/Inter-Regular.woff2
Normal file
BIN
public/fonts/Inter-SemiBold.woff2
Normal file
BIN
public/fonts/Inter-SemiBoldItalic.woff2
Normal file
BIN
public/fonts/Inter-Thin.woff2
Normal file
BIN
public/fonts/Inter-ThinItalic.woff2
Normal file
BIN
public/fonts/InterDisplay-Black.woff2
Normal file
BIN
public/fonts/InterDisplay-BlackItalic.woff2
Normal file
BIN
public/fonts/InterDisplay-Bold.woff2
Normal file
BIN
public/fonts/InterDisplay-BoldItalic.woff2
Normal file
BIN
public/fonts/InterDisplay-ExtraBold.woff2
Normal file
BIN
public/fonts/InterDisplay-ExtraBoldItalic.woff2
Normal file
BIN
public/fonts/InterDisplay-ExtraLight.woff2
Normal file
BIN
public/fonts/InterDisplay-ExtraLightItalic.woff2
Normal file
BIN
public/fonts/InterDisplay-Italic.woff2
Normal file
BIN
public/fonts/InterDisplay-Light.woff2
Normal file
BIN
public/fonts/InterDisplay-LightItalic.woff2
Normal file
BIN
public/fonts/InterDisplay-Medium.woff2
Normal file
BIN
public/fonts/InterDisplay-MediumItalic.woff2
Normal file
BIN
public/fonts/InterDisplay-Regular.woff2
Normal file
BIN
public/fonts/InterDisplay-SemiBold.woff2
Normal file
BIN
public/fonts/InterDisplay-SemiBoldItalic.woff2
Normal file
BIN
public/fonts/InterDisplay-Thin.woff2
Normal file
BIN
public/fonts/InterDisplay-ThinItalic.woff2
Normal file
BIN
public/fonts/InterVariable-Italic.woff2
Normal file
BIN
public/fonts/InterVariable.woff2
Normal file
59
public/fonts/inter.css
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/* Variable fonts usage:
|
||||||
|
:root { font-family: "Inter", sans-serif; }
|
||||||
|
@supports (font-variation-settings: normal) {
|
||||||
|
:root { font-family: "InterVariable", sans-serif; font-optical-sizing: auto; }
|
||||||
|
} */
|
||||||
|
@font-face {
|
||||||
|
font-family: InterVariable;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('InterVariable.woff2?v=4.0') format('woff2');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: InterVariable;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('InterVariable-Italic.woff2?v=4.0') format('woff2');
|
||||||
|
}
|
||||||
|
/* legacy name "Inter var" (Oct 2023) */
|
||||||
|
@font-face { font-family:'Inter var'; font-style:normal; font-weight:100 900; font-display:swap; src: url('InterVariable.woff2?v=4.0') format('woff2'); }
|
||||||
|
@font-face { font-family:'Inter var'; font-style:italic; font-weight:100 900; font-display:swap; src: url('InterVariable-Italic.woff2?v=4.0') format('woff2'); }
|
||||||
|
/* static fonts */
|
||||||
|
@font-face { font-family:Inter; font-style:normal; font-weight:100; font-display:swap; src:url("Inter-Thin.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:italic; font-weight:100; font-display:swap; src:url("Inter-ThinItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:normal; font-weight:200; font-display:swap; src:url("Inter-ExtraLight.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:italic; font-weight:200; font-display:swap; src:url("Inter-ExtraLightItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:normal; font-weight:300; font-display:swap; src:url("Inter-Light.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:italic; font-weight:300; font-display:swap; src:url("Inter-LightItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:normal; font-weight:400; font-display:swap; src:url("Inter-Regular.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:italic; font-weight:400; font-display:swap; src:url("Inter-Italic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:normal; font-weight:500; font-display:swap; src:url("Inter-Medium.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:italic; font-weight:500; font-display:swap; src:url("Inter-MediumItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:normal; font-weight:600; font-display:swap; src:url("Inter-SemiBold.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:italic; font-weight:600; font-display:swap; src:url("Inter-SemiBoldItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:normal; font-weight:700; font-display:swap; src:url("Inter-Bold.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:italic; font-weight:700; font-display:swap; src:url("Inter-BoldItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:normal; font-weight:800; font-display:swap; src:url("Inter-ExtraBold.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:italic; font-weight:800; font-display:swap; src:url("Inter-ExtraBoldItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:normal; font-weight:900; font-display:swap; src:url("Inter-Black.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:Inter; font-style:italic; font-weight:900; font-display:swap; src:url("Inter-BlackItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:normal; font-weight:100; font-display:swap; src:url("InterDisplay-Thin.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:italic; font-weight:100; font-display:swap; src:url("InterDisplay-ThinItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:normal; font-weight:200; font-display:swap; src:url("InterDisplay-ExtraLight.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:italic; font-weight:200; font-display:swap; src:url("InterDisplay-ExtraLightItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:normal; font-weight:300; font-display:swap; src:url("InterDisplay-Light.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:italic; font-weight:300; font-display:swap; src:url("InterDisplay-LightItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:normal; font-weight:400; font-display:swap; src:url("InterDisplay-Regular.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:italic; font-weight:400; font-display:swap; src:url("InterDisplay-Italic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:normal; font-weight:500; font-display:swap; src:url("InterDisplay-Medium.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:italic; font-weight:500; font-display:swap; src:url("InterDisplay-MediumItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:normal; font-weight:600; font-display:swap; src:url("InterDisplay-SemiBold.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:italic; font-weight:600; font-display:swap; src:url("InterDisplay-SemiBoldItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:normal; font-weight:700; font-display:swap; src:url("InterDisplay-Bold.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:italic; font-weight:700; font-display:swap; src:url("InterDisplay-BoldItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:normal; font-weight:800; font-display:swap; src:url("InterDisplay-ExtraBold.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:italic; font-weight:800; font-display:swap; src:url("InterDisplay-ExtraBoldItalic.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:normal; font-weight:900; font-display:swap; src:url("InterDisplay-Black.woff2?v=4.0") format("woff2"); }
|
||||||
|
@font-face { font-family:InterDisplay; font-style:italic; font-weight:900; font-display:swap; src:url("InterDisplay-BlackItalic.woff2?v=4.0") format("woff2"); }
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
BIN
public/img/avatars/duffman.png
Normal file
After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
BIN
public/img/avatars/moleman.png
Normal file
After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
@ -12,7 +12,7 @@
|
||||||
})((function () { 'use strict';
|
})((function () { 'use strict';
|
||||||
|
|
||||||
var themeStorageKey = "tablerTheme";
|
var themeStorageKey = "tablerTheme";
|
||||||
var defaultTheme = "light";
|
var defaultTheme = "dark";
|
||||||
var selectedTheme;
|
var selectedTheme;
|
||||||
var params = new Proxy(new URLSearchParams(window.location.search), {
|
var params = new Proxy(new URLSearchParams(window.location.search), {
|
||||||
get: function get(searchParams, prop) {
|
get: function get(searchParams, prop) {
|
||||||
|
|
|
@ -1,16 +1,6 @@
|
||||||
// SOCKET IO
|
socket.on('connect', () => { console.log('connected'); });
|
||||||
const socket = io({
|
|
||||||
auth: {
|
|
||||||
token: "abc"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ON CONNECT EVENT
|
// Server metrics
|
||||||
socket.on('connect', () => {
|
|
||||||
console.log('Connected');
|
|
||||||
});
|
|
||||||
|
|
||||||
// SELECT METRICS ELEMENTS
|
|
||||||
const cpuText = document.getElementById('cpu-text');
|
const cpuText = document.getElementById('cpu-text');
|
||||||
const cpuBar = document.getElementById('cpu-bar');
|
const cpuBar = document.getElementById('cpu-bar');
|
||||||
const ramText = document.getElementById('ram-text');
|
const ramText = document.getElementById('ram-text');
|
||||||
|
@ -20,19 +10,22 @@ const netBar = document.getElementById('net-bar');
|
||||||
const diskText = document.getElementById('disk-text');
|
const diskText = document.getElementById('disk-text');
|
||||||
const diskBar = document.getElementById('disk-bar');
|
const diskBar = document.getElementById('disk-bar');
|
||||||
|
|
||||||
|
// Container cards
|
||||||
const dockerCards = document.getElementById('cards');
|
const dockerCards = document.getElementById('cards');
|
||||||
|
|
||||||
|
// Container logs
|
||||||
const logViewer = document.getElementById('logView');
|
const logViewer = document.getElementById('logView');
|
||||||
|
|
||||||
//Update usage bars
|
// Server metrics
|
||||||
socket.on('metrics', (data) => {
|
socket.on('metrics', (data) => {
|
||||||
|
let [cpu, ram, tx, rx, disk] = data;
|
||||||
let {cpu, ram, tx, rx, disk} = data;
|
|
||||||
|
|
||||||
cpuText.innerHTML = `<span>CPU ${cpu} %</span>`;
|
cpuText.innerHTML = `<span>CPU ${cpu} %</span>`;
|
||||||
|
if (cpu < 7 ) { cpu = 7; }
|
||||||
cpuBar.innerHTML = `<span style="width: ${cpu}%"><span></span></span>`;
|
cpuBar.innerHTML = `<span style="width: ${cpu}%"><span></span></span>`;
|
||||||
|
|
||||||
ramText.innerHTML = `<span>RAM ${ram} %</span>`;
|
ramText.innerHTML = `<span>RAM ${ram} %</span>`;
|
||||||
|
if (ram < 7 ) { ram = 7; }
|
||||||
ramBar.innerHTML = `<span style="width: ${ram}%"><span></span></span>`;
|
ramBar.innerHTML = `<span style="width: ${ram}%"><span></span></span>`;
|
||||||
|
|
||||||
tx = Math.round(tx / 1024 / 1024);
|
tx = Math.round(tx / 1024 / 1024);
|
||||||
|
@ -42,165 +35,117 @@ socket.on('metrics', (data) => {
|
||||||
netBar.innerHTML = `<span style="width: 50%"><span></span></span>`;
|
netBar.innerHTML = `<span style="width: 50%"><span></span></span>`;
|
||||||
|
|
||||||
diskText.innerHTML = `<span>DISK ${disk} %</span>`;
|
diskText.innerHTML = `<span>DISK ${disk} %</span>`;
|
||||||
|
if (disk < 7 ) { disk = 7; }
|
||||||
diskBar.innerHTML = `<span style="width: ${disk}%"><span></span></span>`;
|
diskBar.innerHTML = `<span style="width: ${disk}%"><span></span></span>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
function drawCharts(name, cpu_array, ram_array) {
|
// Container cards
|
||||||
var elements = document.querySelectorAll(`${name}`);
|
socket.on('containers', (data) => {
|
||||||
|
let deleteMeElements = document.querySelectorAll('.deleteme');
|
||||||
Array.from(elements).forEach(function(element) {
|
deleteMeElements.forEach((element) => {
|
||||||
if (window.ApexCharts) {
|
element.parentNode.removeChild(element);
|
||||||
new ApexCharts(element, {
|
});
|
||||||
chart: {
|
dockerCards.insertAdjacentHTML("afterend", data);
|
||||||
type: "line",
|
|
||||||
fontFamily: 'inherit',
|
|
||||||
height: 40.0,
|
|
||||||
sparkline: {
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
animations: {
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
opacity: 1
|
|
||||||
},
|
|
||||||
stroke: {
|
|
||||||
width: [2, 1],
|
|
||||||
dashArray: [0, 3],
|
|
||||||
lineCap: "round",
|
|
||||||
curve: "smooth"
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
name: "CPU",
|
|
||||||
data: cpu_array
|
|
||||||
}, {
|
|
||||||
name: "RAM",
|
|
||||||
data: ram_array
|
|
||||||
}],
|
|
||||||
tooltip: {
|
|
||||||
theme: 'dark'
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
strokeDashArray: 4
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
labels: {
|
|
||||||
padding: 0
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
type: 'datetime'
|
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
labels: {
|
|
||||||
padding: 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
labels: [
|
|
||||||
'2020-06-20', '2020-06-21', '2020-06-22', '2020-06-23', '2020-06-24', '2020-06-25', '2020-06-26', '2020-06-27', '2020-06-28', '2020-06-29', '2020-06-30', '2020-07-01', '2020-07-02', '2020-07-03', '2020-07-04', '2020-07-05', '2020-07-06', '2020-07-07', '2020-07-08', '2020-07-09', '2020-07-10', '2020-07-11', '2020-07-12', '2020-07-13', '2020-07-14', '2020-07-15', '2020-07-16', '2020-07-17', '2020-07-18', '2020-07-19'
|
|
||||||
],
|
|
||||||
colors: [tabler.getColor("primary"), tabler.getColor("gray-600")],
|
|
||||||
legend: {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
}).render();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// container button actions
|
|
||||||
function buttonAction(button) {
|
|
||||||
socket.emit('clicked', {container: button.name, state: button.id, action: button.value});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function hideContainer(button) {
|
|
||||||
socket.emit('hide', {container: button.name});
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetView() {
|
|
||||||
socket.emit('reset');
|
|
||||||
}
|
|
||||||
|
|
||||||
let containerLogs;
|
|
||||||
|
|
||||||
function viewLogs(button) {
|
|
||||||
|
|
||||||
if (button.name != 'refresh') {
|
|
||||||
containerLogs = button.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
socket.emit('logs', {container: containerLogs});
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on('logString', (data) => {
|
|
||||||
logViewer.innerHTML = `<pre>${data}</pre>`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function drawCharts(name, cpuArray, ramArray) {
|
||||||
|
let element = document.querySelector(`${name}`);
|
||||||
|
|
||||||
socket.on('cards', (data) => {
|
let chart = new ApexCharts(element, {
|
||||||
|
chart: {
|
||||||
console.log('cards deleted');
|
type: "line",
|
||||||
let deleteMeElements = document.querySelectorAll('.deleteme');
|
height: 40.0,
|
||||||
deleteMeElements.forEach((element) => {
|
sparkline: {
|
||||||
element.parentNode.removeChild(element);
|
enabled: true
|
||||||
});
|
},
|
||||||
|
animations: {
|
||||||
dockerCards.insertAdjacentHTML("afterend", data);
|
enabled: false
|
||||||
|
}
|
||||||
// check localStorage for items ending with _cpu and redraw the charts
|
},
|
||||||
for (let i = 0; i < localStorage.length; i++) {
|
fill: {
|
||||||
if (localStorage.key(i).endsWith('_cpu')) {
|
opacity: 1
|
||||||
let name = localStorage.key(i).split('_')[0];
|
},
|
||||||
let cpu_array = JSON.parse(localStorage.getItem(`${name}_cpu`));
|
stroke: {
|
||||||
let ram_array = JSON.parse(localStorage.getItem(`${name}_ram`));
|
width: [2, 1],
|
||||||
drawCharts(`#${name}_chart`, cpu_array, ram_array);
|
dashArray: [0, 3],
|
||||||
|
lineCap: "round",
|
||||||
|
curve: "smooth"
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
name: "CPU",
|
||||||
|
data: cpuArray
|
||||||
|
}, {
|
||||||
|
name: "RAM",
|
||||||
|
data: ramArray
|
||||||
|
}],
|
||||||
|
tooltip: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
strokeDashArray: 4
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
labels: {
|
||||||
|
padding: 0
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
padding: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors: [tabler.getColor("primary"), tabler.getColor("gray-600")],
|
||||||
|
legend: {
|
||||||
|
show: false
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
chart.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons functions
|
||||||
});
|
function clicked(button) {
|
||||||
|
socket.emit('clicked', {name: button.name, id: button.id, value: button.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
socket.on('containerStats', (data) => {
|
socket.on('containerStats', (data) => {
|
||||||
|
let containerStats = data;
|
||||||
|
|
||||||
let {name, cpu, ram} = data;
|
for (const [name, statsArray] of Object.entries(containerStats)) {
|
||||||
|
|
||||||
console.log(`drawing chart for ${name}`)
|
let cpuArray = statsArray.cpuArray;
|
||||||
|
let ramArray = statsArray.ramArray;
|
||||||
|
|
||||||
var cpu_array = JSON.parse(localStorage.getItem(`${name}_cpu`));
|
let chart = document.getElementById(`${name}_chart`);
|
||||||
var ram_array = JSON.parse(localStorage.getItem(`${name}_ram`));
|
if (chart) {
|
||||||
|
chart.innerHTML = '';
|
||||||
if (cpu_array == null) { cpu_array = Array(30).fill(0); }
|
drawCharts(`#${name}_chart`, cpuArray, ramArray);
|
||||||
if (ram_array == null) { ram_array = Array(30).fill(0); }
|
} else {
|
||||||
|
console.log(`Chart element with id ${name}_chart not found in the DOM`);
|
||||||
cpu_array.push(cpu);
|
}
|
||||||
ram_array.push(ram);
|
|
||||||
|
|
||||||
cpu_array = cpu_array.slice(-30);
|
|
||||||
ram_array = ram_array.slice(-30);
|
|
||||||
|
|
||||||
localStorage.setItem(`${name}_cpu`, JSON.stringify(cpu_array));
|
|
||||||
localStorage.setItem(`${name}_ram`, JSON.stringify(ram_array));
|
|
||||||
|
|
||||||
// replace the old chart with the new one
|
|
||||||
let chart = document.getElementById(`${name}_chart`);
|
|
||||||
if (chart) {
|
|
||||||
let newChart = document.createElement('div');
|
|
||||||
newChart.id = `${name}_chart`;
|
|
||||||
chart.parentNode.replaceChild(newChart, chart);
|
|
||||||
drawCharts(`#${name}_chart`, cpu_array, ram_array);
|
|
||||||
} else {
|
|
||||||
console.log(`Chart element with id ${name}_chart not found in the DOM`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('install', (data) => {
|
|
||||||
|
|
||||||
console.log('added install card');
|
|
||||||
dockerCards.insertAdjacentHTML("afterend", data);
|
// socket.on('install', (data) => {
|
||||||
|
// dockerCards.insertAdjacentHTML("afterend", data);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// let containerLogs;
|
||||||
|
|
||||||
|
// function viewLogs(button) {
|
||||||
|
// if (button.name != 'refresh') {
|
||||||
|
// containerLogs = button.name;
|
||||||
|
// }
|
||||||
|
// socket.emit('logs', {container: containerLogs});
|
||||||
|
// }
|
||||||
|
|
||||||
|
socket.on('logs', (data) => {
|
||||||
|
logViewer.innerHTML = `<pre>${data}</pre>`;
|
||||||
});
|
});
|
||||||
|
|
1
public/static/logo-dark.svg
Normal file
After Width: | Height: | Size: 18 KiB |
1
public/static/logo-sm-black.svg
Normal file
After Width: | Height: | Size: 15 KiB |
1
public/static/logo-sm-white.svg
Normal file
After Width: | Height: | Size: 15 KiB |
|
@ -1,3 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 68">
|
|
||||||
<path d="M64.6 16.2C63 9.9 58.1 5 51.8 3.4 40 1.5 28 1.5 16.2 3.4 9.9 5 5 9.9 3.4 16.2 1.5 28 1.5 40 3.4 51.8 5 58.1 9.9 63 16.2 64.6c11.8 1.9 23.8 1.9 35.6 0C58.1 63 63 58.1 64.6 51.8c1.9-11.8 1.9-23.8 0-35.6zM33.3 36.3c-2.8 4.4-6.6 8.2-11.1 11-1.5.9-3.3.9-4.8.1s-2.4-2.3-2.5-4c0-1.7.9-3.3 2.4-4.1 2.3-1.4 4.4-3.2 6.1-5.3-1.8-2.1-3.8-3.8-6.1-5.3-2.3-1.3-3-4.2-1.7-6.4s4.3-2.9 6.5-1.6c4.5 2.8 8.2 6.5 11.1 10.9 1 1.4 1 3.3.1 4.7zM49.2 46H37.8c-2.1 0-3.8-1-3.8-3s1.7-3 3.8-3h11.4c2.1 0 3.8 1 3.8 3s-1.7 3-3.8 3z" fill="#ffffff"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 599 B |
Before Width: | Height: | Size: 5.5 KiB |
|
@ -1,4 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 232 68">
|
|
||||||
<path d="M64.6 16.2C63 9.9 58.1 5 51.8 3.4 40 1.5 28 1.5 16.2 3.4 9.9 5 5 9.9 3.4 16.2 1.5 28 1.5 40 3.4 51.8 5 58.1 9.9 63 16.2 64.6c11.8 1.9 23.8 1.9 35.6 0C58.1 63 63 58.1 64.6 51.8c1.9-11.8 1.9-23.8 0-35.6zM33.3 36.3c-2.8 4.4-6.6 8.2-11.1 11-1.5.9-3.3.9-4.8.1s-2.4-2.3-2.5-4c0-1.7.9-3.3 2.4-4.1 2.3-1.4 4.4-3.2 6.1-5.3-1.8-2.1-3.8-3.8-6.1-5.3-2.3-1.3-3-4.2-1.7-6.4s4.3-2.9 6.5-1.6c4.5 2.8 8.2 6.5 11.1 10.9 1 1.4 1 3.3.1 4.7zM49.2 46H37.8c-2.1 0-3.8-1-3.8-3s1.7-3 3.8-3h11.4c2.1 0 3.8 1 3.8 3s-1.7 3-3.8 3z" fill="#fff"/>
|
|
||||||
<path d="M105.8 46.1c.4 0 .9.2 1.2.6s.6 1 .6 1.7c0 .9-.5 1.6-1.4 2.2s-2 .9-3.2.9c-2 0-3.7-.4-5-1.3s-2-2.6-2-5.4V31.6h-2.2c-.8 0-1.4-.3-1.9-.8s-.9-1.1-.9-1.9c0-.7.3-1.4.8-1.8s1.2-.7 1.9-.7h2.2v-3.1c0-.8.3-1.5.8-2.1s1.3-.8 2.1-.8 1.5.3 2 .8.8 1.3.8 2.1v3.1h3.4c.8 0 1.4.3 1.9.8s.8 1.2.8 1.9-.3 1.4-.8 1.8-1.2.7-1.9.7h-3.4v13c0 .7.2 1.2.5 1.5s.8.5 1.4.5c.3 0 .6-.1 1.1-.2.5-.2.8-.3 1.2-.3zm28-20.7c.8 0 1.5.3 2.1.8.5.5.8 1.2.8 2.1v20.3c0 .8-.3 1.5-.8 2.1-.5.6-1.2.8-2.1.8s-1.5-.3-2-.8-.8-1.2-.8-2.1c-.8.9-1.9 1.7-3.2 2.4-1.3.7-2.8 1-4.3 1-2.2 0-4.2-.6-6-1.7-1.8-1.1-3.2-2.7-4.2-4.7s-1.6-4.3-1.6-6.9c0-2.6.5-4.9 1.5-6.9s2.4-3.6 4.2-4.8c1.8-1.1 3.7-1.7 5.9-1.7 1.5 0 3 .3 4.3.8 1.3.6 2.5 1.3 3.4 2.1 0-.8.3-1.5.8-2.1.5-.5 1.2-.7 2-.7zm-9.7 21.3c2.1 0 3.8-.8 5.1-2.3s2-3.4 2-5.7-.7-4.2-2-5.8c-1.3-1.5-3-2.3-5.1-2.3-2 0-3.7.8-5 2.3-1.3 1.5-2 3.5-2 5.8s.6 4.2 1.9 5.7 3 2.3 5.1 2.3zm32.1-21.3c2.2 0 4.2.6 6 1.7 1.8 1.1 3.2 2.7 4.2 4.7s1.6 4.3 1.6 6.9-.5 4.9-1.5 6.9-2.4 3.6-4.2 4.8c-1.8 1.1-3.7 1.7-5.9 1.7-1.5 0-3-.3-4.3-.9s-2.5-1.4-3.4-2.3v.3c0 .8-.3 1.5-.8 2.1-.5.6-1.2.8-2.1.8s-1.5-.3-2.1-.8c-.5-.5-.8-1.2-.8-2.1V18.9c0-.8.3-1.5.8-2.1.5-.6 1.2-.8 2.1-.8s1.5.3 2.1.8c.5.6.8 1.3.8 2.1v10c.8-1 1.8-1.8 3.2-2.5 1.3-.7 2.8-1 4.3-1zm-.7 21.3c2 0 3.7-.8 5-2.3s2-3.5 2-5.8-.6-4.2-1.9-5.7-3-2.3-5.1-2.3-3.8.8-5.1 2.3-2 3.4-2 5.7.7 4.2 2 5.8c1.3 1.6 3 2.3 5.1 2.3zm23.6 1.9c0 .8-.3 1.5-.8 2.1s-1.3.8-2.1.8-1.5-.3-2-.8-.8-1.3-.8-2.1V18.9c0-.8.3-1.5.8-2.1s1.3-.8 2.1-.8 1.5.3 2 .8.8 1.3.8 2.1v29.7zm29.3-10.5c0 .8-.3 1.4-.9 1.9-.6.5-1.2.7-2 .7h-15.8c.4 1.9 1.3 3.4 2.6 4.4 1.4 1.1 2.9 1.6 4.7 1.6 1.3 0 2.3-.1 3.1-.4.7-.2 1.3-.5 1.8-.8.4-.3.7-.5.9-.6.6-.3 1.1-.4 1.6-.4.7 0 1.2.2 1.7.7s.7 1 .7 1.7c0 .9-.4 1.6-1.3 2.4-.9.7-2.1 1.4-3.6 1.9s-3 .8-4.6.8c-2.7 0-5-.6-7-1.7s-3.5-2.7-4.6-4.6-1.6-4.2-1.6-6.6c0-2.8.6-5.2 1.7-7.2s2.7-3.7 4.6-4.8 3.9-1.7 6-1.7 4.1.6 6 1.7 3.4 2.7 4.5 4.7c.9 1.9 1.5 4.1 1.5 6.3zm-12.2-7.5c-3.7 0-5.9 1.7-6.6 5.2h12.6v-.3c-.1-1.3-.8-2.5-2-3.5s-2.5-1.4-4-1.4zm30.3-5.2c1 0 1.8.3 2.4.8.7.5 1 1.2 1 1.9 0 1-.3 1.7-.8 2.2-.5.5-1.1.8-1.8.7-.5 0-1-.1-1.6-.3-.2-.1-.4-.1-.6-.2-.4-.1-.7-.1-1.1-.1-.8 0-1.6.3-2.4.8s-1.4 1.3-1.9 2.3-.7 2.3-.7 3.7v11.4c0 .8-.3 1.5-.8 2.1-.5.6-1.2.8-2.1.8s-1.5-.3-2.1-.8c-.5-.6-.8-1.3-.8-2.1V28.8c0-.8.3-1.5.8-2.1.5-.6 1.2-.8 2.1-.8s1.5.3 2.1.8c.5.6.8 1.3.8 2.1v.6c.7-1.3 1.8-2.3 3.2-3 1.3-.7 2.8-1 4.3-1z" fill-rule="evenodd" clip-rule="evenodd" fill="#fff"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 18 KiB |
60
router/index.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import express from "express";
|
||||||
|
import { io } from "../app.js";
|
||||||
|
|
||||||
|
import { Login, submitLogin } from "../controllers/login.js";
|
||||||
|
import { Register, submitRegister } from "../controllers/register.js";
|
||||||
|
import { Dashboard, searchDashboard } from "../controllers/dashboard.js";
|
||||||
|
import { Apps } from "../controllers/apps.js";
|
||||||
|
import { Users } from "../controllers/users.js";
|
||||||
|
import { Images } from "../controllers/images.js";
|
||||||
|
import { Account } from "../controllers/account.js";
|
||||||
|
import { Settings } from "../controllers/settings.js";
|
||||||
|
import { Networks } from "../controllers/networks.js";
|
||||||
|
import { Volumes } from "../controllers/volumes.js";
|
||||||
|
import { Syslogs } from "../controllers/syslogs.js";
|
||||||
|
|
||||||
|
export const router = express.Router();
|
||||||
|
|
||||||
|
const auth = (req, res, next) => {
|
||||||
|
if (req.session.role == "admin") {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.redirect("/login");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
router.get("/login", Login);
|
||||||
|
router.post("/login", submitLogin);
|
||||||
|
|
||||||
|
router.get("/register", Register);
|
||||||
|
router.post("/register", submitRegister);
|
||||||
|
|
||||||
|
router.get("/", auth, Dashboard);
|
||||||
|
router.post("/", auth, searchDashboard);
|
||||||
|
router.post("/:search", auth, searchDashboard);
|
||||||
|
|
||||||
|
router.get("/apps", auth, Apps);
|
||||||
|
router.get("/apps/:page", auth, Apps);
|
||||||
|
|
||||||
|
router.get("/users", auth, Users);
|
||||||
|
|
||||||
|
router.get("/images", auth, Images);
|
||||||
|
|
||||||
|
router.get("/networks", auth, Networks);
|
||||||
|
|
||||||
|
router.get("/volumes", auth, Volumes);
|
||||||
|
|
||||||
|
router.get("/syslogs", auth, Syslogs);
|
||||||
|
|
||||||
|
router.get("/account", Account);
|
||||||
|
|
||||||
|
router.get("/settings", auth, Settings);
|
||||||
|
|
||||||
|
router.get("/logout", (req, res) => {
|
||||||
|
const sessionId = req.session.id;
|
||||||
|
req.session.destroy(() => {
|
||||||
|
io.to(sessionId).disconnectSockets();
|
||||||
|
res.redirect("/login");
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,59 +0,0 @@
|
||||||
const express = require("express");
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
const { Dashboard, AddSite, RemoveSite, RefreshSites, DisableSite, EnableSite } = require("../controllers/dashboard");
|
|
||||||
const { Login, processLogin, Logout, Register, processRegister } = require("../controllers/auth");
|
|
||||||
const { Apps, searchApps, Install, Uninstall } = require("../controllers/apps");
|
|
||||||
|
|
||||||
const { Images } = require("../controllers/images");
|
|
||||||
const { Volumes } = require("../controllers/volumes");
|
|
||||||
const { Networks } = require("../controllers/networks");
|
|
||||||
|
|
||||||
const { Users } = require("../controllers/users");
|
|
||||||
const { Account } = require("../controllers/account");
|
|
||||||
const { Settings } = require("../controllers/settings");
|
|
||||||
|
|
||||||
// Authentication middleware
|
|
||||||
const authenticate = (req, res, next) => {
|
|
||||||
if (req.session && req.session.role == "admin") {
|
|
||||||
console.log(`User ${req.session.user} [${req.session.role}] accessed ${req.originalUrl}`)
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
console.log('Not admin')
|
|
||||||
res.redirect("/login");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Dashboard
|
|
||||||
router.get("/", authenticate, Dashboard);
|
|
||||||
router.post("/addsite", authenticate, AddSite);
|
|
||||||
router.post("/removesite", authenticate, RemoveSite);
|
|
||||||
router.get("/refreshsites", authenticate, RefreshSites);
|
|
||||||
router.post("/disablesite", authenticate, DisableSite);
|
|
||||||
router.post("/enablesite", authenticate, EnableSite);
|
|
||||||
|
|
||||||
router.get("/images", authenticate, Images);
|
|
||||||
|
|
||||||
// Auth
|
|
||||||
router.get("/login", Login);
|
|
||||||
router.post("/login", processLogin);
|
|
||||||
router.get("/register", Register);
|
|
||||||
router.post("/register", processRegister);
|
|
||||||
router.get("/logout", Logout);
|
|
||||||
|
|
||||||
// Apps page
|
|
||||||
router.get("/apps", authenticate, Apps);
|
|
||||||
router.get("/apps/:page", authenticate, Apps);
|
|
||||||
router.get("/apps/:template/:page", authenticate, Apps);
|
|
||||||
router.post("/apps", authenticate, searchApps);
|
|
||||||
|
|
||||||
// Settings page
|
|
||||||
router.get("/settings", authenticate, Settings);
|
|
||||||
router.get("/account", authenticate, Account);
|
|
||||||
|
|
||||||
router.post("/install", authenticate, Install);
|
|
||||||
router.post("/uninstall", authenticate, Uninstall);
|
|
||||||
|
|
||||||
router.get("/users", authenticate, Users);
|
|
||||||
|
|
||||||
module.exports = router;
|
|
Before Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 305 KiB |
Before Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 164 KiB |
70
setup.sh
|
@ -1,70 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# To demo DweebUI, run this script on a fresh Debian 12.2 install. This script will open port 443/tcp for Reverse Proxy and 22/tcp for SSH.
|
|
||||||
|
|
||||||
# Manual Install:
|
|
||||||
# cd DweebUI
|
|
||||||
# chmod +x setup.sh
|
|
||||||
# sudo ./setup.sh
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
apt-get install -y curl unzip ufw gnupg ca-certificates lsb-release gpg
|
|
||||||
|
|
||||||
# Enable firewall
|
|
||||||
ufw allow ssh && ufw --force enable
|
|
||||||
|
|
||||||
# Opens port 443/tcp for Reverse Proxy
|
|
||||||
ufw allow https
|
|
||||||
|
|
||||||
# Install Docker
|
|
||||||
install -m 0755 -d /etc/apt/keyrings
|
|
||||||
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
||||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
|
||||||
|
|
||||||
echo \
|
|
||||||
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
|
|
||||||
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
|
|
||||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
|
||||||
|
|
||||||
# Create docker network
|
|
||||||
docker network create -d bridge AppBridge
|
|
||||||
|
|
||||||
# Create a redis docker container with persistent storage and a password
|
|
||||||
docker run -d --name DweebCache --restart unless-stopped -v /home/docker/redis:/data -p 6379:6379 redis redis-server --requirepass "somesupersecretpassword"
|
|
||||||
|
|
||||||
|
|
||||||
# Install redis
|
|
||||||
# curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
|
|
||||||
# echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
|
|
||||||
# apt-get update -y
|
|
||||||
# apt-get install -y redis
|
|
||||||
# systemctl enable --now redis-server
|
|
||||||
|
|
||||||
# Install nodejs
|
|
||||||
mkdir -p /etc/apt/keyrings
|
|
||||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
|
||||||
NODE_MAJOR=20
|
|
||||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install nodejs -y
|
|
||||||
|
|
||||||
npm i
|
|
||||||
|
|
||||||
# Prep for caddy
|
|
||||||
mkdir -p /home/docker/caddy/sites
|
|
||||||
echo "import sites/*" > /home/docker/caddy/Caddyfile.tmp
|
|
||||||
mv /home/docker/caddy/Caddyfile.tmp /home/docker/caddy/Caddyfile
|
|
||||||
|
|
||||||
|
|
||||||
# Install pm2 and start DweebUI
|
|
||||||
npm install pm2 -g
|
|
||||||
pm2 start app.js --name "dweebui"
|
|
||||||
pm2 log
|
|
||||||
|
|
||||||
|
|
||||||
# Creates a 'docker-compose' alias, since the command changed to 'docker compose' in Debian 11.
|
|
||||||
echo '#!/bin/sh
|
|
||||||
docker compose "$@"' > /usr/local/bin/docker-compose
|
|
||||||
chmod +x /usr/local/bin/docker-compose
|
|