The rewrite. v0.20
3
.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"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "weekly"
|
||||
labels:
|
||||
- "🤖 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 gzip compression.
|
||||
* Added PM2.
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:21-alpine
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV LOGGER=true
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
34
README.md
|
@ -1,13 +1,18 @@
|
|||
# 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://hub.docker.com/repository/docker/lllllllillllllillll/dweebui)
|
||||
[](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>
|
||||
|
||||
|
@ -37,12 +42,11 @@ Docker Compose:
|
|||
```
|
||||
version: "3.9"
|
||||
services:
|
||||
|
||||
dweebui:
|
||||
container_name: dweebui
|
||||
image: lllllllillllllillll/dweebui:v0.09-dev
|
||||
# build:
|
||||
# context: .
|
||||
# image: lllllllillllllillll/dweebui:v0.20
|
||||
build:
|
||||
context: .
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 8000
|
||||
|
@ -52,21 +56,15 @@ services:
|
|||
- 8000:8000
|
||||
volumes:
|
||||
- dweebui:/app
|
||||
- caddyfiles:/app/caddyfiles
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
#- ./custom-templates.json:/app/custom-templates.json
|
||||
#- ./composefiles:/app/composefiles
|
||||
networks:
|
||||
- dweeb_network
|
||||
|
||||
- dweebui_net
|
||||
|
||||
volumes:
|
||||
dweebui:
|
||||
caddyfiles:
|
||||
|
||||
|
||||
networks:
|
||||
dweeb_network:
|
||||
dweebui_net:
|
||||
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```.
|
||||
|
||||
|
||||
Using setup.sh:
|
||||
```
|
||||
Extract DweebUI.zip and navigate to /DweebUI
|
||||
cd DweebUI
|
||||
chmod +x setup.sh
|
||||
sudo ./setup.sh
|
||||
```
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
397
app.js
|
@ -1,23 +1,43 @@
|
|||
// Express
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const session = require("express-session");
|
||||
const compression = require('compression');
|
||||
const helmet = require('helmet');
|
||||
const PORT = process.env.PORT || 8000;
|
||||
import express from 'express';
|
||||
import session from 'express-session';
|
||||
import compression from 'compression';
|
||||
import helmet from 'helmet';
|
||||
import Docker from 'dockerode';
|
||||
import cors from 'cors';
|
||||
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
|
||||
const routes = require("./routes");
|
||||
export const app = express();
|
||||
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
|
||||
const { serverStats, containerList, containerStats, containerAction, containerLogs, hiddenContainers, dockerImages, dockerVolumes, dockerNetworks } = require('./functions/system');
|
||||
let sentList, clicked;
|
||||
app.locals.site_list = '';
|
||||
// socket.io admin ui
|
||||
export const io = new Server(server, {
|
||||
connectionStateRecovery: {},
|
||||
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');
|
||||
|
||||
|
||||
// Configure Session
|
||||
// Session middleware
|
||||
const sessionMiddleware = session({
|
||||
secret: "keyboard cat",
|
||||
resave: false,
|
||||
|
@ -26,137 +46,280 @@ const sessionMiddleware = session({
|
|||
secure: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.
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
io.engine.use(sessionMiddleware);
|
||||
|
||||
// Middleware
|
||||
// Express middleware
|
||||
app.set('view engine', 'ejs');
|
||||
app.use([
|
||||
compression(),
|
||||
cors(),
|
||||
helmet({contentSecurityPolicy: false}),
|
||||
express.static("public"),
|
||||
express.json(),
|
||||
express.urlencoded({ extended: true }),
|
||||
sessionMiddleware,
|
||||
routes
|
||||
router
|
||||
]);
|
||||
|
||||
// Start Express server
|
||||
const server = app.listen(PORT, async () => {
|
||||
console.log(`App listening on port ${PORT}`);
|
||||
// Initialize server
|
||||
server.listen(port, () => {
|
||||
async function init() {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('[Connected to DB]');
|
||||
} catch (error) {
|
||||
console.log('[Could not connect to DB]', error);
|
||||
}
|
||||
try {
|
||||
await sequelize.sync();
|
||||
console.log('[Models Synced]');
|
||||
hidden = await Container.findAll({ where: {visibility:false}});
|
||||
containerCards();
|
||||
} catch (error) {
|
||||
console.log('[Could not Sync Models]', error);
|
||||
}
|
||||
console.log(`\nServer listening on http://localhost:${port}`);
|
||||
}
|
||||
init();
|
||||
});
|
||||
|
||||
// Start Socket.io
|
||||
const io = require('socket.io')(server);
|
||||
io.engine.use(sessionMiddleware);
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
// 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))) {
|
||||
|
||||
// Set user session
|
||||
const user_session = socket.request.session;
|
||||
console.log(`${user_session.user} connected from ${socket.handshake.headers.host} ${socket.handshake.address}`);
|
||||
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 {}
|
||||
|
||||
// Check if a list of containers or an install card needs to be sent
|
||||
if (sentList != null) { socket.emit('cards', sentList); }
|
||||
if((app.locals.install != '') && (app.locals.install != null)){ socket.emit('install', app.locals.install); }
|
||||
let external_port = ports_list[0]?.external || 0;
|
||||
let internal_port = ports_list[0]?.internal || 0;
|
||||
|
||||
|
||||
async function dockerStuff(){
|
||||
let i = await dockerImages();
|
||||
let v = await dockerVolumes();
|
||||
let n = await dockerNetworks();
|
||||
|
||||
// console.log(i, v, n);
|
||||
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;
|
||||
}
|
||||
|
||||
dockerStuff();
|
||||
// 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);
|
||||
|
||||
// Send server metrics
|
||||
let ServerStats = setInterval(async () => {
|
||||
socket.emit('metrics', await serverStats());
|
||||
}, 1000);
|
||||
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));
|
||||
|
||||
// Send list of containers
|
||||
let ContainerList = setInterval(async () => {
|
||||
let cardList = await containerList();
|
||||
if (sentList !== cardList) {
|
||||
sentList = cardList;
|
||||
app.locals.install = '';
|
||||
socket.emit('cards', cardList);
|
||||
statsArray[name].cpuArray = statsArray[name].cpuArray.slice(-15);
|
||||
statsArray[name].ramArray = statsArray[name].ramArray.slice(-15);
|
||||
}
|
||||
}, 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;
|
||||
// Store docker events
|
||||
docker.getEvents((err, stream) => {
|
||||
if (err) throw err;
|
||||
stream.on('data', (chunk) => {
|
||||
dockerEvents += chunk.toString('utf8');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
socket.on('hide', async (data) => {
|
||||
console.log(`Hide ${data.container}`);
|
||||
// Check for docker events
|
||||
setInterval( () => {
|
||||
if (dockerEvents != '') {
|
||||
getHidden();
|
||||
containerCards();
|
||||
dockerEvents = '';
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
let containerExists = await Containers.findOne({ where: {name:data.container}});
|
||||
// Get hidden containers
|
||||
async function getHidden() {
|
||||
hidden = await Container.findAll({ where: {visibility:false}});
|
||||
}
|
||||
|
||||
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.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');
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reset', (data) => {
|
||||
// set visibility to true for all containers
|
||||
Containers.update({ visibility: true }, { where: {} });
|
||||
console.log('All containers visible');
|
||||
hiddenContainers();
|
||||
});
|
||||
setInterval(() => {
|
||||
socket.emit('metrics', [cpu, ram, tx, rx, disk]);
|
||||
if (sent != cardList) {
|
||||
sent = cardList;
|
||||
socket.emit('containers', cardList);
|
||||
}
|
||||
socket.emit('containerStats', statsArray);
|
||||
}, 1000);
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
|
||||
// On disconnect
|
||||
socket.on('disconnect', () => {
|
||||
clearInterval(ServerStats);
|
||||
clearInterval(ContainerList);
|
||||
clearInterval(ContainerStats);
|
||||
});
|
||||
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
|
||||
let app_name = data.name || data.title.toLowerCase();
|
||||
|
@ -227,11 +227,11 @@ function appCard(data) {
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
Learn More
|
||||
</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>
|
||||
Install
|
||||
</a>
|
||||
|
@ -989,6 +989,4 @@ function appCard(data) {
|
|||
</div>`;
|
||||
|
||||
|
||||
}
|
||||
|
||||
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')
|
||||
const { dashCard } = require('../components/dashCard');
|
||||
const { install, uninstall } = require('../functions/package_manager');
|
||||
import { readFileSync } from 'fs';
|
||||
import { appCard } from '../components/appCard.js';
|
||||
|
||||
const templates_json = require('../templates.json');
|
||||
let templates = templates_json.templates;
|
||||
let templatesJSON = readFileSync('./templates.json');
|
||||
let templates = JSON.parse(templatesJSON).templates;
|
||||
|
||||
// sort templates alphabetically
|
||||
templates = templates.sort((a, b) => {
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
exports.Apps = async function(req, res) {
|
||||
});
|
||||
|
||||
export const Apps = (req, res) => {
|
||||
let page = Number(req.params.page) || 1;
|
||||
let list_start = (page - 1) * 28;
|
||||
let list_end = (page * 28);
|
||||
let last_page = Math.ceil(templates.length / 28);
|
||||
let list_start = (page-1)*28;
|
||||
let list_end = (page*28);
|
||||
let last_page = Math.ceil(templates.length/28);
|
||||
|
||||
let prev = '/apps/' + (page - 1);
|
||||
let next = '/apps/' + (page + 1);
|
||||
let prev = '/apps/' + (page-1);
|
||||
let next = '/apps/' + (page+1);
|
||||
if (page == 1) {
|
||||
prev = '/apps/' + (page);
|
||||
}
|
||||
|
@ -36,12 +32,10 @@ exports.Apps = async function(req, res) {
|
|||
apps_list += app_card;
|
||||
}
|
||||
|
||||
// Render the home page
|
||||
res.render("pages/apps", {
|
||||
res.render("apps", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.avatar,
|
||||
isLoggedIn: true,
|
||||
list_start: list_start + 1,
|
||||
list_end: list_end,
|
||||
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 list_start = (page - 1) * 28;
|
||||
|
@ -98,12 +92,10 @@ exports.searchApps = async function(req, res) {
|
|||
apps_list += app_card;
|
||||
}
|
||||
|
||||
// Render the home page
|
||||
res.render("pages/apps", {
|
||||
res.render("apps", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.avatar,
|
||||
isLoggedIn: true,
|
||||
list_start: list_start + 1,
|
||||
list_end: list_end,
|
||||
app_count: templates.length,
|
||||
|
@ -112,63 +104,4 @@ exports.searchApps = async function(req, res) {
|
|||
apps_list: apps_list
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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");
|
||||
const { siteCard } = require('../components/siteCard');
|
||||
const { containerExec } = require('../functions/system')
|
||||
|
||||
export const Dashboard = (req, res) => {
|
||||
|
||||
|
||||
|
||||
exports.Dashboard = async function (req, res) {
|
||||
|
||||
let caddy = 'd-none';
|
||||
|
||||
if (process.env.Proxy_Manager == 'enabled') {
|
||||
caddy = '';
|
||||
}
|
||||
|
||||
// Render the home page
|
||||
res.render("pages/dashboard", {
|
||||
res.render("dashboard", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
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) {
|
||||
|
||||
let { domain, type, host, port } = req.body;
|
||||
|
||||
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.render("dashboard", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.avatar,
|
||||
});
|
||||
|
||||
|
||||
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("pages/images", {
|
||||
res.render("images", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
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
|
||||
res.render("pages/users", {
|
||||
export const Networks = async function(req, res) {
|
||||
|
||||
|
||||
res.render("networks", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
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("pages/settings", {
|
||||
res.render("settings", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
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 = `
|
||||
<tr>
|
||||
|
@ -12,36 +12,48 @@ exports.Users = async function(req, res) {
|
|||
<th>Email</th>
|
||||
<th>UUID</th>
|
||||
<th>Role</th>
|
||||
<th>Last Login</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>`
|
||||
|
||||
let users = await User.findAll();
|
||||
users.forEach((account) => {
|
||||
full_name = account.first_name + ' ' + account.last_name;
|
||||
user_info = `
|
||||
let allUsers = await User.findAll();
|
||||
allUsers.forEach((account) => {
|
||||
|
||||
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>
|
||||
<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>${full_name}</td>
|
||||
<td>${account.name}</td>
|
||||
<td>${account.username}</td>
|
||||
<td>${account.email}</td>
|
||||
<td>${account.UUID}</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>
|
||||
</tr>`
|
||||
|
||||
user_list += user_info;
|
||||
user_list += info;
|
||||
});
|
||||
|
||||
// Render the home page
|
||||
res.render("pages/users", {
|
||||
|
||||
res.render("users", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.avatar,
|
||||
isLoggedIn: true,
|
||||
user_list: user_list
|
||||
});
|
||||
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
const User = require('../database/UserModel');
|
||||
import { docker } from '../app.js';
|
||||
|
||||
exports.Volumes = async function(req, res) {
|
||||
|
||||
|
||||
|
||||
// Render the home page
|
||||
res.render("pages/volumes", {
|
||||
export const Volumes = (req, res) => {
|
||||
res.render("volumes", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
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:
|
||||
dweebui:
|
||||
container_name: dweebui
|
||||
image: lllllllillllllillll/dweebui:v0.09-dev
|
||||
# build:
|
||||
# context: .
|
||||
# image: lllllllillllllillll/dweebui:v0.20
|
||||
build:
|
||||
context: .
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 8000
|
||||
|
@ -14,17 +14,13 @@ services:
|
|||
- 8000:8000
|
||||
volumes:
|
||||
- dweebui:/app
|
||||
- caddyfiles:/app/caddyfiles
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
#- ./custom-templates.json:/app/custom-templates.json
|
||||
#- ./composefiles:/app/composefiles
|
||||
networks:
|
||||
- dweeb_network
|
||||
- dweebui_net
|
||||
|
||||
volumes:
|
||||
dweebui:
|
||||
caddyfiles:
|
||||
|
||||
networks:
|
||||
dweeb_network:
|
||||
dweebui_net:
|
||||
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",
|
||||
"version": "1.0.0",
|
||||
"description": "A web UI for Docker",
|
||||
"main": "app.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "mocha --require @babel/register --exit"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.0",
|
||||
"child_process": "^1.0.2",
|
||||
"@babel/register": "^7.23.7",
|
||||
"@journeyapps/sqlcipher": "^5.3.1",
|
||||
"@socket.io/admin-ui": "^0.5.1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"chai": "^5.0.0",
|
||||
"compression": "^1.7.4",
|
||||
"dockerode": "^4.0.0",
|
||||
"dockerode-compose": "^1.4.0",
|
||||
"cors": "^2.8.5",
|
||||
"dockerode": "^4.0.1",
|
||||
"ejs": "^3.1.9",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"helmet": "^7.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mocha": "^10.2.0",
|
||||
"sequelize": "^6.35.2",
|
||||
"socket.io": "^4.6.1",
|
||||
"sinon": "^17.0.1",
|
||||
"socket.io": "^4.7.2",
|
||||
"sqlite3": "^5.1.6",
|
||||
"systeminformation": "^5.21.20"
|
||||
},
|
||||
"description": ""
|
||||
"stream": "^0.0.2",
|
||||
"supertest": "^6.3.3",
|
||||
"systeminformation": "^5.21.22"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
.meter {
|
||||
box-sizing: content-box;
|
||||
height: 15px; /* Can be anything */
|
||||
height: 15px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
position: relative;
|
||||
|
|
72
public/css/tabler.min.css
vendored
|
@ -11462,47 +11462,38 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.column-gap-0 {
|
||||
-moz-column-gap: 0 !important;
|
||||
column-gap: 0 !important
|
||||
}
|
||||
|
||||
.column-gap-1 {
|
||||
-moz-column-gap: .25rem !important;
|
||||
column-gap: .25rem !important
|
||||
}
|
||||
|
||||
.column-gap-2 {
|
||||
-moz-column-gap: .5rem !important;
|
||||
column-gap: .5rem !important
|
||||
}
|
||||
|
||||
.column-gap-3 {
|
||||
-moz-column-gap: 1rem !important;
|
||||
column-gap: 1rem !important
|
||||
}
|
||||
|
||||
.column-gap-4 {
|
||||
-moz-column-gap: 1.5rem !important;
|
||||
column-gap: 1.5rem !important
|
||||
}
|
||||
|
||||
.column-gap-5 {
|
||||
-moz-column-gap: 2rem !important;
|
||||
column-gap: 2rem !important
|
||||
}
|
||||
|
||||
.column-gap-6 {
|
||||
-moz-column-gap: 3rem !important;
|
||||
column-gap: 3rem !important
|
||||
}
|
||||
|
||||
.column-gap-7 {
|
||||
-moz-column-gap: 5rem !important;
|
||||
column-gap: 5rem !important
|
||||
}
|
||||
|
||||
.column-gap-8 {
|
||||
-moz-column-gap: 8rem !important;
|
||||
column-gap: 8rem !important
|
||||
}
|
||||
|
||||
|
@ -12910,17 +12901,14 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.columns-2 {
|
||||
-moz-columns: 2 !important;
|
||||
columns: 2 !important
|
||||
}
|
||||
|
||||
.columns-3 {
|
||||
-moz-columns: 3 !important;
|
||||
columns: 3 !important
|
||||
}
|
||||
|
||||
.columns-4 {
|
||||
-moz-columns: 4 !important;
|
||||
columns: 4 !important
|
||||
}
|
||||
|
||||
|
@ -13821,47 +13809,38 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.column-gap-sm-0 {
|
||||
-moz-column-gap: 0 !important;
|
||||
column-gap: 0 !important
|
||||
}
|
||||
|
||||
.column-gap-sm-1 {
|
||||
-moz-column-gap: .25rem !important;
|
||||
column-gap: .25rem !important
|
||||
}
|
||||
|
||||
.column-gap-sm-2 {
|
||||
-moz-column-gap: .5rem !important;
|
||||
column-gap: .5rem !important
|
||||
}
|
||||
|
||||
.column-gap-sm-3 {
|
||||
-moz-column-gap: 1rem !important;
|
||||
column-gap: 1rem !important
|
||||
}
|
||||
|
||||
.column-gap-sm-4 {
|
||||
-moz-column-gap: 1.5rem !important;
|
||||
column-gap: 1.5rem !important
|
||||
}
|
||||
|
||||
.column-gap-sm-5 {
|
||||
-moz-column-gap: 2rem !important;
|
||||
column-gap: 2rem !important
|
||||
}
|
||||
|
||||
.column-gap-sm-6 {
|
||||
-moz-column-gap: 3rem !important;
|
||||
column-gap: 3rem !important
|
||||
}
|
||||
|
||||
.column-gap-sm-7 {
|
||||
-moz-column-gap: 5rem !important;
|
||||
column-gap: 5rem !important
|
||||
}
|
||||
|
||||
.column-gap-sm-8 {
|
||||
-moz-column-gap: 8rem !important;
|
||||
column-gap: 8rem !important
|
||||
}
|
||||
|
||||
|
@ -13878,17 +13857,14 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.columns-sm-2 {
|
||||
-moz-columns: 2 !important;
|
||||
columns: 2 !important
|
||||
}
|
||||
|
||||
.columns-sm-3 {
|
||||
-moz-columns: 3 !important;
|
||||
columns: 3 !important
|
||||
}
|
||||
|
||||
.columns-sm-4 {
|
||||
-moz-columns: 4 !important;
|
||||
columns: 4 !important
|
||||
}
|
||||
}
|
||||
|
@ -14790,47 +14766,38 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.column-gap-md-0 {
|
||||
-moz-column-gap: 0 !important;
|
||||
column-gap: 0 !important
|
||||
}
|
||||
|
||||
.column-gap-md-1 {
|
||||
-moz-column-gap: .25rem !important;
|
||||
column-gap: .25rem !important
|
||||
}
|
||||
|
||||
.column-gap-md-2 {
|
||||
-moz-column-gap: .5rem !important;
|
||||
column-gap: .5rem !important
|
||||
}
|
||||
|
||||
.column-gap-md-3 {
|
||||
-moz-column-gap: 1rem !important;
|
||||
column-gap: 1rem !important
|
||||
}
|
||||
|
||||
.column-gap-md-4 {
|
||||
-moz-column-gap: 1.5rem !important;
|
||||
column-gap: 1.5rem !important
|
||||
}
|
||||
|
||||
.column-gap-md-5 {
|
||||
-moz-column-gap: 2rem !important;
|
||||
column-gap: 2rem !important
|
||||
}
|
||||
|
||||
.column-gap-md-6 {
|
||||
-moz-column-gap: 3rem !important;
|
||||
column-gap: 3rem !important
|
||||
}
|
||||
|
||||
.column-gap-md-7 {
|
||||
-moz-column-gap: 5rem !important;
|
||||
column-gap: 5rem !important
|
||||
}
|
||||
|
||||
.column-gap-md-8 {
|
||||
-moz-column-gap: 8rem !important;
|
||||
column-gap: 8rem !important
|
||||
}
|
||||
|
||||
|
@ -14847,17 +14814,14 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.columns-md-2 {
|
||||
-moz-columns: 2 !important;
|
||||
columns: 2 !important
|
||||
}
|
||||
|
||||
.columns-md-3 {
|
||||
-moz-columns: 3 !important;
|
||||
columns: 3 !important
|
||||
}
|
||||
|
||||
.columns-md-4 {
|
||||
-moz-columns: 4 !important;
|
||||
columns: 4 !important
|
||||
}
|
||||
}
|
||||
|
@ -15759,47 +15723,38 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.column-gap-lg-0 {
|
||||
-moz-column-gap: 0 !important;
|
||||
column-gap: 0 !important
|
||||
}
|
||||
|
||||
.column-gap-lg-1 {
|
||||
-moz-column-gap: .25rem !important;
|
||||
column-gap: .25rem !important
|
||||
}
|
||||
|
||||
.column-gap-lg-2 {
|
||||
-moz-column-gap: .5rem !important;
|
||||
column-gap: .5rem !important
|
||||
}
|
||||
|
||||
.column-gap-lg-3 {
|
||||
-moz-column-gap: 1rem !important;
|
||||
column-gap: 1rem !important
|
||||
}
|
||||
|
||||
.column-gap-lg-4 {
|
||||
-moz-column-gap: 1.5rem !important;
|
||||
column-gap: 1.5rem !important
|
||||
}
|
||||
|
||||
.column-gap-lg-5 {
|
||||
-moz-column-gap: 2rem !important;
|
||||
column-gap: 2rem !important
|
||||
}
|
||||
|
||||
.column-gap-lg-6 {
|
||||
-moz-column-gap: 3rem !important;
|
||||
column-gap: 3rem !important
|
||||
}
|
||||
|
||||
.column-gap-lg-7 {
|
||||
-moz-column-gap: 5rem !important;
|
||||
column-gap: 5rem !important
|
||||
}
|
||||
|
||||
.column-gap-lg-8 {
|
||||
-moz-column-gap: 8rem !important;
|
||||
column-gap: 8rem !important
|
||||
}
|
||||
|
||||
|
@ -15816,17 +15771,14 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.columns-lg-2 {
|
||||
-moz-columns: 2 !important;
|
||||
columns: 2 !important
|
||||
}
|
||||
|
||||
.columns-lg-3 {
|
||||
-moz-columns: 3 !important;
|
||||
columns: 3 !important
|
||||
}
|
||||
|
||||
.columns-lg-4 {
|
||||
-moz-columns: 4 !important;
|
||||
columns: 4 !important
|
||||
}
|
||||
}
|
||||
|
@ -16728,47 +16680,38 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.column-gap-xl-0 {
|
||||
-moz-column-gap: 0 !important;
|
||||
column-gap: 0 !important
|
||||
}
|
||||
|
||||
.column-gap-xl-1 {
|
||||
-moz-column-gap: .25rem !important;
|
||||
column-gap: .25rem !important
|
||||
}
|
||||
|
||||
.column-gap-xl-2 {
|
||||
-moz-column-gap: .5rem !important;
|
||||
column-gap: .5rem !important
|
||||
}
|
||||
|
||||
.column-gap-xl-3 {
|
||||
-moz-column-gap: 1rem !important;
|
||||
column-gap: 1rem !important
|
||||
}
|
||||
|
||||
.column-gap-xl-4 {
|
||||
-moz-column-gap: 1.5rem !important;
|
||||
column-gap: 1.5rem !important
|
||||
}
|
||||
|
||||
.column-gap-xl-5 {
|
||||
-moz-column-gap: 2rem !important;
|
||||
column-gap: 2rem !important
|
||||
}
|
||||
|
||||
.column-gap-xl-6 {
|
||||
-moz-column-gap: 3rem !important;
|
||||
column-gap: 3rem !important
|
||||
}
|
||||
|
||||
.column-gap-xl-7 {
|
||||
-moz-column-gap: 5rem !important;
|
||||
column-gap: 5rem !important
|
||||
}
|
||||
|
||||
.column-gap-xl-8 {
|
||||
-moz-column-gap: 8rem !important;
|
||||
column-gap: 8rem !important
|
||||
}
|
||||
|
||||
|
@ -16785,17 +16728,14 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.columns-xl-2 {
|
||||
-moz-columns: 2 !important;
|
||||
columns: 2 !important
|
||||
}
|
||||
|
||||
.columns-xl-3 {
|
||||
-moz-columns: 3 !important;
|
||||
columns: 3 !important
|
||||
}
|
||||
|
||||
.columns-xl-4 {
|
||||
-moz-columns: 4 !important;
|
||||
columns: 4 !important
|
||||
}
|
||||
}
|
||||
|
@ -17697,47 +17637,38 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.column-gap-xxl-0 {
|
||||
-moz-column-gap: 0 !important;
|
||||
column-gap: 0 !important
|
||||
}
|
||||
|
||||
.column-gap-xxl-1 {
|
||||
-moz-column-gap: .25rem !important;
|
||||
column-gap: .25rem !important
|
||||
}
|
||||
|
||||
.column-gap-xxl-2 {
|
||||
-moz-column-gap: .5rem !important;
|
||||
column-gap: .5rem !important
|
||||
}
|
||||
|
||||
.column-gap-xxl-3 {
|
||||
-moz-column-gap: 1rem !important;
|
||||
column-gap: 1rem !important
|
||||
}
|
||||
|
||||
.column-gap-xxl-4 {
|
||||
-moz-column-gap: 1.5rem !important;
|
||||
column-gap: 1.5rem !important
|
||||
}
|
||||
|
||||
.column-gap-xxl-5 {
|
||||
-moz-column-gap: 2rem !important;
|
||||
column-gap: 2rem !important
|
||||
}
|
||||
|
||||
.column-gap-xxl-6 {
|
||||
-moz-column-gap: 3rem !important;
|
||||
column-gap: 3rem !important
|
||||
}
|
||||
|
||||
.column-gap-xxl-7 {
|
||||
-moz-column-gap: 5rem !important;
|
||||
column-gap: 5rem !important
|
||||
}
|
||||
|
||||
.column-gap-xxl-8 {
|
||||
-moz-column-gap: 8rem !important;
|
||||
column-gap: 8rem !important
|
||||
}
|
||||
|
||||
|
@ -17754,17 +17685,14 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.columns-xxl-2 {
|
||||
-moz-columns: 2 !important;
|
||||
columns: 2 !important
|
||||
}
|
||||
|
||||
.columns-xxl-3 {
|
||||
-moz-columns: 3 !important;
|
||||
columns: 3 !important
|
||||
}
|
||||
|
||||
.columns-xxl-4 {
|
||||
-moz-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';
|
||||
|
||||
var themeStorageKey = "tablerTheme";
|
||||
var defaultTheme = "light";
|
||||
var defaultTheme = "dark";
|
||||
var selectedTheme;
|
||||
var params = new Proxy(new URLSearchParams(window.location.search), {
|
||||
get: function get(searchParams, prop) {
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
// SOCKET IO
|
||||
const socket = io({
|
||||
auth: {
|
||||
token: "abc"
|
||||
}
|
||||
});
|
||||
socket.on('connect', () => { console.log('connected'); });
|
||||
|
||||
// ON CONNECT EVENT
|
||||
socket.on('connect', () => {
|
||||
console.log('Connected');
|
||||
});
|
||||
|
||||
// SELECT METRICS ELEMENTS
|
||||
// Server metrics
|
||||
const cpuText = document.getElementById('cpu-text');
|
||||
const cpuBar = document.getElementById('cpu-bar');
|
||||
const ramText = document.getElementById('ram-text');
|
||||
|
@ -20,19 +10,22 @@ const netBar = document.getElementById('net-bar');
|
|||
const diskText = document.getElementById('disk-text');
|
||||
const diskBar = document.getElementById('disk-bar');
|
||||
|
||||
// Container cards
|
||||
const dockerCards = document.getElementById('cards');
|
||||
|
||||
// Container logs
|
||||
const logViewer = document.getElementById('logView');
|
||||
|
||||
//Update usage bars
|
||||
// Server metrics
|
||||
socket.on('metrics', (data) => {
|
||||
|
||||
let {cpu, ram, tx, rx, disk} = data;
|
||||
let [cpu, ram, tx, rx, disk] = data;
|
||||
|
||||
cpuText.innerHTML = `<span>CPU ${cpu} %</span>`;
|
||||
if (cpu < 7 ) { cpu = 7; }
|
||||
cpuBar.innerHTML = `<span style="width: ${cpu}%"><span></span></span>`;
|
||||
|
||||
ramText.innerHTML = `<span>RAM ${ram} %</span>`;
|
||||
if (ram < 7 ) { ram = 7; }
|
||||
ramBar.innerHTML = `<span style="width: ${ram}%"><span></span></span>`;
|
||||
|
||||
tx = Math.round(tx / 1024 / 1024);
|
||||
|
@ -42,165 +35,117 @@ socket.on('metrics', (data) => {
|
|||
netBar.innerHTML = `<span style="width: 50%"><span></span></span>`;
|
||||
|
||||
diskText.innerHTML = `<span>DISK ${disk} %</span>`;
|
||||
if (disk < 7 ) { disk = 7; }
|
||||
diskBar.innerHTML = `<span style="width: ${disk}%"><span></span></span>`;
|
||||
});
|
||||
|
||||
function drawCharts(name, cpu_array, ram_array) {
|
||||
var elements = document.querySelectorAll(`${name}`);
|
||||
|
||||
Array.from(elements).forEach(function(element) {
|
||||
if (window.ApexCharts) {
|
||||
new ApexCharts(element, {
|
||||
chart: {
|
||||
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>`;
|
||||
// Container cards
|
||||
socket.on('containers', (data) => {
|
||||
let deleteMeElements = document.querySelectorAll('.deleteme');
|
||||
deleteMeElements.forEach((element) => {
|
||||
element.parentNode.removeChild(element);
|
||||
});
|
||||
dockerCards.insertAdjacentHTML("afterend", data);
|
||||
});
|
||||
|
||||
|
||||
function drawCharts(name, cpuArray, ramArray) {
|
||||
let element = document.querySelector(`${name}`);
|
||||
|
||||
socket.on('cards', (data) => {
|
||||
|
||||
console.log('cards deleted');
|
||||
let deleteMeElements = document.querySelectorAll('.deleteme');
|
||||
deleteMeElements.forEach((element) => {
|
||||
element.parentNode.removeChild(element);
|
||||
});
|
||||
|
||||
dockerCards.insertAdjacentHTML("afterend", data);
|
||||
|
||||
// check localStorage for items ending with _cpu and redraw the charts
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
if (localStorage.key(i).endsWith('_cpu')) {
|
||||
let name = localStorage.key(i).split('_')[0];
|
||||
let cpu_array = JSON.parse(localStorage.getItem(`${name}_cpu`));
|
||||
let ram_array = JSON.parse(localStorage.getItem(`${name}_ram`));
|
||||
drawCharts(`#${name}_chart`, cpu_array, ram_array);
|
||||
let chart = new ApexCharts(element, {
|
||||
chart: {
|
||||
type: "line",
|
||||
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: 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) => {
|
||||
|
||||
let {name, cpu, ram} = data;
|
||||
|
||||
console.log(`drawing chart for ${name}`)
|
||||
|
||||
var cpu_array = JSON.parse(localStorage.getItem(`${name}_cpu`));
|
||||
var ram_array = JSON.parse(localStorage.getItem(`${name}_ram`));
|
||||
|
||||
if (cpu_array == null) { cpu_array = Array(30).fill(0); }
|
||||
if (ram_array == null) { ram_array = Array(30).fill(0); }
|
||||
|
||||
cpu_array.push(cpu);
|
||||
ram_array.push(ram);
|
||||
let containerStats = data;
|
||||
|
||||
cpu_array = cpu_array.slice(-30);
|
||||
ram_array = ram_array.slice(-30);
|
||||
for (const [name, statsArray] of Object.entries(containerStats)) {
|
||||
|
||||
localStorage.setItem(`${name}_cpu`, JSON.stringify(cpu_array));
|
||||
localStorage.setItem(`${name}_ram`, JSON.stringify(ram_array));
|
||||
let cpuArray = statsArray.cpuArray;
|
||||
let ramArray = statsArray.ramArray;
|
||||
|
||||
// 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`);
|
||||
let chart = document.getElementById(`${name}_chart`);
|
||||
if (chart) {
|
||||
chart.innerHTML = '';
|
||||
drawCharts(`#${name}_chart`, cpuArray, ramArray);
|
||||
} 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
|