Merge pull request #12 from lllllllillllllillll/dev
dev to main: v0.05 release
This commit is contained in:
commit
8ac19ada26
17 changed files with 507 additions and 246 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,3 +1,17 @@
|
|||
## v0.05 ( Nov 17th 2023 )
|
||||
* Environment Variables and Labels are now unchecked by default.
|
||||
* Support for Docker volumes.
|
||||
* Fixed app uninstall.
|
||||
* Fixed Proxy Manager.
|
||||
* Updated functions to ignore the three DweebUI containers: DweebUI, DweebCache(redis), and DweebProxy(caddy).
|
||||
* Visual updates: Tabs for networks, images, and volumes. Added 'update' option in container drop-down.
|
||||
* Updated main.js to prevent javascript errors.
|
||||
* Fix for templates using 'set' instead of 'default' in environment variables.
|
||||
* Fixes for templates with no volumes or no labels.
|
||||
* New README.md.
|
||||
* New screenshots.
|
||||
* Automatically persists data in docker volumes if there is no bind mount.
|
||||
|
||||
## v0.04 (Nov 11th 2023)
|
||||
* Docker Image and Compose file available.
|
||||
* The containers DweebUI and DweebCache are hidden from the dashboard.
|
||||
|
|
98
README.md
98
README.md
|
@ -1,57 +1,82 @@
|
|||
# DweebUI
|
||||
|
||||
|
||||
DweebUI is a simple Docker web interface created with javascript and node.js
|
||||
|
||||
Pre-Pre-Pre-Pre-Pre Alpha v 0.04 ( :fire: Experimental. Don't install on any servers you care about :fire: )
|
||||
Pre-Pre-Pre-Pre-Pre Alpha v0.05 ( :fire: Experimental. Don't install on any servers you care about :fire: )
|
||||
|
||||
[](https://github.com/lllllllillllllillll/DweebUI/blob/main/LICENSE)
|
||||
[](https://github.com/lllllllillllllillll)
|
||||
|
||||
|
||||
* I haven't used Github very much and I'm still new to javascript.
|
||||
* This is the first project I've ever released and I'm sure it's full of plenty of bugs and mistakes.
|
||||
* I probably should have waited a lot longer to share this :|
|
||||
|
||||
Requirements: Docker
|
||||
<a href="https://raw.githubusercontent.com//lllllllillllllillll/DweebUI/main/screenshots/dashboard.png"><img src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI/main/screenshots/dashboard.png" width="50%"/></a>
|
||||
|
||||
<a href="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI/main/screenshots/apps.png"><img src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI/main/screenshots/apps.png" width="50%"/></a>
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
* [x] Dashboard provides server metrics (cpu, ram, network, disk) and container controls on a single page.
|
||||
* [x] Light/Dark Mode.
|
||||
* [x] Easy to install app templates.
|
||||
* [x] Automatically persists data in docker volumes if bind mount isn't used.
|
||||
* [x] Proxy manager for Caddy.
|
||||
* [x] Partial Portainer Template Support (Network Mode, Ports, Volumes, Enviroment Variables, Labels, Commands, Restart Policy, Nvidia Hardware Acceleration).
|
||||
* [x] Multi-User built-in.
|
||||
* [ ] User pages: Shortcuts, Requests, Support. (planned)
|
||||
* [x] Support for Windows, Linux, and MacOS.
|
||||
* [ ] Import compose files. (planned)
|
||||
* [x] Pure javascript. No frameworks or typescript.
|
||||
* [x] Templates.json maintains compatability with Portainer, allowing you to use the template without needing to use DweebUI.
|
||||
* [ ] Manage your Docker networks, images, and volumes. (planned)
|
||||
* [ ] Preset variables. (planned)
|
||||
|
||||
* Dashboard provides server metrics (cpu, ram, network, disk) and container controls on a single page.
|
||||
* Partial Portainer Template Support (Network Mode, Ports, Volumes, Enviroment Variables, Labels, Commands, Restart Policy, Nvidia Hardware Acceleration).
|
||||
* Light/Dark Mode.
|
||||
* Support for multiple users is built in (but unused).
|
||||
* ~~Caddy Proxy Manager (very simple. proof of concept)~~ Broken since moving to docker container.
|
||||
* Pure javascript. No frameworks or typescript.
|
||||
* User data is stored in a sqlite database and uses browser sessions and a redis store for authentication.
|
||||
* Templates.json maintains compatability with Portainer, so you can use the template without needing to use DweebUI.
|
||||
|
||||
## Setup
|
||||
|
||||
* Docker compose.yaml:
|
||||
```
|
||||
services:
|
||||
dweebui:
|
||||
container_name: DweebUI
|
||||
image: lllllllillllllillll/dweebui:v0.04
|
||||
ports:
|
||||
- 8000:8000
|
||||
depends_on:
|
||||
- cache
|
||||
links:
|
||||
- cache
|
||||
volumes:
|
||||
- dweebui:/app
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
cache:
|
||||
container_name: DweebCache
|
||||
image: redis:6.2-alpine
|
||||
restart: always
|
||||
command: redis-server --save 20 1 --loglevel warning --requirepass eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81
|
||||
volumes:
|
||||
- cache:/data
|
||||
|
||||
dweebui:
|
||||
container_name: DweebUI
|
||||
image: lllllllillllllillll/dweebui:v0.05
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
depends_on:
|
||||
- cache
|
||||
links:
|
||||
- cache
|
||||
volumes:
|
||||
- dweebui:/app
|
||||
- ./caddyfiles/Caddyfile:/app/caddyfiles/Caddyfile
|
||||
- ./caddyfiles/sites:/app/caddyfiles/sites
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
cache:
|
||||
container_name: DweebCache
|
||||
image: redis:6.2-alpine
|
||||
restart: always
|
||||
command: redis-server --save 20 1 --loglevel warning --requirepass eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81
|
||||
volumes:
|
||||
- cache:/data
|
||||
proxy:
|
||||
container_name: DweebProxy
|
||||
image: caddy:2.4.5-alpine
|
||||
depends_on:
|
||||
- dweebui
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
volumes:
|
||||
- caddy:/data
|
||||
- caddy:/config
|
||||
- ./caddyfiles/Caddyfile:/etc/caddy/Caddyfile
|
||||
- ./caddyfiles/sites:/etc/caddy/sites
|
||||
|
||||
volumes:
|
||||
dweebui:
|
||||
cache:
|
||||
dweebui:
|
||||
cache:
|
||||
caddy:
|
||||
```
|
||||
|
||||
* Using setup.sh:
|
||||
|
@ -61,8 +86,7 @@ cd DweebUI
|
|||
chmod +x setup.sh
|
||||
sudo ./setup.sh
|
||||
```
|
||||
Once setup is complete, I recommend installing Caddy first, then something like code-server.
|
||||
The template is very rough.
|
||||
|
||||
|
||||
## Credit
|
||||
|
||||
|
|
2
app.js
2
app.js
|
@ -5,8 +5,10 @@ const app = express();
|
|||
const routes = require("./routes");
|
||||
|
||||
const { serverStats, containerList, containerStats, containerAction } = require('./functions/system_information');
|
||||
const { RefreshSites } = require('./controllers/site_actions');
|
||||
|
||||
let sent_list, clicked;
|
||||
app.locals.site_list = '';
|
||||
|
||||
const redisClient = require('redis').createClient({
|
||||
url: 'redis://DweebCache:6379',
|
||||
|
|
1
caddyfiles/Caddyfile
Normal file
1
caddyfiles/Caddyfile
Normal file
|
@ -0,0 +1 @@
|
|||
import ./sites/*
|
|
@ -139,11 +139,11 @@ function appCard(data) {
|
|||
try {
|
||||
let volumes = data.volumes[i];
|
||||
let volume_check = volumes ? "checked" : "";
|
||||
let volume_bind = volumes.bind.split(":")[0] ? volumes.bind.split(":")[0] : "";
|
||||
let volume_container = volumes.container.split(":")[0] ? volumes.container.split(":")[0] : "";
|
||||
let volume_readwrite = "rw"
|
||||
let volume_bind = volumes.bind ? volumes.bind : "";
|
||||
let volume_container = volumes.container ? volumes.container.split(":")[0] : "";
|
||||
let volume_readwrite = "rw";
|
||||
|
||||
if ((volumes.readonly == true) || (volumes.container.endsWith(":ro"))) {
|
||||
if (volumes.readonly == true) {
|
||||
volume_readwrite = "ro";
|
||||
}
|
||||
|
||||
|
@ -166,8 +166,9 @@ function appCard(data) {
|
|||
// Get environment details
|
||||
try {
|
||||
let env = data.env[i];
|
||||
let env_check = env ? "checked" : "";
|
||||
let env_check = "";
|
||||
let env_default = env.default ? env.default : "";
|
||||
if (env.set) { env_default = env.set;}
|
||||
let env_description = env.description ? env.description : "";
|
||||
let env_label = env.label ? env.label : "";
|
||||
let env_name = env.name ? env.name : "";
|
||||
|
@ -193,7 +194,7 @@ function appCard(data) {
|
|||
|
||||
try {
|
||||
let label = data.labels[i];
|
||||
let label_check = label ? "checked" : "";
|
||||
let label_check = "";
|
||||
let label_name = label.name ? label.name : "";
|
||||
let label_value = label.value ? label.value : "";
|
||||
|
||||
|
|
|
@ -117,12 +117,10 @@ module.exports.dashCard = function dashCard(data) {
|
|||
<div class="card-stamp card-stamp-sm">
|
||||
<img heigh="150px" width="150px" 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/dweebui.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="buttonAction(this)" name="${name}" value="start" id="${state}" class="btn-action" title="Start" ${enabled}><!-- 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>
|
||||
|
@ -142,13 +140,13 @@ module.exports.dashCard = function dashCard(data) {
|
|||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#${name}_modal-details" href="#">Details</a>
|
||||
<a class="dropdown-item" href="#">Logs</a>
|
||||
<a class="dropdown-item" onclick="viewLogs(this)" name="${name}" data-bs-toggle="modal" data-bs-target="#${name}_logs" href="#">Logs</a>
|
||||
<a class="dropdown-item" href="#">Edit</a>
|
||||
<a class="dropdown-item text-primary" href="#">Update</a>
|
||||
<a class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#${name}_modal-danger" href="#">Remove</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -170,84 +168,168 @@ module.exports.dashCard = function dashCard(data) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="modal modal-blur fade deleteme" id="${name}_modal-danger" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="modal-status bg-danger"></div>
|
||||
<div class="modal-body text-center py-3">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/alert-triangle -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon mb-2 text-danger icon-lg" 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 9v2m0 4v.01"></path><path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path></svg>
|
||||
<h3>Remove ${name}?</h3>
|
||||
<form action="/uninstall" id="uninstall" method="POST">
|
||||
<input type="text" class="form-control" name="service_name" value="${app_name}" hidden/>
|
||||
<div class="mb-3"> </div>
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="divide-y">
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">Remove Volumes</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" checked="" name="remove_volumes">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">Remove Image</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" checked="" name="remove_image">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">Remove Backups</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" checked="" name="remove_backups">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-1"> </div>
|
||||
<div class="text-muted">Enter "Yes" below to remove the container.</div>
|
||||
<input type="text" class="form-control mb-2" name="confirm">
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="w-100">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="#" class="btn w-100" data-bs-dismiss="modal">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="submit" form="uninstall" class="btn btn-danger w-100" value="Uninstall"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="modal-status bg-danger"></div>
|
||||
<div class="modal-body text-center py-3">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/alert-triangle -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon mb-2 text-danger icon-lg" 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 9v2m0 4v.01"></path><path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path></svg>
|
||||
<h3>Remove ${name}?</h3>
|
||||
<form action="/uninstall" id="uninstall" method="POST">
|
||||
<input type="text" class="form-control" name="service_name" value="${app_name}" hidden/>
|
||||
<div class="mb-3"> </div>
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="divide-y">
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">Remove Volumes</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" checked="" name="remove_volumes">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Remove Image
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" checked="" name="remove_image">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Remove Backups
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" checked="" name="remove_backups">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1"> </div>
|
||||
<div class="text-muted">Enter "Yes" below to remove the container.</div>
|
||||
<input type="text" class="form-control mb-2" name="confirm" autocomplete="off">
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="w-100">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="#" class="btn w-100" data-bs-dismiss="modal">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="submit" form="uninstall" class="btn btn-danger w-100" value="Uninstall"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="modal modal-blur fade" id="${name}_logs" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Scrollable modal</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas
|
||||
eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>
|
||||
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue
|
||||
laoreet rutrum faucibus dolor auctor.</p>
|
||||
<p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl
|
||||
consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p>
|
||||
<p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas
|
||||
eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>
|
||||
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue
|
||||
laoreet rutrum faucibus dolor auctor.</p>
|
||||
<p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl
|
||||
consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p>
|
||||
<p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas
|
||||
eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>
|
||||
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue
|
||||
laoreet rutrum faucibus dolor auctor.</p>
|
||||
<p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl
|
||||
consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p>
|
||||
<p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas
|
||||
eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>
|
||||
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue
|
||||
laoreet rutrum faucibus dolor auctor.</p>
|
||||
<p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl
|
||||
consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p>
|
||||
<p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas
|
||||
eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>
|
||||
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue
|
||||
laoreet rutrum faucibus dolor auctor.</p>
|
||||
<p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl
|
||||
consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p>
|
||||
<p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas
|
||||
eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>
|
||||
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue
|
||||
laoreet rutrum faucibus dolor auctor.</p>
|
||||
<p>Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl
|
||||
consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="modal modal-blur fade" id="${name}_modal-details" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
|
|
25
compose.yaml
25
compose.yaml
|
@ -1,10 +1,8 @@
|
|||
services:
|
||||
dweebui:
|
||||
container_name: DweebUI
|
||||
build:
|
||||
context: .
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
image: lllllllillllllillll/dweebui:v0.05
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
depends_on:
|
||||
|
@ -13,6 +11,8 @@ services:
|
|||
- cache
|
||||
volumes:
|
||||
- dweebui:/app
|
||||
- ./caddyfiles/Caddyfile:/app/caddyfiles/Caddyfile
|
||||
- ./caddyfiles/sites:/app/caddyfiles/sites
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
cache:
|
||||
container_name: DweebCache
|
||||
|
@ -21,7 +21,20 @@ services:
|
|||
command: redis-server --save 20 1 --loglevel warning --requirepass eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81
|
||||
volumes:
|
||||
- cache:/data
|
||||
|
||||
proxy:
|
||||
container_name: DweebProxy
|
||||
image: caddy:2.4.5-alpine
|
||||
depends_on:
|
||||
- dweebui
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
volumes:
|
||||
- caddy:/data
|
||||
- caddy:/config
|
||||
- ./caddyfiles/Caddyfile:/etc/caddy/Caddyfile
|
||||
- ./caddyfiles/sites:/etc/caddy/sites
|
||||
|
||||
volumes:
|
||||
dweebui:
|
||||
cache:
|
||||
cache:
|
||||
caddy:
|
|
@ -144,6 +144,8 @@ 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 = {
|
||||
|
|
|
@ -13,7 +13,8 @@ exports.Dashboard = async function (req, res) {
|
|||
name: user.first_name + ' ' + user.last_name,
|
||||
role: user.role,
|
||||
avatar: user.avatar,
|
||||
isLoggedIn: true
|
||||
isLoggedIn: true,
|
||||
site_list: req.app.locals.site_list,
|
||||
});
|
||||
} else {
|
||||
// Redirect to the login page
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const { readFileSync, writeFileSync, appendFileSync, readdirSync } = require('fs');
|
||||
const { execSync } = require("child_process");
|
||||
const { siteCard } = require('../components/siteCard');
|
||||
|
||||
const { containerExec } = require('../functions/system_information')
|
||||
|
||||
exports.AddSite = async function (req, res) {
|
||||
|
||||
|
@ -20,31 +20,54 @@ exports.AddSite = async function (req, res) {
|
|||
caddyfile += `\n\t}`
|
||||
caddyfile += `\n}`
|
||||
|
||||
// save caddyfile
|
||||
writeFileSync(`/home/docker/caddy/sites/${domain}.Caddyfile`, caddyfile, function (err) { console.log(err) });
|
||||
|
||||
// save caddyfile
|
||||
writeFileSync(`./caddyfiles/sites/${domain}.Caddyfile`, caddyfile, function (err) { console.log(err) });
|
||||
|
||||
|
||||
// format caddyfile
|
||||
execSync(`docker exec caddy caddy fmt --overwrite /etc/caddy/sites/${domain}.Caddyfile`, (err, stdout, stderr) => {
|
||||
if (err) { console.error(`error: ${err.message}`); return; }
|
||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
||||
if (stdout) { console.log(`stdout:\n${stdout}`); return; }
|
||||
console.log(`Formatted ${domain}.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);
|
||||
|
||||
// reload caddy config to enable new site
|
||||
execSync(`docker exec caddy caddy reload --config /etc/caddy/Caddyfile`, (err, stdout, stderr) => {
|
||||
if (err) { console.error(`error: ${err.message}`); return; }
|
||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
||||
if (stdout) { console.log(`stdout:\n${stdout}`); return; }
|
||||
console.log(`reloaded caddy config`)
|
||||
});
|
||||
req.app.locals.site_list += site;
|
||||
|
||||
// append the site to site_list.ejs
|
||||
appendFileSync('./views/partials/site_list.ejs', site, function (err) { console.log(err) });
|
||||
|
||||
res.redirect("/");
|
||||
} else {
|
||||
|
@ -61,22 +84,20 @@ exports.RemoveSite = async function (req, res) {
|
|||
|
||||
|
||||
for (const [key, value] of Object.entries(req.body)) {
|
||||
console.log(`${key}: ${value}`);
|
||||
execSync(`rm /home/docker/caddy/sites/${value}.Caddyfile`, (err, stdout, stderr) => {
|
||||
|
||||
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`);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
// reload caddy config to disable sites
|
||||
try {
|
||||
execSync(`docker exec caddy caddy reload --config /etc/caddy/Caddyfile`, (err, stdout, stderr) => {
|
||||
if (err) { console.error(`error: ${err.message}`); return; }
|
||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
||||
console.log(`reloaded caddy config`)
|
||||
}); } catch (error) { console.log("No sites to reload") }
|
||||
let reload = {
|
||||
container: 'DweebProxy',
|
||||
command: `caddy reload --config /etc/caddy/Caddyfile`
|
||||
}
|
||||
await containerExec(reload);
|
||||
|
||||
|
||||
console.log('Removed Site(s)')
|
||||
|
@ -98,21 +119,15 @@ exports.RefreshSites = async function (req, res) {
|
|||
|
||||
|
||||
// Clear site_list.ejs
|
||||
writeFileSync('./views/partials/site_list.ejs', '', function (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log('site_list.ejs has been cleared');
|
||||
}
|
||||
});
|
||||
req.app.locals.site_list = "";
|
||||
|
||||
|
||||
// check if /home/docker/caddy/sites/ contains any .json files, then delete them
|
||||
// check if ./caddyfiles/sites contains any .json files, then delete them
|
||||
try {
|
||||
let files = readdirSync('/home/docker/caddy/sites/');
|
||||
let files = readdirSync('./caddyfiles/sites/');
|
||||
files.forEach(file => {
|
||||
if (file.includes(".json")) {
|
||||
execSync(`rm /home/docker/caddy/sites/${file}`, (err, stdout, stderr) => {
|
||||
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}`);
|
||||
|
@ -122,23 +137,25 @@ exports.RefreshSites = async function (req, res) {
|
|||
} catch (error) { console.log("No .json files to delete") }
|
||||
|
||||
// get list of Caddyfiles
|
||||
let sites = readdirSync('/home/docker/caddy/sites/');
|
||||
let sites = readdirSync('./caddyfiles/sites/');
|
||||
|
||||
|
||||
sites.forEach(site_name => {
|
||||
|
||||
// convert the caddyfile of each site to json
|
||||
execSync(`docker exec caddy caddy adapt --config /etc/caddy/sites/${site_name} --pretty >> /home/docker/caddy/sites/${site_name}.json`, (err, stdout, stderr) => {
|
||||
if (err) { console.error(`error: ${err.message}`); return; }
|
||||
if (stderr) { console.error(`stderr: ${stderr}`); return; }
|
||||
console.log(`stdout:\n${stdout}`);
|
||||
});
|
||||
|
||||
// read the json file
|
||||
let site_file = readFileSync(`/home/docker/caddy/sites/${site_name}.json`, 'utf8');
|
||||
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") }
|
||||
|
@ -149,13 +166,13 @@ exports.RefreshSites = async function (req, res) {
|
|||
// build the site card
|
||||
let site = siteCard(type, domain, host, port, id);
|
||||
|
||||
// append the site card to site_list.ejs
|
||||
appendFileSync('./views/partials/site_list.ejs', site, function (err) { console.log(err) });
|
||||
// append the site card to site_list
|
||||
req.app.locals.site_list += site;
|
||||
|
||||
id++;
|
||||
|
||||
});
|
||||
|
||||
|
||||
res.redirect("/");
|
||||
} else {
|
||||
// Redirect to the login page
|
||||
|
|
|
@ -9,6 +9,7 @@ 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;
|
||||
|
@ -16,6 +17,8 @@ module.exports.install = async function (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}`);
|
||||
|
@ -71,9 +74,18 @@ module.exports.install = async function (data) {
|
|||
compose_file += `\n volumes:`
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (data[`volume${i}`] == 'on') {
|
||||
|
||||
// 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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,6 +134,22 @@ module.exports.install = async function (data) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// 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) });
|
||||
|
@ -154,16 +182,16 @@ module.exports.uninstall = async function (data) {
|
|||
var containerName = docker.getContainer(`${data.service_name}`);
|
||||
|
||||
try {
|
||||
containerName.stop(function (err, data) {
|
||||
});
|
||||
} catch { console.log('unable to stop container') }
|
||||
|
||||
|
||||
try {
|
||||
containerName.remove(function (err, data) {
|
||||
});
|
||||
} catch { console.log('unable to remove container') }
|
||||
|
||||
containerName.stop(function (err, data) {
|
||||
if (data) {
|
||||
containerName.remove(function (err, data) {
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
containerName.remove(function (err, data) {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ module.exports.containerList = async function () {
|
|||
for (const container of data) {
|
||||
|
||||
|
||||
if ((container.Names[0].slice(1) != 'DweebUI') && (container.Names[0].slice(1) != 'DweebCache')) {
|
||||
if ((container.Names[0].slice(1) != 'DweebUI') && (container.Names[0].slice(1) != 'DweebCache') && (container.Names[0].slice(1) != 'DweebProxy')) {
|
||||
|
||||
let imageVersion = container.Image.split('/');
|
||||
let service = imageVersion[imageVersion.length - 1].split(':')[0];
|
||||
|
@ -40,20 +40,20 @@ module.exports.containerList = async function () {
|
|||
let containerId = docker.getContainer(container.Id);
|
||||
let containerInfo = await containerId.inspect();
|
||||
|
||||
let external_port = 0;
|
||||
let internal_port = 0;
|
||||
|
||||
// Get ports
|
||||
// Get ports //////////////////////////
|
||||
let ports_list = [];
|
||||
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]
|
||||
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);
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch { console.log('no ports') }
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (ports_list[i] == undefined) {
|
||||
let ports = {
|
||||
|
@ -64,20 +64,20 @@ module.exports.containerList = async function () {
|
|||
}
|
||||
ports_list[i] = ports;
|
||||
}
|
||||
}
|
||||
} /////////////////////////////////////
|
||||
|
||||
|
||||
// Get volumes.
|
||||
// Get volumes ////////////////////////
|
||||
let volumes_list = [];
|
||||
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);
|
||||
}
|
||||
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 = {
|
||||
|
@ -88,19 +88,19 @@ module.exports.containerList = async function () {
|
|||
}
|
||||
volumes_list[i] = volumes;
|
||||
}
|
||||
}
|
||||
} /////////////////////////////////////
|
||||
|
||||
|
||||
// Get environment variables.
|
||||
let environment_variables = [];
|
||||
for (const [key, value] of Object.entries(containerInfo.Config.Env)) {
|
||||
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 = {
|
||||
|
@ -140,8 +140,8 @@ module.exports.containerList = async function () {
|
|||
id: container.Id,
|
||||
state: container.State,
|
||||
image: container.Image,
|
||||
external_port: external_port,
|
||||
internal_port: internal_port,
|
||||
external_port: ports_list[0].external || 0,
|
||||
internal_port: ports_list[0].internal || 0,
|
||||
ports: ports_list,
|
||||
volumes: volumes_list,
|
||||
environment_variables: environment_variables,
|
||||
|
@ -172,15 +172,17 @@ module.exports.containerStats = async function () {
|
|||
|
||||
for (const container of data) {
|
||||
|
||||
const stats = await dockerContainerStats(container.Id);
|
||||
let container_stat = {
|
||||
name: container.Names[0].slice(1),
|
||||
cpu: Math.round(stats[0].cpuPercent),
|
||||
ram: Math.round(stats[0].memPercent)
|
||||
}
|
||||
if ((container.Names[0].slice(1) != 'DweebUI') && (container.Names[0].slice(1) != 'DweebCache') && (container.Names[0].slice(1) != 'DweebProxy')) {
|
||||
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);
|
||||
//push stats to an array
|
||||
container_stats.push(container_stat);
|
||||
}
|
||||
}
|
||||
return container_stats;
|
||||
}
|
||||
|
@ -219,5 +221,36 @@ module.exports.containerAction = async function (data) {
|
|||
|
||||
|
||||
|
||||
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;
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -115,6 +115,10 @@ function buttonAction(button) {
|
|||
socket.emit('clicked', {container: button.name, state: button.id, action: button.value});
|
||||
}
|
||||
|
||||
function viewLogs(button) {
|
||||
console.log(`trying to view logs for ${button.name}`);
|
||||
}
|
||||
|
||||
socket.on('cards', (data) => {
|
||||
|
||||
console.log('cards deleted');
|
||||
|
@ -143,31 +147,31 @@ socket.on('container_stats', (data) => {
|
|||
|
||||
let {name, cpu, ram} = data;
|
||||
|
||||
// get cpu and ram array of the container from local storage
|
||||
var cpu_array = JSON.parse(localStorage.getItem(`${name}_cpu`));
|
||||
var ram_array = JSON.parse(localStorage.getItem(`${name}_ram`));
|
||||
|
||||
// if the cpu and ram arrays are null, create both arrays with 30 values of 0
|
||||
if (cpu_array == null) { cpu_array = Array(30).fill(0); }
|
||||
if (ram_array == null) { ram_array = Array(30).fill(0); }
|
||||
|
||||
// add the new cpu and ram values to the arrays, but limit the array to 30 values
|
||||
cpu_array.push(cpu);
|
||||
ram_array.push(ram);
|
||||
|
||||
|
||||
cpu_array = cpu_array.slice(-30);
|
||||
ram_array = ram_array.slice(-30);
|
||||
|
||||
// save the arrays to local storage
|
||||
localStorage.setItem(`${name}_cpu`, JSON.stringify(cpu_array));
|
||||
localStorage.setItem(`${name}_ram`, JSON.stringify(ram_array));
|
||||
|
||||
// replace the old chart with the new one
|
||||
let chart = document.getElementById(`${name}_chart`);
|
||||
let newChart = document.createElement('div');
|
||||
newChart.id = `${name}_chart`;
|
||||
chart.parentNode.replaceChild(newChart, chart);
|
||||
drawCharts(`#${name}_chart`, cpu_array, ram_array);
|
||||
if (chart) {
|
||||
let newChart = document.createElement('div');
|
||||
newChart.id = `${name}_chart`;
|
||||
chart.parentNode.replaceChild(newChart, chart);
|
||||
drawCharts(`#${name}_chart`, cpu_array, ram_array);
|
||||
} else {
|
||||
console.log(`Chart element with id ${name}_chart not found in the DOM`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('install', (data) => {
|
||||
|
|
|
@ -910,18 +910,13 @@
|
|||
"name": "PGID",
|
||||
"set": "1000"
|
||||
},
|
||||
{
|
||||
"label": "UMASK_SET",
|
||||
"name": "UMASK_SET",
|
||||
"set": "000"
|
||||
},
|
||||
{
|
||||
"label": "TZ",
|
||||
"name": "TZ",
|
||||
"set": "America/Chicago"
|
||||
}
|
||||
],
|
||||
"image": "linuxserver/deluge:latest",
|
||||
"image": "lscr.io/linuxserver/deluge:latest",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/deluge.png",
|
||||
"platform": "linux",
|
||||
"title": "Deluge",
|
||||
|
@ -934,6 +929,11 @@
|
|||
{
|
||||
"container": "/downloads"
|
||||
}
|
||||
],
|
||||
"ports": [
|
||||
"8112/tcp",
|
||||
"6881/tcp",
|
||||
"6881/udp"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -3705,11 +3705,11 @@
|
|||
"container": "/data"
|
||||
},
|
||||
{
|
||||
"bind": "/home/docker/caddy/Caddyfile",
|
||||
"bind": "caddy/Caddyfile",
|
||||
"container": "/etc/caddy/Caddyfile"
|
||||
},
|
||||
{
|
||||
"bind": "/home/docker/caddy/sites",
|
||||
"bind": "caddy/sites",
|
||||
"container": "/etc/caddy/sites"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -224,7 +224,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
|
||||
<%- include('../partials/site_list.ejs') %>
|
||||
<%- site_list %>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -248,7 +248,7 @@
|
|||
|
||||
</form>
|
||||
|
||||
<p class="m-0 text-muted ms-auto">Imported: /home/docker/caddy/Caddyfile</p>
|
||||
<p class="m-0 text-muted ms-auto">./caddyfiles/Caddyfile</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a href="#" class="link-secondary" rel="noopener">
|
||||
v0.04
|
||||
v0.05
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -206,6 +206,45 @@
|
|||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-world" 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 d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /><path d="M3.6 9h16.8" /><path d="M3.6 15h16.8" /><path d="M11.5 3a17 17 0 0 0 0 18" /><path d="M12.5 3a17 17 0 0 1 0 18" /></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Networks
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-augmented-reality" 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 d="M4 8v-2a2 2 0 0 1 2 -2h2" /><path d="M4 16v2a2 2 0 0 0 2 2h2" /><path d="M16 4h2a2 2 0 0 1 2 2v2" /><path d="M16 20h2a2 2 0 0 0 2 -2v-2" /><path d="M12 12.5l4 -2.5" /><path d="M8 10l4 2.5v4.5l4 -2.5v-4.5l-4 -2.5z" /><path d="M8 10v4.5l4 2.5" /></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Images
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-database" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 6m-8 0a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"></path> <path d="M4 6v6a8 3 0 0 0 16 0v-6"></path> <path d="M4 12v6a8 3 0 0 0 16 0v-6"></path></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Volumes
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#navbar-help" data-bs-toggle="dropdown"
|
||||
data-bs-auto-close="outside" role="button" aria-expanded="false">
|
||||
|
|
Loading…
Add table
Reference in a new issue