5
.dockerignore
Normal file
|
@ -0,0 +1,5 @@
|
|||
**/db.sqlite
|
||||
**/node_modules
|
||||
**/appdata
|
||||
.gitignore
|
||||
**/screenshots
|
11
.gitignore
vendored
|
@ -1,8 +1,3 @@
|
|||
**/node_modules/
|
||||
**/database.sqlite
|
||||
**/appdata/
|
||||
.github
|
||||
test
|
||||
.dockerignore
|
||||
.gitignore
|
||||
docker-compose.yaml
|
||||
**/db.sqlite
|
||||
**/node_modules
|
||||
**/appdata
|
24
CHANGELOG.md
|
@ -1,3 +1,27 @@
|
|||
## v0.40 (Feb 26th 2024) - HTMX rewrite
|
||||
* Pages rewritten to use HTMX.
|
||||
* Removed Socket.io.
|
||||
* Changed view files to *.HTML instead of *.EJS.
|
||||
* Removed "USER root" from Dockerfile.
|
||||
* Express sessions configured to use memorystore.
|
||||
* Improved chart rendering.
|
||||
* Improvements to container charts.
|
||||
* Created Variables page.
|
||||
* Created Supporters page.
|
||||
* Ability to remove images, volumes, or networks.
|
||||
* Fixed list.js sorting.
|
||||
* Fixed apps.js page navigation.
|
||||
* Removed stackfiles from templates.json and updated some icons.
|
||||
* New logo.
|
||||
* Improved handling of Docker events.
|
||||
* Improved dashboard responsiveness.
|
||||
* Updated server metrics styles.
|
||||
* Container cards display pending action.
|
||||
* Container charts only rendered if container running.
|
||||
* Created permissions modal.
|
||||
* Podman support (untested).
|
||||
* Started a new template for FOSS apps.
|
||||
|
||||
## v0.20 (Jan 20th 2024) - The rewrite. Jumping all the way to v0.20.
|
||||
* Changed to ES6 imports.
|
||||
* Cleaned up file structure and code layout.
|
||||
|
|
18
Dockerfile
|
@ -1,19 +1,7 @@
|
|||
FROM node:21-alpine
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=bind,source=package.json,target=package.json \
|
||||
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
||||
--mount=type=cache,target=/root/.npm \
|
||||
npm ci --omit=dev
|
||||
|
||||
|
||||
USER root
|
||||
|
||||
COPY . .
|
||||
|
||||
COPY . /app
|
||||
RUN npm install
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
CMD node server.js
|
18
README.md
|
@ -1,7 +1,7 @@
|
|||
# DweebUI
|
||||
DweebUI is a web interface for managing Docker, with a zero-config dashboard for controlling and monitoring your containers.
|
||||
|
||||
Alpha v0.20 ( :fire: Experimental :fire: )
|
||||
Alpha v0.40 ( :fire: Experimental :fire: )
|
||||
|
||||
|
||||
[:warning: DweebUI is a management interface and should not be directly exposed to the internet :warning:](https://github.com/lllllllillllllillll/DweebUI/wiki/Exposing-DweebUI-to-the-Internet)
|
||||
|
@ -10,7 +10,7 @@ Alpha v0.20 ( :fire: Experimental :fire: )
|
|||
[](https://github.com/lllllllillllllillll)
|
||||
[](https://hub.docker.com/repository/docker/lllllllillllllillll/dweebui)
|
||||
[](https://github.com/lllllllillllllillll/DweebUI/blob/main/LICENSE)
|
||||
[](https://www.buymeacoffee.com/lllllllillllllillll)
|
||||
[](https://www.buymeacoffee.com/lllllllillllllillll)
|
||||
|
||||
* This is a personal project I started to get more familiar with Javascript and Node.js.
|
||||
* Some UI elements are placeholders and every version may have breaking changes.
|
||||
|
@ -31,8 +31,9 @@ Alpha v0.20 ( :fire: Experimental :fire: )
|
|||
* [x] Dashboard provides server metrics, container metrics, and container controls, on a single page.
|
||||
* [x] View container logs.
|
||||
* [ ] Update containers (planned).
|
||||
* [ ] Manage your Docker networks, images, and volumes (in development).
|
||||
* [x] Manage your Docker networks, images, and volumes.
|
||||
* [x] Light/Dark Mode.
|
||||
* [x] Mobile Friendly.
|
||||
* [x] Easy to install app templates.
|
||||
* [x] Multi-User built-in.
|
||||
* [ ] Permissions system (in development).
|
||||
|
@ -41,6 +42,7 @@ Alpha v0.20 ( :fire: Experimental :fire: )
|
|||
* [x] Templates.json maintains compatability with Portainer, allowing you to use the template without needing to use DweebUI.
|
||||
* [x] Automatically persists data in docker volumes if bind mount isn't used.
|
||||
* [ ] Preset variables (planned).
|
||||
* [ ] Themes (planned).
|
||||
|
||||
|
||||
## Setup
|
||||
|
@ -51,9 +53,8 @@ version: "3.9"
|
|||
services:
|
||||
dweebui:
|
||||
container_name: dweebui
|
||||
image: lllllllillllllillll/dweebui:v0.20
|
||||
image: lllllllillllllillll/dweebui:v0.40
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 8000
|
||||
SECRET: MrWiskers
|
||||
restart: unless-stopped
|
||||
|
@ -61,7 +62,11 @@ services:
|
|||
- 8000:8000
|
||||
volumes:
|
||||
- dweebui:/app
|
||||
# Docker socket
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Podman socket
|
||||
#- /run/podman/podman.sock:/var/run/docker.sock
|
||||
|
||||
networks:
|
||||
- dweebui_net
|
||||
|
||||
|
@ -91,4 +96,5 @@ Compose setup:
|
|||
|
||||
## Supporters
|
||||
|
||||
* MM (Patreon)
|
||||
* MM (Patreon)
|
||||
* PD (Buymeacoffee)
|
|
@ -1,32 +1,29 @@
|
|||
// 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;
|
||||
let disable = "";
|
||||
let chartName = name.replace(/-/g, '');
|
||||
|
||||
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=""';
|
||||
}
|
||||
// shorten long names
|
||||
if (name.length > 13) { wrapped = name.slice(0, 10) + '...'; }
|
||||
// disable buttons for dweebui
|
||||
if (name.startsWith('dweebui')) { disable = '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';
|
||||
state = 'stopped';
|
||||
state_indicator = 'red';
|
||||
} else if (state == 'paused') {
|
||||
state_indicator = 'orange';
|
||||
state_indicator = 'orange';
|
||||
}
|
||||
|
||||
let noChart = 'hx-swap="none"';
|
||||
if (state == 'running') { noChart = ''; }
|
||||
|
||||
let ports_data = [];
|
||||
if (ports) {
|
||||
ports_data = ports;
|
||||
|
@ -49,7 +46,7 @@ export const containerCard = (data) => {
|
|||
|
||||
|
||||
return `
|
||||
<div class="col-sm-6 col-lg-3 deleteme">
|
||||
<div class="col-sm-6 col-lg-3 pt-1">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-stamp card-stamp-sm">
|
||||
|
@ -60,16 +57,16 @@ export const containerCard = (data) => {
|
|||
<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 -->
|
||||
<button class="btn-action" title="Start" data-hx-post="/start" data-hx-trigger="click" data-hx-target="#${name}state" name="${name}" id="${state}" ${disable}><!-- 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 -->
|
||||
<button class="btn-action" title="Stop" data-hx-post="/stop" data-hx-trigger="click" data-hx-target="#${name}state" name="${name}" id="${state}" ${disable}><!-- 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 -->
|
||||
<button class="btn-action" title="Pause" data-hx-post="/pause" data-hx-trigger="click" data-hx-target="#${name}state" name="${name}" id="${state}" ${disable}><!-- 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 -->
|
||||
<button class="btn-action" title="Restart" data-hx-post="/restart" data-hx-trigger="click" data-hx-target="#${name}state" name="${name}" id="${state}" ${disable}><!-- 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">
|
||||
|
@ -77,11 +74,11 @@ export const containerCard = (data) => {
|
|||
<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="#${name}_uninstall_modal" href="#">Remove</button>
|
||||
<button class="dropdown-item text-secondary" name="${name}" data-hx-get="/modal" data-hx-target="#modals-here" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
|
||||
<button class="dropdown-item text-secondary" name="${name}" id="logs" data-hx-get="/logs" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
|
||||
<button class="dropdown-item text-secondary" name="${name}" id="edit">Edit</button>
|
||||
<button class="dropdown-item text-primary" name="${name}" id="update" disabled="">Update</button>
|
||||
<button class="dropdown-item text-danger" name="${name}" id="remove" data-bs-toggle="modal" data-bs-target="#${name}_uninstall_modal" href="#">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
|
@ -89,9 +86,9 @@ export const containerCard = (data) => {
|
|||
<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 text-secondary" onclick="clicked(this)" name="${name}" id="hide" value="hide">Hide</button>
|
||||
<button class="dropdown-item text-secondary" onclick="clicked(this)" name="${name}" id="resetView" value="resetView">Reset View</button>
|
||||
<button class="dropdown-item text-secondary" onclick="clicked(this)" name="${name}" id="permissions" value="permissions" data-bs-toggle="modal" data-bs-target="#${name}_permissions">Permissions</button>
|
||||
<button class="dropdown-item text-secondary" data-hx-post="/hide" data-hx-trigger="click" data-hx-swap="none" name="${name}" id="hide" value="hide">Hide</button>
|
||||
<button class="dropdown-item text-secondary" data-hx-post="/reset" data-hx-trigger="click" data-hx-swap="none" name="${name}" id="reset" value="reset">Reset View</button>
|
||||
<button class="dropdown-item text-secondary" name="${name}" id="permissions" data-hx-get="/modal" data-hx-target="#modals-here" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -105,13 +102,27 @@ export const containerCard = (data) => {
|
|||
</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>
|
||||
<label id="${name}state">
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="${chart}_chart" class="chart-sm"></div>
|
||||
|
||||
<script>
|
||||
var ${chartName}chart = new ApexCharts(document.querySelector("#${chartName}_chart"), options);
|
||||
</script>
|
||||
|
||||
<div class="chart-sm">
|
||||
<div id="${chartName}_chart" data-hx-trigger="load, every 2s" data-hx-get="/chart" name="${chartName}" ${noChart}>
|
||||
<script>
|
||||
${chartName}chart.render();
|
||||
</script>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -206,18 +217,35 @@ export const containerCard = (data) => {
|
|||
|
||||
<div class="mb-2">
|
||||
<div class="divide-y">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<div class="d-flex align-items-center">
|
||||
User:
|
||||
<select class="form-select ms-2">
|
||||
<option value="john_doe" selected hidden>John Doe</option>
|
||||
<option value="john_doe">John Doe</option>
|
||||
<option value="jane_doe">Jane Doe</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">Install</span>
|
||||
<span class="col">
|
||||
View
|
||||
</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" name="remove_volumes">
|
||||
<input class="form-check-input" type="checkbox" name="remove_image">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
|
@ -232,6 +260,7 @@ export const containerCard = (data) => {
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
|
|
893
components/modal.js
Normal file
|
@ -0,0 +1,893 @@
|
|||
export const modal = (data) => {
|
||||
|
||||
let { name, state, image } = data;
|
||||
|
||||
let ports_data = [];
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
let volumes_data = [];
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
|
||||
let vol_check = "checked";
|
||||
let bind = i;
|
||||
let container = i;
|
||||
let readwrite = "rw";
|
||||
|
||||
volumes_data.push({
|
||||
check: vol_check,
|
||||
bind: bind,
|
||||
container: container,
|
||||
readwrite: readwrite
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
let env_data = [];
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
|
||||
let env_check = "checked";
|
||||
let env_name = i;
|
||||
let env_default = i;
|
||||
|
||||
env_data.push({
|
||||
check: env_check,
|
||||
name: env_name,
|
||||
default: env_default
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
let label_data = [];
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
|
||||
let label_check = "checked";
|
||||
let label_name = i;
|
||||
let label_default = i;
|
||||
|
||||
label_data.push({
|
||||
check: label_check,
|
||||
name: label_name,
|
||||
value: label_default
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
let modal = `
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Details</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre class="text-secondary">note</pre>
|
||||
<form action="" id="details_modal" method="POST">
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label">Container Name: </label>
|
||||
<input type="text" class="form-control" name="service_name" value="${name}" hidden/>
|
||||
<input type="text" class="form-control" name="name" value="${name}"/>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Image: </label>
|
||||
<input type="text" class="form-control" name="image" value="${image}"/>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Restart Policy: </label>
|
||||
<select class="form-select" name="restart_policy" value="">
|
||||
<option value="1">unless-stopped</option>
|
||||
<option value="2">on-failure</option>
|
||||
<option value="3">never</option>
|
||||
<option value="4">always</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label class="form-label">Network Mode</label>
|
||||
<div class="form-selectgroup-boxes row mb-3">
|
||||
<div class="col">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="report-type" value="1" class="form-selectgroup-input">
|
||||
<span class="form-selectgroup-label d-flex align-items-center p-3">
|
||||
<span class="me-3">
|
||||
<span class="form-selectgroup-check"></span>
|
||||
</span>
|
||||
<span class="form-selectgroup-label-content">
|
||||
<span class="form-selectgroup-title strong mb-1">Host Network</span>
|
||||
<span class="d-block text-secondary">Same as host. No isolation. ex.127.0.0.1</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="report-type" class="form-selectgroup-input">
|
||||
<span class="form-selectgroup-label d-flex align-items-center p-3">
|
||||
<span class="me-3">
|
||||
<span class="form-selectgroup-check"></span>
|
||||
</span>
|
||||
<span class="form-selectgroup-label-content">
|
||||
<span class="form-selectgroup-title strong mb-1">Bridge Network</span>
|
||||
<span class="d-block text-secondary">Containers can communicate using names.</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="report-type" class="form-selectgroup-input">
|
||||
<span class="form-selectgroup-label d-flex align-items-center p-3">
|
||||
<span class="me-3">
|
||||
<span class="form-selectgroup-check"></span>
|
||||
</span>
|
||||
<span class="form-selectgroup-label-content">
|
||||
<span class="form-selectgroup-title strong mb-1">Docker Network</span>
|
||||
<span class="d-block text-secondary">Isolated on the docker network. ex.172.0.34.2</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion" id="modal-accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-1">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-1" aria-expanded="false">
|
||||
Ports
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-1" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_0_check" type="checkbox" ${ports_data[0].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">External Port</label>
|
||||
<input type="text" class="form-control" name="port_0_external" value="${ports_data[0].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Internal Port</label>
|
||||
<input type="text" class="form-control" name="port_0_internal" value="${ports_data[0].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<label class="form-label">Protocol</label>
|
||||
<select class="form-select" name="port_0_protocol">
|
||||
<option value="${ports_data[0].protocol}" selected hidden>${ports_data[0].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_1_check" type="checkbox" ${ports_data[1].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_external" value="${ports_data[1].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_internal" value="${ports_data[1].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_1_protocol">
|
||||
<option value="${ports_data[1].protocol}" selected hidden>${ports_data[1].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_2_check" type="checkbox" ${ports_data[2].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_external" value="${ports_data[2].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_internal" value="${ports_data[2].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_2_protocol">
|
||||
<option value="${ports_data[2].protocol}" selected hidden>${ports_data[2].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_3_check" type="checkbox" ${ports_data[3].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_external" value="${ports_data[3].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_internal" value="${ports_data[3].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_3_protocol">
|
||||
<option value="${ports_data[3].protocol}" selected hidden>${ports_data[3].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_4_check" type="checkbox" ${ports_data[4].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_external" value="${ports_data[4].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_internal" value="${ports_data[4].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_4_protocol">
|
||||
<option value="${ports_data[4].protocol}" selected hidden>${ports_data[4].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_5_check" type="checkbox" ${ports_data[5].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_5_external" value="${ports_data[5].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_5_internal" value="${ports_data[5].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_5_protocol">
|
||||
<option value="${ports_data[5].protocol}" selected hidden>${ports_data[5].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-2">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-2" aria-expanded="false">
|
||||
Volumes
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-2" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_0_check" type="checkbox" ${volumes_data[0].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_bind" value="${volumes_data[0].bind}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_container" value="${volumes_data[0].container}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_0_readwrite">
|
||||
<option value="${volumes_data[0].readwrite}" selected hidden>${volumes_data[0].readwrite}</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_1_check" type="checkbox" ${volumes_data[1].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_bind" value="${volumes_data[1].bind}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_container" value="${volumes_data[1].container}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_1_readwrite">
|
||||
<option value="${volumes_data[1].readwrite}" selected hidden>${volumes_data[1].readwrite}</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_2_check" type="checkbox" ${volumes_data[2].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_bind" value="${volumes_data[2].bind}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_container" value="${volumes_data[2].container}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_2_readwrite">
|
||||
<option value="${volumes_data[2].readwrite}" selected hidden>${volumes_data[2].readwrite}</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_3_check" type="checkbox" ${volumes_data[3].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_bind" value="${volumes_data[3].bind}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_container" value="${volumes_data[3].container}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_3_readwrite">
|
||||
<option value="${volumes_data[3].readwrite}" selected hidden>${volumes_data[3].readwrite}</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_4_check" type="checkbox" ${volumes_data[4].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_bind" value="${volumes_data[4].bind}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_container" value="${volumes_data[4].container}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_4_readwrite">
|
||||
<option value="${volumes_data[4].readwrite}" selected hidden>${volumes_data[4].readwrite}</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume_5_check" type="checkbox" ${volumes_data[5].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_bind" value="${volumes_data[5].bind}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_container" value="${volumes_data[5].container}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_5_readwrite">
|
||||
<option value="${volumes_data[5].readwrite}" selected hidden>${volumes_data[5].readwrite}</option>
|
||||
<option value="rw">rw</option>
|
||||
<option value="ro">ro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-3">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-3" aria-expanded="false">
|
||||
Environment Variables
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-3" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_0_check" ${env_data[0].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Variable</label>
|
||||
<input type="text" class="form-control" name="env_0_name" value="${env_data[0].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Value</label>
|
||||
<input type="text" class="form-control" name="env_0_default" value="${env_data[0].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_1_check" ${env_data[1].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_1_name" value="${env_data[1].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_1_default" value="${env_data[1].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_2_check" ${env_data[2].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_2_name" value="${env_data[2].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_2_default" value="${env_data[2].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_3_check" ${env_data[3].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_3_name" value="${env_data[3].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_3_default" value="${env_data[3].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_4_check" ${env_data[4].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_4_name" value="${env_data[4].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_4_default" value="${env_data[4].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_5_check" ${env_data[5].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_5_name" value="${env_data[5].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_5_default" value="${env_data[5].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_6_check" ${env_data[6].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_6_name" value="${env_data[6].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_6_default" value="${env_data[6].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_7_check" ${env_data[7].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_7_name" value="${env_data[7].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_7_default" value="${env_data[7].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_8_check" ${env_data[8].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_8_name" value="${env_data[8].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_8_default" value="${env_data[8].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_9_check" ${env_data[9].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_9_name" value="${env_data[9].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_9_default" value="${env_data[9].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_10_check" ${env_data[10].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_10_name" value="${env_data[10].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_10_default" value="${env_data[10].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_11_check" ${env_data[11].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_11_name" value="${env_data[11].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_11_default" value="${env_data[11].default}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-4">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-4" aria-expanded="false">
|
||||
Labels
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-4" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_0_check" ${label_data[0].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Variable</label>
|
||||
<input type="text" class="form-control" name="label_0_name" value="${label_data[0].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Value</label>
|
||||
<input type="text" class="form-control" name="label_0_value" value="${label_data[0].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_1_check" ${label_data[1].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_name" value="${label_data[1].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_value" value="${label_data[1].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_2_check" ${label_data[2].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_name" value="${label_data[2].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_value" value="${label_data[2].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_3_check" ${label_data[3].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_name" value="${label_data[3].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_value" value="${label_data[3].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_4_check" ${label_data[4].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_name" value="${label_data[4].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_value" value="${label_data[4].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_5_check" ${label_data[5].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_5_name" value="${label_data[5].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_5_value" value="${label_data[5].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_6_check" ${label_data[6].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_6_name" value="${label_data[6].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_6_value" value="${label_data[6].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_7_check" ${label_data[7].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_7_name" value="${label_data[7].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_7_value" value="${label_data[7].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_8_check" ${label_data[8].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_8_name" value="${label_data[8].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_8_value" value="${label_data[8].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_9_check" ${label_data[9].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_9_name" value="${label_data[9].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_9_value" value="${label_data[9].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_10_check" ${label_data[10].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_10_name" value="${label_data[10].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_10_value" value="${label_data[10].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_11_check" ${label_data[11].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="${label_data[11].name}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="${label_data[11].value}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-5">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-5" aria-expanded="false">
|
||||
Extras
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-5" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_0_check" type="checkbox" ${ports_data[0].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">External Port</label>
|
||||
<input type="text" class="form-control" name="port_0_external" value="${ports_data[0].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Internal Port</label>
|
||||
<input type="text" class="form-control" name="port_0_internal" value="${ports_data[0].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<label class="form-label">Protocol</label>
|
||||
<select class="form-select" name="port_0_protocol">
|
||||
<option value="${ports_data[0].protocol}" selected hidden>${ports_data[0].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_1_check" type="checkbox" ${ports_data[1].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_external" value="${ports_data[1].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_internal" value="${ports_data[1].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_1_protocol">
|
||||
<option value="${ports_data[1].protocol}" selected hidden>${ports_data[1].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_2_check" type="checkbox" ${ports_data[2].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_external" value="${ports_data[2].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_internal" value="${ports_data[2].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_2_protocol">
|
||||
<option value="${ports_data[2].protocol}" selected hidden>${ports_data[2].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_3_check" type="checkbox" ${ports_data[3].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_external" value="${ports_data[3].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_internal" value="${ports_data[3].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_3_protocol">
|
||||
<option value="${ports_data[3].protocol}" selected hidden>${ports_data[3].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port_4_check" type="checkbox" ${ports_data[4].check}>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_external" value="${ports_data[4].external}"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_internal" value="${ports_data[4].internal}"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_4_protocol">
|
||||
<option value="${ports_data[4].protocol}" selected hidden>${ports_data[4].protocol}</option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="" type="checkbox" >
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">External Port</label>
|
||||
<input type="text" class="form-control" name="" value=""/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Internal Port</label>
|
||||
<input type="text" class="form-control" name="" value=""/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<label class="form-label">Protocol</label>
|
||||
<select class="form-select" name="">
|
||||
<option value="" selected hidden></option>
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
return modal;
|
||||
}
|
371
components/permissions_modal.js
Normal file
|
@ -0,0 +1,371 @@
|
|||
export const permissionsModal = (data) => {
|
||||
|
||||
|
||||
let modal = `
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered modal-dialog-scrollables">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Permissions</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="" id="permissions_modal" method="POST">
|
||||
|
||||
<div class="accordion" id="modal-accordion">
|
||||
|
||||
|
||||
<div class="accordion-item mb-3" style="border: 1px solid grey;">
|
||||
<h2 class="accordion-header" id="heading-1">
|
||||
<button class="accordion-button collapsed row" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-1" aria-expanded="false">
|
||||
<span class="avatar avatar-sm bg-green-lt col-3 text-start">JD</span>
|
||||
<div class="col text-end" style="margin-right: 10px;">Jane Doe</div>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-1" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
|
||||
<div class="">
|
||||
<div class="">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
All
|
||||
</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" name="select" onclick="selectAll('select')">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Uninstall
|
||||
</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" name="select">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Edit
|
||||
</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" name="select">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Upgrade
|
||||
</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" name="select">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Start
|
||||
</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" name="select">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Stop
|
||||
</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" name="select">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Pause
|
||||
</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" name="select">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Restart
|
||||
</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" name="select">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Logs
|
||||
</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" name="select">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<button class="btn" type="submit" formaction="/updatePermissions">Update</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="accordion-item mb-3" style="border: 1px solid grey;">
|
||||
<h2 class="accordion-header" id="heading-2">
|
||||
<button class="accordion-button collapsed row" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-2" aria-expanded="false">
|
||||
<span class="avatar avatar-sm bg-cyan-lt col-3 text-start">JD</span>
|
||||
<div class="col text-end" style="margin-right: 10px;">John Doe</div>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-2" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
|
||||
<div class="">
|
||||
<div class="">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
All
|
||||
</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" name="remove_image">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
View
|
||||
</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" name="remove_image">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Uninstall
|
||||
</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" name="remove_image">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Edit
|
||||
</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" name="remove_backups">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Upgrade
|
||||
</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" name="remove_backups">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Start
|
||||
</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" name="remove_backups">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Stop
|
||||
</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" name="remove_backups">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Pause
|
||||
</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" name="remove_backups">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Restart
|
||||
</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" name="remove_backups">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Logs
|
||||
</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" name="remove_backups">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" disabled="">Reset</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
return modal;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { appCard } from '../components/appCard.js';
|
||||
|
||||
let templatesJSON = readFileSync('./templates.json');
|
||||
let templatesJSON = readFileSync('./templates/templates.json');
|
||||
let templates = JSON.parse(templatesJSON).templates;
|
||||
|
||||
templates = templates.sort((a, b) => {
|
||||
|
|
|
@ -1,23 +1,298 @@
|
|||
import { Readable } from 'stream';
|
||||
import { Permission, Container } from '../database/models.js';
|
||||
import { modal } from '../components/modal.js';
|
||||
import { permissionsModal } from '../components/permissions_modal.js';
|
||||
import { setEvent, cpu, ram, tx, rx, disk, docker } from '../server.js';
|
||||
import { dockerContainerStats } from 'systeminformation';
|
||||
import { containerCard } from '../components/containerCard.js';
|
||||
|
||||
let [ hidden, cardList ] = [ '', '' ];
|
||||
|
||||
export const Dashboard = (req, res) => {
|
||||
|
||||
|
||||
res.render("dashboard", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.avatar,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export const searchDashboard = (req, res) => {
|
||||
export const Logs = (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
function containerLogs (data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let logString = '';
|
||||
var options = {
|
||||
follow: false,
|
||||
stdout: true,
|
||||
stderr: false,
|
||||
timestamps: false
|
||||
};
|
||||
var containerName = docker.getContainer(data);
|
||||
containerName.logs(options, function (err, stream) {
|
||||
if (err) { reject(err); return; }
|
||||
const readableStream = Readable.from(stream);
|
||||
readableStream.on('data', function (chunk) {
|
||||
logString += chunk.toString('utf8');
|
||||
});
|
||||
readableStream.on('end', function () {
|
||||
resolve(logString);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
containerLogs(name).then((data) => {
|
||||
res.send(`<pre>${data}</pre> `)
|
||||
});
|
||||
}
|
||||
|
||||
console.log(req.params);
|
||||
export const Modal = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let id = req.header('hx-trigger');
|
||||
if (id == 'permissions') {
|
||||
let containerPermissions = await Permission.findAll({ where: {containerName: name}});
|
||||
let form = permissionsModal();
|
||||
res.send(form);
|
||||
return;
|
||||
}
|
||||
let containerId = docker.getContainer(name);
|
||||
let containerInfo = await containerId.inspect();
|
||||
let ports_list = [];
|
||||
try {
|
||||
for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {}
|
||||
let external_port = ports_list[0]?.external || 0;
|
||||
let internal_port = ports_list[0]?.internal || 0;
|
||||
|
||||
let container_info = {
|
||||
name: containerInfo.Name.slice(1),
|
||||
state: containerInfo.State.Status,
|
||||
image: containerInfo.Config.Image,
|
||||
external_port: external_port,
|
||||
internal_port: internal_port,
|
||||
ports: ports_list,
|
||||
link: 'localhost',
|
||||
}
|
||||
let form = modal(container_info);
|
||||
res.send(form);
|
||||
}
|
||||
|
||||
export const Stats = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let color = req.header('hx-trigger');
|
||||
let value = 0;
|
||||
switch (name) {
|
||||
case 'CPU': value = cpu;
|
||||
break;
|
||||
case 'RAM': value = ram;
|
||||
break;
|
||||
case 'TX': value = tx;
|
||||
break;
|
||||
case 'RX': value = rx;
|
||||
break;
|
||||
case 'DISK': value = disk;
|
||||
break;
|
||||
}
|
||||
let info = `<div class="font-weight-medium">
|
||||
<label class="cpu-text mb-1">${name} ${value}%</label>
|
||||
</div>
|
||||
<div class="cpu-bar meter animate ${color}">
|
||||
<span style="width:${value}%"><span></span></span>
|
||||
</div>`;
|
||||
res.send(info);
|
||||
}
|
||||
|
||||
|
||||
export const Hide = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let exists = await Container.findOne({ where: {name: name}});
|
||||
if (!exists) {
|
||||
const newContainer = await Container.create({ name: name, visibility: false, });
|
||||
} else {
|
||||
exists.update({ visibility: false });
|
||||
}
|
||||
setEvent(true, 'docker');
|
||||
res.send("ok");
|
||||
}
|
||||
|
||||
export const Reset = async (req, res) => {
|
||||
Container.update({ visibility: true }, { where: {} });
|
||||
setEvent(true, 'docker');
|
||||
res.send("ok");
|
||||
}
|
||||
|
||||
|
||||
let stats = {};
|
||||
export const Chart = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
// create an empty array if it doesn't exist
|
||||
if (!stats[name]) {
|
||||
stats[name] = { cpuArray: Array(15).fill(0), ramArray: Array(15).fill(0) };
|
||||
}
|
||||
// get the stats
|
||||
const info = await dockerContainerStats(name);
|
||||
// update the arrays
|
||||
stats[name].cpuArray.push(Math.round(info[0].cpuPercent));
|
||||
stats[name].ramArray.push(Math.round(info[0].memPercent));
|
||||
// slice them down to the last 15 values
|
||||
stats[name].cpuArray = stats[name].cpuArray.slice(-15);
|
||||
stats[name].ramArray = stats[name].ramArray.slice(-15);
|
||||
// replace the chart with the new data
|
||||
let chart = `
|
||||
<script>
|
||||
${name}chart.updateSeries([{
|
||||
data: [${stats[name].cpuArray}]
|
||||
}, {
|
||||
data: [${stats[name].ramArray}]
|
||||
}])
|
||||
</script>`
|
||||
res.send(chart);
|
||||
}
|
||||
|
||||
// Get hidden containers
|
||||
async function getHidden() {
|
||||
hidden = await Container.findAll({ where: {visibility:false}});
|
||||
hidden = hidden.map((container) => container.name);
|
||||
}
|
||||
|
||||
// Create list of docker containers cards
|
||||
async function containerCards() {
|
||||
let list = '';
|
||||
const allContainers = await docker.listContainers({ all: true });
|
||||
for (const container of allContainers) {
|
||||
if (!hidden.includes(container.Names[0].slice(1))) {
|
||||
|
||||
let imageVersion = container.Image.split('/');
|
||||
let service = imageVersion[imageVersion.length - 1].split(':')[0];
|
||||
let containerId = docker.getContainer(container.Id);
|
||||
let containerInfo = await containerId.inspect();
|
||||
let ports_list = [];
|
||||
try {
|
||||
for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
let external_port = ports_list[0]?.external || 0;
|
||||
let internal_port = ports_list[0]?.internal || 0;
|
||||
|
||||
let container_info = {
|
||||
name: container.Names[0].slice(1),
|
||||
service: service,
|
||||
id: container.Id,
|
||||
state: container.State,
|
||||
image: container.Image,
|
||||
external_port: external_port,
|
||||
internal_port: internal_port,
|
||||
ports: ports_list,
|
||||
link: 'localhost',
|
||||
}
|
||||
let card = containerCard(container_info);
|
||||
list += card;
|
||||
}
|
||||
}
|
||||
cardList = list;
|
||||
}
|
||||
|
||||
export const Containers = async (req, res) => {
|
||||
await getHidden();
|
||||
await containerCards();
|
||||
res.send(cardList);
|
||||
}
|
||||
|
||||
|
||||
function status (state) {
|
||||
let status = `<span class="text-yellow align-items-center lh-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-point-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 7a5 5 0 1 1 -4.995 5.217l-.005 -.217l.005 -.217a5 5 0 0 1 4.995 -4.783z" stroke-width="0" fill="currentColor"></path></svg>
|
||||
${state}
|
||||
</span>`;
|
||||
return status;
|
||||
}
|
||||
|
||||
export const Start = (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let state = req.header('hx-trigger');
|
||||
if (state == 'stopped') {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.start();
|
||||
} else if (state == 'paused') {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.unpause();
|
||||
}
|
||||
|
||||
res.send(status('starting'));
|
||||
}
|
||||
|
||||
export const Stop = (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let state = req.header('hx-trigger');
|
||||
if (state != 'stopped') {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.stop();
|
||||
}
|
||||
res.send(status('stopping'));
|
||||
}
|
||||
|
||||
export const Pause = (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let state = req.header('hx-trigger');
|
||||
if (state == 'running') {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.pause();
|
||||
} else if (state == 'paused') {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.unpause();
|
||||
}
|
||||
res.send(status('pausing'));
|
||||
}
|
||||
|
||||
export const Restart = (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.restart();
|
||||
res.send(status('restarting'));
|
||||
}
|
||||
|
||||
export const Installs = async (req, res) => {
|
||||
|
||||
let name = req.header('hx-trigger-name');
|
||||
let all_containers = '';
|
||||
|
||||
await docker.listContainers({ all: true }).then(containers => {
|
||||
containers.forEach(container => {
|
||||
if (container.Names[0].slice(1) == name) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
let install_info = {
|
||||
name: name,
|
||||
service: 'Service Name',
|
||||
id: '',
|
||||
state: '',
|
||||
image: '',
|
||||
external_port: 0,
|
||||
internal_port: 0,
|
||||
ports: '',
|
||||
link: 'localhost',
|
||||
}
|
||||
let card = containerCard(install_info);
|
||||
res.send(card);
|
||||
|
||||
res.render("dashboard", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.avatar,
|
||||
});
|
||||
|
||||
|
||||
}
|
|
@ -8,13 +8,13 @@ export const Images = async function(req, res) {
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="w-1"><input class="form-check-input m-0 align-middle" name="select" type="checkbox" aria-label="Select all" onclick="selectAll()"></th>
|
||||
<th><button class="table-sort" data-sort="sort-name">Name</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-city">ID</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-type">Tag</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-score">Status</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-date">Created</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-quantity">Size</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-progress">Action</button></th>
|
||||
<th><label class="table-sort" data-sort="sort-name">Name</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-city">ID</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-type">Tag</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-score">Status</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-date">Created</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-quantity">Size</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-progress">Action</label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">`
|
||||
|
@ -30,11 +30,11 @@ export const Images = async function(req, res) {
|
|||
|
||||
let details = `
|
||||
<tr>
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="" type="checkbox" aria-label="Select"></td>
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="${images[i].Id}" type="checkbox" aria-label="Select"></td>
|
||||
<td class="sort-name">${images[i].RepoTags}</td>
|
||||
<td class="sort-city">${images[i].Id}</td>
|
||||
<td class="sort-type">Latest</td>
|
||||
<td class="sort-score text-green">In use</td>
|
||||
<td class="sort-type"> - </td>
|
||||
<td class="sort-score text-green"> - </td>
|
||||
<td class="sort-date" data-date="1628122643">${created}</td>
|
||||
<td class="sort-quantity">${size} MB</td>
|
||||
<td class="text-end"><a class="btn" href="#">Details</a></td>
|
||||
|
@ -53,4 +53,28 @@ export const Images = async function(req, res) {
|
|||
image_count: images.length
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const removeImage = async function(req, res) {
|
||||
let images = req.body.select;
|
||||
|
||||
if (typeof(images) == 'string') {
|
||||
images = [images];
|
||||
}
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
|
||||
if (images[i] != 'on') {
|
||||
try {
|
||||
console.log(`Removing image: ${images[i]}`);
|
||||
let image = docker.getImage(images[i]);
|
||||
await image.remove();
|
||||
} catch (error) {
|
||||
console.log(`Unable to remove image: ${images[i]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
res.redirect("/images");
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import { User, Syslog } from '../database/models.js';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
|
||||
|
||||
export const Login = function(req,res){
|
||||
if(req.session.user){
|
||||
res.redirect("/logout");
|
||||
|
|
|
@ -9,29 +9,27 @@ export const Networks = async function(req, res) {
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="w-1"><input class="form-check-input m-0 align-middle" name="select" type="checkbox" aria-label="Select all" onclick="selectAll()"></th>
|
||||
<th><button class="table-sort" data-sort="sort-name">Name</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-city">ID</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-score">Status</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-date">Created</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-progress">Action</button></th>
|
||||
<th><label class="table-sort" data-sort="sort-name">Name</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-city">ID</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-score">Status</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-date">Created</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-progress">Action</label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">`
|
||||
<tbody class="table-tbody">`
|
||||
|
||||
|
||||
for (let i = 0; i < networks.length; i++) {
|
||||
|
||||
// let date = new Date(images[i].Created * 1000);
|
||||
// let created = date.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
|
||||
|
||||
|
||||
|
||||
|
||||
let details = `
|
||||
<tr>
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="" type="checkbox" aria-label="Select"></td>
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="${networks[i].Id}" type="checkbox" aria-label="Select"></td>
|
||||
<td class="sort-name">${networks[i].Name}</td>
|
||||
<td class="sort-city">${networks[i].Id}</td>
|
||||
<td class="sort-score text-green">In use</td>
|
||||
<td class="sort-score text-green"> - </td>
|
||||
<td class="sort-date" data-date="1628122643">${networks[i].Created}</td>
|
||||
<td class="text-end"><a class="btn" href="#">Details</a></td>
|
||||
</tr>`
|
||||
|
@ -40,7 +38,6 @@ export const Networks = async function(req, res) {
|
|||
|
||||
network_list += `</tbody>`
|
||||
|
||||
|
||||
res.render("networks", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
|
@ -48,5 +45,29 @@ export const Networks = async function(req, res) {
|
|||
network_list: network_list,
|
||||
network_count: networks.length
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const removeNetwork = async function(req, res) {
|
||||
let networks = req.body.select;
|
||||
|
||||
if (typeof(networks) == 'string') {
|
||||
networks = [networks];
|
||||
}
|
||||
|
||||
for (let i = 0; i < networks.length; i++) {
|
||||
|
||||
if (networks[i] != 'on') {
|
||||
try {
|
||||
console.log(`Removing network: ${networks[i]}`);
|
||||
let network = docker.getNetwork(networks[i]);
|
||||
await network.remove();
|
||||
} catch (error) {
|
||||
console.log(`Unable to remove network: ${networks[i]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
res.redirect("/networks");
|
||||
}
|
|
@ -55,7 +55,7 @@ export const submitRegister = async function(req,res){
|
|||
password: bcrypt.hashSync(password,10),
|
||||
role: await userRole(),
|
||||
group: 'all',
|
||||
avatar: `<img src="img/avatars/${avatar}">`,
|
||||
avatar: `<img src="/images/avatars/${avatar}">`,
|
||||
lastLogin: newLogin,
|
||||
});
|
||||
|
||||
|
|
32
controllers/supporters.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { User } from "../database/models.js";
|
||||
|
||||
export const Supporters = async (req, res) => {
|
||||
|
||||
if (!req.session.UUID) return res.redirect("/login");
|
||||
|
||||
let user = await User.findOne({ where: { UUID: req.session.UUID }});
|
||||
|
||||
|
||||
res.render("supporters", {
|
||||
first_name: user.name,
|
||||
last_name: user.name,
|
||||
name: user.name,
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
avatar: user.avatar,
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
let thanks = 0;
|
||||
export const Thanks = async (req, res) => {
|
||||
thanks++;
|
||||
let data = thanks.toString();
|
||||
if (thanks > 999) {
|
||||
data = 'Did you really click 1000 times?!';
|
||||
}
|
||||
res.send(data);
|
||||
}
|
9
controllers/variables.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
export const Variables = (req, res) => {
|
||||
|
||||
res.render("variables", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.avatar,
|
||||
});
|
||||
}
|
|
@ -10,12 +10,12 @@ export const Volumes = async function(req, res) {
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="w-1"><input class="form-check-input m-0 align-middle" name="select" type="checkbox" aria-label="Select all" onclick="selectAll()"></th>
|
||||
<th><button class="table-sort" data-sort="sort-name">Name</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-city">Mount point</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-score">Status</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-date">Created</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-quantity">Size</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-progress">Action</button></th>
|
||||
<th><label class="table-sort" data-sort="sort-name">Name</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-city">Mount point</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-score">Status</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-date">Created</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-quantity">Size</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-progress">Action</label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">`
|
||||
|
@ -44,10 +44,10 @@ export const Volumes = async function(req, res) {
|
|||
|
||||
let details = `
|
||||
<tr>
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="" type="checkbox" aria-label="Select"></td>
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="${name}" type="checkbox" aria-label="Select"></td>
|
||||
<td class="sort-name">${name}</td>
|
||||
<td class="sort-city">${mount}</td>
|
||||
<td class="sort-score text-green">In use</td>
|
||||
<td class="sort-score text-green"> - </td>
|
||||
<td class="sort-date" data-date="1628122643">${volume.CreatedAt}</td>
|
||||
<td class="sort-quantity">MB</td>
|
||||
<td class="text-end"><a class="btn" href="#">Details</a></td>
|
||||
|
@ -67,4 +67,38 @@ export const Volumes = async function(req, res) {
|
|||
volume_count: volumes.length
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export const createVolume = async function(req, res) {
|
||||
|
||||
let name = req.body.name;
|
||||
|
||||
docker.createVolume({
|
||||
Name: name
|
||||
});
|
||||
res.redirect("/volumes");
|
||||
}
|
||||
|
||||
|
||||
export const removeVolume = async function(req, res) {
|
||||
let volumes = req.body.select;
|
||||
|
||||
if (typeof(volumes) == 'string') {
|
||||
volumes = [volumes];
|
||||
}
|
||||
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
|
||||
if (volumes[i] != 'on') {
|
||||
try {
|
||||
console.log(`Removing volume: ${volumes[i]}`);
|
||||
let volume = docker.getVolume(volumes[i]);
|
||||
await volume.remove();
|
||||
} catch (error) {
|
||||
console.log(`Unable to remove volume: ${volumes[i]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.redirect("/volumes");
|
||||
}
|
|
@ -1,17 +1,9 @@
|
|||
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',
|
||||
storage: './database/db.sqlite',
|
||||
logging: false,
|
||||
});
|
||||
|
||||
|
@ -55,25 +47,60 @@ export const User = sequelize.define('User', {
|
|||
});
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
visibility: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
service: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
state: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
image: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
external_port: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
internal_port: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
ports: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
volumes: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
environment_variables: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
labels: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
IPv4: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
style: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
cpu: {
|
||||
// store the last 15 values from dockerContainerStats
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
ram: {
|
||||
// store the last 15 values from dockerContainerStats
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
});
|
||||
|
||||
export const Permission = sequelize.define('Permission', {
|
||||
id: {
|
||||
|
|
|
@ -2,9 +2,8 @@ version: "3.9"
|
|||
services:
|
||||
dweebui:
|
||||
container_name: dweebui
|
||||
image: lllllllillllllillll/dweebui:v0.20
|
||||
image: lllllllillllllillll/dweebui:v0.40
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 8000
|
||||
SECRET: MrWiskers
|
||||
restart: unless-stopped
|
||||
|
@ -12,7 +11,11 @@ services:
|
|||
- 8000:8000
|
||||
volumes:
|
||||
- dweebui:/app
|
||||
# Docker socket
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Podman socket
|
||||
#- /run/podman/podman.sock:/var/run/docker.sock
|
||||
|
||||
networks:
|
||||
- dweebui_net
|
||||
|
||||
|
@ -21,4 +24,4 @@ volumes:
|
|||
|
||||
networks:
|
||||
dweebui_net:
|
||||
driver: bridge
|
||||
driver: bridge
|
|
@ -9,8 +9,6 @@ import { containerCard } from "../components/containerCard.js";
|
|||
// This entire page hurts to look at.
|
||||
export const Install = async (req, res) => {
|
||||
|
||||
console.log(req.app.locals.installCard);
|
||||
|
||||
let data = req.body;
|
||||
|
||||
let { service_name, name, image, command_check, command, net_mode, restart_policy } = data;
|
||||
|
@ -170,39 +168,40 @@ export const Install = async (req, res) => {
|
|||
try {
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
writeFileSync(`./appdata/${name}/docker-compose.yml`, compose_file, function (err) { console.log(err) });
|
||||
var compose = new DockerodeCompose(docker, `./appdata/${name}/docker-compose.yml`, `${name}`);
|
||||
|
||||
} catch { console.log('error creating directory or compose file') }
|
||||
} catch {
|
||||
const syslog = await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${name} installation failed - error creating directory or compose file : ${err}`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
}
|
||||
|
||||
var compose = new DockerodeCompose(docker, `./appdata/${name}/docker-compose.yml`, `${name}`);
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
try {
|
||||
(async () => {
|
||||
await compose.pull();
|
||||
await compose.up().then(() => {
|
||||
const syslog = Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${name} installed successfully`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await compose.up();
|
||||
|
||||
const syslog = await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${name} installation failed: ${err}`,
|
||||
message: `${name} installed successfully`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
}
|
||||
})();
|
||||
});
|
||||
})();
|
||||
} catch (err) {
|
||||
const syslog = await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${name} installation failed: ${err}`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
res.redirect('/');
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,17 +6,20 @@ export const Uninstall = async (req, res) => {
|
|||
|
||||
let { confirm, service_name } = req.body;
|
||||
|
||||
console.log(`Uninstalling ${service_name}...`);
|
||||
|
||||
if (confirm == 'Yes') {
|
||||
|
||||
var containerName = docker.getContainer(`${service_name}`);
|
||||
try {
|
||||
await containerName.stop();
|
||||
} catch {
|
||||
console.log(`Error stopping ${service_name} container`);
|
||||
}
|
||||
try {
|
||||
await containerName.remove();
|
||||
|
||||
|
||||
try {
|
||||
containerName.remove();
|
||||
|
||||
const syslog = await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
|
|
2723
package-lock.json
generated
24
package.json
|
@ -1,37 +1,27 @@
|
|||
{
|
||||
"name": "dweebui",
|
||||
"version": "1.0.0",
|
||||
"description": "A web UI for Docker",
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "mocha --require @babel/register"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/register": "^7.23.7",
|
||||
"@socket.io/admin-ui": "^0.5.1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"chai": "^5.0.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"dockerode": "^4.0.2",
|
||||
"dockerode-compose": "^1.4.0",
|
||||
"ejs": "^3.1.9",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"express-session": "^1.17.3",
|
||||
"helmet": "^7.1.0",
|
||||
"express-session": "^1.18.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mocha": "^10.2.0",
|
||||
"sequelize": "^6.35.2",
|
||||
"sinon": "^17.0.1",
|
||||
"socket.io": "^4.7.4",
|
||||
"memorystore": "^1.6.7",
|
||||
"sequelize": "^6.37.1",
|
||||
"sqlite3": "^5.1.7",
|
||||
"stream": "^0.0.2",
|
||||
"supertest": "^6.3.3",
|
||||
"systeminformation": "^5.21.22"
|
||||
"systeminformation": "^5.22.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,6 +83,10 @@
|
|||
.blue > span {
|
||||
background-image: linear-gradient(#2478f5, #22017e);
|
||||
}
|
||||
|
||||
.purple > span {
|
||||
background-image: linear-gradient(#bd14d3, #670370);
|
||||
}
|
||||
|
||||
.nostripes > span > span,
|
||||
.nostripes > span::after {
|
||||
|
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 45 KiB 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 |
Before Width: | Height: | Size: 69 KiB 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 |
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 413 B |
BIN
public/images/logo.png
Normal file
After Width: | Height: | Size: 19 KiB |
355
public/js/htmx-sse.js
Normal file
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
Server Sent Events Extension
|
||||
============================
|
||||
This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions.
|
||||
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
/** @type {import("../htmx").HtmxInternalApi} */
|
||||
var api;
|
||||
|
||||
htmx.defineExtension("sse", {
|
||||
|
||||
/**
|
||||
* Init saves the provided reference to the internal HTMX API.
|
||||
*
|
||||
* @param {import("../htmx").HtmxInternalApi} api
|
||||
* @returns void
|
||||
*/
|
||||
init: function(apiRef) {
|
||||
// store a reference to the internal API.
|
||||
api = apiRef;
|
||||
|
||||
// set a function in the public API for creating new EventSource objects
|
||||
if (htmx.createEventSource == undefined) {
|
||||
htmx.createEventSource = createEventSource;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* onEvent handles all events passed to this extension.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Event} evt
|
||||
* @returns void
|
||||
*/
|
||||
onEvent: function(name, evt) {
|
||||
|
||||
switch (name) {
|
||||
|
||||
case "htmx:beforeCleanupElement":
|
||||
var internalData = api.getInternalData(evt.target)
|
||||
// Try to remove remove an EventSource when elements are removed
|
||||
if (internalData.sseEventSource) {
|
||||
internalData.sseEventSource.close();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
// Try to create EventSources when elements are processed
|
||||
case "htmx:afterProcessNode":
|
||||
ensureEventSourceOnElement(evt.target);
|
||||
registerSSE(evt.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// HELPER FUNCTIONS
|
||||
///////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* createEventSource is the default method for creating new EventSource objects.
|
||||
* it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns EventSource
|
||||
*/
|
||||
function createEventSource(url) {
|
||||
return new EventSource(url, { withCredentials: true });
|
||||
}
|
||||
|
||||
function splitOnWhitespace(trigger) {
|
||||
return trigger.trim().split(/\s+/);
|
||||
}
|
||||
|
||||
function getLegacySSEURL(elt) {
|
||||
var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
|
||||
if (legacySSEValue) {
|
||||
var values = splitOnWhitespace(legacySSEValue);
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var value = values[i].split(/:(.+)/);
|
||||
if (value[0] === "connect") {
|
||||
return value[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLegacySSESwaps(elt) {
|
||||
var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
|
||||
var returnArr = [];
|
||||
if (legacySSEValue != null) {
|
||||
var values = splitOnWhitespace(legacySSEValue);
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var value = values[i].split(/:(.+)/);
|
||||
if (value[0] === "swap") {
|
||||
returnArr.push(value[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return returnArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* registerSSE looks for attributes that can contain sse events, right
|
||||
* now hx-trigger and sse-swap and adds listeners based on these attributes too
|
||||
* the closest event source
|
||||
*
|
||||
* @param {HTMLElement} elt
|
||||
*/
|
||||
function registerSSE(elt) {
|
||||
// Find closest existing event source
|
||||
var sourceElement = api.getClosestMatch(elt, hasEventSource);
|
||||
if (sourceElement == null) {
|
||||
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
|
||||
return null; // no eventsource in parentage, orphaned element
|
||||
}
|
||||
|
||||
// Set internalData and source
|
||||
var internalData = api.getInternalData(sourceElement);
|
||||
var source = internalData.sseEventSource;
|
||||
|
||||
// Add message handlers for every `sse-swap` attribute
|
||||
queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) {
|
||||
|
||||
var sseSwapAttr = api.getAttributeValue(child, "sse-swap");
|
||||
if (sseSwapAttr) {
|
||||
var sseEventNames = sseSwapAttr.split(",");
|
||||
} else {
|
||||
var sseEventNames = getLegacySSESwaps(child);
|
||||
}
|
||||
|
||||
for (var i = 0; i < sseEventNames.length; i++) {
|
||||
var sseEventName = sseEventNames[i].trim();
|
||||
var listener = function(event) {
|
||||
|
||||
// If the source is missing then close SSE
|
||||
if (maybeCloseSSESource(sourceElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the body no longer contains the element, remove the listener
|
||||
if (!api.bodyContains(child)) {
|
||||
source.removeEventListener(sseEventName, listener);
|
||||
}
|
||||
|
||||
// swap the response into the DOM and trigger a notification
|
||||
swap(child, event.data);
|
||||
api.triggerEvent(elt, "htmx:sseMessage", event);
|
||||
};
|
||||
|
||||
// Register the new listener
|
||||
api.getInternalData(child).sseEventListener = listener;
|
||||
source.addEventListener(sseEventName, listener);
|
||||
}
|
||||
});
|
||||
|
||||
// Add message handlers for every `hx-trigger="sse:*"` attribute
|
||||
queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) {
|
||||
|
||||
var sseEventName = api.getAttributeValue(child, "hx-trigger");
|
||||
if (sseEventName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process hx-triggers for events with the "sse:" prefix
|
||||
if (sseEventName.slice(0, 4) != "sse:") {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the sse: prefix from here on out
|
||||
sseEventName = sseEventName.substr(4);
|
||||
|
||||
var listener = function() {
|
||||
if (maybeCloseSSESource(sourceElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!api.bodyContains(child)) {
|
||||
source.removeEventListener(sseEventName, listener);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ensureEventSourceOnElement creates a new EventSource connection on the provided element.
|
||||
* If a usable EventSource already exists, then it is returned. If not, then a new EventSource
|
||||
* is created and stored in the element's internalData.
|
||||
* @param {HTMLElement} elt
|
||||
* @param {number} retryCount
|
||||
* @returns {EventSource | null}
|
||||
*/
|
||||
function ensureEventSourceOnElement(elt, retryCount) {
|
||||
|
||||
if (elt == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// handle extension source creation attribute
|
||||
queryAttributeOnThisOrChildren(elt, "sse-connect").forEach(function(child) {
|
||||
var sseURL = api.getAttributeValue(child, "sse-connect");
|
||||
if (sseURL == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureEventSource(child, sseURL, retryCount);
|
||||
});
|
||||
|
||||
// handle legacy sse, remove for HTMX2
|
||||
queryAttributeOnThisOrChildren(elt, "hx-sse").forEach(function(child) {
|
||||
var sseURL = getLegacySSEURL(child);
|
||||
if (sseURL == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureEventSource(child, sseURL, retryCount);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function ensureEventSource(elt, url, retryCount) {
|
||||
var source = htmx.createEventSource(url);
|
||||
|
||||
source.onerror = function(err) {
|
||||
|
||||
// Log an error event
|
||||
api.triggerErrorEvent(elt, "htmx:sseError", { error: err, source: source });
|
||||
|
||||
// If parent no longer exists in the document, then clean up this EventSource
|
||||
if (maybeCloseSSESource(elt)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, try to reconnect the EventSource
|
||||
if (source.readyState === EventSource.CLOSED) {
|
||||
retryCount = retryCount || 0;
|
||||
var timeout = Math.random() * (2 ^ retryCount) * 500;
|
||||
window.setTimeout(function() {
|
||||
ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1));
|
||||
}, timeout);
|
||||
}
|
||||
};
|
||||
|
||||
source.onopen = function(evt) {
|
||||
api.triggerEvent(elt, "htmx:sseOpen", { source: source });
|
||||
}
|
||||
|
||||
api.getInternalData(elt).sseEventSource = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* maybeCloseSSESource confirms that the parent element still exists.
|
||||
* If not, then any associated SSE source is closed and the function returns true.
|
||||
*
|
||||
* @param {HTMLElement} elt
|
||||
* @returns boolean
|
||||
*/
|
||||
function maybeCloseSSESource(elt) {
|
||||
if (!api.bodyContains(elt)) {
|
||||
var source = api.getInternalData(elt).sseEventSource;
|
||||
if (source != undefined) {
|
||||
source.close();
|
||||
// source = null
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
|
||||
*
|
||||
* @param {HTMLElement} elt
|
||||
* @param {string} attributeName
|
||||
*/
|
||||
function queryAttributeOnThisOrChildren(elt, attributeName) {
|
||||
|
||||
var result = [];
|
||||
|
||||
// If the parent element also contains the requested attribute, then add it to the results too.
|
||||
if (api.hasAttribute(elt, attributeName)) {
|
||||
result.push(elt);
|
||||
}
|
||||
|
||||
// Search all child nodes that match the requested attribute
|
||||
elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "]").forEach(function(node) {
|
||||
result.push(node);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} elt
|
||||
* @param {string} content
|
||||
*/
|
||||
function swap(elt, content) {
|
||||
|
||||
api.withExtensions(elt, function(extension) {
|
||||
content = extension.transformResponse(content, null, elt);
|
||||
});
|
||||
|
||||
var swapSpec = api.getSwapSpecification(elt);
|
||||
var target = api.getTarget(elt);
|
||||
var settleInfo = api.makeSettleInfo(elt);
|
||||
|
||||
api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo);
|
||||
|
||||
settleInfo.elts.forEach(function(elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.add(htmx.config.settlingClass);
|
||||
}
|
||||
api.triggerEvent(elt, 'htmx:beforeSettle');
|
||||
});
|
||||
|
||||
// Handle settle tasks (with delay if requested)
|
||||
if (swapSpec.settleDelay > 0) {
|
||||
setTimeout(doSettle(settleInfo), swapSpec.settleDelay);
|
||||
} else {
|
||||
doSettle(settleInfo)();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* doSettle mirrors much of the functionality in htmx that
|
||||
* settles elements after their content has been swapped.
|
||||
* TODO: this should be published by htmx, and not duplicated here
|
||||
* @param {import("../htmx").HtmxSettleInfo} settleInfo
|
||||
* @returns () => void
|
||||
*/
|
||||
function doSettle(settleInfo) {
|
||||
|
||||
return function() {
|
||||
settleInfo.tasks.forEach(function(task) {
|
||||
task.call();
|
||||
});
|
||||
|
||||
settleInfo.elts.forEach(function(elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.remove(htmx.config.settlingClass);
|
||||
}
|
||||
api.triggerEvent(elt, 'htmx:afterSettle');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hasEventSource(node) {
|
||||
return api.getInternalData(node).sseEventSource != null;
|
||||
}
|
||||
|
||||
})();
|
1
public/js/htmx.min.js
vendored
Normal file
|
@ -1,141 +0,0 @@
|
|||
socket.on('connect', () => {
|
||||
console.log('connected');
|
||||
//clear localStorage (because of code in old versions)
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
// Server metrics
|
||||
const cpuText = document.getElementById('cpu-text');
|
||||
const cpuBar = document.getElementById('cpu-bar');
|
||||
const ramText = document.getElementById('ram-text');
|
||||
const ramBar = document.getElementById('ram-bar');
|
||||
const netText = document.getElementById('net-text');
|
||||
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');
|
||||
|
||||
// Server metrics
|
||||
socket.on('metrics', (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);
|
||||
rx = Math.round(rx / 1024 / 1024);
|
||||
|
||||
netText.innerHTML = `<span>Down: ${rx}MB</span><span> Up: ${tx}MB</span>`;
|
||||
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>`;
|
||||
});
|
||||
|
||||
// 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}`);
|
||||
|
||||
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 containerStats = data;
|
||||
|
||||
for (const [name, statsArray] of Object.entries(containerStats)) {
|
||||
|
||||
let cpuArray = statsArray.cpuArray;
|
||||
let ramArray = statsArray.ramArray;
|
||||
|
||||
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('logs', (data) => {
|
||||
logViewer.innerHTML = `<pre>${data}</pre>`;
|
||||
});
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB |
|
@ -4,21 +4,19 @@ export const router = express.Router();
|
|||
// Controllers
|
||||
import { Login, submitLogin, Logout } from "../controllers/login.js";
|
||||
import { Register, submitRegister } from "../controllers/register.js";
|
||||
import { Dashboard, searchDashboard } from "../controllers/dashboard.js";
|
||||
import { Dashboard, Start, Stop, Pause, Restart, Logs, Modal, Stats, Hide, Reset, Chart, Containers, Installs } from "../controllers/dashboard.js";
|
||||
import { Apps, appSearch } from "../controllers/apps.js";
|
||||
import { Users } from "../controllers/users.js";
|
||||
import { Images } from "../controllers/images.js";
|
||||
import { Images, removeImage } from "../controllers/images.js";
|
||||
import { Networks, removeNetwork } from "../controllers/networks.js";
|
||||
import { Volumes, removeVolume } from "../controllers/volumes.js";
|
||||
import { Account } from "../controllers/account.js";
|
||||
import { Variables } from "../controllers/variables.js";
|
||||
import { Settings } from "../controllers/settings.js";
|
||||
import { Networks } from "../controllers/networks.js";
|
||||
import { Volumes } from "../controllers/volumes.js";
|
||||
import { Supporters, Thanks } from "../controllers/supporters.js";
|
||||
import { Syslogs } from "../controllers/syslogs.js";
|
||||
import { Portal } from "../controllers/portal.js"
|
||||
|
||||
/// Functions
|
||||
import { Install } from "../functions/install.js"
|
||||
import { Uninstall } from "../functions/uninstall.js"
|
||||
|
||||
// Auth middleware
|
||||
const auth = (req, res, next) => {
|
||||
if (req.session.role == "admin") {
|
||||
|
@ -28,22 +26,29 @@ const auth = (req, res, next) => {
|
|||
}
|
||||
};
|
||||
|
||||
// Routes
|
||||
router.get("/login", Login);
|
||||
router.post("/login", submitLogin);
|
||||
router.get("/logout", Logout);
|
||||
|
||||
router.get("/register", Register);
|
||||
router.post("/register", submitRegister);
|
||||
|
||||
|
||||
// Admin routes
|
||||
router.get("/", auth, Dashboard);
|
||||
router.post("/", auth, searchDashboard);
|
||||
router.get("/containers", auth, Containers);
|
||||
router.post("/start", auth, Start);
|
||||
router.post("/stop", auth, Stop);
|
||||
router.post("/pause", auth, Pause);
|
||||
router.post("/restart", auth, Restart);
|
||||
router.get("/logs", auth, Logs);
|
||||
router.get ("/modal", auth, Modal);
|
||||
router.get("/stats", auth, Stats);
|
||||
router.post("/hide", auth, Hide);
|
||||
router.post("/reset", auth, Reset);
|
||||
router.get("/chart", auth, Chart);
|
||||
router.get("/installs", auth, Installs);
|
||||
|
||||
router.get("/images", auth, Images);
|
||||
router.post("/removeImage", auth, removeImage);
|
||||
|
||||
router.get("/volumes", auth, Volumes);
|
||||
router.post("/removeVolume", auth, removeVolume);
|
||||
|
||||
router.get("/networks", auth, Networks);
|
||||
router.get("/portal", Portal)
|
||||
router.post("/removeNetwork", auth, removeNetwork);
|
||||
|
||||
router.get("/apps", auth, Apps);
|
||||
router.get("/apps/:page", auth, Apps);
|
||||
|
@ -52,10 +57,25 @@ router.post("/apps", auth, appSearch);
|
|||
router.get("/users", auth, Users);
|
||||
router.get("/syslogs", auth, Syslogs);
|
||||
|
||||
|
||||
router.get("/account", Account);
|
||||
router.get("/variables", auth, Variables);
|
||||
router.get("/settings", auth, Settings);
|
||||
|
||||
// User routes
|
||||
router.get("/portal", Portal);
|
||||
router.get("/account", Account);
|
||||
router.get("/supporters", Supporters);
|
||||
router.post("/thank", Thanks);
|
||||
|
||||
router.get("/login", Login);
|
||||
router.post("/login", submitLogin);
|
||||
router.get("/register", Register);
|
||||
router.post("/register", submitRegister);
|
||||
router.get("/logout", Logout);
|
||||
|
||||
|
||||
// Functions
|
||||
import { Install } from "../functions/install.js"
|
||||
import { Uninstall } from "../functions/uninstall.js"
|
||||
|
||||
router.post("/install", auth, Install);
|
||||
router.post("/uninstall", auth, Uninstall);
|
||||
router.post("/uninstall", auth, Uninstall);
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 222 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 30 KiB |
343
server.js
|
@ -1,44 +1,24 @@
|
|||
import express from 'express';
|
||||
import session from 'express-session';
|
||||
import compression from 'compression';
|
||||
import helmet from 'helmet';
|
||||
import memorystore from 'memorystore';
|
||||
import ejs from 'ejs';
|
||||
import Docker from 'dockerode';
|
||||
import cors from 'cors';
|
||||
import { Readable } from 'stream';
|
||||
import { rateLimit } from 'express-rate-limit';
|
||||
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';
|
||||
import { sequelize } from './database/models.js';
|
||||
import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
|
||||
|
||||
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 = {};
|
||||
export { setEvent, cpu, ram, tx, rx, disk }
|
||||
|
||||
// Socket.io admin ui
|
||||
export const io = new Server(server, {
|
||||
cors: {
|
||||
origin: ['http://localhost:8000', 'https://admin.socket.io'],
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true
|
||||
}
|
||||
});
|
||||
instrument(io, {
|
||||
auth: false,
|
||||
readonly: true
|
||||
});
|
||||
const app = express();
|
||||
const MemoryStore = memorystore(session);
|
||||
const port = process.env.PORT || 8000;
|
||||
let [ cpu, ram, tx, rx, disk ] = [0, 0, 0, 0, 0];
|
||||
let [ event, eventType ] = [false, 'docker'];
|
||||
|
||||
// Session middleware
|
||||
const sessionMiddleware = session({
|
||||
store: new MemoryStore({ checkPeriod: 86400000 }), // Prune expired entries every 24h
|
||||
secret: "keyboard cat",
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
|
@ -49,46 +29,36 @@ const sessionMiddleware = session({
|
|||
}
|
||||
});
|
||||
|
||||
// Make session data available to socket.io
|
||||
io.engine.use(sessionMiddleware);
|
||||
|
||||
// Rate limiter
|
||||
const limiter = rateLimit({
|
||||
windowMs: 5 * 60 * 1000, // 5 minutes
|
||||
limit: 50, // Limit each IP to 50 requests per `window`.
|
||||
standardHeaders: 'draft-7',
|
||||
legacyHeaders: false,
|
||||
})
|
||||
|
||||
// Express middleware
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('view engine', 'html');
|
||||
app.engine('html', ejs.renderFile);
|
||||
app.use([
|
||||
compression(),
|
||||
cors(),
|
||||
helmet({contentSecurityPolicy: false}),
|
||||
express.static("public"),
|
||||
express.static('public'),
|
||||
express.json(),
|
||||
express.urlencoded({ extended: true }),
|
||||
sessionMiddleware,
|
||||
router,
|
||||
limiter
|
||||
router
|
||||
]);
|
||||
|
||||
// Initialize server
|
||||
server.listen(port, async () => {
|
||||
app.listen(port, async () => {
|
||||
async function init() {
|
||||
try { await sequelize.authenticate().then(() => { console.log('[Connected to DB]') }); }
|
||||
catch { console.log('[Could not connect to DB]'); }
|
||||
try { await sequelize.sync().then(() => { console.log('[Models Synced]') }); }
|
||||
catch { console.log('[Could not Sync Models]', error); }
|
||||
await getHidden();
|
||||
containerCards();
|
||||
}
|
||||
await init();
|
||||
app.emit("appStarted");
|
||||
console.log(`\nServer listening on http://localhost:${port}`);
|
||||
try { await sequelize.authenticate().then(
|
||||
() => { console.log('DB Connection: ✔️') }); }
|
||||
catch { console.log('DB Connection: ❌'); }
|
||||
try { await sequelize.sync().then( // check out that formatting
|
||||
() => { console.log('Synced Models: ✔️') }); }
|
||||
catch { console.log('Synced Models: ❌'); } }
|
||||
await init().then(() => {
|
||||
console.log(`Listening on http://localhost:${port}`);
|
||||
});
|
||||
});
|
||||
|
||||
function setEvent(value, type) {
|
||||
event = value;
|
||||
eventType = type;
|
||||
}
|
||||
|
||||
// Server metrics
|
||||
let serverMetrics = async () => {
|
||||
currentLoad().then(data => {
|
||||
|
@ -105,239 +75,26 @@ let serverMetrics = async () => {
|
|||
disk = data[0].use;
|
||||
});
|
||||
}
|
||||
setInterval(serverMetrics, 1000);
|
||||
|
||||
// List docker containers
|
||||
let containerCards = async () => {
|
||||
let list = '';
|
||||
const allContainers = await docker.listContainers({ all: true });
|
||||
for (const container of allContainers) {
|
||||
if (!hidden.includes(container.Names[0].slice(1))) {
|
||||
|
||||
let imageVersion = container.Image.split('/');
|
||||
let service = imageVersion[imageVersion.length - 1].split(':')[0];
|
||||
let containerId = docker.getContainer(container.Id);
|
||||
let containerInfo = await containerId.inspect();
|
||||
let ports_list = [];
|
||||
try {
|
||||
for (const [key, value] of Object.entries(containerInfo.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
let external_port = ports_list[0]?.external || 0;
|
||||
let internal_port = ports_list[0]?.internal || 0;
|
||||
|
||||
let container_info = {
|
||||
name: container.Names[0].slice(1),
|
||||
service: service,
|
||||
id: container.Id,
|
||||
state: container.State,
|
||||
image: container.Image,
|
||||
external_port: external_port,
|
||||
internal_port: internal_port,
|
||||
ports: ports_list,
|
||||
link: 'localhost',
|
||||
}
|
||||
let card = containerCard(container_info);
|
||||
list += card;
|
||||
let sent_list = '';
|
||||
router.get('/sse_event', (req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', });
|
||||
let eventCheck = setInterval(async() => {
|
||||
let all_containers = '';
|
||||
await docker.listContainers({ all: true }).then(containers => {
|
||||
containers.forEach(container => {
|
||||
all_containers += `${container.Names}: ${container.State}\n`;
|
||||
});
|
||||
});
|
||||
if ((all_containers != sent_list) || (event == true)) {
|
||||
sent_list = all_containers;
|
||||
event = false;
|
||||
res.write(`event: ${eventType}\n`);
|
||||
res.write(`data: there was an event!\n\n`);
|
||||
}
|
||||
}
|
||||
cardList = list;
|
||||
}
|
||||
|
||||
// Container metrics
|
||||
let containerStats = async () => {
|
||||
const data = await docker.listContainers({ all: true });
|
||||
for (const container of data) {
|
||||
if (!hidden.includes(container.Names[0].slice(1))) {
|
||||
const stats = await dockerContainerStats(container.Id);
|
||||
const name = container.Names[0].slice(1);
|
||||
|
||||
if (!statsArray[name]) {
|
||||
statsArray[name] = {
|
||||
cpuArray: Array(15).fill(0),
|
||||
ramArray: Array(15).fill(0)
|
||||
};
|
||||
}
|
||||
statsArray[name].cpuArray.push(Math.round(stats[0].cpuPercent));
|
||||
statsArray[name].ramArray.push(Math.round(stats[0].memPercent));
|
||||
|
||||
statsArray[name].cpuArray = statsArray[name].cpuArray.slice(-15);
|
||||
statsArray[name].ramArray = statsArray[name].ramArray.slice(-15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store docker events
|
||||
docker.getEvents((err, stream) => {
|
||||
if (err) throw err;
|
||||
stream.on('data', (chunk) => {
|
||||
dockerEvents += chunk.toString('utf8');
|
||||
}, 1000);
|
||||
req.on('close', () => {
|
||||
clearInterval(eventCheck);
|
||||
});
|
||||
});
|
||||
|
||||
// Check for docker events
|
||||
setInterval(async () => {
|
||||
if (dockerEvents != '') {
|
||||
await getHidden();
|
||||
await containerCards();
|
||||
dockerEvents = '';
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Get hidden containers
|
||||
async function getHidden() {
|
||||
hidden = await Container.findAll({ where: {visibility:false}});
|
||||
hidden = hidden.map((container) => container.name);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
serverMetrics();
|
||||
metricsInterval = setInterval(serverMetrics, 1000);
|
||||
console.log('Metrics interval started');
|
||||
}
|
||||
if (!cardsInterval) {
|
||||
containerCards();
|
||||
cardsInterval = setInterval(containerCards, 1000);
|
||||
console.log('Cards interval started');
|
||||
}
|
||||
if (!graphsInterval) {
|
||||
containerStats();
|
||||
graphsInterval = setInterval(containerStats, 1000);
|
||||
console.log('Graphs interval started');
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
socket.emit('metrics', [cpu, ram, tx, rx, disk]);
|
||||
if (sent != cardList) {
|
||||
sent = cardList;
|
||||
socket.emit('containers', cardList);
|
||||
}
|
||||
socket.emit('containerStats', statsArray);
|
||||
}, 1000);
|
||||
|
||||
|
||||
// Client input
|
||||
socket.on('clicked', (data) => {
|
||||
let { name, id, value } = data;
|
||||
console.log(`${sessionData.user} clicked: ${id} ${value} ${name}`);
|
||||
if (clicked == true) { return; } clicked = true;
|
||||
|
||||
// View container logs
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
// start, stop, pause, restart container
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// hide container
|
||||
if (id == 'hide') {
|
||||
async function hideContainer() {
|
||||
let containerExists = await Container.findOne({ where: {name: name}});
|
||||
if(!containerExists) {
|
||||
const newContainer = await Container.create({ name: name, visibility: false, });
|
||||
getHidden();
|
||||
} else {
|
||||
containerExists.update({ visibility: false });
|
||||
getHidden();
|
||||
}
|
||||
|
||||
}
|
||||
hideContainer();
|
||||
}
|
||||
|
||||
// unhide containers
|
||||
if (id == 'resetView') {
|
||||
Container.update({ visibility: true }, { where: {} });
|
||||
getHidden();
|
||||
}
|
||||
|
||||
clicked = false;
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log(`${sessionData.user} disconnected`);
|
||||
socket.disconnect();
|
||||
// clear intervals if no users are connected
|
||||
if (io.engine.clientsCount == 0) {
|
||||
clearInterval(metricsInterval);
|
||||
clearInterval(cardsInterval);
|
||||
clearInterval(graphsInterval);
|
||||
metricsInterval = null;
|
||||
cardsInterval = null;
|
||||
graphsInterval = null;
|
||||
console.log('All intervals cleared');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('Missing session data');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// let link = '';
|
||||
// networkInterfaces().then(data => {
|
||||
// link = data[0].ip4;
|
||||
// });
|
||||
|
||||
});
|
5357
templates/foss.json
Normal file
|
@ -5914,7 +5914,7 @@
|
|||
"note": "",
|
||||
"description": "Nvidia HWA Test is a test container for NVIDIA hardware acceleration. Start the container then check the logs to confirm your output matches the example from this page: <a href='https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/sample-workload.html'>Running a Sample Workload</a>. <a href='https://www.nvidia.com/' target='_blank'>Website</a>. <a href='https://hub.docker.com/r/nvidia/cuda' target='_blank'>Docker Hub</a>",
|
||||
"platform": "linux",
|
||||
"logo": "https://avatars.githubusercontent.com/u/1728152",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/nvidia.png",
|
||||
"image": "nvidia/cuda:12.2.0-base-ubuntu20.04",
|
||||
"env": [
|
||||
{
|
5539
templates/templates.json
Normal file
|
@ -1,144 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Account</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.ejs') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Account Settings
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
<div class="col-3 d-none d-md-block border-end">
|
||||
<div class="card-body">
|
||||
<h4 class="subheader">Business settings</h4>
|
||||
<div class="list-group list-group-transparent">
|
||||
<a href="/account" class="list-group-item list-group-item-action d-flex align-items-center active">Accounts</a>
|
||||
<!-- <a href="#" class="list-group-item list-group-item-action d-flex align-items-center">My Notifications</a>
|
||||
<a href="#" class="list-group-item list-group-item-action d-flex align-items-center">Connected Apps</a> -->
|
||||
<a href="/settings" class="list-group-item list-group-item-action d-flex align-items-center">Settings</a>
|
||||
<!-- <a href="#" class="list-group-item list-group-item-action d-flex align-items-center">Billing & Invoices</a> -->
|
||||
</div>
|
||||
<h4 class="subheader mt-4">Experience</h4>
|
||||
<div class="list-group list-group-transparent">
|
||||
<a href="#" class="list-group-item list-group-item-action">Credits</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col d-flex flex-column">
|
||||
<div class="card-body">
|
||||
<h2 class="mb-4">My Account</h2>
|
||||
<h3 class="card-title">Profile Details</h3>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto"><span class="avatar avatar-xl"><%- avatar %></span>
|
||||
</div>
|
||||
<div class="col-auto"><a href="#" class="btn">
|
||||
Change avatar
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-auto"><a href="#" class="btn btn-ghost-danger">
|
||||
Delete avatar
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Profile</h3>
|
||||
<div class="row g-3">
|
||||
<div class="col-md">
|
||||
<div class="form-label">Full Name</div>
|
||||
<input type="text" class="form-control" value="<%= name %>" readonly="<%= name %>">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">First Name</div>
|
||||
<input type="text" class="form-control" value="<%= first_name %>" readonly="<%= first_name %>">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Last Name</div>
|
||||
<input type="text" class="form-control" value="<%= last_name %>" readonly="<%= last_name %>">
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Email</h3>
|
||||
<p class="card-subtitle">This contact will be shown to others publicly, so choose it carefully.</p>
|
||||
<div>
|
||||
<div class="row g-2">
|
||||
<div class="col-auto">
|
||||
<input type="text" class="form-control w-auto" value="<%= email %>" readonly="<%= email %>">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="btn">Change</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Password</h3>
|
||||
<p class="card-subtitle">You can set a permanent password if you don't want to use temporary login codes.</p>
|
||||
<div>
|
||||
<a href="#" class="btn">
|
||||
Set new password
|
||||
</a>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Public profile</h3>
|
||||
<p class="card-subtitle">Making your profile public means that anyone on the Dashkit network will be able to find
|
||||
you.</p>
|
||||
<div>
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" >
|
||||
<span class="form-check-label form-check-label-on">You're currently visible</span>
|
||||
<span class="form-check-label form-check-label-off">You're
|
||||
currently invisible</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent mt-auto">
|
||||
<div class="btn-list justify-content-end">
|
||||
<a href="#" class="btn">
|
||||
Cancel
|
||||
</a>
|
||||
<a href="#" class="btn btn-primary">
|
||||
Submit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('footer.ejs') %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Libs JS -->
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
131
views/account.html
Normal file
|
@ -0,0 +1,131 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Account</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Settings
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
|
||||
<%- include('sidebar.html') %>
|
||||
|
||||
<div class="col d-flex flex-column">
|
||||
<div class="card-body">
|
||||
<h2 class="mb-4">My Account</h2>
|
||||
<h3 class="card-title">Profile Details</h3>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto"><span class="avatar avatar-xl"><%- avatar %></span>
|
||||
</div>
|
||||
<div class="col-auto"><a href="#" class="btn">
|
||||
Change avatar
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-auto"><a href="#" class="btn btn-ghost-danger">
|
||||
Delete avatar
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Profile</h3>
|
||||
<div class="row g-3">
|
||||
<div class="col-md">
|
||||
<div class="form-label">Full Name</div>
|
||||
<input type="text" class="form-control" value="<%= name %>" readonly="<%= name %>">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">First Name</div>
|
||||
<input type="text" class="form-control" value="<%= first_name %>" readonly="<%= first_name %>">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Last Name</div>
|
||||
<input type="text" class="form-control" value="<%= last_name %>" readonly="<%= last_name %>">
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Email</h3>
|
||||
<p class="card-subtitle">This contact will be shown to others publicly, so choose it carefully.</p>
|
||||
<div>
|
||||
<div class="row g-2">
|
||||
<div class="col-auto">
|
||||
<input type="text" class="form-control w-auto" value="<%= email %>" readonly="<%= email %>">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="btn">Change</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Password</h3>
|
||||
<p class="card-subtitle">You can set a permanent password if you don't want to use temporary login codes.</p>
|
||||
<div>
|
||||
<a href="#" class="btn">
|
||||
Set new password
|
||||
</a>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Public profile</h3>
|
||||
<p class="card-subtitle">Making your profile public means that anyone on the Dashkit network will be able to find
|
||||
you.</p>
|
||||
<div>
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" >
|
||||
<span class="form-check-label form-check-label-on">You're currently visible</span>
|
||||
<span class="form-check-label form-check-label-off">You're
|
||||
currently invisible</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent mt-auto">
|
||||
<div class="btn-list justify-content-end">
|
||||
<a href="#" class="btn">
|
||||
Cancel
|
||||
</a>
|
||||
<a href="#" class="btn btn-primary">
|
||||
Submit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('footer.html') %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Libs JS -->
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
|
@ -22,7 +22,7 @@
|
|||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
|
||||
<%- include('navbar.ejs') %>
|
||||
<%- include('navbar.html') %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
@ -82,11 +82,11 @@
|
|||
prev
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item"><a class="page-link" href="/apps?page=1">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps?page=2">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps?page=3">3</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps?page=4">4</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps?page=5">5</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/1">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/2">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/3">3</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/4">4</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/5">5</a></li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<%- next %>">
|
||||
next <!-- Download SVG icon from http://tabler-icons.io/i/chevron-right -->
|
||||
|
@ -98,7 +98,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.ejs') %>
|
||||
<%- include('footer.html') %>
|
||||
|
||||
</div>
|
||||
</div>
|
1011
views/dashboard.ejs
282
views/dashboard.html
Normal file
|
@ -0,0 +1,282 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Dashboard</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/meters.css" rel="stylesheet"/>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
<script src="/js/htmx-sse.js"></script>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
|
||||
<div class="page">
|
||||
|
||||
<%- include('navbar.html') %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards" hx-ext="sse" sse-connect="/sse_event">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="row row-cards">
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-green text-white avatar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-cpu" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 5m0 1a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1z"></path><path d="M9 9h6v6h-6z"></path><path d="M3 10h2"></path><path d="M3 14h2"></path><path d="M10 3v2"></path><path d="M14 3v2"></path><path d="M21 10h-2"></path><path d="M21 14h-2"></path><path d="M14 21v-2"></path><path d="M10 21v-2"></path></svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- HTMX -->
|
||||
<div class="col" name="CPU" id="green" data-hx-get="/stats" data-hx-trigger="load, every 1s">
|
||||
<div class="font-weight-medium">
|
||||
<label class="cpu-text mb-1" for="cpu">CPU 0%</label>
|
||||
</div>
|
||||
<div class="cpu-bar meter animate green">
|
||||
<span style="width:20%"><span></span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-blue text-white avatar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-container" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M20 4v.01"></path> <path d="M20 20v.01"></path> <path d="M20 16v.01"></path> <path d="M20 12v.01"></path> <path d="M20 8v.01"></path> <path d="M8 4m0 1a1 1 0 0 1 1 -1h6a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-6a1 1 0 0 1 -1 -1z"></path> <path d="M4 4v.01"></path> <path d="M4 20v.01"></path> <path d="M4 16v.01"></path> <path d="M4 12v.01"></path> <path d="M4 8v.01"></path> </svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- HTMX -->
|
||||
<div class="col" name="RAM" id="blue" data-hx-get="/stats" data-hx-trigger="load, every 2s">
|
||||
<div class="font-weight-medium">
|
||||
<label class="ram-text mb-1" for="ram">RAM 0%</label>
|
||||
</div>
|
||||
<div class="ram-bar meter animate blue">
|
||||
<span style="width:20%"><span></span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-purple text-white avatar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrows-left-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M21 17l-18 0"></path> <path d="M6 10l-3 -3l3 -3"></path> <path d="M3 7l18 0"></path> <path d="M18 20l3 -3l-3 -3"></path> </svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- HTMX -->
|
||||
<div class="col" name="NET" id="purple">
|
||||
<div class="font-weight-medium">
|
||||
<label id="net-text" class="net-text mb-1" for="network">Down: 0MB Up: 0MB</label>
|
||||
</div>
|
||||
<div class="ram-bar meter animate purple">
|
||||
<span style="width:20%"><span></span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-orange text-white avatar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-database" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 6m-8 0a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"></path> <path d="M4 6v6a8 3 0 0 0 16 0v-6"></path> <path d="M4 12v6a8 3 0 0 0 16 0v-6"></path></svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- HTMX -->
|
||||
<div class="col" name="DISK" id="orange" data-hx-get="/stats" data-hx-trigger="load, every 3s">
|
||||
<div class="font-weight-medium">
|
||||
<label class="disk-text mb-1" for="disk">DISK 0%</label>
|
||||
</div>
|
||||
<div class="meter animate orange">
|
||||
<span style="width:20%"><span></span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- HTMX -->
|
||||
<div class="col-12">
|
||||
<div class="row row-cards" id="containerCards" data-hx-get="/containers" data-hx-trigger="load, sse:docker" data-hx-swap="innerHTML">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HTMX -->
|
||||
<!-- <div class="col-12">
|
||||
<div class="row row-cards" data-hx-get="/installs" name="jellyfin" data-hx-trigger="load delay:2s, sse:install" data-hx-swap="beforeend" hx-target="#containerCards">
|
||||
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- HTMX Target-->
|
||||
<div id="modals-here" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal modal-blur fade" id="log_view" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Logs</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="card-body">
|
||||
<h4>Logs:</h4>
|
||||
<div id="logView">
|
||||
<pre>No logs available</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-info" onclick="viewLogs(this)" name="refresh">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path> <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path> </svg>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.html') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/libs/apexcharts/dist/apexcharts.min.js"></script>
|
||||
<script src="/js/tabler.min.js"></script>
|
||||
<script>
|
||||
var options = {
|
||||
chart: {
|
||||
type: "line",
|
||||
height: 40.0,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
animations: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
opacity: 1
|
||||
},
|
||||
stroke: {
|
||||
width: [2, 1],
|
||||
dashArray: [0, 3],
|
||||
lineCap: "round",
|
||||
curve: "smooth"
|
||||
},
|
||||
series: [{
|
||||
name: "CPU",
|
||||
data: []
|
||||
}, {
|
||||
name: "RAM",
|
||||
data: []
|
||||
}],
|
||||
tooltip: {
|
||||
enabled: false
|
||||
},
|
||||
grid: {
|
||||
strokeDashArray: 4
|
||||
},
|
||||
xaxis: {
|
||||
labels: {
|
||||
padding: 0
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
labels: {
|
||||
padding: 4
|
||||
}
|
||||
},
|
||||
colors: [tabler.getColor("primary"), tabler.getColor("gray-600")],
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
function selectAll(group) {
|
||||
|
||||
let checkboxes = document.getElementsByName(group);
|
||||
if (checkboxes[0].checked == true) {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = true;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -23,8 +23,8 @@
|
|||
All rights reserved.
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a href="#" class="link-secondary" rel="noopener">
|
||||
v0.10
|
||||
<a href="https://github.com/lllllllillllllillll/DweebUI/releases" class="link-secondary" rel="noopener">
|
||||
v0.40
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
188
views/images.ejs
|
@ -1,188 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Images</title>
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.ejs') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
|
||||
<div class="col-12 mt-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Docker Images</h3>
|
||||
<div class="card-options btn-list">
|
||||
<a href="#" class="btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path> <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path> </svg>
|
||||
Refresh
|
||||
</a>
|
||||
<a href="#" class="btn" data-bs-toggle="modal" data-bs-target="#not_add-site">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" 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 5l0 14"></path> <path d="M5 12l14 0"></path> </svg>
|
||||
New Image
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal modal-blur fade" id="add-site" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
|
||||
<form action="/addsite" id="addsite" method="POST">
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Type</div>
|
||||
<select class="form-select" name="type">
|
||||
<option value="reverse_proxy">Reverse Proxy</option>
|
||||
<option value="proxy">Proxy</option>
|
||||
<option value="file_server">File Server</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Domain / Subdomain</label>
|
||||
<input type="text" class="form-control" name="domain" placeholder="media.mydomainname.com">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="row g-2">
|
||||
<div class="col-8">
|
||||
<label class="form-label">Hostname / Host IP</label>
|
||||
<input type="text" class="form-control" name="host" placeholder="localhost">
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label class="form-label">Port</label>
|
||||
<input type="text" class="form-control" name="port" placeholder="8000">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="divide-y">
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col" title="HTTP Strict Transport Security (HSTS) is a simple and widely supported standard to protect visitors by ensuring that their browsers always connect to a website over HTTPS.">HSTS</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="hsts" checked="" disabled="">
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Container</div>
|
||||
<select class="form-select" name="container" disabled="">
|
||||
<option value="0" selected></option>
|
||||
<option value="1">Jellyfin</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link link-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
|
||||
<input type="submit" form="addsite" class="btn btn-success" value="Add"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
|
||||
<%- image_list %>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
|
||||
<span class="dropdown">
|
||||
<button class="btn dropdown-toggle align-text-top" data-bs-toggle="dropdown">Actions</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item" type="submit" formaction="/enablesite">
|
||||
Enable
|
||||
</button>
|
||||
<button class="dropdown-item" type="submit" formaction="/disablesite">
|
||||
Disable
|
||||
</button>
|
||||
<button class="dropdown-item" type="submit" formaction="/removesite">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
</form>
|
||||
|
||||
<p class="m-0 text-muted ms-auto"><%- image_count %> Images</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.ejs') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const list = new List('table-default', {
|
||||
sortClass: 'table-sort',
|
||||
listClass: 'table-tbody',
|
||||
valueNames: [ 'sort-name', 'sort-type', 'sort-city', 'sort-score',
|
||||
{ attr: 'data-date', name: 'sort-date' },
|
||||
{ attr: 'data-progress', name: 'sort-progress' },
|
||||
'sort-quantity'
|
||||
]
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function selectAll() {
|
||||
let checkboxes = document.getElementsByName('select');
|
||||
if (checkboxes[0].checked == true) {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = true;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
116
views/images.html
Normal file
|
@ -0,0 +1,116 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Images</title>
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
|
||||
<div class="col-12 mt-12">
|
||||
<div class="card">
|
||||
<form method="post">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Docker Images</h3>
|
||||
<div class="card-options btn-list">
|
||||
<a href="#" class="btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path> <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path> </svg>
|
||||
Refresh
|
||||
</a>
|
||||
<a href="#" class="btn" data-bs-toggle="modal" data-bs-target="#not_add-site">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" 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 5l0 14"></path> <path d="M5 12l14 0"></path> </svg>
|
||||
New Image
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
|
||||
<%- image_list %>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
|
||||
<button class="btn" type="submit" formaction="/removeImage">Remove</button>
|
||||
|
||||
</form>
|
||||
|
||||
<p class="m-0 text-muted ms-auto"><%- image_count %> Images</p>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.html') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const list = new List('table-default', {
|
||||
sortClass: 'table-sort',
|
||||
listClass: 'table-tbody',
|
||||
valueNames: [ 'sort-name', 'sort-type', 'sort-city', 'sort-score',
|
||||
{ attr: 'data-date', name: 'sort-date' },
|
||||
{ attr: 'data-progress', name: 'sort-progress' },
|
||||
'sort-quantity'
|
||||
]
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function selectAll() {
|
||||
let checkboxes = document.getElementsByName('select');
|
||||
if (checkboxes[0].checked == true) {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = true;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -9,7 +9,7 @@
|
|||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('fonts/inter.css');
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
@ -22,19 +22,27 @@
|
|||
<script src="/js/demo-theme.js"></script>
|
||||
<div class="page page-center">
|
||||
<div class="container container-tight py-4">
|
||||
<div class="text-center mb-4">
|
||||
<a href="." class="navbar-brand navbar-brand-autodark"><img src="/static/logo.svg" height="50" alt=""></a>
|
||||
<div class="text-center">
|
||||
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||
<img src="/images/logo.png" alt="DweebUI" title="DweebUI" height="100px">
|
||||
</h1>
|
||||
</div>
|
||||
<div class="text-center mb-4">
|
||||
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||
<img src="/images/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="card card-md">
|
||||
<div class="card-body">
|
||||
<h2 class="h2 text-center mb-4">Login to your account</h2>
|
||||
<form action="/login" method="POST" novalidate>
|
||||
|
||||
<% if(error) { %>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<%= error %>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if(error) { %>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<%= error %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Email address</label>
|
||||
|
@ -70,9 +78,12 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -33,10 +33,15 @@
|
|||
aria-controls="navbar-menu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0">
|
||||
<a href="#">
|
||||
<img src="/static/logo.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
|
||||
<img src="/images/logo.png" alt="DweebUI" title="DweebUI" height="40px">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img src="/images/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
|
||||
</a>
|
||||
|
||||
|
||||
</h1>
|
||||
<div class="navbar-nav flex-row order-md-last">
|
||||
<div class="nav-item d-none d-md-flex me-3">
|
||||
|
@ -79,11 +84,15 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6" /> <path d="M9 17v1a3 3 0 0 0 6 0v-1" /> </svg>
|
||||
<!-- <span class="badge bg-red"></span> -->
|
||||
</a>
|
||||
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-arrow dropdown-menu-end dropdown-menu-card">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Notifications</h3>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="list-group list-group-flush list-group-hoverable">
|
||||
<div class="list-group-item">
|
||||
<div class="row align-items-center">
|
||||
|
@ -120,7 +129,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -138,11 +149,11 @@
|
|||
</div>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
|
||||
<!-- <a href="#" class="dropdown-item">Status</a> -->
|
||||
<a href="/account" class="dropdown-item">Account</a>
|
||||
<!-- <a href="#" class="dropdown-item">Feedback</a> -->
|
||||
<!-- <div class="dropdown-divider"></div> -->
|
||||
<a href="/variables" class="dropdown-item">Variables</a>
|
||||
<a href="/settings" class="dropdown-item">Settings</a>
|
||||
<!-- <div class="dropdown-divider"></div> -->
|
||||
|
||||
<a href="/logout" class="dropdown-item">Logout</a>
|
||||
</div>
|
||||
</div>
|
|
@ -1,188 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Networks</title>
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.ejs') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
|
||||
<div class="col-12 mt-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Docker Networks</h3>
|
||||
<div class="card-options btn-list">
|
||||
<a href="#" class="btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path> <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path> </svg>
|
||||
Refresh
|
||||
</a>
|
||||
<a href="#" class="btn" data-bs-toggle="modal" data-bs-target="#not_add-site">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" 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 5l0 14"></path> <path d="M5 12l14 0"></path> </svg>
|
||||
New Network
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal modal-blur fade" id="add-site" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
|
||||
<form action="/addsite" id="addsite" method="POST">
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Type</div>
|
||||
<select class="form-select" name="type">
|
||||
<option value="reverse_proxy">Reverse Proxy</option>
|
||||
<option value="proxy">Proxy</option>
|
||||
<option value="file_server">File Server</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Domain / Subdomain</label>
|
||||
<input type="text" class="form-control" name="domain" placeholder="media.mydomainname.com">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="row g-2">
|
||||
<div class="col-8">
|
||||
<label class="form-label">Hostname / Host IP</label>
|
||||
<input type="text" class="form-control" name="host" placeholder="localhost">
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label class="form-label">Port</label>
|
||||
<input type="text" class="form-control" name="port" placeholder="8000">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="divide-y">
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col" title="HTTP Strict Transport Security (HSTS) is a simple and widely supported standard to protect visitors by ensuring that their browsers always connect to a website over HTTPS.">HSTS</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="hsts" checked="" disabled="">
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Container</div>
|
||||
<select class="form-select" name="container" disabled="">
|
||||
<option value="0" selected></option>
|
||||
<option value="1">Jellyfin</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link link-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
|
||||
<input type="submit" form="addsite" class="btn btn-success" value="Add"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
|
||||
<%- network_list %>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
|
||||
<span class="dropdown">
|
||||
<button class="btn dropdown-toggle align-text-top" data-bs-toggle="dropdown">Actions</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item" type="submit" formaction="/enablesite">
|
||||
Enable
|
||||
</button>
|
||||
<button class="dropdown-item" type="submit" formaction="/disablesite">
|
||||
Disable
|
||||
</button>
|
||||
<button class="dropdown-item" type="submit" formaction="/removesite">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
</form>
|
||||
|
||||
<p class="m-0 text-muted ms-auto"><%- network_count %> Networks</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.ejs') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const list = new List('table-default', {
|
||||
sortClass: 'table-sort',
|
||||
listClass: 'table-tbody',
|
||||
valueNames: [ 'sort-name', 'sort-type', 'sort-city', 'sort-score',
|
||||
{ attr: 'data-date', name: 'sort-date' },
|
||||
{ attr: 'data-progress', name: 'sort-progress' },
|
||||
'sort-quantity'
|
||||
]
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function selectAll() {
|
||||
let checkboxes = document.getElementsByName('select');
|
||||
if (checkboxes[0].checked == true) {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = true;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
131
views/networks.html
Normal file
|
@ -0,0 +1,131 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Networks</title>
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
|
||||
<div class="col-12 mt-12">
|
||||
<div class="card">
|
||||
<form method="post">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Docker Networks</h3>
|
||||
<div class="card-options btn-list">
|
||||
<a href="#" class="btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path> <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path> </svg>
|
||||
Refresh
|
||||
</a>
|
||||
<a href="#" class="btn" data-bs-toggle="modal" data-bs-target="#not_add-site">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" 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 5l0 14"></path> <path d="M5 12l14 0"></path> </svg>
|
||||
New Network
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
|
||||
<%- network_list %>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
|
||||
|
||||
<button class="btn" type="submit" formaction="/removeNetwork">Remove</button>
|
||||
|
||||
<!-- <span class="dropdown">
|
||||
<button class="btn dropdown-toggle align-text-top" data-bs-toggle="dropdown">Actions</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item" type="submit" formaction="/submitNetworks">
|
||||
Enable
|
||||
</button>
|
||||
<button class="dropdown-item" type="submit" formaction="/submitNetworks">
|
||||
Disable
|
||||
</button>
|
||||
<button class="dropdown-item" type="submit" formaction="/submitNetworks">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</span> -->
|
||||
|
||||
|
||||
|
||||
<p class="m-0 text-muted ms-auto"><%- network_count %> Networks</p>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.html') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const list = new List('table-default', {
|
||||
sortClass: 'table-sort',
|
||||
listClass: 'table-tbody',
|
||||
valueNames: [ 'sort-name', 'sort-type', 'sort-city', 'sort-score',
|
||||
{ attr: 'data-date', name: 'sort-date' },
|
||||
{ attr: 'data-progress', name: 'sort-progress' },
|
||||
'sort-quantity'
|
||||
]
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function selectAll() {
|
||||
let checkboxes = document.getElementsByName('select');
|
||||
if (checkboxes[0].checked == true) {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = true;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -21,7 +21,7 @@
|
|||
<body >
|
||||
<div class="page">
|
||||
|
||||
<%- include('navbar.ejs') %>
|
||||
<%- include('navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
|
||||
<div class="page-body">
|
||||
|
@ -131,7 +131,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.ejs') %>
|
||||
<%- include('footer.html') %>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -9,7 +9,7 @@
|
|||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('fonts/inter.css');
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
|||
<form class="container container-tight py-4" action="/register" method="POST" novalidate>
|
||||
|
||||
<div class="text-center mb-4">
|
||||
<a href="#" class="navbar-brand navbar-brand-autodark d-none"><img src="/static/logo.svg" height="36" alt=""></a>
|
||||
<a href="#" class="navbar-brand navbar-brand-autodark d-none"><img src="/images/logo.svg" height="36" alt=""></a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
|
@ -88,7 +88,7 @@
|
|||
<label class="form-imagecheck mb-2">
|
||||
<input name="avatar" type="radio" value="rus.jpg" class="form-imagecheck-input" checked/>
|
||||
<span class="form-imagecheck-figure">
|
||||
<img src="img/avatars/rus.jpg" alt="Rich Uncle Skeleton" title="Rich Uncle Skeleton" class="form-imagecheck-image" width="100px">
|
||||
<img src="/images/avatars/rus.jpg" alt="Rich Uncle Skeleton" title="Rich Uncle Skeleton" class="form-imagecheck-image" width="100px">
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -96,7 +96,7 @@
|
|||
<label class="form-imagecheck mb-2">
|
||||
<input name="avatar" type="radio" value="burns.jpg" class="form-imagecheck-input"/>
|
||||
<span class="form-imagecheck-figure">
|
||||
<img src="img/avatars/burns.jpg" alt="Montgomery Burns" title="Montgomery Burns" class="form-imagecheck-image" width="100px">
|
||||
<img src="/images/avatars/burns.jpg" alt="Montgomery Burns" title="Montgomery Burns" class="form-imagecheck-image" width="100px">
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -104,7 +104,7 @@
|
|||
<label class="form-imagecheck mb-2">
|
||||
<input name="avatar" type="radio" value="frank.jpg" class="form-imagecheck-input" />
|
||||
<span class="form-imagecheck-figure">
|
||||
<img src="img/avatars/frank.jpg" alt="Frank Grimes" title= "Frank Grimes" class="form-imagecheck-image" width="100px">
|
||||
<img src="/images/avatars/frank.jpg" alt="Frank Grimes" title= "Frank Grimes" class="form-imagecheck-image" width="100px">
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -112,7 +112,7 @@
|
|||
<label class="form-imagecheck mb-2">
|
||||
<input name="avatar" type="radio" value="moe.jpg" class="form-imagecheck-input"/>
|
||||
<span class="form-imagecheck-figure">
|
||||
<img src="img/avatars/moe.jpg" alt="Moe Szyslak" title="Moe Szyslak" class="form-imagecheck-image" width="100px">
|
||||
<img src="/images/avatars/moe.jpg" alt="Moe Szyslak" title="Moe Szyslak" class="form-imagecheck-image" width="100px">
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -120,7 +120,7 @@
|
|||
<label class="form-imagecheck mb-2">
|
||||
<input name="avatar" type="radio" value="poochie.jpg" class="form-imagecheck-input" />
|
||||
<span class="form-imagecheck-figure">
|
||||
<img src="img/avatars/poochie.jpg" alt="Poochie" title="Poochie" class="form-imagecheck-image" width="100px">
|
||||
<img src="/images/avatars/poochie.jpg" alt="Poochie" title="Poochie" class="form-imagecheck-image" width="100px">
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -128,7 +128,7 @@
|
|||
<label class="form-imagecheck mb-2">
|
||||
<input name="avatar" type="radio" value="skinner.jpg" class="form-imagecheck-input" />
|
||||
<span class="form-imagecheck-figure">
|
||||
<img src="img/avatars/skinner.jpg" alt="Seymour Skinner" title="Seymour Skinner" class="form-imagecheck-image" width="100px">
|
||||
<img src="/images/avatars/skinner.jpg" alt="Seymour Skinner" title="Seymour Skinner" class="form-imagecheck-image" width="100px">
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -136,7 +136,7 @@
|
|||
<label class="form-imagecheck mb-2">
|
||||
<input name="avatar" type="radio" value="moleman.png" class="form-imagecheck-input" />
|
||||
<span class="form-imagecheck-figure">
|
||||
<img src="img/avatars/moleman.png" alt="Hans Moleman" title="Hans Moleman" class="form-imagecheck-image" width="100px">
|
||||
<img src="/images/avatars/moleman.png" alt="Hans Moleman" title="Hans Moleman" class="form-imagecheck-image" width="100px">
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -144,7 +144,7 @@
|
|||
<label class="form-imagecheck mb-2">
|
||||
<input name="avatar" type="radio" value="duffman.png" class="form-imagecheck-input" />
|
||||
<span class="form-imagecheck-figure">
|
||||
<img src="img/avatars/duffman.png" alt="Duffman" title="Duffman" class="form-imagecheck-image" width="100px">
|
||||
<img src="/images/avatars/duffman.png" alt="Duffman" title="Duffman" class="form-imagecheck-image" width="100px">
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -176,11 +176,9 @@
|
|||
<div class="d-none d-md-flex">
|
||||
|
||||
<a href="?theme=dark" class="nav-link px-0 hide-theme-dark" title="Enable dark mode" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/moon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" /></svg>
|
||||
</a>
|
||||
<a href="?theme=light" class="nav-link px-0 hide-theme-light" title="Enable light mode" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/sun -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" 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="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7" /></svg>
|
||||
</a>
|
||||
</div>
|
|
@ -1,136 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Settings</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.ejs') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Account Settings
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
<div class="col-3 d-none d-md-block border-end">
|
||||
<div class="card-body">
|
||||
<h4 class="subheader">Business settings</h4>
|
||||
<div class="list-group list-group-transparent">
|
||||
<a href="/account" class="list-group-item list-group-item-action d-flex align-items-center">Account</a>
|
||||
<!-- <a href="#" class="list-group-item list-group-item-action d-flex align-items-center">My Notifications</a>
|
||||
<a href="#" class="list-group-item list-group-item-action d-flex align-items-center">Connected Apps</a> -->
|
||||
<a href="/settings" class="list-group-item list-group-item-action d-flex align-items-center active">Settings</a>
|
||||
<!-- <a href="#" class="list-group-item list-group-item-action d-flex align-items-center">Billing & Invoices</a> -->
|
||||
</div>
|
||||
<h4 class="subheader mt-4">Experience</h4>
|
||||
<div class="list-group list-group-transparent">
|
||||
<a href="#" class="list-group-item list-group-item-action">Credits</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col d-flex flex-column">
|
||||
|
||||
<div class="card-body">
|
||||
<h2 class="mb-2">Settings</h2>
|
||||
<p class="text-muted mb-4">Configure server below</p>
|
||||
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<a href="./QuickConnect.bat" class="btn" download="QuickConnect.bat">
|
||||
<!-- Download SVG icon from https://tabler-icons.io/i/brand-tabler-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-windows" 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="M17.8 20l-12 -1.5c-1 -.1 -1.8 -.9 -1.8 -1.9v-9.2c0 -1 .8 -1.8 1.8 -1.9l12 -1.5c1.2 -.1 2.2 .8 2.2 1.9v12.1c0 1.2 -1.1 2.1 -2.2 1.9z"></path> <path d="M12 5l0 14"></path> <path d="M4 12l16 0"></path> </svg>
|
||||
Windows QuickConnect
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md">
|
||||
<div class="form-label">Full Name</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">First Name</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Last Name</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Email</h3>
|
||||
<p class="card-subtitle">This contact will be shown to others publicly, so choose it carefully.</p>
|
||||
<div>
|
||||
<div class="row g-2">
|
||||
<div class="col-auto">
|
||||
<input type="text" class="form-control w-auto" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="btn">Change</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Password</h3>
|
||||
<p class="card-subtitle">You can set a permanent password if you don't want to use temporary login codes.</p>
|
||||
<div>
|
||||
<a href="#" class="btn">
|
||||
Set new password
|
||||
</a>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Public profile</h3>
|
||||
<p class="card-subtitle">Making your profile public means that anyone on the Dashkit network will be able to find
|
||||
you.</p>
|
||||
<div>
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" >
|
||||
<span class="form-check-label form-check-label-on">You're currently visible</span>
|
||||
<span class="form-check-label form-check-label-off">You're
|
||||
currently invisible</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.ejs') %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Libs JS -->
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
120
views/settings.html
Normal file
|
@ -0,0 +1,120 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Settings</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Settings
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
<%- include('sidebar.html') %>
|
||||
<div class="col d-flex flex-column">
|
||||
|
||||
<div class="card-body">
|
||||
<h2 class="mb-2">Settings</h2>
|
||||
<p class="text-muted mb-4">Configure server below</p>
|
||||
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<!-- <a href="./QuickConnect.bat" class="btn" download="QuickConnect.bat">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-windows" 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="M17.8 20l-12 -1.5c-1 -.1 -1.8 -.9 -1.8 -1.9v-9.2c0 -1 .8 -1.8 1.8 -1.9l12 -1.5c1.2 -.1 2.2 .8 2.2 1.9v12.1c0 1.2 -1.1 2.1 -2.2 1.9z"></path> <path d="M12 5l0 14"></path> <path d="M4 12l16 0"></path> </svg>
|
||||
Windows QuickConnect
|
||||
</a> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md">
|
||||
<div class="form-label">Full Name</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">First Name</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Last Name</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Email</h3>
|
||||
<p class="card-subtitle">This contact will be shown to others publicly, so choose it carefully.</p>
|
||||
<div>
|
||||
<div class="row g-2">
|
||||
<div class="col-auto">
|
||||
<input type="text" class="form-control w-auto" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="btn">Change</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Password</h3>
|
||||
<p class="card-subtitle">You can set a permanent password if you don't want to use temporary login codes.</p>
|
||||
<div>
|
||||
<a href="#" class="btn">
|
||||
Set new password
|
||||
</a>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Public profile</h3>
|
||||
<p class="card-subtitle">Making your profile public means that anyone on the Dashkit network will be able to find
|
||||
you.</p>
|
||||
<div>
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" >
|
||||
<span class="form-check-label form-check-label-on">You're currently visible</span>
|
||||
<span class="form-check-label form-check-label-off">You're
|
||||
currently invisible</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.html') %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Libs JS -->
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
14
views/sidebar.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<div class="col-3 d-none d-md-block border-end">
|
||||
<div class="card-body">
|
||||
<h4 class="subheader">Menu</h4>
|
||||
<div class="list-group list-group-transparent">
|
||||
<a href="/account" class="list-group-item list-group-item-action d-flex align-items-center">Accounts</a>
|
||||
<a href="/variables" class="list-group-item list-group-item-action d-flex align-items-center" disabled="">Variables</a>
|
||||
<a href="/settings" class="list-group-item list-group-item-action d-flex align-items-center">Settings</a>
|
||||
</div>
|
||||
<h4 class="subheader mt-4">Other</h4>
|
||||
<div class="list-group list-group-transparent">
|
||||
<a href="/supporters" class="list-group-item list-group-item-action">Supporters</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
85
views/supporters.html
Normal file
|
@ -0,0 +1,85 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Settings</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Settings
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
<%- include('sidebar.html') %>
|
||||
<div class="col d-flex flex-column">
|
||||
|
||||
<div class="card-body">
|
||||
<h2 class="mb-2">Supporters</h2>
|
||||
<p class="text-muted mb-4">[Click to Thank]</p>
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
|
||||
<span type="button" class="avatar avatar-md bg-green-lt" hx-trigger="load, click" hx-post="/thank" hx-target="#count" name="MM" title="MM" style="margin-right: 5px;">mm</span>
|
||||
|
||||
<span type="button" class="avatar avatar-md bg-cyan-lt" hx-trigger="click" hx-post="/thank" hx-target="#count" name="PD" title="PD" style="margin-right: 5px;">pd</span>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted mb-4">Thanks counter:</p>
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
|
||||
<span class="avatar avatar-md bg-yellow-lt" id="count" style="margin-right: 5px;">0</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.html') %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Libs JS -->
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
|
@ -21,7 +21,7 @@
|
|||
<div class="page">
|
||||
|
||||
|
||||
<%- include('navbar.ejs') %>
|
||||
<%- include('navbar.html') %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
|
@ -68,7 +68,7 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
<%- include('footer.ejs') %>
|
||||
<%- include('footer.html') %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Libs JS -->
|
|
@ -21,7 +21,7 @@
|
|||
<div class="page">
|
||||
|
||||
|
||||
<%- include('navbar.ejs') %>
|
||||
<%- include('navbar.html') %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
|
@ -59,7 +59,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('footer.ejs') %>
|
||||
<%- include('footer.html') %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Libs JS -->
|
120
views/variables.html
Normal file
|
@ -0,0 +1,120 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Settings</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Settings
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
<%- include('sidebar.html') %>
|
||||
<div class="col d-flex flex-column">
|
||||
|
||||
<div class="card-body">
|
||||
<h2 class="mb-2">Variables</h2>
|
||||
<p class="text-muted mb-4">Configure default variables below.</p>
|
||||
|
||||
<!-- <div class="row align-items-center">
|
||||
<div class="col">
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md">
|
||||
<div class="form-label">Match 1</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Match 2</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Default</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md">
|
||||
<div class="form-label">Match 1</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Match 2</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Default</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md">
|
||||
<div class="form-label">Match 1</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Match 2</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Default</div>
|
||||
<input type="text" class="form-control" value="" readonly="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.html') %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Libs JS -->
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,188 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Volumes</title>
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.ejs') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
|
||||
<div class="col-12 mt-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Docker Volumes</h3>
|
||||
<div class="card-options btn-list">
|
||||
<a href="#" class="btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path> <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path> </svg>
|
||||
Refresh
|
||||
</a>
|
||||
<a href="#" class="btn" data-bs-toggle="modal" data-bs-target="#not_add-site">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" 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 5l0 14"></path> <path d="M5 12l14 0"></path> </svg>
|
||||
New Volume
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal modal-blur fade" id="add-site" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
|
||||
<form action="/addsite" id="addsite" method="POST">
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Type</div>
|
||||
<select class="form-select" name="type">
|
||||
<option value="reverse_proxy">Reverse Proxy</option>
|
||||
<option value="proxy">Proxy</option>
|
||||
<option value="file_server">File Server</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Domain / Subdomain</label>
|
||||
<input type="text" class="form-control" name="domain" placeholder="media.mydomainname.com">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="row g-2">
|
||||
<div class="col-8">
|
||||
<label class="form-label">Hostname / Host IP</label>
|
||||
<input type="text" class="form-control" name="host" placeholder="localhost">
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label class="form-label">Port</label>
|
||||
<input type="text" class="form-control" name="port" placeholder="8000">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="divide-y">
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col" title="HTTP Strict Transport Security (HSTS) is a simple and widely supported standard to protect visitors by ensuring that their browsers always connect to a website over HTTPS.">HSTS</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="hsts" checked="" disabled="">
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Container</div>
|
||||
<select class="form-select" name="container" disabled="">
|
||||
<option value="0" selected></option>
|
||||
<option value="1">Jellyfin</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link link-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
|
||||
<input type="submit" form="addsite" class="btn btn-success" value="Add"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
|
||||
<%- volume_list %>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
|
||||
<span class="dropdown">
|
||||
<button class="btn dropdown-toggle align-text-top" data-bs-toggle="dropdown">Actions</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item" type="submit" formaction="/enablesite">
|
||||
Enable
|
||||
</button>
|
||||
<button class="dropdown-item" type="submit" formaction="/disablesite">
|
||||
Disable
|
||||
</button>
|
||||
<button class="dropdown-item" type="submit" formaction="/removesite">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
</form>
|
||||
|
||||
<p class="m-0 text-muted ms-auto"><%- volume_count %> Volumes</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.ejs') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const list = new List('table-default', {
|
||||
sortClass: 'table-sort',
|
||||
listClass: 'table-tbody',
|
||||
valueNames: [ 'sort-name', 'sort-type', 'sort-city', 'sort-score',
|
||||
{ attr: 'data-date', name: 'sort-date' },
|
||||
{ attr: 'data-progress', name: 'sort-progress' },
|
||||
'sort-quantity'
|
||||
]
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function selectAll() {
|
||||
let checkboxes = document.getElementsByName('select');
|
||||
if (checkboxes[0].checked == true) {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = true;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
130
views/volumes.html
Normal file
|
@ -0,0 +1,130 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>DweebUI - Volumes</title>
|
||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||
<style>
|
||||
@import url('/fonts/inter.css');
|
||||
:root {
|
||||
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
||||
}
|
||||
body {
|
||||
font-feature-settings: "cv03", "cv04", "cv11";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!-- Navbar -->
|
||||
<%- include('navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
|
||||
<div class="col-12 mt-12">
|
||||
<div class="card">
|
||||
<form method="post">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Docker Volumes</h3>
|
||||
<div class="card-options btn-list">
|
||||
<a href="#" class="btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path> <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path> </svg>
|
||||
Refresh
|
||||
</a>
|
||||
<a href="#" class="btn" data-bs-toggle="modal" data-bs-target="#not_add-site">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" 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 5l0 14"></path> <path d="M5 12l14 0"></path> </svg>
|
||||
New Volume
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
|
||||
<%- volume_list %>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
|
||||
<button class="btn" type="submit" formaction="/removeVolume">Remove</button>
|
||||
|
||||
<!-- <span class="dropdown">
|
||||
<button class="btn dropdown-toggle align-text-top" data-bs-toggle="dropdown">Actions</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item" type="submit" formaction="/submitVolumes">
|
||||
Enable
|
||||
</button>
|
||||
<button class="dropdown-item" type="submit" formaction="/submitVolumes">
|
||||
Disable
|
||||
</button>
|
||||
<button class="dropdown-item" type="submit" formaction="/submitVolumes">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</span> -->
|
||||
|
||||
|
||||
<p class="m-0 text-muted ms-auto"><%- volume_count %> Volumes</p>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('footer.html') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const list = new List('table-default', {
|
||||
sortClass: 'table-sort',
|
||||
listClass: 'table-tbody',
|
||||
valueNames: [ 'sort-name', 'sort-type', 'sort-city', 'sort-score',
|
||||
{ attr: 'data-date', name: 'sort-date' },
|
||||
{ attr: 'data-progress', name: 'sort-progress' },
|
||||
'sort-quantity'
|
||||
]
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function selectAll() {
|
||||
let checkboxes = document.getElementsByName('select');
|
||||
if (checkboxes[0].checked == true) {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = true;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < checkboxes.length; i++) {
|
||||
checkboxes[i].checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|