Compare commits
12 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7d0bbc27fa | ||
![]() |
47c9ab68ce | ||
![]() |
518bc46dab | ||
![]() |
00216663a5 | ||
![]() |
f46648e598 | ||
![]() |
d905a95764 | ||
![]() |
a67f65996e | ||
![]() |
4b5ae32a97 | ||
![]() |
ee9870f554 | ||
![]() |
8f97e17765 | ||
![]() |
c7d79b296c | ||
![]() |
c3f10fbb7c |
101 changed files with 34872 additions and 73613 deletions
|
@ -1,9 +1,8 @@
|
|||
**/*.sqlite
|
||||
**/db.sqlite
|
||||
**/node_modules
|
||||
**/screenshots
|
||||
.gitignore
|
||||
.github
|
||||
.git
|
||||
Dockerfile
|
||||
docker-compose.yaml
|
||||
**/*.yaml
|
||||
docker-compose.yaml
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -55,7 +55,7 @@ jobs:
|
|||
|
||||
# Build image and only publish if not a Pull Request
|
||||
- name: Build and Publish Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
timeout-minutes: 30
|
||||
with:
|
||||
context: .
|
||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,7 +1,5 @@
|
|||
sessions.sqlite
|
||||
settings.sqlite
|
||||
**/db.sqlite
|
||||
**/node_modules
|
||||
**/*.yaml
|
||||
**/appdata
|
||||
.github
|
||||
.git
|
||||
package-lock.json
|
||||
.git
|
44
CHANGELOG.md
44
CHANGELOG.md
|
@ -1,47 +1,3 @@
|
|||
## v0.70 (dev)
|
||||
* Fixed installs having to be run twice.
|
||||
* Updated systeminformation.
|
||||
* Updated adm-zip.
|
||||
* Updated yaml.
|
||||
* Pushed new docker image with 'latest' tag.
|
||||
* Fixed container card links.
|
||||
* Moved 'Reset view' button.
|
||||
* New - 'Grid view' and 'List view' button (non-functioning).
|
||||
* Added try blocks to volumes, images, and networks pages to address GitHub issues.
|
||||
* Fixed HTTPS env.
|
||||
* New - Authentication can be reduced or disabled.
|
||||
* New (again) - PM2 to keep the app running if it encounters an error.
|
||||
* New - User registration enabled/disabled from Settings page.
|
||||
* Removed 'SECRET' environment variable.
|
||||
* New - Custom container_card ports links.
|
||||
* New - Custom container_card title links.
|
||||
* Fixed issue updating view permission.
|
||||
* Fixed issue viewing container logs.
|
||||
* App icons are now determined by service label instead of image name.
|
||||
* App icons sourced from new repo with 1000+ icons.
|
||||
* Rewrote most of the app to use containerIDs and UUIDs universally.
|
||||
* Dashboard updates now triggered by Docker events instead of constantly polling the API.
|
||||
* Sessions now stored in sqlite database instead of memory.
|
||||
* Updated tabler from 1.0.0-beta16 to 1.0.0-beta20.
|
||||
* Updated htmx (2.0.1) and sse plugin (2.2.1).
|
||||
* Seperated css and js customizations into dweebui.css and dweebui.js.
|
||||
* New - Preferences page for individual user settings, like language choice.
|
||||
* New - Hide username from dashboard.
|
||||
* New - Footer displays version with build number.
|
||||
* Updated hide container_card to be **instant**.
|
||||
* Improved console.log and syslog messages.
|
||||
* Fixed modal close buttons.
|
||||
* Reduced amount of html being stored in js files.
|
||||
* CSS and pages tweaks to make the style more consistent.
|
||||
* Improved container cards to be more compact.
|
||||
* Improved sponsors and credits pages.
|
||||
* New - Secret code for sponsors.
|
||||
* Fixed installs not appearing or appearing multiple times.
|
||||
* Improved log view and fixed refresh button.
|
||||
* Made app cards more compact.
|
||||
* Updated container_card to only show exposed ports.
|
||||
|
||||
|
||||
## v0.60 (June 9th 2024) - Permissions system and import templates
|
||||
* Converted JS template literals into HTML.
|
||||
* Converted modals into HTML/HTMX.
|
||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -1,9 +1,7 @@
|
|||
FROM node:23-alpine
|
||||
FROM node:22-alpine
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /dweebui
|
||||
COPY package.json /dweebui
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN npm install
|
||||
RUN npm install pm2 -g
|
||||
COPY . /dweebui
|
||||
EXPOSE 8000
|
||||
CMD ["pm2-runtime", "server.js"]
|
||||
CMD node server.js
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023-2024 lllllllillllllillll
|
||||
Copyright (c) 2023 lllllllillllllillll
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
35
README.md
35
README.md
|
@ -1,5 +1,5 @@
|
|||
<h3 align="center"><img width="150" src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI/main/public/img/logo.png"></h3>
|
||||
<h4 align="center">DweebUI v0.70 ( :fire: Experimental :fire: )</h4>
|
||||
<h4 align="center">DweebUI Beta v0.60 ( :fire: Experimental :fire: )</h4>
|
||||
<h3 align="center">Free and Open-Source WebUI For Managing Your Containers.</h3>
|
||||
<p align="center">
|
||||
<a href=""><img src="https://img.shields.io/github/stars/lllllllillllllillll/DweebUI?style=flat"/></a>
|
||||
|
@ -15,20 +15,18 @@
|
|||
## Features
|
||||
|
||||
* [x] A dynamically updating dashboard that displays server metrics along with container metrics and container controls.
|
||||
* [x] Container actions: Start, Stop, Pause, Restart, View Details, View Logs.
|
||||
* [x] Multi-user support with permissions system.
|
||||
* [x] Support for multiple hosts.
|
||||
* [x] View and manage images, volumes, and networks.
|
||||
* [x] Container actions: Start, Stop, Pause, Restart, View Details, View Logs.
|
||||
* [x] Windows, Linux, and MacOS compatable.
|
||||
* [x] Light/Dark Mode.
|
||||
* [x] Mobile Friendly.
|
||||
* [x] Easy to install app templates (Compatible with Portainer).
|
||||
* [x] Docker Compose.
|
||||
* [ ] Available updates without image pull (in development).
|
||||
* [x] Manage your Docker networks, images, and volumes.
|
||||
* [x] Easy to install app templates.
|
||||
* [x] Docker Compose Support.
|
||||
* [ ] Update containers (planned).
|
||||
* [x] Templates.json maintains compatability with Portainer, allowing you to use the template without needing to use DweebUI.
|
||||
* [ ] Preset variables (planned).
|
||||
* [ ] Themes (planned).
|
||||
* [*] International language support (Languages still being updated).
|
||||
|
||||
## About
|
||||
|
||||
|
@ -39,13 +37,7 @@
|
|||
|
||||
## Setup
|
||||
|
||||
|
||||
### Docker Run:
|
||||
```
|
||||
docker run -d --name=DweebUI -p 8000:8000 -v dweebui:/app/database -v /var/run/docker.sock:/var/run/docker.sock lllllllillllllillll/dweebui:v0.7X-dev
|
||||
```
|
||||
|
||||
### Docker Compose:
|
||||
Docker Compose:
|
||||
```
|
||||
version: "3.9"
|
||||
services:
|
||||
|
@ -54,8 +46,7 @@ services:
|
|||
image: lllllllillllllillll/dweebui
|
||||
environment:
|
||||
PORT: 8000
|
||||
HTTPS: false
|
||||
NO_AUTH: false
|
||||
SECRET: MrWiskers
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
|
@ -76,15 +67,19 @@ networks:
|
|||
dweebui_net:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
[Windows and MacOS Setup](https://github.com/lllllllillllllillll/DweebUI/wiki/Setup)
|
||||
|
||||
Compose setup:
|
||||
|
||||
* Paste the above content into a file named ```docker-compose.yml``` then place it in a folder named ```dweebui```.
|
||||
* Open a terminal in the ```dweebui``` folder, then enter ```docker compose up -d```.
|
||||
* You may need to use ```docker-compose up -d``` or execute the command as root with either ```sudo docker compose up -d``` or ```sudo docker-compose up -d```.
|
||||
|
||||
[Windows and MacOS Setup](https://github.com/lllllllillllllillll/DweebUI/wiki/Setup)
|
||||
[Troubleshooting](https://github.com/lllllllillllllillll/DweebUI/wiki/Troubleshooting)
|
||||
Configuration:
|
||||
|
||||
* `PORT` - Specifies which port the service binds to on startup. Default is `8000`.
|
||||
* `SECRET` - A shared secret used by the registration page.
|
||||
|
||||
## Credits
|
||||
|
||||
|
@ -97,4 +92,4 @@ Compose setup:
|
|||
## Supporters
|
||||
|
||||
* MM (Patreon)
|
||||
* PD (Buymeacoffee)
|
||||
* PD (Buymeacoffee)
|
||||
|
|
1
appdata/compose/.gitignore
vendored
1
appdata/compose/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
!.gitignore
|
1
appdata/tmp/.gitignore
vendored
1
appdata/tmp/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
!.gitignore
|
|
@ -2,16 +2,15 @@ version: "3.9"
|
|||
services:
|
||||
dweebui:
|
||||
container_name: dweebui
|
||||
image: lllllllillllllillll/dweebui:v0.70-dev
|
||||
image: lllllllillllllillll/dweebui:v0.60
|
||||
environment:
|
||||
PORT: 8000
|
||||
HTTPS: false
|
||||
NO_AUTH: false
|
||||
SECRET: MrWiskers
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- dweebui:/app
|
||||
- dweebui:/app/config
|
||||
# Docker socket
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Podman socket
|
||||
|
@ -25,4 +24,4 @@ volumes:
|
|||
|
||||
networks:
|
||||
dweebui_net:
|
||||
driver: bridge
|
||||
driver: bridge
|
||||
|
|
|
@ -1,47 +1,20 @@
|
|||
import { User, ServerSettings } from '../db/config.js';
|
||||
import { Alert, getLanguage, Navbar, Sidebar, Footer } from '../utils/system.js';
|
||||
import { User } from "../database/models.js";
|
||||
|
||||
export const Account = async function(req,res){
|
||||
export const Account = async (req, res) => {
|
||||
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
let user = await User.findOne({ where: { UUID: req.session.UUID }});
|
||||
|
||||
let container_links = await ServerSettings.findOne({ where: {key: 'container_links'}});
|
||||
let user_registration = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||
|
||||
let user = await User.findOne({ where: {userID: req.session.userID}});
|
||||
|
||||
res.render("account",{
|
||||
alert: '',
|
||||
res.render("account", {
|
||||
first_name: user.name,
|
||||
last_name: user.name,
|
||||
name: user.name,
|
||||
username: req.session.username,
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
avatar: user.avatar,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
sidebar: await Sidebar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const searchAccount = async function (req, res) {
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
export const submitAccount = async function(req,res){
|
||||
|
||||
console.log(req.body);
|
||||
|
||||
res.render("account",{
|
||||
role: user.role,
|
||||
avatar: req.session.user.charAt(0).toUpperCase(),
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
sidebar: await Sidebar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,100 +1,86 @@
|
|||
import { Alert, getLanguage, Navbar, Footer, Capitalize } from '../utils/system.js';
|
||||
import { readFileSync, readdirSync, renameSync, mkdirSync, unlinkSync, existsSync } from 'fs';
|
||||
import { parse } from 'yaml';
|
||||
import multer from 'multer';
|
||||
import AdmZip from 'adm-zip';
|
||||
|
||||
const upload = multer({storage: multer.diskStorage({
|
||||
destination: function (req, file, cb) { cb(null, 'templates/tmp/') },
|
||||
filename: function (req, file, cb) { cb(null, file.originalname) },
|
||||
})});
|
||||
|
||||
let alert = '';
|
||||
let templates_global = '';
|
||||
let json_templates = '';
|
||||
let remove_button = '';
|
||||
|
||||
|
||||
|
||||
const upload = multer({storage: multer.diskStorage({
|
||||
destination: function (req, file, cb) { cb(null, 'appdata/tmp/') },
|
||||
filename: function (req, file, cb) { cb(null, file.originalname) },
|
||||
})});
|
||||
|
||||
|
||||
|
||||
export const searchApps = async function (req, res) {
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
export const Apps = async function(req,res){
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
export const Apps = async (req, res) => {
|
||||
|
||||
let [apps_list, app_count] = ['', ''];
|
||||
let page = Number(req.params.page) || 1;
|
||||
let template = req.params.template || 'default';
|
||||
let template_param = req.params.template || 'default';
|
||||
|
||||
if ((template_param != 'default') && (template_param != 'compose')) {
|
||||
remove_button = `<a href="/remove_template/${template_param}" class="btn" hx-confirm="Are you sure you want to remove this template?">Remove</a>`;
|
||||
} else {
|
||||
remove_button = '';
|
||||
}
|
||||
|
||||
// Pagination
|
||||
let list_start = (page - 1) * 40 + 1;
|
||||
let list_end = (page * 40);
|
||||
let last_page = '';
|
||||
|
||||
let pages =`<li class="page-item"><a class="page-link" href="/apps/1/${template}">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/2/${template}">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/3/${template}">3</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/4/${template}">4</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/5/${template}">5</a></li>`
|
||||
|
||||
let prev = '/apps/' + (page - 1) + '/' + template;
|
||||
let next = '/apps/' + (page + 1) + '/' + template;
|
||||
if (page == 1) { prev = '/apps/' + (page) + '/' + template; }
|
||||
if (page == last_page) { next = '/apps/' + (page) + '/' + template;}
|
||||
|
||||
// Add a remove button if it's not the default template or a compose template
|
||||
if ((template != 'default') && (template != 'compose')) {
|
||||
remove_button = `<a href="/remove_template/${template}" class="btn" hx-confirm="Are you sure you want to remove this template?">Remove</a>`;
|
||||
} else {
|
||||
remove_button = '';
|
||||
json_templates = '';
|
||||
let json_files = readdirSync('templates/json/');
|
||||
for (let i = 0; i < json_files.length; i++) {
|
||||
if (json_files[i] != 'default.json') {
|
||||
let filename = json_files[i].split('.')[0];
|
||||
let link = `<li><a class="dropdown-item" href="/apps/1/${filename}">${filename}</a></li>`
|
||||
json_templates += link;
|
||||
}
|
||||
}
|
||||
|
||||
let apps_list = '';
|
||||
let app_count = '';
|
||||
|
||||
let list_start = (page - 1) * 28;
|
||||
let list_end = (page * 28);
|
||||
let last_page = '';
|
||||
|
||||
let pages = `<li class="page-item"><a class="page-link" href="/apps/1/${template_param}">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/2/${template_param}">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/3/${template_param}">3</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/4/${template_param}">4</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/5/${template_param}">5</a></li>`
|
||||
|
||||
|
||||
let prev = '/apps/' + (page - 1) + '/' + template_param;
|
||||
let next = '/apps/' + (page + 1) + '/' + template_param;
|
||||
if (page == 1) { prev = '/apps/' + (page) + '/' + template_param; }
|
||||
if (page == last_page) { next = '/apps/' + (page) + '/' + template_param;}
|
||||
|
||||
|
||||
if (template_param == 'compose') {
|
||||
let compose_files = readdirSync('templates/compose/');
|
||||
|
||||
// Check for other templates and add them to the dropdown list
|
||||
json_templates = '';
|
||||
let json_files = readdirSync('appdata/templates/').filter(file => file.endsWith('.json'));
|
||||
for (let i = 0; i < json_files.length; i++) {
|
||||
if (json_files[i] != 'default.json') {
|
||||
let filename = json_files[i].split('.')[0];
|
||||
let link = `<li><a class="dropdown-item" href="/apps/1/${filename}">${filename}</a></li>`
|
||||
json_templates += link;
|
||||
}
|
||||
}
|
||||
app_count = compose_files.length;
|
||||
last_page = Math.ceil(compose_files.length/28);
|
||||
|
||||
// Display compose files if the template is set to 'compose'
|
||||
if (template == 'compose') {
|
||||
let compose_files = readdirSync('appdata/compose/');
|
||||
|
||||
app_count = compose_files.length;
|
||||
last_page = Math.ceil(compose_files.length/40);
|
||||
|
||||
compose_files.forEach(file => {
|
||||
if (file == '.gitignore') { return; }
|
||||
|
||||
let compose = readFileSync(`appdata/compose/${file}/compose.yaml`, 'utf8');
|
||||
let compose_data = parse(compose);
|
||||
let service_name = Object.keys(compose_data.services)
|
||||
let container = compose_data.services[service_name].container_name;
|
||||
let image = compose_data.services[service_name].image;
|
||||
|
||||
// let appCard = readFileSync('./views/partials/appCard.html', 'utf8');
|
||||
let appCard = readFileSync('views/partials/app_card.html', 'utf8');
|
||||
appCard = appCard.replace(/AppName/g, service_name);
|
||||
appCard = appCard.replace(/AppDescription/g, 'Compose File');
|
||||
appCard = appCard.replace(/AppIcon/g, `https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/${service_name}.png`);
|
||||
// appCard = appCard.replace(/AppCategories/g, '<span class="badge bg-orange-lt">Compose</span> ');
|
||||
appCard = appCard.replace(/AppType/g, 'compose');
|
||||
apps_list += appCard;
|
||||
});
|
||||
} else {
|
||||
compose_files.forEach(file => {
|
||||
if (file == '.gitignore') { return; }
|
||||
|
||||
let template_file = readFileSync(`appdata/templates/${template}.json`);
|
||||
let compose = readFileSync(`templates/compose/${file}/compose.yaml`, 'utf8');
|
||||
let compose_data = parse(compose);
|
||||
let service_name = Object.keys(compose_data.services)
|
||||
let container = compose_data.services[service_name].container_name;
|
||||
let image = compose_data.services[service_name].image;
|
||||
|
||||
let appCard = readFileSync('./views/partials/appCard.html', 'utf8');
|
||||
appCard = appCard.replace(/AppName/g, service_name);
|
||||
appCard = appCard.replace(/AppShortName/g, service_name);
|
||||
appCard = appCard.replace(/AppDesc/g, 'Compose File');
|
||||
appCard = appCard.replace(/AppLogo/g, `https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/${service_name}.png`);
|
||||
appCard = appCard.replace(/AppCategories/g, '<span class="badge bg-orange-lt">Compose</span> ');
|
||||
appCard = appCard.replace(/AppType/g, 'compose');
|
||||
apps_list += appCard;
|
||||
});
|
||||
} else {
|
||||
|
||||
let template_file = readFileSync(`./templates/json/${template_param}.json`);
|
||||
let templates = JSON.parse(template_file).templates;
|
||||
templates = templates.sort((a, b) => { if (a.name < b.name) { return -1; } });
|
||||
app_count = templates.length;
|
||||
|
@ -102,13 +88,11 @@ export const Apps = async function(req,res){
|
|||
templates_global = templates;
|
||||
|
||||
apps_list = '';
|
||||
for (let i = list_start; i < list_end && i < templates_global.length; i++) {
|
||||
let appCard = readFileSync('views/partials/app_card.html', 'utf8');
|
||||
for (let i = list_start; i < list_end && i < templates.length; i++) {
|
||||
let appCard = readFileSync('./views/partials/appCard.html', 'utf8');
|
||||
let name = templates[i].name || templates[i].title.toLowerCase();
|
||||
let title = templates[i].title || templates[i].name;
|
||||
// let desc = templates[i].description.slice(0, 75) + "...";
|
||||
let desc = templates[i].description || "no description available";
|
||||
|
||||
let desc = templates[i].description.slice(0, 60) + "...";
|
||||
let description = templates[i].description.replaceAll(". ", ".\n") || "no description available";
|
||||
let note = templates[i].note ? templates[i].note.replaceAll(". ", ".\n") : "no notes available";
|
||||
let image = templates[i].image;
|
||||
|
@ -125,41 +109,143 @@ export const Apps = async function(req,res){
|
|||
appCard = appCard.replace(/AppName/g, name);
|
||||
appCard = appCard.replace(/AppTitle/g, title);
|
||||
appCard = appCard.replace(/AppShortName/g, name);
|
||||
appCard = appCard.replace(/AppDescription/g, desc);
|
||||
appCard = appCard.replace(/AppIcon/g, logo);
|
||||
appCard = appCard.replace(/AppDesc/g, desc);
|
||||
appCard = appCard.replace(/AppLogo/g, logo);
|
||||
appCard = appCard.replace(/AppCategories/g, categories);
|
||||
appCard = appCard.replace(/AppType/g, 'json');
|
||||
apps_list += appCard;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
app_count = `${list_start} - ${list_end} of ${templates_global.length} Apps`;
|
||||
|
||||
res.render("apps",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
app_count: app_count,
|
||||
remove_button: '',
|
||||
json_templates: '',
|
||||
apps_list: apps_list,
|
||||
prev: prev,
|
||||
next: next,
|
||||
pages: pages,
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const submitApps = async function (req, res) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
res.render("apps", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.user.charAt(0).toUpperCase(),
|
||||
list_start: list_start + 1,
|
||||
list_end: list_end,
|
||||
app_count: app_count,
|
||||
prev: prev,
|
||||
next: next,
|
||||
apps_list: apps_list,
|
||||
alert: alert,
|
||||
template_list: '',
|
||||
json_templates: json_templates,
|
||||
pages: pages,
|
||||
remove_button: remove_button
|
||||
});
|
||||
alert = '';
|
||||
}
|
||||
|
||||
export const removeTemplate = async (req, res) => {
|
||||
let template = req.params.template;
|
||||
unlinkSync(`templates/json/${template}.json`);
|
||||
res.redirect('/apps');
|
||||
}
|
||||
|
||||
|
||||
export const appSearch = async (req, res) => {
|
||||
|
||||
let search = req.body.search;
|
||||
|
||||
let page = Number(req.params.page) || 1;
|
||||
|
||||
let template_param = req.params.template || 'default';
|
||||
|
||||
let template_file = readFileSync(`./templates/json/${template_param}.json`);
|
||||
|
||||
let templates = JSON.parse(template_file).templates;
|
||||
|
||||
templates = templates.sort((a, b) => {
|
||||
if (a.name < b.name) { return -1; }
|
||||
});
|
||||
|
||||
let pages = `<li class="page-item"><a class="page-link" href="/apps/1/${template_param}">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/2/${template_param}">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/3/${template_param}">3</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/4/${template_param}">4</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/apps/5/${template_param}">5</a></li>`
|
||||
|
||||
|
||||
let list_start = (page-1)*28;
|
||||
let list_end = (page*28);
|
||||
let last_page = Math.ceil(templates.length/28);
|
||||
let prev = '/apps/' + (page-1);
|
||||
let next = '/apps/' + (page+1);
|
||||
if (page == 1) { prev = '/apps/' + (page); }
|
||||
if (page == last_page) { next = '/apps/' + (page); }
|
||||
|
||||
|
||||
let apps_list = '';
|
||||
let results = [];
|
||||
let [cat_1, cat_2, cat_3] = ['','',''];
|
||||
|
||||
function searchTemplates(terms) {
|
||||
terms = terms.toLowerCase();
|
||||
for (let i = 0; i < templates.length; i++) {
|
||||
if (templates[i].categories) {
|
||||
if (templates[i].categories[0]) {
|
||||
cat_1 = (templates[i].categories[0]).toLowerCase();
|
||||
}
|
||||
if (templates[i].categories[1]) {
|
||||
cat_2 = (templates[i].categories[1]).toLowerCase();
|
||||
}
|
||||
if (templates[i].categories[2]) {
|
||||
cat_3 = (templates[i].categories[2]).toLowerCase();
|
||||
}
|
||||
}
|
||||
if ((templates[i].description.includes(terms)) || (templates[i].name.includes(terms)) || (templates[i].title.includes(terms)) || (cat_1.includes(terms)) || (cat_2.includes(terms)) || (cat_3.includes(terms))){
|
||||
results.push(templates[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
searchTemplates(search);
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let appCard = readFileSync('./views/partials/appCard.html', 'utf8');
|
||||
let name = results[i].name || results[i].title.toLowerCase();
|
||||
let desc = results[i].description.slice(0, 60) + "...";
|
||||
let description = results[i].description.replaceAll(". ", ".\n") || "no description available";
|
||||
let note = results[i].note ? results[i].note.replaceAll(". ", ".\n") : "no notes available";
|
||||
let image = results[i].image;
|
||||
let logo = results[i].logo;let categories = '';
|
||||
// set data.catagories to 'other' if data.catagories is empty or undefined
|
||||
if (results[i].categories == null || results[i].categories == undefined || results[i].categories == '') {
|
||||
results[i].categories = ['Other'];
|
||||
}
|
||||
// loop through the categories and add the badge to the card
|
||||
for (let j = 0; j < results[i].categories.length; j++) {
|
||||
categories += CatagoryColor(results[i].categories[j]);
|
||||
}
|
||||
appCard = appCard.replace(/AppName/g, name);
|
||||
appCard = appCard.replace(/AppShortName/g, name);
|
||||
appCard = appCard.replace(/AppDesc/g, desc);
|
||||
appCard = appCard.replace(/AppLogo/g, logo);
|
||||
appCard = appCard.replace(/AppCategories/g, categories);
|
||||
appCard = appCard.replace(/AppType/g, 'json');
|
||||
|
||||
apps_list += appCard;
|
||||
}
|
||||
res.render("apps", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.user.charAt(0).toUpperCase(),
|
||||
list_start: list_start + 1,
|
||||
list_end: list_end,
|
||||
app_count: results.length,
|
||||
prev: prev,
|
||||
next: next,
|
||||
apps_list: apps_list,
|
||||
alert: alert,
|
||||
template_list: '',
|
||||
json_templates: json_templates,
|
||||
pages: pages,
|
||||
remove_button: remove_button
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function CatagoryColor(category) {
|
||||
|
@ -207,231 +293,295 @@ function CatagoryColor(category) {
|
|||
}
|
||||
}
|
||||
|
||||
export const InstallModal = async (req, res) => {
|
||||
let input = req.header('hx-trigger-name');
|
||||
let type = req.header('hx-trigger');
|
||||
|
||||
|
||||
export const appsModals = async function (req, res) {
|
||||
let app_name = req.header('hx-trigger-name');
|
||||
let app_type = req.header('hx-trigger');
|
||||
let modal = req.params.modal;
|
||||
|
||||
// console.log(`[submitApps] app_name: ${app_name} app_type: ${app_type} modal: ${modal}`);
|
||||
|
||||
// Modal for compose files
|
||||
if (modal == 'view_install' && app_type == 'compose') {
|
||||
let compose = readFileSync(`appdata/compose/${app_name}/compose.yaml`, 'utf8');
|
||||
let modal = readFileSync('views/partials/compose.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, app_name);
|
||||
modal = modal.replace(/COMPOSE_CONTENT/g, compose);
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
// More Info modal
|
||||
if (modal == 'info' && app_type == 'json') {
|
||||
|
||||
let modal = readFileSync('views/partials/info.html', 'utf8');
|
||||
|
||||
let app_title = Capitalize(app_name);
|
||||
modal = modal.replace(/AppTitle/g, app_title);
|
||||
|
||||
let result = templates_global.find(t => t.name == app_name);
|
||||
|
||||
modal = modal.replace(/AppDescription/g, result.description);
|
||||
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Modal for json templates
|
||||
if (modal == 'view_install' && app_type == 'json') {
|
||||
|
||||
|
||||
let result = templates_global.find(t => t.name == app_name);
|
||||
let name = result.name || result.title.toLowerCase();
|
||||
let short_name = name.slice(0, 25) + "...";
|
||||
let desc = result.description.replaceAll(". ", ".\n") || "no description available";
|
||||
let short_desc = desc.slice(0, 60) + "...";
|
||||
let modal_name = name.replaceAll(" ", "-");
|
||||
let form_id = name.replaceAll("-", "_");
|
||||
let note = result.note ? result.note.replaceAll(". ", ".\n") : "no notes available";
|
||||
let command = result.command ? result.command : "";
|
||||
let command_check = command ? "checked" : "";
|
||||
let privileged = result.privileged || "";
|
||||
let privileged_check = privileged ? "checked" : "";
|
||||
let repository = result.repository || "";
|
||||
let image = result.image || "";
|
||||
let net_host, net_bridge, net_docker = '';
|
||||
let net_name = 'AppBridge';
|
||||
let restart_policy = result.restart_policy || 'unless-stopped';
|
||||
|
||||
switch (result.network) {
|
||||
case 'host':
|
||||
net_host = 'checked';
|
||||
break;
|
||||
case 'bridge':
|
||||
net_bridge = 'checked';
|
||||
net_name = result.network;
|
||||
break;
|
||||
default:
|
||||
net_docker = 'checked';
|
||||
}
|
||||
|
||||
if (repository != "") {
|
||||
image = (`${repository.url}/raw/master/${repository.stackfile}`);
|
||||
}
|
||||
|
||||
let [ports_data, volumes_data, env_data, label_data] = [[], [], [], []];
|
||||
|
||||
|
||||
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
|
||||
// Get port details
|
||||
try {
|
||||
let ports = result.ports[i];
|
||||
let port_check = ports ? "checked" : "";
|
||||
let port_external = ports.split(":")[0] ? ports.split(":")[0] : ports.split("/")[0];
|
||||
let port_internal = ports.split(":")[1] ? ports.split(":")[1].split("/")[0] : ports.split("/")[0];
|
||||
let port_protocol = ports.split("/")[1] ? ports.split("/")[1] : "";
|
||||
|
||||
// remove /tcp or /udp from port_external if it exists
|
||||
if (port_external.includes("/")) {
|
||||
port_external = port_external.split("/")[0];
|
||||
}
|
||||
|
||||
ports_data.push({
|
||||
check: port_check,
|
||||
external: port_external,
|
||||
internal: port_internal,
|
||||
protocol: port_protocol
|
||||
});
|
||||
} catch {
|
||||
ports_data.push({
|
||||
check: "",
|
||||
external: "",
|
||||
internal: "",
|
||||
protocol: ""
|
||||
});
|
||||
}
|
||||
|
||||
// Get volume details
|
||||
try {
|
||||
let volumes = result.volumes[i];
|
||||
let volume_check = volumes ? "checked" : "";
|
||||
let volume_bind = volumes.bind ? volumes.bind : "";
|
||||
let volume_container = volumes.container ? volumes.container.split(":")[0] : "";
|
||||
let volume_readwrite = "rw";
|
||||
|
||||
if (volumes.readonly == true) {
|
||||
volume_readwrite = "ro";
|
||||
}
|
||||
|
||||
volumes_data.push({
|
||||
check: volume_check,
|
||||
bind: volume_bind,
|
||||
container: volume_container,
|
||||
readwrite: volume_readwrite
|
||||
});
|
||||
} catch {
|
||||
volumes_data.push({
|
||||
check: "",
|
||||
bind: "",
|
||||
container: "",
|
||||
readwrite: ""
|
||||
});
|
||||
}
|
||||
|
||||
// Get environment details
|
||||
try {
|
||||
let env = result.env[i];
|
||||
let env_check = "";
|
||||
let env_default = env.default ? env.default : "";
|
||||
if (env.set) { env_default = env.set;}
|
||||
let env_description = env.description ? env.description : "";
|
||||
let env_label = env.label ? env.label : "";
|
||||
let env_name = env.name ? env.name : "";
|
||||
|
||||
env_data.push({
|
||||
check: env_check,
|
||||
default: env_default,
|
||||
description: env_description,
|
||||
label: env_label,
|
||||
name: env_name
|
||||
});
|
||||
} catch {
|
||||
env_data.push({
|
||||
check: "",
|
||||
default: "",
|
||||
description: "",
|
||||
label: "",
|
||||
name: ""
|
||||
});
|
||||
}
|
||||
|
||||
// Get label details
|
||||
try {
|
||||
let label = result.labels[i];
|
||||
let label_check = "";
|
||||
let label_name = label.name ? label.name : "";
|
||||
let label_value = label.value ? label.value : "";
|
||||
|
||||
label_data.push({
|
||||
check: label_check,
|
||||
name: label_name,
|
||||
value: label_value
|
||||
});
|
||||
} catch {
|
||||
label_data.push({
|
||||
check: "",
|
||||
name: "",
|
||||
value: ""
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let modal = readFileSync('views/partials/install.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, name);
|
||||
modal = modal.replace(/AppNote/g, note);
|
||||
modal = modal.replace(/AppImage/g, image);
|
||||
modal = modal.replace(/RestartPolicy/g, restart_policy);
|
||||
modal = modal.replace(/NetHost/g, net_host);
|
||||
modal = modal.replace(/NetBridge/g, net_bridge);
|
||||
modal = modal.replace(/NetDocker/g, net_docker);
|
||||
modal = modal.replace(/NetName/g, net_name);
|
||||
modal = modal.replace(/ModalName/g, modal_name);
|
||||
modal = modal.replace(/FormId/g, form_id);
|
||||
modal = modal.replace(/CommandCheck/g, command_check);
|
||||
modal = modal.replace(/CommandValue/g, command);
|
||||
modal = modal.replace(/PrivilegedCheck/g, privileged_check);
|
||||
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
modal = modal.replaceAll(`Port${i}Check`, ports_data[i].check);
|
||||
modal = modal.replaceAll(`Port${i}External`, ports_data[i].external);
|
||||
modal = modal.replaceAll(`Port${i}Internal`, ports_data[i].internal);
|
||||
modal = modal.replaceAll(`Port${i}Protocol`, ports_data[i].protocol);
|
||||
|
||||
modal = modal.replaceAll(`Vol${i}Check`, volumes_data[i].check);
|
||||
modal = modal.replaceAll(`Vol${i}Source`, volumes_data[i].bind);
|
||||
modal = modal.replaceAll(`Vol${i}Destination`, volumes_data[i].container);
|
||||
modal = modal.replaceAll(`Vol${i}RW`, volumes_data[i].readwrite);
|
||||
|
||||
modal = modal.replaceAll(`Env${i}Check`, env_data[i].check);
|
||||
modal = modal.replaceAll(`Env${i}Key`, env_data[i].name);
|
||||
modal = modal.replaceAll(`Env${i}Value`, env_data[i].default);
|
||||
|
||||
modal = modal.replaceAll(`Env${i}Description`, env_data[i].description);
|
||||
modal = modal.replaceAll(`Env${i}Label`, env_data[i].label);
|
||||
|
||||
modal = modal.replaceAll(`Label${i}Check`, label_data[i].check);
|
||||
modal = modal.replaceAll(`Label${i}Key`, label_data[i].name);
|
||||
modal = modal.replaceAll(`Label${i}Value`, label_data[i].value);
|
||||
}
|
||||
|
||||
if (type == 'compose') {
|
||||
let compose = readFileSync(`templates/compose/${input}/compose.yaml`, 'utf8');
|
||||
let modal = readFileSync('./views/modals/compose.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, input);
|
||||
modal = modal.replace(/COMPOSE_CONTENT/g, compose);
|
||||
res.send(modal);
|
||||
return;
|
||||
} else {
|
||||
let result = templates_global.find(t => t.name == input);
|
||||
let name = result.name || result.title.toLowerCase();
|
||||
let short_name = name.slice(0, 25) + "...";
|
||||
let desc = result.description.replaceAll(". ", ".\n") || "no description available";
|
||||
let short_desc = desc.slice(0, 60) + "...";
|
||||
let modal_name = name.replaceAll(" ", "-");
|
||||
let form_id = name.replaceAll("-", "_");
|
||||
let note = result.note ? result.note.replaceAll(". ", ".\n") : "no notes available";
|
||||
let command = result.command ? result.command : "";
|
||||
let command_check = command ? "checked" : "";
|
||||
let privileged = result.privileged || "";
|
||||
let privileged_check = privileged ? "checked" : "";
|
||||
let repository = result.repository || "";
|
||||
let image = result.image || "";
|
||||
let net_host, net_bridge, net_docker = '';
|
||||
let net_name = 'AppBridge';
|
||||
let restart_policy = result.restart_policy || 'unless-stopped';
|
||||
|
||||
switch (result.network) {
|
||||
case 'host':
|
||||
net_host = 'checked';
|
||||
break;
|
||||
case 'bridge':
|
||||
net_bridge = 'checked';
|
||||
net_name = result.network;
|
||||
break;
|
||||
default:
|
||||
net_docker = 'checked';
|
||||
}
|
||||
|
||||
if (repository != "") {
|
||||
image = (`${repository.url}/raw/master/${repository.stackfile}`);
|
||||
}
|
||||
|
||||
let [ports_data, volumes_data, env_data, label_data] = [[], [], [], []];
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
|
||||
// Get port details
|
||||
try {
|
||||
let ports = result.ports[i];
|
||||
let port_check = ports ? "checked" : "";
|
||||
let port_external = ports.split(":")[0] ? ports.split(":")[0] : ports.split("/")[0];
|
||||
let port_internal = ports.split(":")[1] ? ports.split(":")[1].split("/")[0] : ports.split("/")[0];
|
||||
let port_protocol = ports.split("/")[1] ? ports.split("/")[1] : "";
|
||||
|
||||
// remove /tcp or /udp from port_external if it exists
|
||||
if (port_external.includes("/")) {
|
||||
port_external = port_external.split("/")[0];
|
||||
}
|
||||
|
||||
ports_data.push({
|
||||
check: port_check,
|
||||
external: port_external,
|
||||
internal: port_internal,
|
||||
protocol: port_protocol
|
||||
});
|
||||
} catch {
|
||||
ports_data.push({
|
||||
check: "",
|
||||
external: "",
|
||||
internal: "",
|
||||
protocol: ""
|
||||
});
|
||||
}
|
||||
|
||||
// Get volume details
|
||||
try {
|
||||
let volumes = result.volumes[i];
|
||||
let volume_check = volumes ? "checked" : "";
|
||||
let volume_bind = volumes.bind ? volumes.bind : "";
|
||||
let volume_container = volumes.container ? volumes.container.split(":")[0] : "";
|
||||
let volume_readwrite = "rw";
|
||||
|
||||
if (volumes.readonly == true) {
|
||||
volume_readwrite = "ro";
|
||||
}
|
||||
|
||||
volumes_data.push({
|
||||
check: volume_check,
|
||||
bind: volume_bind,
|
||||
container: volume_container,
|
||||
readwrite: volume_readwrite
|
||||
});
|
||||
} catch {
|
||||
volumes_data.push({
|
||||
check: "",
|
||||
bind: "",
|
||||
container: "",
|
||||
readwrite: ""
|
||||
});
|
||||
}
|
||||
|
||||
// Get environment details
|
||||
try {
|
||||
let env = result.env[i];
|
||||
let env_check = "";
|
||||
let env_default = env.default ? env.default : "";
|
||||
if (env.set) { env_default = env.set;}
|
||||
let env_description = env.description ? env.description : "";
|
||||
let env_label = env.label ? env.label : "";
|
||||
let env_name = env.name ? env.name : "";
|
||||
|
||||
env_data.push({
|
||||
check: env_check,
|
||||
default: env_default,
|
||||
description: env_description,
|
||||
label: env_label,
|
||||
name: env_name
|
||||
});
|
||||
} catch {
|
||||
env_data.push({
|
||||
check: "",
|
||||
default: "",
|
||||
description: "",
|
||||
label: "",
|
||||
name: ""
|
||||
});
|
||||
}
|
||||
|
||||
// Get label details
|
||||
try {
|
||||
let label = result.labels[i];
|
||||
let label_check = "";
|
||||
let label_name = label.name ? label.name : "";
|
||||
let label_value = label.value ? label.value : "";
|
||||
|
||||
label_data.push({
|
||||
check: label_check,
|
||||
name: label_name,
|
||||
value: label_value
|
||||
});
|
||||
} catch {
|
||||
label_data.push({
|
||||
check: "",
|
||||
name: "",
|
||||
value: ""
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let modal = readFileSync('./views/modals/json.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, name);
|
||||
modal = modal.replace(/AppNote/g, note);
|
||||
modal = modal.replace(/AppImage/g, image);
|
||||
modal = modal.replace(/RestartPolicy/g, restart_policy);
|
||||
modal = modal.replace(/NetHost/g, net_host);
|
||||
modal = modal.replace(/NetBridge/g, net_bridge);
|
||||
modal = modal.replace(/NetDocker/g, net_docker);
|
||||
modal = modal.replace(/NetName/g, net_name);
|
||||
modal = modal.replace(/ModalName/g, modal_name);
|
||||
modal = modal.replace(/FormId/g, form_id);
|
||||
modal = modal.replace(/CommandCheck/g, command_check);
|
||||
modal = modal.replace(/CommandValue/g, command);
|
||||
modal = modal.replace(/PrivilegedCheck/g, privileged_check);
|
||||
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
modal = modal.replaceAll(`Port${i}Check`, ports_data[i].check);
|
||||
modal = modal.replaceAll(`Port${i}External`, ports_data[i].external);
|
||||
modal = modal.replaceAll(`Port${i}Internal`, ports_data[i].internal);
|
||||
modal = modal.replaceAll(`Port${i}Protocol`, ports_data[i].protocol);
|
||||
|
||||
modal = modal.replaceAll(`Volume${i}Check`, volumes_data[i].check);
|
||||
modal = modal.replaceAll(`Volume${i}Bind`, volumes_data[i].bind);
|
||||
modal = modal.replaceAll(`Volume${i}Container`, volumes_data[i].container);
|
||||
modal = modal.replaceAll(`Volume${i}RW`, volumes_data[i].readwrite);
|
||||
|
||||
modal = modal.replaceAll(`Env${i}Check`, env_data[i].check);
|
||||
modal = modal.replaceAll(`Env${i}Default`, env_data[i].default);
|
||||
modal = modal.replaceAll(`Env${i}Description`, env_data[i].description);
|
||||
modal = modal.replaceAll(`Env${i}Label`, env_data[i].label);
|
||||
modal = modal.replaceAll(`Env${i}Name`, env_data[i].name);
|
||||
|
||||
modal = modal.replaceAll(`Label${i}Check`, label_data[i].check);
|
||||
modal = modal.replaceAll(`Label${i}Name`, label_data[i].name);
|
||||
modal = modal.replaceAll(`Label${i}Value`, label_data[i].value);
|
||||
}
|
||||
res.send(modal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const LearnMore = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let id = req.header('hx-trigger');
|
||||
|
||||
if (id == 'compose') {
|
||||
let modal = readFileSync('./views/modals/learnmore.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, name);
|
||||
modal = modal.replace(/AppDesc/g, 'Compose File');
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
let result = templates_global.find(t => t.name == name);
|
||||
|
||||
let modal = readFileSync('./views/modals/learnmore.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, result.title);
|
||||
modal = modal.replace(/AppDesc/g, result.description);
|
||||
|
||||
res.send(modal);
|
||||
}
|
||||
|
||||
|
||||
export const ImportModal = async (req, res) => {
|
||||
let modal = readFileSync('./views/modals/import.html', 'utf8');
|
||||
res.send(modal);
|
||||
}
|
||||
|
||||
|
||||
export const Upload = (req, res) => {
|
||||
upload.array('files', 10)(req, res, () => {
|
||||
|
||||
alert = `<div class="alert alert-success alert-dismissible mb-0 py-2" role="alert">
|
||||
<div class="d-flex">
|
||||
<div><svg xmlns="http://www.w3.org/2000/svg" class="icon alert-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><path d="M5 12l5 5l10 -10"></path></svg> </div>
|
||||
<div>Template(s) Uploaded!</div>
|
||||
</div>
|
||||
<a class="btn-close" data-bs-dismiss="alert" aria-label="close" style="padding-top: 0.5rem;"></a>
|
||||
</div>`;
|
||||
|
||||
|
||||
let exists_alert = `<div class="alert alert-danger alert-dismissible mb-0 py-2" role="alert">
|
||||
<div class="d-flex">
|
||||
<div><svg xmlns="http://www.w3.org/2000/svg" class="icon alert-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><path d="M5 12l5 5l10 -10"></path></svg> </div>
|
||||
<div>Template already exists</div>
|
||||
</div>
|
||||
<a class="btn-close" data-bs-dismiss="alert" aria-label="close" style="padding-top: 0.5rem;"></a>
|
||||
</div>`;
|
||||
|
||||
let files = readdirSync('templates/tmp/');
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
|
||||
if (files[i].endsWith('.zip')) {
|
||||
let zip = new AdmZip(`templates/tmp/${files[i]}`);
|
||||
zip.extractAllTo('templates/compose', true);
|
||||
unlinkSync(`templates/tmp/${files[i]}`);
|
||||
} else if (files[i].endsWith('.json')) {
|
||||
if (existsSync(`templates/json/${files[i]}`)) {
|
||||
unlinkSync(`templates/tmp/${files[i]}`);
|
||||
alert = exists_alert;
|
||||
res.redirect('/apps');
|
||||
return;
|
||||
}
|
||||
renameSync(`templates/tmp/${files[i]}`, `templates/json/${files[i]}`);
|
||||
} else if (files[i].endsWith('.yml')) {
|
||||
let compose = readFileSync(`templates/tmp/${files[i]}`, 'utf8');
|
||||
let compose_data = parse(compose);
|
||||
let service_name = Object.keys(compose_data.services);
|
||||
if (existsSync(`templates/compose/${service_name}`)) {
|
||||
unlinkSync(`templates/tmp/${files[i]}`);
|
||||
alert = exists_alert;
|
||||
res.redirect('/apps');
|
||||
return;
|
||||
}
|
||||
mkdirSync(`templates/compose/${service_name}`);
|
||||
renameSync(`templates/tmp/${files[i]}`, `templates/compose/${service_name}/compose.yaml`);
|
||||
} else if (files[i].endsWith('.yaml')) {
|
||||
let compose = readFileSync(`templates/tmp/${files[i]}`, 'utf8');
|
||||
let compose_data = parse(compose);
|
||||
let service_name = Object.keys(compose_data.services);
|
||||
if (existsSync(`templates/compose/${service_name}`)) {
|
||||
unlinkSync(`templates/tmp/${files[i]}`);
|
||||
alert = exists_alert;
|
||||
res.redirect('/apps');
|
||||
return;
|
||||
}
|
||||
mkdirSync(`templates/compose/${service_name}`);
|
||||
renameSync(`templates/tmp/${files[i]}`, `templates/compose/${service_name}/compose.yaml`);
|
||||
} else {
|
||||
// unsupported file type
|
||||
unlinkSync(`templates/tmp/${files[i]}`);
|
||||
}
|
||||
}
|
||||
res.redirect('/apps');
|
||||
});
|
||||
};
|
|
@ -1,35 +0,0 @@
|
|||
import { ServerSettings, User } from '../db/config.js';
|
||||
import { Alert, getLanguage, Navbar, Sidebar, Footer, Capitalize } from '../utils/system.js';
|
||||
import { readdirSync, readFileSync } from 'fs';
|
||||
|
||||
export const Credits = async function (req, res) {
|
||||
|
||||
let language = await getLanguage(req.session.userID);
|
||||
let Language = Capitalize(language);
|
||||
let selected = `<option value="${language}" selected hidden>${Language}</option>`;
|
||||
|
||||
let user = await User.findOne({ where: { userID: req.session.userID }});
|
||||
let preferences = JSON.parse(user.preferences);
|
||||
let hide_profile = preferences.hide_profile;
|
||||
|
||||
let checked = ''; if (hide_profile == true) { checked = 'checked'; }
|
||||
|
||||
res.render("credits",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
sidebar: await Sidebar(req),
|
||||
footer: await Footer(req),
|
||||
selected: selected,
|
||||
hide_profile: checked,
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const searchCredits = async function (req, res) {
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
|
@ -1,48 +1,360 @@
|
|||
import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
|
||||
import { docker, containerInfo, containerLogs, GetContainerLists, containerStats, trigger_docker_event } from '../utils/docker.js';
|
||||
import { Readable } from 'stream';
|
||||
import { Permission, User } from '../database/models.js';
|
||||
import { docker } from '../server.js';
|
||||
import { dockerContainerStats } from 'systeminformation';
|
||||
import { readFileSync } from 'fs';
|
||||
import { User, Permission, ServerSettings, ContainerLists, Container } from '../db/config.js';
|
||||
import { Alert, Navbar, Footer, Capitalize } from '../utils/system.js';
|
||||
import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
let hidden = '';
|
||||
let alert = '';
|
||||
let [ cardList, newCards, stats ] = [ '', '', {}];
|
||||
let [ports_data, volumes_data, env_data, label_data] = [[], [], [], []];
|
||||
|
||||
// Dashboard
|
||||
export const Dashboard = async function (req, res) {
|
||||
// The page
|
||||
export const Dashboard = (req, res) => {
|
||||
|
||||
console.log(`[Dashboard] ${req.session.username}`);
|
||||
|
||||
let username = req.session.username;
|
||||
let userID = req.session.userID;
|
||||
let name = req.session.user;
|
||||
let role = req.session.role;
|
||||
let host = req.session.host;
|
||||
alert = req.session.alert;
|
||||
|
||||
// Create the lists needed for the dashboard
|
||||
const [list, created] = await ContainerLists.findOrCreate({
|
||||
where: { userID: userID },
|
||||
defaults: { userID: userID, username: username, containers: '[]', new: '[]', updates: '[]', sent: '[]', },
|
||||
});
|
||||
|
||||
|
||||
res.render("dashboard",{
|
||||
alert: '',
|
||||
username: username,
|
||||
res.render("dashboard", {
|
||||
name: name,
|
||||
avatar: name.charAt(0).toUpperCase(),
|
||||
role: role,
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
alert: alert,
|
||||
});
|
||||
}
|
||||
|
||||
// The page actions
|
||||
export const DashboardAction = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let value = req.header('hx-trigger');
|
||||
let action = req.params.action;
|
||||
let modal = '';
|
||||
|
||||
// Dashboard search
|
||||
export const searchDashboard = async function (req, res) {
|
||||
// console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
switch (action) {
|
||||
case 'permissions':
|
||||
let title = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
let permissions_list = '';
|
||||
let permissions_modal = readFileSync('./views/modals/permissions.html', 'utf8');
|
||||
permissions_modal = permissions_modal.replace(/PermissionsTitle/g, title);
|
||||
permissions_modal = permissions_modal.replace(/PermissionsContainer/g, name);
|
||||
let users = await User.findAll({ attributes: ['username', 'UUID']});
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
let user_permissions = readFileSync('./views/partials/user_permissions.html', 'utf8');
|
||||
let exists = await Permission.findOne({ where: {containerName: name, user: users[i].username}});
|
||||
if (!exists) { const newPermission = await Permission.create({ containerName: name, user: users[i].username, userID: users[i].UUID}); }
|
||||
let permissions = await Permission.findOne({ where: {containerName: name, user: users[i].username}});
|
||||
if (permissions.uninstall == true) { user_permissions = user_permissions.replace(/data-UninstallCheck/g, 'checked'); }
|
||||
if (permissions.edit == true) { user_permissions = user_permissions.replace(/data-EditCheck/g, 'checked'); }
|
||||
if (permissions.upgrade == true) { user_permissions = user_permissions.replace(/data-UpgradeCheck/g, 'checked'); }
|
||||
if (permissions.start == true) { user_permissions = user_permissions.replace(/data-StartCheck/g, 'checked'); }
|
||||
if (permissions.stop == true) { user_permissions = user_permissions.replace(/data-StopCheck/g, 'checked'); }
|
||||
if (permissions.pause == true) { user_permissions = user_permissions.replace(/data-PauseCheck/g, 'checked'); }
|
||||
if (permissions.restart == true) { user_permissions = user_permissions.replace(/data-RestartCheck/g, 'checked'); }
|
||||
if (permissions.logs == true) { user_permissions = user_permissions.replace(/data-LogsCheck/g, 'checked'); }
|
||||
if (permissions.view == true) { user_permissions = user_permissions.replace(/data-ViewCheck/g, 'checked'); }
|
||||
user_permissions = user_permissions.replace(/EntryNumber/g, i);
|
||||
user_permissions = user_permissions.replace(/EntryNumber/g, i);
|
||||
user_permissions = user_permissions.replace(/EntryNumber/g, i);
|
||||
user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
|
||||
user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
|
||||
user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
|
||||
user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
|
||||
user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
|
||||
user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
|
||||
permissions_list += user_permissions;
|
||||
}
|
||||
permissions_modal = permissions_modal.replace(/PermissionsList/g, permissions_list);
|
||||
res.send(permissions_modal);
|
||||
return;
|
||||
case 'uninstall':
|
||||
modal = readFileSync('./views/modals/uninstall.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, name);
|
||||
res.send(modal);
|
||||
return;
|
||||
case 'details':
|
||||
modal = readFileSync('./views/modals/details.html', 'utf8');
|
||||
let details = await containerInfo(name);
|
||||
|
||||
modal = modal.replace(/AppName/g, details.name);
|
||||
modal = modal.replace(/AppImage/g, details.image);
|
||||
|
||||
for (let i = 0; i <= 6; i++) {
|
||||
modal = modal.replaceAll(`Port${i}Check`, details.ports[i]?.check || '');
|
||||
modal = modal.replaceAll(`Port${i}External`, details.ports[i]?.external || '');
|
||||
modal = modal.replaceAll(`Port${i}Internal`, details.ports[i]?.internal || '');
|
||||
modal = modal.replaceAll(`Port${i}Protocol`, details.ports[i]?.protocol || '');
|
||||
}
|
||||
|
||||
for (let i = 0; i <= 6; i++) {
|
||||
modal = modal.replaceAll(`Vol${i}Source`, details.volumes[i]?.Source || '');
|
||||
modal = modal.replaceAll(`Vol${i}Destination`, details.volumes[i]?.Destination || '');
|
||||
modal = modal.replaceAll(`Vol${i}RW`, details.volumes[i]?.RW || '');
|
||||
}
|
||||
|
||||
|
||||
for (let i = 0; i <= 19; i++) {
|
||||
modal = modal.replaceAll(`Label${i}Key`, Object.keys(details.labels)[i] || '');
|
||||
modal = modal.replaceAll(`Label${i}Value`, Object.values(details.labels)[i] || '');
|
||||
}
|
||||
|
||||
// console.log(details.env);
|
||||
for (let i = 0; i <= 19; i++) {
|
||||
modal = modal.replaceAll(`Env${i}Key`, details.env[i]?.split('=')[0] || '');
|
||||
modal = modal.replaceAll(`Env${i}Value`, details.env[i]?.split('=')[1] || '');
|
||||
}
|
||||
|
||||
res.send(modal);
|
||||
return;
|
||||
case 'updates':
|
||||
res.send(newCards);
|
||||
newCards = '';
|
||||
return;
|
||||
case 'card':
|
||||
await userCards(req.session);
|
||||
if (!req.session.container_list.find(c => c.container === name)) {
|
||||
res.send('');
|
||||
return;
|
||||
} else {
|
||||
let details = await containerInfo(name);
|
||||
let card = await createCard(details);
|
||||
res.send(card);
|
||||
return;
|
||||
}
|
||||
case 'logs':
|
||||
let logString = '';
|
||||
let options = { follow: true, stdout: true, stderr: false, timestamps: false };
|
||||
docker.getContainer(name).logs(options, function (err, stream) {
|
||||
if (err) { console.log(err); return; }
|
||||
const readableStream = Readable.from(stream);
|
||||
readableStream.on('data', function (chunk) {
|
||||
logString += chunk.toString('utf8');
|
||||
});
|
||||
readableStream.on('end', function () {
|
||||
res.send(`<pre>${logString}</pre>`);
|
||||
});
|
||||
});
|
||||
return;
|
||||
case 'hide':
|
||||
let user = req.session.user;
|
||||
let exists = await Permission.findOne({ where: {containerName: name, user: user}});
|
||||
if (!exists) { const newPermission = await Permission.create({ containerName: name, user: user, hide: true, userID: req.session.UUID}); }
|
||||
else { exists.update({ hide: true }); }
|
||||
hidden = await Permission.findAll({ where: {user: user, hide: true}}, { attributes: ['containerName'] });
|
||||
hidden = hidden.map((container) => container.containerName);
|
||||
res.send("ok");
|
||||
return;
|
||||
case 'reset':
|
||||
await Permission.update({ hide: false }, { where: { user: req.session.user } });
|
||||
res.send("ok");
|
||||
return;
|
||||
case 'alert':
|
||||
req.session.alert = '';
|
||||
res.send('');
|
||||
return;
|
||||
}
|
||||
|
||||
function status (state) {
|
||||
return(`<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>`);
|
||||
}
|
||||
|
||||
// Container actions
|
||||
if ((action == 'start') && (value == 'stopped')) {
|
||||
docker.getContainer(name).start();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'start') && (value == 'paused')) {
|
||||
docker.getContainer(name).unpause();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'stop') && (value != 'stopped')) {
|
||||
docker.getContainer(name).stop();
|
||||
res.send(status('stopping'));
|
||||
} else if ((action == 'pause') && (value == 'paused')) {
|
||||
docker.getContainer(name).unpause();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'pause') && (value == 'running')) {
|
||||
docker.getContainer(name).pause();
|
||||
res.send(status('pausing'));
|
||||
} else if (action == 'restart') {
|
||||
docker.getContainer(name).restart();
|
||||
res.send(status('restarting'));
|
||||
}
|
||||
}
|
||||
|
||||
async function containerInfo (containerName) {
|
||||
// get the container info
|
||||
let container = docker.getContainer(containerName);
|
||||
let info = await container.inspect();
|
||||
let image = info.Config.Image;
|
||||
// grab the service name from the end of the image name
|
||||
let service = image.split('/').pop();
|
||||
// remove the tag from the service name if it exists
|
||||
try { service = service.split(':')[0]; } catch {}
|
||||
let ports_list = [];
|
||||
let external = 0;
|
||||
let internal = 0;
|
||||
|
||||
try {
|
||||
for (const [key, value] of Object.entries(info.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
external = ports_list[0].external;
|
||||
internal = ports_list[0].internal;
|
||||
} catch {}
|
||||
|
||||
// console.log(ports_list);
|
||||
// console.log(info.HostConfig.PortBindings);
|
||||
|
||||
// console.log(info.HostConfig.Binds);
|
||||
|
||||
// console.log(info.Config.Env);
|
||||
// console.log(info.Config.Labels);
|
||||
|
||||
let details = {
|
||||
name: containerName,
|
||||
image: image,
|
||||
service: service,
|
||||
state: info.State.Status,
|
||||
external_port: external,
|
||||
internal_port: internal,
|
||||
ports: ports_list,
|
||||
volumes: info.Mounts,
|
||||
env: info.Config.Env,
|
||||
labels: info.Config.Labels,
|
||||
link: 'localhost',
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
async function createCard (details) {
|
||||
let shortname = details.name.slice(0, 10) + '...';
|
||||
let trigger = 'data-hx-trigger="load, every 3s"';
|
||||
let state = details.state;
|
||||
let card = readFileSync('./views/partials/containerFull.html', 'utf8');
|
||||
|
||||
let state_color = '';
|
||||
switch (state) {
|
||||
case 'running':
|
||||
state_color = 'green';
|
||||
break;
|
||||
case 'exited':
|
||||
state = 'stopped';
|
||||
state_color = 'red';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
case 'paused':
|
||||
state_color = 'orange';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
case 'installing':
|
||||
state_color = 'blue';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
}
|
||||
// if (name.startsWith('dweebui')) { disable = 'disabled=""'; }
|
||||
|
||||
card = card.replace(/AppName/g, details.name);
|
||||
card = card.replace(/AppShortName/g, shortname);
|
||||
card = card.replace(/AppIcon/g, details.service);
|
||||
card = card.replace(/AppState/g, state);
|
||||
card = card.replace(/StateColor/g, state_color);
|
||||
card = card.replace(/ExternalPort/g, details.external_port);
|
||||
card = card.replace(/InternalPort/g, details.internal_port);
|
||||
card = card.replace(/ChartName/g, details.name.replace(/-/g, ''));
|
||||
card = card.replace(/AppNameState/g, `${details.name}State`);
|
||||
card = card.replace(/data-trigger=""/, trigger);
|
||||
return card;
|
||||
}
|
||||
|
||||
async function userCards (session) {
|
||||
session.container_list = [];
|
||||
// check what containers the user wants hidden
|
||||
let hidden = await Permission.findAll({ where: {user: session.user, hide: true}}, { attributes: ['containerName'] });
|
||||
hidden = hidden.map((container) => container.containerName);
|
||||
// check what containers the user has permission to view
|
||||
let visable = await Permission.findAll({ where: { user: session.user, [Op.or]: [{ uninstall: true }, { edit: true }, { upgrade: true }, { start: true }, { stop: true }, { pause: true }, { restart: true }, { logs: true }, { view: true }] } });
|
||||
visable = visable.map((container) => container.containerName);
|
||||
// get all containers
|
||||
let containers = await docker.listContainers({ all: true });
|
||||
// loop through containers
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
let container_name = containers[i].Names[0].replace('/', '');
|
||||
// skip hidden containers
|
||||
if (hidden.includes(container_name)) { continue; }
|
||||
// admin can see all containers that they don't have hidden
|
||||
if (session.role == 'admin') { session.container_list.push({ container: container_name, state: containers[i].State }); }
|
||||
// user can see any containers that they have any permissions for
|
||||
else if (visable.includes(container_name)){ session.container_list.push({ container: container_name, state: containers[i].State }); }
|
||||
}
|
||||
// create a sent list if it doesn't exist
|
||||
if (!session.sent_list) { session.sent_list = []; }
|
||||
if (!session.update_list) { session.update_list = []; }
|
||||
if (!session.new_cards) { session.new_cards = []; }
|
||||
}
|
||||
|
||||
async function updateDashboard (session) {
|
||||
let container_list = session.container_list;
|
||||
let sent_list = session.sent_list;
|
||||
session.new_cards = [];
|
||||
session.update_list = [];
|
||||
// loop through the containers list
|
||||
container_list.forEach(info => {
|
||||
let { container, state } = info;
|
||||
let sent = sent_list.find(c => c.container === container);
|
||||
if (!sent) { session.new_cards.push(container);}
|
||||
else if (sent.state !== state) { session.update_list.push(container); }
|
||||
});
|
||||
// loop through the sent list to see if any containers have been removed
|
||||
sent_list.forEach(info => {
|
||||
let { container } = info;
|
||||
let exists = container_list.find(c => c.container === container);
|
||||
if (!exists) { session.update_list.push(container); }
|
||||
});
|
||||
}
|
||||
|
||||
// HTMX server-side events
|
||||
export const SSE = async (req, res) => {
|
||||
// set the headers for server-sent events
|
||||
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
|
||||
// check for container changes every 500ms
|
||||
let eventCheck = setInterval(async () => {
|
||||
await userCards(req.session);
|
||||
// check if the cards displayed are the same as what's in the session
|
||||
if ((JSON.stringify(req.session.container_list) === JSON.stringify(req.session.sent_list))) { return; }
|
||||
await updateDashboard(req.session);
|
||||
|
||||
for (let i = 0; i < req.session.new_cards.length; i++) {
|
||||
let details = await containerInfo(req.session.new_cards[i]);
|
||||
let card = await createCard(details);
|
||||
newCards += card;
|
||||
req.session.alert = '';
|
||||
}
|
||||
for (let i = 0; i < req.session.update_list.length; i++) {
|
||||
res.write(`event: ${req.session.update_list[i]}\n`);
|
||||
res.write(`data: 'update cards'\n\n`);
|
||||
}
|
||||
res.write(`event: update\n`);
|
||||
res.write(`data: 'update cards'\n\n`);
|
||||
req.session.sent_list = req.session.container_list.slice();
|
||||
}, 500);
|
||||
req.on('close', () => {
|
||||
clearInterval(eventCheck);
|
||||
});
|
||||
};
|
||||
|
||||
// Server metrics (CPU, RAM, TX, RX, DISK)
|
||||
export const ServerMetrics = async (req, res) => {
|
||||
export const Stats = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let color = req.header('hx-trigger');
|
||||
let value = 0;
|
||||
|
@ -65,448 +377,72 @@ export const ServerMetrics = async (req, res) => {
|
|||
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>`;
|
||||
<div class="cpu-bar meter animate ${color}"> <span style="width:${value}%"><span></span></span> </div>`;
|
||||
res.send(info);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function userCards (req) {
|
||||
|
||||
let container_list = [];
|
||||
// Check what containers the user has hidden.
|
||||
let hidden = await Permission.findAll({ where: {userID: req.session.userID, hide: true}}, { attributes: ['containerID'] });
|
||||
hidden = hidden.map((container) => container.containerID);
|
||||
|
||||
// Check what containers the user has permission for.
|
||||
let visable = await Permission.findAll({ where: { userID: req.session.userID, [Op.or]: [{ uninstall: true }, { edit: true }, { upgrade: true }, { start: true }, { stop: true }, { pause: true }, { restart: true }, { logs: true }, { view: true }] }, attributes: ['containerID'] });
|
||||
visable = visable.map((container) => container.containerID);
|
||||
|
||||
let containers = await GetContainerLists(req.session.host);
|
||||
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
let container_name = containers[i].Names[0].split('/').pop();
|
||||
// Skip if the ID is found in the hidden list.
|
||||
if (hidden.includes(containers[i].Id)) { continue; }
|
||||
// Skip if the state is 'created'.
|
||||
if (containers[i].State == 'created') { continue; }
|
||||
// Admin can see all containers that they don't have hidden.
|
||||
if (req.session.role == 'admin') { container_list.push({ containerName: container_name, containerID: containers[i].Id, containerState: containers[i].State }); }
|
||||
// User can see any containers that they have any permissions for.
|
||||
else if (visable.includes(containers[i].Id)){ container_list.push({ containerName: container_name, containerID: containers[i].Id, containerState: containers[i].State }); }
|
||||
}
|
||||
return container_list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
async function createCard (details) {
|
||||
|
||||
let container_card = readFileSync('./views/partials/container_card.html', 'utf8');
|
||||
|
||||
let containerName = details.containerName;
|
||||
let containerTitle = Capitalize(containerName); if (containerTitle.length > 14) { containerTitle = containerTitle.substring(0, 14) + '...'; }
|
||||
let containerID = details.containerID;
|
||||
let containerState = details.containerState;
|
||||
let containerService = details.containerService;
|
||||
let AltID = `a${containerID}`;
|
||||
|
||||
let chart_trigger = `<div name="${containerName}" id="${AltID}info" hx-get="/dashboard/view/chart/${containerID}" hx-swap="outerHTML" hx-trigger="every 3s" hx-target="#${AltID}info">
|
||||
</div>`;
|
||||
|
||||
let containerStateColor = '';
|
||||
switch (containerState) {
|
||||
case 'running': containerStateColor = 'green'; break;
|
||||
case 'exited': containerStateColor = 'red'; containerState = 'stopped'; chart_trigger = ''; break;
|
||||
case 'paused': containerStateColor = 'orange'; break;
|
||||
default: containerStateColor = 'blue'; break;
|
||||
}
|
||||
|
||||
let [title_link, created] = await Container.findOrCreate({ where: { containerID: details.containerID }, defaults: { containerName: containerName, containerID: containerID, link: '', cpu: '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]', ram: '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]' } });
|
||||
if (title_link.link != '') { title_link = `<a href="${title_link.link}" class="nav-link" target="_blank">${containerTitle}</a>`; }
|
||||
else { title_link = containerTitle; }
|
||||
|
||||
let [port_link, created_link] = await ServerSettings.findOrCreate({ where: { key: 'custom_link' }, defaults: { key: 'custom_link', value: 'http://localhost' } });
|
||||
port_link = port_link.value;
|
||||
|
||||
let exposed_ports = '';
|
||||
for (let i = 0; i < details.ports.length; i++) {
|
||||
if (details.ports[i].external != '' && details.ports[i].protocol != 'udp') { exposed_ports += `<a href="${port_link}:${details.ports[i].external}" target="_blank" style="color: inherit; text-decoration: none;"> ${details.ports[i].external}</a> `; }
|
||||
}
|
||||
|
||||
container_card = container_card.replace(/AppName/g, containerName);
|
||||
container_card = container_card.replace(/ContainerID/g, containerID);
|
||||
container_card = container_card.replaceAll(/AltID/g, AltID);
|
||||
container_card = container_card.replace(/AppPorts/g, exposed_ports);
|
||||
container_card = container_card.replace(/TitleLink/g, title_link);
|
||||
container_card = container_card.replace(/AppTitle/g, containerTitle);
|
||||
container_card = container_card.replace(/AppService/g, containerService);
|
||||
container_card = container_card.replace(/AppState/g, containerState);
|
||||
container_card = container_card.replace(/StateColor/g, containerStateColor);
|
||||
container_card = container_card.replace(/ChartTrigger/g, chart_trigger);
|
||||
|
||||
return container_card;
|
||||
}
|
||||
|
||||
|
||||
// HTMX - Server-side events
|
||||
export const SSE = async (req, res) => {
|
||||
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive'
|
||||
});
|
||||
|
||||
async function eventCheck () {
|
||||
|
||||
let list = await ContainerLists.findOne({ where: { userID: req.session.userID }, attributes: ['sent'] });
|
||||
let container_list = await userCards(req);
|
||||
|
||||
let new_cards = [];
|
||||
let update_list = [];
|
||||
let sent_cards = [];
|
||||
sent_cards = JSON.parse(list.sent);
|
||||
|
||||
if (JSON.stringify(container_list) == list.sent) { return; }
|
||||
|
||||
// console.log(`Update for ${req.session.username}`);
|
||||
|
||||
// loop through the containers list to see if any new containers have been added or changed
|
||||
container_list.forEach(container => {
|
||||
let { containerName, containerID, containerState } = container;
|
||||
if (list.sent) { sent_cards = JSON.parse(list.sent); }
|
||||
|
||||
let found = sent_cards.find(c => c.containerID === containerID);
|
||||
if (!found) { new_cards.push(containerID); }
|
||||
else if (found.containerState !== containerState) { update_list.push(containerID); }
|
||||
});
|
||||
|
||||
// loop through the sent list to see if any containers have been removed
|
||||
sent_cards.forEach(container => {
|
||||
let { containerName, containerID, containerState } = container;
|
||||
let found = container_list.find(c => c.containerID === containerID);
|
||||
if (!found) { update_list.push(containerID); }
|
||||
});
|
||||
|
||||
await ContainerLists.update({ new: JSON.stringify(new_cards), sent: JSON.stringify(container_list), containers: JSON.stringify(container_list) }, { where: { userID: req.session.userID } });
|
||||
|
||||
if (update_list.length > 0 ) {
|
||||
for (let i = 0; i < update_list.length; i++) {
|
||||
res.write(`event: ${update_list[i]}\n`);
|
||||
res.write(`data: 'update cards'\n\n`);
|
||||
}
|
||||
}
|
||||
|
||||
if (new_cards.length > 0) {
|
||||
res.write(`event: update\n`);
|
||||
res.write(`data: 'card updates'\n\n`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
docker.getEvents({}, async function (err, data) {
|
||||
data.on('data', async function () {
|
||||
await eventCheck();
|
||||
});
|
||||
});
|
||||
|
||||
req.on('close', async () => {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const DashboardView = async function (req, res) {
|
||||
|
||||
let container_name = req.header('hx-trigger-name');
|
||||
let view = req.params.view;
|
||||
let containerID = req.params.id;
|
||||
let AltID = `a${containerID}`;
|
||||
|
||||
// console.log(`[container_name] ${container_name} [view] ${view} [containerID] ${containerID}`);
|
||||
|
||||
// Container CPU and RAM chart
|
||||
|
||||
if (view == 'chart') {
|
||||
let container = await Container.findOne({ where: { containerID: containerID } });
|
||||
// Get the cpu and ram stats, remove the oldest entry, add the newest stats, then update container info.
|
||||
let stats = await containerStats(containerID);
|
||||
let cpu = JSON.parse(container.cpu); cpu.shift(); cpu.push(stats.cpu);
|
||||
let ram = JSON.parse(container.ram); ram.shift(); ram.push(stats.ram);
|
||||
container.update({ cpu: JSON.stringify(cpu), ram: JSON.stringify(ram) });
|
||||
|
||||
let chartData = `<div name="${container_name}" id="${AltID}info" hx-get="/dashboard/view/chart/${containerID}" hx-swap="outerHTML" hx-trigger="every 3s" hx-target="#${AltID}info">
|
||||
<script>
|
||||
${AltID}chart.updateSeries([{
|
||||
name: 'CPU',
|
||||
data: ${container.cpu}
|
||||
}, {
|
||||
name: 'RAM',
|
||||
data: ${container.ram}
|
||||
}]);
|
||||
</script>
|
||||
</div>`;
|
||||
res.send(chartData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Permissions modal
|
||||
|
||||
if (view == 'permissions') {
|
||||
let title = Capitalize(container_name);
|
||||
let users = await User.findAll({ attributes: ['username', 'userID'] });
|
||||
|
||||
let modal =`<div class="modal-header">
|
||||
<h5 class="modal-title">${title} Permissions</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
// Imported by utils/install.js
|
||||
export async function addAlert (session, type, message) {
|
||||
session.alert = `<div class="alert alert-${type} alert-dismissible py-2 mb-0" role="alert" id="alert">
|
||||
<div class="d-flex">
|
||||
<div class="spinner-border text-info nav-link">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<div class="modal-body"><div class="accordion" id="accordion-example">`;
|
||||
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
if (users.length == 1) { modal += 'No other users.'; break; }
|
||||
// Skip the admin user.
|
||||
else if (i == 0) { continue; }
|
||||
let exists = await Permission.findOne({ where: {containerID: containerID, userID: users[i].userID}});
|
||||
if (!exists) { await Permission.create({ containerName: container_name, containerID: containerID, userID: users[i].userID, username: users[i].username}); }
|
||||
let permissions = await Permission.findOne({ where: {containerID: containerID, userID: users[i].userID}});
|
||||
let user_permissions = readFileSync('./views/partials/permissions.html', 'utf8');
|
||||
if (permissions.uninstall == true && permissions.edit == true && permissions.upgrade == true && permissions.start == true && permissions.stop == true && permissions.pause == true && permissions.restart == true && permissions.logs == true && permissions.view == true) { user_permissions = user_permissions.replace(/data-AllCheck/g, 'checked'); }
|
||||
if (permissions.uninstall == true) { user_permissions = user_permissions.replace(/data-UninstallCheck/g, 'checked'); }
|
||||
if (permissions.edit == true) { user_permissions = user_permissions.replace(/data-EditCheck/g, 'checked'); }
|
||||
if (permissions.upgrade == true) { user_permissions = user_permissions.replace(/data-UpgradeCheck/g, 'checked'); }
|
||||
if (permissions.start == true) { user_permissions = user_permissions.replace(/data-StartCheck/g, 'checked'); }
|
||||
if (permissions.stop == true) { user_permissions = user_permissions.replace(/data-StopCheck/g, 'checked'); }
|
||||
if (permissions.pause == true) { user_permissions = user_permissions.replace(/data-PauseCheck/g, 'checked'); }
|
||||
if (permissions.restart == true) { user_permissions = user_permissions.replace(/data-RestartCheck/g, 'checked'); }
|
||||
if (permissions.logs == true) { user_permissions = user_permissions.replace(/data-LogsCheck/g, 'checked'); }
|
||||
if (permissions.view == true) { user_permissions = user_permissions.replace(/data-ViewCheck/g, 'checked'); }
|
||||
user_permissions = user_permissions.replace(/Entry/g, i);
|
||||
user_permissions = user_permissions.replace(/Entry/g, i);
|
||||
user_permissions = user_permissions.replace(/Entry/g, i);
|
||||
user_permissions = user_permissions.replace(/container_id/g, containerID);
|
||||
user_permissions = user_permissions.replace(/container_name/g, container_name);
|
||||
user_permissions = user_permissions.replace(/user_id/g, users[i].userID);
|
||||
user_permissions = user_permissions.replace(/Username/g, users[i].username);
|
||||
modal += user_permissions;
|
||||
}
|
||||
modal += `</div></div>
|
||||
<div class="modal-footer">
|
||||
|
||||
<form id="reset_permissions" class="me-auto">
|
||||
<input type="hidden" name="containerID" value="${containerID}">
|
||||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" name="reset_permissions" id="submit" hx-post="/dashboard/action/update_permissions/${containerID}" hx-confirm="Are you sure you want to reset permissions for this container?">Reset</button>
|
||||
</form>
|
||||
|
||||
<button type="button" class="btn" data-bs-dismiss="modal">Close</button>
|
||||
</div>`
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
// Logs modal
|
||||
|
||||
if (view == 'logs') {
|
||||
let logs = await containerLogs(containerID);
|
||||
let modal = readFileSync('./views/partials/logs.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, container_name);
|
||||
modal = modal.replace(/ContainerID/g, containerID);
|
||||
modal = modal.replace(/ContainerLogs/g, logs);
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
// Details modal
|
||||
|
||||
if (view == 'details') {
|
||||
let container = await containerInfo(containerID);
|
||||
let modal = readFileSync('./views/partials/details.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, container.containerName);
|
||||
modal = modal.replace(/AppImage/g, container.containerImage);
|
||||
for (let i = 0; i <= 6; i++) {
|
||||
modal = modal.replaceAll(`Port${i}Check`, container.ports[i]?.check || '');
|
||||
modal = modal.replaceAll(`Port${i}External`, container.ports[i]?.external || '');
|
||||
modal = modal.replaceAll(`Port${i}Internal`, container.ports[i]?.internal || '');
|
||||
modal = modal.replaceAll(`Port${i}Protocol`, container.ports[i]?.protocol || '');
|
||||
}
|
||||
for (let i = 0; i <= 6; i++) {
|
||||
modal = modal.replaceAll(`Vol${i}Source`, container.volumes[i]?.Source || '');
|
||||
modal = modal.replaceAll(`Vol${i}Destination`, container.volumes[i]?.Destination || '');
|
||||
modal = modal.replaceAll(`Vol${i}RW`, container.volumes[i]?.RW || '');
|
||||
}
|
||||
for (let i = 0; i <= 19; i++) {
|
||||
modal = modal.replaceAll(`Label${i}Key`, Object.keys(container.labels)[i] || '');
|
||||
modal = modal.replaceAll(`Label${i}Value`, Object.values(container.labels)[i] || '');
|
||||
}
|
||||
for (let i = 0; i <= 19; i++) {
|
||||
modal = modal.replaceAll(`Env${i}Key`, container.env[i]?.split('=')[0] || '');
|
||||
modal = modal.replaceAll(`Env${i}Value`, container.env[i]?.split('=')[1] || '');
|
||||
}
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
// Uninstall modal
|
||||
|
||||
if (view == 'uninstall') {
|
||||
let modal = readFileSync('./views/partials/uninstall.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, container_name);
|
||||
modal = modal.replace(/ContainerID/g, containerID);
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update link modal
|
||||
|
||||
if (view == 'link_modal') {
|
||||
const [container, created] = await Container.findOrCreate({ where: { containerID: containerID }, defaults: { containerName: container_name, containerID: containerID, link: '' } });
|
||||
let modal = readFileSync('./views/partials/link.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, container_name);
|
||||
modal = modal.replace(/ContainerID/g, containerID);
|
||||
modal = modal.replace(/AppLink/g, container.link);
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update container_card
|
||||
|
||||
if (view == 'update_card'){
|
||||
|
||||
let lists = await ContainerLists.findOne({ where: { userID: req.session.userID }, attributes: ['containers'] });
|
||||
let container_list = JSON.parse(lists.containers);
|
||||
|
||||
let found = container_list.find(c => c.containerID === containerID);
|
||||
if (!found) { res.send(''); return; }
|
||||
let details = await containerInfo(containerID);
|
||||
let card = await createCard(details);
|
||||
res.send(card);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate list of container_cards for the dashboard
|
||||
|
||||
if (view == 'card_list'){
|
||||
let cards_list = '';
|
||||
// Check if there are any new cards in queue.
|
||||
let new_cards = await ContainerLists.findOne({ where: { userID: req.session.userID }, attributes: ['new'] });
|
||||
let new_list = JSON.parse(new_cards.new);
|
||||
// Check what containers the user should see.
|
||||
let containers = await userCards(req);
|
||||
// Create the cards.
|
||||
if (new_list.length > 0) {
|
||||
for (let i = 0; i < new_list.length; i++) {
|
||||
let details = await containerInfo(new_list[i]);
|
||||
let card = await createCard(details);
|
||||
cards_list += card;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
let details = await containerInfo(containers[i].containerID);
|
||||
let card = await createCard(details);
|
||||
cards_list += card;
|
||||
}
|
||||
}
|
||||
// Update lists, clear the queue, and send the cards.
|
||||
await ContainerLists.update({ containers: JSON.stringify(containers), sent: JSON.stringify(containers), new: '[]' }, { where: { userID: req.session.userID } });
|
||||
res.send(cards_list);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
<div>
|
||||
${message}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-close" data-hx-post="/dashboard/alert" data-hx-trigger="click" data-hx-target="#alert" data-hx-swap="outerHTML" style="padding-top: 0.5rem;" ></button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Container actions (start, stop, pause, restart, hide)
|
||||
export const DashboardAction = async (req, res) => {
|
||||
|
||||
// let trigger_id = req.header('hx-trigger');
|
||||
let container_name = req.header('hx-trigger-name');
|
||||
let action = req.params.action;
|
||||
let containerID = req.params.id;
|
||||
|
||||
// console.log(`[container_name] ${container_name} [action] ${action} [containerID] ${containerID}`);
|
||||
|
||||
if (action == 'reset') {
|
||||
await Permission.update({ hide: false }, { where: { userID: req.session.userID } });
|
||||
res.redirect('/dashboard');
|
||||
return;
|
||||
} else if (action == 'update_link') {
|
||||
let url = req.body.url;
|
||||
let container = await Container.findOne({ where: { containerID: containerID } });
|
||||
container.update({ link: url });
|
||||
res.redirect('/dashboard');
|
||||
return;
|
||||
} else if (action == 'update_permissions') {
|
||||
let { userID, username, reset_permissions, select } = req.body;
|
||||
let button_id = req.header('hx-trigger');
|
||||
// Replaces the update button if it's been pressed.
|
||||
if (button_id == 'confirmed') { res.send(`<button class="btn" type="button" id="submit" hx-post="/dashboard/action/update_permissions/${containerID}" hx-swap="outerHTML">Update </button>`); return; }
|
||||
// Reset all permissions for the container.
|
||||
if (reset_permissions == '') { await Permission.update({ uninstall: false, edit: false, upgrade: false, start: false, stop: false, pause: false, restart: false, logs: false, view: false }, { where: { containerID: containerID } }); trigger_docker_event(); return; }
|
||||
// Make sure req.body[select] is an array
|
||||
if (typeof req.body[select] == 'string') { req.body[select] = [req.body[select]]; }
|
||||
|
||||
await Permission.update({ uninstall: false, edit: false, upgrade: false, start: false, stop: false, pause: false, restart: false, logs: false, view: false }, { where: { containerID: containerID, userID: userID } });
|
||||
if (req.body[select]) {
|
||||
for (let i = 0; i < req.body[select].length; i++) {
|
||||
let permissions = req.body[select][i];
|
||||
if (permissions == 'uninstall') { await Permission.update({ uninstall: true }, { where: {containerID: containerID, userID: userID}}); }
|
||||
if (permissions == 'edit') { await Permission.update({ edit: true }, { where: {containerID: containerID, userID: userID}}); }
|
||||
if (permissions == 'upgrade') { await Permission.update({ upgrade: true }, { where: {containerID: containerID, userID: userID}}); }
|
||||
if (permissions == 'start') { await Permission.update({ start: true }, { where: {containerID: containerID, userID: userID}}); }
|
||||
if (permissions == 'stop') { await Permission.update({ stop: true }, { where: {containerID: containerID, userID: userID}}); }
|
||||
if (permissions == 'pause') { await Permission.update({ pause: true }, { where: {containerID: containerID, userID: userID}}); }
|
||||
if (permissions == 'restart') { await Permission.update({ restart: true }, { where: {containerID: containerID, userID: userID}}); }
|
||||
if (permissions == 'logs') { await Permission.update({ logs: true }, { where: {containerID: containerID, userID: userID}}); }
|
||||
if (permissions == 'view') { await Permission.update({ view: true }, { where: {containerID: containerID, userID: userID}}); }
|
||||
}
|
||||
}
|
||||
trigger_docker_event();
|
||||
res.send(`<button class="btn" type="button" id="confirmed" hx-post="/dashboard/action/update_permissions/${containerID}" hx-swap="outerHTML" hx-trigger="load delay:1s">Update ✔️</button>`);
|
||||
return;
|
||||
} else if (action == 'switch_host') {
|
||||
req.session.host = req.body.host;
|
||||
console.log(`Switched to host ${req.session.host}`);
|
||||
res.redirect('/dashboard');
|
||||
export const UpdatePermissions = async (req, res) => {
|
||||
let { user, container, reset_permissions } = req.body;
|
||||
let id = req.header('hx-trigger');
|
||||
if (reset_permissions) {
|
||||
await Permission.update({ uninstall: false, edit: false, upgrade: false, start: false, stop: false, pause: false, restart: false, logs: false, view: false }, { where: { containerName: container} });
|
||||
return;
|
||||
}
|
||||
// Inspect the container
|
||||
let info = docker.getContainer(containerID);
|
||||
let container = await info.inspect();
|
||||
let containerState = container.State.Status;
|
||||
|
||||
// Displays container state (starting, stopping, restarting, pausing)
|
||||
function status (state) {
|
||||
return(`<div class="text-yellow d-inline-flex align-items-center lh-1 ms-auto" id="AltIDState">
|
||||
<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>
|
||||
<strong>${state}</strong>
|
||||
</div>`);
|
||||
await Permission.update({ uninstall: false, edit: false, upgrade: false, start: false, stop: false, pause: false, restart: false, logs: false }, { where: { containerName: container, user: user } });
|
||||
Object.keys(req.body).forEach(async function(key) {
|
||||
if (key != 'user' && key != 'container') {
|
||||
let permissions = req.body[key];
|
||||
if (permissions.includes('uninstall')) { await Permission.update({ uninstall: true }, { where: {containerName: container, user: user}}); }
|
||||
if (permissions.includes('edit')) { await Permission.update({ edit: true }, { where: {containerName: container, user: user}}); }
|
||||
if (permissions.includes('upgrade')) { await Permission.update({ upgrade: true }, { where: {containerName: container, user: user}}); }
|
||||
if (permissions.includes('start')) { await Permission.update({ start: true }, { where: {containerName: container, user: user}}); }
|
||||
if (permissions.includes('stop')) { await Permission.update({ stop: true }, { where: {containerName: container, user: user}}); }
|
||||
if (permissions.includes('pause')) { await Permission.update({ pause: true }, { where: {containerName: container, user: user}}); }
|
||||
if (permissions.includes('restart')) { await Permission.update({ restart: true }, { where: {containerName: container, user: user}}); }
|
||||
if (permissions.includes('logs')) { await Permission.update({ logs: true }, { where: {containerName: container, user: user}}); }
|
||||
if (permissions.includes('view')) { await Permission.update({ view: true }, { where: {containerName: container, user: user}}); }
|
||||
}
|
||||
});
|
||||
if (id == 'submit') {
|
||||
res.send('<button class="btn" type="button" id="confirmed" hx-post="/updatePermissions" hx-swap="outerHTML" hx-trigger="load delay:2s">Update ✔️</button>');
|
||||
return;
|
||||
} else if (id == 'confirmed') {
|
||||
res.send('<button class="btn" type="button" id="submit" hx-post="/updatePermissions" hx-vals="#updatePermissions" hx-swap="outerHTML">Update </button>');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((action == 'start') && (containerState == 'exited')) {
|
||||
info.start();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'start') && (containerState == 'paused')) {
|
||||
info.unpause();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'stop') && (containerState != 'exited')) {
|
||||
info.stop();
|
||||
res.send(status('stopping'));
|
||||
} else if ((action == 'pause') && (containerState == 'paused')) {
|
||||
info.unpause();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'pause') && (containerState == 'running')) {
|
||||
info.pause();
|
||||
res.send(status('pausing'));
|
||||
} else if (action == 'restart') {
|
||||
info.restart();
|
||||
res.send(status('restarting'));
|
||||
} else if (action == 'hide') {
|
||||
let exists = await Permission.findOne({ where: { containerID: containerID, userID: req.session.userID }});
|
||||
if (!exists) { const newPermission = await Permission.create({ containerName: container_name, containerID: containerID, username: req.session.username, userID: req.session.userID, hide: true }); }
|
||||
else { exists.update({ hide: true }); }
|
||||
res.send('ok');
|
||||
}
|
||||
// Container charts
|
||||
export const Chart = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
if (!stats[name]) { stats[name] = { cpuArray: Array(15).fill(0), ramArray: Array(15).fill(0) }; }
|
||||
const info = await dockerContainerStats(name);
|
||||
stats[name].cpuArray.push(Math.round(info[0].cpuPercent));
|
||||
stats[name].ramArray.push(Math.round(info[0].memPercent));
|
||||
stats[name].cpuArray = stats[name].cpuArray.slice(-15);
|
||||
stats[name].ramArray = stats[name].ramArray.slice(-15);
|
||||
let chart = `
|
||||
<script>
|
||||
${name}chart.updateSeries([{
|
||||
data: [${stats[name].cpuArray}]
|
||||
}, {
|
||||
data: [${stats[name].ramArray}]
|
||||
}])
|
||||
</script>`
|
||||
res.send(chart);
|
||||
}
|
|
@ -1,31 +1,75 @@
|
|||
import { Alert, getLanguage, Navbar, Footer } from '../utils/system.js';
|
||||
import { imageList, GetContainerLists } from '../utils/docker.js';
|
||||
import { docker } from '../server.js';
|
||||
import { addAlert } from './dashboard.js';
|
||||
|
||||
export const Images = async function(req,res){
|
||||
export const Images = async function(req, res) {
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
let action = req.params.action;
|
||||
|
||||
if (action == "remove") {
|
||||
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");
|
||||
return;
|
||||
} else if (action == "add") {
|
||||
let image = req.body.image;
|
||||
let tag = req.body.tag || 'latest';
|
||||
|
||||
try {
|
||||
console.log(`Pulling image: ${image}:${tag}`);
|
||||
await docker.pull(`${image}:${tag}`);
|
||||
} catch (error) {
|
||||
console.log(`Unable to pull image: ${image}:${tag}`);
|
||||
}
|
||||
res.redirect("/images");
|
||||
return;
|
||||
}
|
||||
|
||||
let containers = await docker.listContainers({ all: true });
|
||||
let container_images = [];
|
||||
let image_list = '';
|
||||
|
||||
let containers = await GetContainerLists();
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
container_images.push(containers[i].Image);
|
||||
}
|
||||
|
||||
let images = await imageList();
|
||||
let images = await docker.listImages({ all: true });
|
||||
|
||||
let image_list = `
|
||||
<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><label class="table-sort" data-sort="sort-name">Name</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-type">Tag</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-quantity">Size</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-progress">Action</label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">`
|
||||
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
|
||||
|
||||
let name = '';
|
||||
let tag = '';
|
||||
try { name = images[i].RepoTags[0].split(':')[0]; } catch {}
|
||||
try { tag = images[i].RepoTags[0].split(':')[1]; } catch {}
|
||||
|
||||
// let image_id = images[i].Id.split(':')[1].substring(0, 12);
|
||||
let image_id = images[i].Id.split(':')[1];
|
||||
|
||||
let date = new Date(images[i].Created * 1000);
|
||||
let created = date.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
|
||||
|
||||
|
@ -33,60 +77,34 @@ export const Images = async function(req,res){
|
|||
size = size.toFixed(2);
|
||||
|
||||
let status = '';
|
||||
try {
|
||||
if (container_images.includes(images[i].RepoTags[0])) {
|
||||
status = 'In use';
|
||||
}
|
||||
} catch {}
|
||||
if (container_images.includes(images[i].RepoTags[0])) {
|
||||
status = 'In use';
|
||||
}
|
||||
|
||||
let details = `
|
||||
<tr>
|
||||
<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">${name}</td>
|
||||
<td class="sort-type">${tag}</td>
|
||||
<td class="sort-city">${image_id}</td>
|
||||
<td class="sort-city">${images[i].Id}</td>
|
||||
<td class="sort-score text-green">${status}</td>
|
||||
<td class="sort-quantity">${size} MB</td>
|
||||
<td class="sort-date" data-date="1628122643">${created}</td>
|
||||
<td class=""><a class="container-action" href="#"><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></a></td>
|
||||
<td class="sort-quantity">${size} MB</td>
|
||||
<td class="text-end"><a class="btn" href="#"><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></a></td>
|
||||
</tr>`
|
||||
image_list += details;
|
||||
}
|
||||
|
||||
image_list += `</tbody>`
|
||||
|
||||
res.render("images",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
|
||||
res.render("images", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
image_count: '',
|
||||
avatar: req.session.user.charAt(0).toUpperCase(),
|
||||
image_list: image_list,
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
}
|
||||
|
||||
export const searchImages = async function (req, res) {
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
export const submitImages = async function(req,res){
|
||||
|
||||
// console.log(req.body);
|
||||
|
||||
let trigger_name = req.header('hx-trigger-name');
|
||||
let trigger_id = req.header('hx-trigger');
|
||||
|
||||
console.log(`trigger_name: ${trigger_name} - trigger_id: ${trigger_id}`);
|
||||
|
||||
|
||||
res.render("images",{
|
||||
image_count: images.length,
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
|
||||
}
|
|
@ -1,74 +1,70 @@
|
|||
import { User, Syslog } from '../database/models.js';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { User, Syslog, ServerSettings } from '../db/config.js';
|
||||
|
||||
|
||||
export const Login = async function (req, res) {
|
||||
|
||||
if (req.session.userID) { res.redirect("/dashboard"); return; }
|
||||
|
||||
// Check authentication settings
|
||||
let authentication = await ServerSettings.findOne({ where: { key: 'authentication' }});
|
||||
if (!authentication) { await ServerSettings.create({ key: 'authentication', value: 'default' }); }
|
||||
authentication = await ServerSettings.findOne({ where: { key: 'authentication' }});
|
||||
|
||||
// Create an empty session and redirect if authentication is disabled
|
||||
if (authentication.value == 'localhost' && req.hostname == 'localhost') {
|
||||
req.session.username = 'Localhost';
|
||||
req.session.userID = '00000000-0000-0000-0000-000000000000';
|
||||
req.session.role = 'admin';
|
||||
await Syslog.create({ username: 'Localhost', uniqueID: 'localhost', event: "Login", message: "User logged in", ip: req.ip });
|
||||
res.redirect("/dashboard");
|
||||
return;
|
||||
} else if (authentication.value == 'no_auth') {
|
||||
req.session.username = 'No Auth';
|
||||
req.session.userID = '00000000-0000-0000-0000-000000000000';
|
||||
req.session.role = 'admin';
|
||||
await Syslog.create({ username: 'No Auth', uniqueID: 'no_auth', event: "Login", message: "User logged in", ip: req.ip });
|
||||
res.redirect("/dashboard");
|
||||
return;
|
||||
}
|
||||
res.render("login",{
|
||||
"error":"",
|
||||
});
|
||||
export const Login = function(req,res){
|
||||
if (req.session.user) { res.redirect("/logout"); }
|
||||
else { res.render("login",{ "error":"", }); }
|
||||
}
|
||||
|
||||
export const submitLogin = async function(req,res){
|
||||
let { email, password } = req.body;
|
||||
email = email.toLowerCase();
|
||||
|
||||
export const submitLogin = async function (req, res) {
|
||||
if (email && password) {
|
||||
let existingUser = await User.findOne({ where: {email:email}});
|
||||
if (existingUser) {
|
||||
|
||||
const { password } = req.body;
|
||||
let email = req.body.email.toLowerCase();
|
||||
let match = await bcrypt.compare(password,existingUser.password);
|
||||
|
||||
// If one of the fields is empty.
|
||||
if (!email || !password) { res.render("login",{ "error": "Invalid credentials." }); return; }
|
||||
if (match) {
|
||||
let currentDate = new Date();
|
||||
let newLogin = currentDate.toLocaleString();
|
||||
await User.update({lastLogin: newLogin}, {where: {UUID:existingUser.UUID}});
|
||||
|
||||
let user = await User.findOne({ where: { email: email }});
|
||||
req.session.user = existingUser.username;
|
||||
req.session.UUID = existingUser.UUID;
|
||||
req.session.role = existingUser.role;
|
||||
req.session.avatar = existingUser.avatar;
|
||||
|
||||
// If there is no users with that email or the password is incorrect.
|
||||
if (!user || !await bcrypt.compare(password, user.password)) {
|
||||
await Syslog.create({ username: '', uniqueID: email, event: "Login Attempt", message: "User login failed", ip: req.ip });
|
||||
res.render("login",{ "error": "Invalid credentials." });
|
||||
return;
|
||||
}
|
||||
// Log the user in.
|
||||
else {
|
||||
req.session.username = user.username;
|
||||
req.session.userID = user.userID;
|
||||
req.session.role = user.role;
|
||||
let newLogin = new Date().toLocaleString();
|
||||
await User.update({ lastLogin: newLogin }, { where: { email: email } });
|
||||
const syslog = await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: email,
|
||||
event: "Successful Login",
|
||||
message: "User logged in successfully",
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
|
||||
res.redirect("/dashboard");
|
||||
} else {
|
||||
|
||||
console.log(`${req.session.username} logged in`);
|
||||
const syslog = await Syslog.create({
|
||||
user: null,
|
||||
email: email,
|
||||
event: "Bad Login",
|
||||
message: "Invalid password",
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
|
||||
await Syslog.create({ username: user.username, uniqueID: email, event: "Login", message: "User logged in", ip: req.ip });
|
||||
res.redirect("/dashboard");
|
||||
return;
|
||||
res.render("login",{
|
||||
"error":"Invalid password",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.render("login",{
|
||||
"error":"User with that email does not exist.",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(400);
|
||||
res.render("login",{
|
||||
"error":"Please fill in all the fields.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const Logout = async function(req,res){
|
||||
console.log(`User ${req.session.username} logged out \n`);
|
||||
await Syslog.create({ username: req.session.username, uniqueID: req.session.userID, event: "Logout", message: "User logged out", ip: req.ip });
|
||||
export const Logout = function(req,res){
|
||||
req.session.destroy(() => {
|
||||
res.redirect("/login");
|
||||
});
|
||||
|
|
|
@ -1,30 +1,44 @@
|
|||
import { Alert, getLanguage, Navbar, Footer } from '../utils/system.js';
|
||||
import { networkList, GetContainerLists, removeNetwork } from '../utils/docker.js';
|
||||
import { docker } from '../server.js';
|
||||
|
||||
|
||||
export const Networks = async function(req, res) {
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
|
||||
let container_networks = [];
|
||||
let network_name = '';
|
||||
|
||||
let containers = await GetContainerLists();
|
||||
|
||||
// List all containers
|
||||
let containers = await docker.listContainers({ all: true });
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
|
||||
try { network_name += containers[i].HostConfig.NetworkMode; } catch {}
|
||||
try { container_networks.push(containers[i].NetworkSettings.Networks[network_name].NetworkID); } catch {}
|
||||
let network_name = containers[i].HostConfig.NetworkMode;
|
||||
try { container_networks.push(containers[i].NetworkSettings.Networks[network_name].NetworkID) } catch {}
|
||||
}
|
||||
|
||||
let networks = await networkList();
|
||||
|
||||
let network_list = '';
|
||||
let networks = await docker.listNetworks({ all: true });
|
||||
|
||||
let network_list = `
|
||||
<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><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">`
|
||||
|
||||
|
||||
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 status = '';
|
||||
// Check if the network is in use
|
||||
try { if (container_networks.includes(networks[i].Id)) { status = `In use`; } } catch {}
|
||||
// Create the row for the network entry
|
||||
if (container_networks.includes(networks[i].Id)) {
|
||||
status = `In use`;
|
||||
}
|
||||
|
||||
let details = `
|
||||
<tr>
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="${networks[i].Id}" type="checkbox" aria-label="Select"></td>
|
||||
|
@ -32,57 +46,44 @@ export const Networks = async function(req, res) {
|
|||
<td class="sort-city">${networks[i].Id}</td>
|
||||
<td class="sort-score text-green">${status}</td>
|
||||
<td class="sort-date" data-date="1628122643">${networks[i].Created}</td>
|
||||
<td class=""><button class="badge badge-outline text-grey" id="" data-hx-get="/users/usersModals/user/" hx-target="#modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Details</button></td>
|
||||
<td class="text-end"><a class="btn" href="#">Details</a></td>
|
||||
</tr>`
|
||||
// Add the row to the network list
|
||||
network_list += details;
|
||||
}
|
||||
|
||||
network_list += `</tbody>`
|
||||
|
||||
res.render("networks",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
res.render("networks", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
network_count: '',
|
||||
avatar: req.session.user.charAt(0).toUpperCase(),
|
||||
network_list: network_list,
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
network_count: networks.length,
|
||||
alert: '',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const NetworkAction = async function(req,res){
|
||||
|
||||
// let trigger_name = req.header('hx-trigger-name');
|
||||
// let trigger_id = req.header('hx-trigger');
|
||||
// console.log(`trigger_name: ${trigger_name} - trigger_id: ${trigger_id}`);
|
||||
// console.log(req.body);
|
||||
|
||||
|
||||
// Grab the list of networks
|
||||
export const removeNetwork = async function(req, res) {
|
||||
let networks = req.body.select;
|
||||
// Make sure the value is an array
|
||||
if (typeof(networks) == 'string') { networks = [networks]; }
|
||||
// Loop through the array
|
||||
|
||||
if (typeof(networks) == 'string') {
|
||||
networks = [networks];
|
||||
}
|
||||
|
||||
for (let i = 0; i < networks.length; i++) {
|
||||
|
||||
if (networks[i] != 'on') {
|
||||
try {
|
||||
await removeNetwork(networks[i]);
|
||||
console.log(`Network removed: ${networks[i]}`);
|
||||
}
|
||||
catch {
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const searchNetworks = async function (req, res) {
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
389
controllers/portal.js
Normal file
389
controllers/portal.js
Normal file
|
@ -0,0 +1,389 @@
|
|||
import { Readable } from 'stream';
|
||||
import { Permission, Container, User } from '../database/models.js';
|
||||
import { docker } from '../server.js';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
let hidden = '';
|
||||
|
||||
// The actual page
|
||||
export const Portal = (req, res) => {
|
||||
let name = req.session.user;
|
||||
let role = req.session.role;
|
||||
let avatar = name.charAt(0).toUpperCase();
|
||||
|
||||
res.render("portal", {
|
||||
name: name,
|
||||
avatar: avatar,
|
||||
role: role,
|
||||
alert: '',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function CardList () {
|
||||
let name = req.session.user;
|
||||
let containers = await Permission.findAll({ attributes: ['containerName'], where: { user: name }});
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
let details = await containerInfo(containers[i].containerName);
|
||||
let card = await createCard(details);
|
||||
cardList += card;
|
||||
}
|
||||
}
|
||||
|
||||
export const UserContainers = async (req, res) => {
|
||||
let cardList = '';
|
||||
let name = req.session.user;
|
||||
let containers = await Permission.findAll({ attributes: ['containerName'], where: { user: name }});
|
||||
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
if (containers[i].containerName == null) { continue; }
|
||||
let details = await containerInfo(containers[i].containerName);
|
||||
let card = await createCard(details);
|
||||
cardList += card;
|
||||
}
|
||||
res.send(cardList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function containerInfo (containerName) {
|
||||
let container = docker.getContainer(containerName);
|
||||
let info = await container.inspect();
|
||||
let image = info.Config.Image.split('/');
|
||||
let ports_list = [];
|
||||
try {
|
||||
for (const [key, value] of Object.entries(info.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {
|
||||
// no exposed ports
|
||||
}
|
||||
|
||||
let external = 0;
|
||||
let internal = 0;
|
||||
try {
|
||||
external = ports_list[0].external;
|
||||
internal = ports_list[0].internal;
|
||||
} catch {
|
||||
// no exposed ports
|
||||
}
|
||||
|
||||
|
||||
let details = {
|
||||
name: containerName,
|
||||
image: image,
|
||||
service: image[image.length - 1].split(':')[0],
|
||||
state: info.State.Status,
|
||||
external_port: external,
|
||||
internal_port: internal,
|
||||
ports: ports_list,
|
||||
link: 'localhost',
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
async function createCard (details) {
|
||||
if (hidden.includes(details.name)) { return;}
|
||||
let shortname = details.name.slice(0, 10) + '...';
|
||||
let trigger = 'data-hx-trigger="load, every 3s"';
|
||||
let state = details.state;
|
||||
let state_color = '';
|
||||
switch (state) {
|
||||
case 'running':
|
||||
state_color = 'green';
|
||||
break;
|
||||
case 'exited':
|
||||
state = 'stopped';
|
||||
state_color = 'red';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
case 'paused':
|
||||
state_color = 'orange';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
case 'installing':
|
||||
state_color = 'blue';
|
||||
trigger = 'data-hx-trigger="load"';
|
||||
break;
|
||||
}
|
||||
// if (name.startsWith('dweebui')) { disable = 'disabled=""'; }
|
||||
let card = readFileSync('./views/partials/containerSimple.html', 'utf8');
|
||||
card = card.replace(/AppName/g, details.name);
|
||||
card = card.replace(/AppShortName/g, shortname);
|
||||
card = card.replace(/AppIcon/g, details.service);
|
||||
card = card.replace(/AppState/g, state);
|
||||
card = card.replace(/StateColor/g, state_color);
|
||||
card = card.replace(/ExternalPort/g, details.external_port);
|
||||
card = card.replace(/InternalPort/g, details.internal_port);
|
||||
card = card.replace(/ChartName/g, details.name.replace(/-/g, ''));
|
||||
card = card.replace(/AppNameState/g, `${details.name}State`);
|
||||
card = card.replace(/data-trigger=""/, trigger);
|
||||
return card;
|
||||
}
|
||||
|
||||
|
||||
let [ cardList, newCards, containersArray, sentArray, updatesArray ] = [ '', '', [], [], [] ];
|
||||
|
||||
export async function addCard (name, state) {
|
||||
console.log(`Adding card for ${name}: ${state}`);
|
||||
|
||||
let details = {
|
||||
name: name,
|
||||
image: name,
|
||||
service: name,
|
||||
state: 'installing',
|
||||
external_port: 0,
|
||||
internal_port: 0,
|
||||
ports: [],
|
||||
link: 'localhost',
|
||||
|
||||
}
|
||||
createCard(details).then(card => {
|
||||
cardList += card;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// HTMX server-side events
|
||||
export const SSE = async (req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
|
||||
|
||||
let eventCheck = setInterval(async () => {
|
||||
// builds array of containers and their states
|
||||
containersArray = [];
|
||||
await docker.listContainers({ all: true }).then(containers => {
|
||||
containers.forEach(container => {
|
||||
let name = container.Names[0].replace('/', '');
|
||||
if (!hidden.includes(name)) { // if not hidden
|
||||
containersArray.push({ container: name, state: container.State });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if ((JSON.stringify(containersArray) !== JSON.stringify(sentArray))) {
|
||||
cardList = '';
|
||||
newCards = '';
|
||||
containersArray.forEach(container => {
|
||||
const { container: containerName, state } = container;
|
||||
const existingContainer = sentArray.find(c => c.container === containerName);
|
||||
if (!existingContainer) {
|
||||
containerInfo(containerName).then(details => {
|
||||
createCard(details).then(card => {
|
||||
newCards += card;
|
||||
});
|
||||
});
|
||||
res.write(`event: update\n`);
|
||||
res.write(`data: 'update cards'\n\n`);
|
||||
} else if (existingContainer.state !== state) {
|
||||
updatesArray.push(containerName);
|
||||
}
|
||||
containerInfo(containerName).then(details => {
|
||||
createCard(details).then(card => {
|
||||
cardList += card;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sentArray.forEach(container => {
|
||||
const { container: containerName } = container;
|
||||
const existingContainer = containersArray.find(c => c.container === containerName);
|
||||
if (!existingContainer) {
|
||||
updatesArray.push(containerName);
|
||||
}
|
||||
});
|
||||
|
||||
for (let i = 0; i < updatesArray.length; i++) {
|
||||
res.write(`event: ${updatesArray[i]}\n`);
|
||||
res.write(`data: 'update cards'\n\n`);
|
||||
}
|
||||
updatesArray = [];
|
||||
sentArray = containersArray.slice();
|
||||
}
|
||||
|
||||
}, 500);
|
||||
|
||||
|
||||
req.on('close', () => {
|
||||
clearInterval(eventCheck);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const updateCards = async (req, res) => {
|
||||
console.log('updateCards called');
|
||||
res.send(newCards);
|
||||
newCards = '';
|
||||
}
|
||||
|
||||
|
||||
export const Containers = async (req, res) => {
|
||||
CardList();
|
||||
// res.send(cardList);
|
||||
}
|
||||
|
||||
export const Card = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
console.log(`${name} requesting updated card`);
|
||||
// return nothing if in hidden or not found in containersArray
|
||||
if (hidden.includes(name) || !containersArray.find(c => c.container === name)) {
|
||||
res.send('');
|
||||
return;
|
||||
} else {
|
||||
let details = await containerInfo(name);
|
||||
let card = await createCard(details);
|
||||
res.send(card);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 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> `)
|
||||
});
|
||||
}
|
||||
|
||||
export const Action = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let state = req.header('hx-trigger');
|
||||
let action = req.params.action;
|
||||
// Start
|
||||
if ((action == 'start') && (state == 'stopped')) {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.start();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'start') && (state == 'paused')) {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.unpause();
|
||||
res.send(status('starting'));
|
||||
// Stop
|
||||
} else if ((action == 'stop') && (state != 'stopped')) {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.stop();
|
||||
res.send(status('stopping'));
|
||||
// Pause
|
||||
} else if ((action == 'pause') && (state == 'paused')) {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.unpause();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'pause') && (state == 'running')) {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.pause();
|
||||
res.send(status('pausing'));
|
||||
// Restart
|
||||
} else if (action == 'restart') {
|
||||
var containerName = docker.getContainer(name);
|
||||
containerName.restart();
|
||||
res.send(status('restarting'));
|
||||
// Hide
|
||||
} else if (action == 'hide') {
|
||||
let exists = await Container.findOne({ where: {name: name}});
|
||||
if (!exists) {
|
||||
const newContainer = await Container.create({ name: name, visibility: false, });
|
||||
} else {
|
||||
exists.update({ visibility: false });
|
||||
}
|
||||
hidden = await Container.findAll({ where: {visibility:false}});
|
||||
hidden = hidden.map((container) => container.name);
|
||||
res.send("ok");
|
||||
// Reset View
|
||||
} else if (action == 'reset') {
|
||||
await Container.update({ visibility: true }, { where: {} });
|
||||
hidden = await Container.findAll({ where: {visibility:false}});
|
||||
hidden = hidden.map((container) => container.name);
|
||||
res.send("ok");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const Modals = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let id = req.header('hx-trigger');
|
||||
let title = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
|
||||
if (id == 'permissions') {
|
||||
let permissions_list = '';
|
||||
let permissions_modal = readFileSync('./views/modals/permissions.html', 'utf8');
|
||||
permissions_modal = permissions_modal.replace(/PermissionsTitle/g, title);
|
||||
let users = await User.findAll({ attributes: ['username', 'UUID']});
|
||||
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
let user_permissions = readFileSync('./views/partials/user_permissions.html', 'utf8');
|
||||
let exists = await Permission.findOne({ where: {containerName: name, user: users[i].username}});
|
||||
if (!exists) {
|
||||
const newPermission = await Permission.create({ containerName: name, user: users[i].username, userID: users[i].UUID});
|
||||
}
|
||||
|
||||
let permissions = await Permission.findOne({ where: {containerName: name, user: users[i].username}});
|
||||
if (permissions.uninstall == true) { user_permissions = user_permissions.replace(/data-UninstallCheck/g, 'checked'); }
|
||||
if (permissions.edit == true) { user_permissions = user_permissions.replace(/data-EditCheck/g, 'checked'); }
|
||||
if (permissions.upgrade == true) { user_permissions = user_permissions.replace(/data-UpgradeCheck/g, 'checked'); }
|
||||
if (permissions.start == true) { user_permissions = user_permissions.replace(/data-StartCheck/g, 'checked'); }
|
||||
if (permissions.stop == true) { user_permissions = user_permissions.replace(/data-StopCheck/g, 'checked'); }
|
||||
if (permissions.pause == true) { user_permissions = user_permissions.replace(/data-PauseCheck/g, 'checked'); }
|
||||
if (permissions.restart == true) { user_permissions = user_permissions.replace(/data-RestartCheck/g, 'checked'); }
|
||||
if (permissions.logs == true) { user_permissions = user_permissions.replace(/data-LogsCheck/g, 'checked'); }
|
||||
|
||||
user_permissions = user_permissions.replace(/EntryNumber/g, i);
|
||||
user_permissions = user_permissions.replace(/PermissionsUsername/g, users[i].username);
|
||||
user_permissions = user_permissions.replace(/PermissionsContainer/g, name);
|
||||
|
||||
permissions_list += user_permissions;
|
||||
}
|
||||
|
||||
permissions_modal = permissions_modal.replace(/PermissionsList/g, permissions_list);
|
||||
res.send(permissions_modal);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (id == 'uninstall') {
|
||||
let modal = readFileSync('./views/modals/uninstall.html', 'utf8');
|
||||
modal = modal.replace(/AppName/g, name);
|
||||
// let containerPermissions = await Permission.findAll({ where: {containerName: name}});
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
let modal = readFileSync('./views/modals/details.html', 'utf8');
|
||||
let details = await containerInfo(name);
|
||||
|
||||
modal = modal.replace(/AppName/g, details.name);
|
||||
modal = modal.replace(/AppImage/g, details.image);
|
||||
res.send(modal);
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import { ServerSettings, User } from '../db/config.js';
|
||||
import { Alert, getLanguage, Navbar, Sidebar, Footer, Capitalize } from '../utils/system.js';
|
||||
|
||||
export const Preferences = async function(req,res){
|
||||
|
||||
let language = await getLanguage(req.session.userID);
|
||||
let Language = Capitalize(language);
|
||||
let selected = `<option value="${language}" selected hidden>${Language}</option>`;
|
||||
|
||||
let user = await User.findOne({ where: { userID: req.session.userID }});
|
||||
let preferences = JSON.parse(user.preferences);
|
||||
let hide_profile = preferences.hide_profile;
|
||||
let checked = ''; if (hide_profile == true) { checked = 'checked'; }
|
||||
|
||||
res.render("preferences",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
sidebar: await Sidebar(req),
|
||||
footer: await Footer(req),
|
||||
selected: selected,
|
||||
hide_profile: checked,
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const submitPreferences = async function(req,res){
|
||||
|
||||
let { language_input, hidden_input, check_languages } = req.body;
|
||||
|
||||
if (hidden_input == 'on') { hidden_input = true; } else { hidden_input = false; }
|
||||
|
||||
let user_preferences = {
|
||||
hide_profile: hidden_input,
|
||||
};
|
||||
|
||||
if (language_input != undefined && hidden_input != undefined) {
|
||||
await User.update({ preferences: JSON.stringify(user_preferences), language: language_input }, { where: { userID: req.session.userID }});
|
||||
}
|
||||
res.redirect('/preferences');
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const searchPreferences = async function (req, res) {
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
|
@ -1,113 +1,104 @@
|
|||
import bcrypt from "bcrypt";
|
||||
import { Op } from "sequelize";
|
||||
import { User, ServerSettings, Permission, Syslog } from "../db/config.js";
|
||||
import { User, Syslog, Permission } from '../database/models.js';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
let SECRET = process.env.SECRET || "MrWiskers"
|
||||
|
||||
export const Register = async function(req,res){
|
||||
|
||||
if (req.session.username) { res.redirect("/dashboard"); }
|
||||
|
||||
let secret_input = '';
|
||||
let user_registration = await ServerSettings.findOne({ where: { key: 'user_registration' }});
|
||||
if (user_registration == null ) { user_registration = false; }
|
||||
else { user_registration = user_registration.value; }
|
||||
|
||||
if (user_registration) {
|
||||
secret_input = `<div class="mb-3"><label class="form-label">Secret</label>
|
||||
<div class="input-group input-group-flat">
|
||||
<input type="text" class="form-control" autocomplete="off" name="registration_secret">
|
||||
</div>
|
||||
</div>`}
|
||||
|
||||
// If there are no users, or registration has been enabled, display the registration page.
|
||||
if ((await User.count() == 0) || (user_registration)) {
|
||||
res.render("register",{
|
||||
"error": "",
|
||||
"reg_secret": secret_input,
|
||||
});
|
||||
export const Register = function(req,res){
|
||||
if(req.session.user){
|
||||
res.redirect("/logout");
|
||||
} else {
|
||||
res.render("login", {
|
||||
"error": "User registration is disabled."
|
||||
res.render("register",{
|
||||
"error":"",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const submitRegister = async function(req,res){
|
||||
|
||||
const { name, username, password, confirm, secret } = req.body;
|
||||
let email = req.body.email.toLowerCase();
|
||||
let { name, username, email, password, confirmPassword, secret } = req.body;
|
||||
email = email.toLowerCase();
|
||||
|
||||
let registration_secret = await ServerSettings.findOne({ where: { key: 'registration_secret' }}).value;
|
||||
|
||||
let error = '';
|
||||
if (!name || !username || !email || !password || !confirm) { error = "All fields are required"; }
|
||||
else if (password !== confirm) { error = "Passwords do not match"; }
|
||||
|
||||
else if (registration_secret && secret !== registration_secret) {
|
||||
error = "Invalid secret";
|
||||
await Syslog.create({ username: user.username, uniqueID: email, event: "Failed Registration", message: "Invalid Secret", ip: req.ip });
|
||||
if (secret != SECRET) {
|
||||
const syslog = await Syslog.create({
|
||||
user: username,
|
||||
email: email,
|
||||
event: "Failed Registration",
|
||||
message: "Invalid secret",
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
}
|
||||
|
||||
else if (await User.findOne({ where: { [Op.or]: [{ username: username }, { email: email }] }})) {
|
||||
error = "Username or email already exists";
|
||||
await Syslog.create({ username: username, uniqueID: email, event: "Failed Registration", message: "Username or email already exists", ip: req.ip });
|
||||
}
|
||||
if((name && email && password && confirmPassword && username) && (secret == SECRET) && (password == confirmPassword)){
|
||||
|
||||
if (error != '') {
|
||||
|
||||
let secret_input = '';
|
||||
let user_registration = await ServerSettings.findOne({ where: { key: 'user_registration' }});
|
||||
if (user_registration == null ) { user_registration = false; }
|
||||
else { user_registration = user_registration.value; }
|
||||
|
||||
if (user_registration) {
|
||||
secret_input = `<div class="mb-3"><label class="form-label">Secret</label>
|
||||
<div class="input-group input-group-flat">
|
||||
<input type="text" class="form-control" autocomplete="off" name="registration_secret">
|
||||
</div>
|
||||
</div>`}
|
||||
async function userRole () {
|
||||
let userCount = await User.count();
|
||||
if(userCount == 0){
|
||||
return "admin";
|
||||
}else{
|
||||
return "user";
|
||||
}
|
||||
}
|
||||
|
||||
res.render("register", {
|
||||
"error": error,
|
||||
"reg_secret": secret_input,
|
||||
});
|
||||
return;
|
||||
}
|
||||
let existingUser = await User.findOne({ where: {email:email}});
|
||||
if(!existingUser){
|
||||
|
||||
try {
|
||||
let currentDate = new Date();
|
||||
let newLogin = currentDate.toLocaleString();
|
||||
|
||||
// Returns 'admin' if no users have been created.
|
||||
async function Role() {
|
||||
if (await User.count() == 0) { return "admin"; }
|
||||
else { return "user"; }
|
||||
}
|
||||
const user = await User.create({
|
||||
name: name,
|
||||
username: username,
|
||||
email: email,
|
||||
password: bcrypt.hashSync(password,10),
|
||||
role: await userRole(),
|
||||
group: 'all',
|
||||
lastLogin: newLogin,
|
||||
});
|
||||
|
||||
// Create the user.
|
||||
await User.create({
|
||||
name: name,
|
||||
username: username,
|
||||
email: email,
|
||||
password: bcrypt.hashSync(password, 10),
|
||||
role: await Role(),
|
||||
preferences: JSON.stringify({ hidden_profile: false }),
|
||||
lastLogin: new Date().toLocaleString(),
|
||||
});
|
||||
// make sure the user was created and get the UUID.
|
||||
let newUser = await User.findOne({ where: {email:email}});
|
||||
let match = await bcrypt.compare(password,newUser.password);
|
||||
|
||||
// Make sure the user was created and get the UUID.
|
||||
let user = await User.findOne({ where: { email: email }});
|
||||
let match = await bcrypt.compare(password, user.password);
|
||||
if (match) {
|
||||
req.session.username = user.username;
|
||||
req.session.userID = user.userID;
|
||||
req.session.role = user.role;
|
||||
|
||||
await Syslog.create({ username: user.username, uniqueID: user.email, event: "Registration", message: "User created", ip: req.ip });
|
||||
if(match){
|
||||
req.session.user = newUser.username;
|
||||
req.session.UUID = newUser.UUID;
|
||||
req.session.role = newUser.role;
|
||||
|
||||
console.log(`User ${username} created`);
|
||||
const permission = await Permission.create({
|
||||
user: newUser.username,
|
||||
userID: newUser.UUID
|
||||
});
|
||||
|
||||
res.redirect("/dashboard");
|
||||
const syslog = await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: email,
|
||||
event: "Successful Registration",
|
||||
message: "User registered successfully",
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
|
||||
res.redirect("/dashboard");
|
||||
}
|
||||
} catch(err) {
|
||||
res.render("register",{
|
||||
"error":"Something went wrong when creating account.",
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
// return an error.
|
||||
res.render("register",{
|
||||
"error":"User with that email already exists.",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await Syslog.create({ username: user.username, uniqueID: user.email, event: "Failed Registration", message: "Error. User not created", ip: req.ip });
|
||||
res.render("register", { "error": "Error. User not created" });
|
||||
// Redirect to the signup page.
|
||||
res.render("register",{
|
||||
"error":"Please fill in all the fields.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,252 +1,10 @@
|
|||
import { ServerSettings } from '../db/config.js';
|
||||
import { configureHost } from '../utils/docker.js';
|
||||
import { Alert, Navbar, Sidebar, Footer } from '../utils/system.js';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
|
||||
export const Settings = async function(req,res){
|
||||
export const Settings = (req, res) => {
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let user_registration = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||
let registration_secret = await ServerSettings.findOne({ where: {key: 'registration_secret'}});
|
||||
|
||||
let authentication = await ServerSettings.findOne({ where: {key: 'authentication'}}) || { value: 'default' };
|
||||
|
||||
let user_registration_enabled = '';
|
||||
try { if (user_registration.value == true) { user_registration_enabled = 'checked'; } } catch { }
|
||||
|
||||
let registration_secret_value = '';
|
||||
try { registration_secret_value = registration_secret.value; } catch { }
|
||||
|
||||
let custom_link = await ServerSettings.findOne({ where: {key: 'custom_link'}});
|
||||
let link_url = await ServerSettings.findOne({ where: {key: 'link_url'}});
|
||||
|
||||
let custom_link_enabled = '';
|
||||
try { if (custom_link.value == true) { custom_link_enabled = 'checked'; } } catch { }
|
||||
|
||||
let link_url_value = '';
|
||||
try { link_url_value = link_url.value; } catch { }
|
||||
|
||||
|
||||
let host2 = await ServerSettings.findOne({ where: {key: 'host2'}});
|
||||
let host3 = await ServerSettings.findOne({ where: {key: 'host3'}});
|
||||
let host4 = await ServerSettings.findOne({ where: {key: 'host4'}});
|
||||
|
||||
let [host2_toggle, host2_tag, host2_ip, host2_port] = ['', '', '', ''];
|
||||
let [host3_toggle, host3_tag, host3_ip, host3_port] = ['', '', '', ''];
|
||||
let [host4_toggle, host4_tag, host4_ip, host4_port] = ['', '', '', ''];
|
||||
|
||||
if (host2.value) { host2_toggle = 'checked'; [host2_tag, host2_ip, host2_port] = host2.value.split(','); }
|
||||
if (host3.value) { host3_toggle = 'checked'; [host3_tag, host3_ip, host3_port] = host3.value.split(','); }
|
||||
if (host4.value) { host4_toggle = 'checked'; [host4_tag, host4_ip, host4_port] = host4.value.split(','); }
|
||||
|
||||
|
||||
res.render("settings",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
res.render("settings", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
user_registration: user_registration_enabled,
|
||||
registration_secret: registration_secret_value,
|
||||
custom_link: custom_link_enabled,
|
||||
link_url: link_url_value,
|
||||
authentication: authentication.value,
|
||||
host2_toggle: host2_toggle,
|
||||
host2_tag: host2_tag,
|
||||
host2_ip: host2_ip,
|
||||
host2_port: host2_port,
|
||||
host3_toggle: host3_toggle,
|
||||
host3_tag: host3_tag,
|
||||
host3_ip: host3_ip,
|
||||
host3_port: host3_port,
|
||||
host4_toggle: host4_toggle,
|
||||
host4_tag: host4_tag,
|
||||
host4_ip: host4_ip,
|
||||
host4_port: host4_port,
|
||||
selected: 'english',
|
||||
navbar: await Navbar(req),
|
||||
sidebar: await Sidebar(req),
|
||||
footer: await Footer(req),
|
||||
avatar: req.session.user.charAt(0).toUpperCase(),
|
||||
alert: '',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const SettingsAction = async function (req, res) {
|
||||
|
||||
let action = req.params.action;
|
||||
let id = req.params.id;
|
||||
|
||||
let { user_registration, registration_secret, custom_link, link_url, authentication } = req.body;
|
||||
let { host2, tag2, ip2, port2 } = req.body;
|
||||
let { host3, tag3, ip3, port3 } = req.body;
|
||||
let { host4, tag4, ip4, port4 } = req.body;
|
||||
|
||||
if (tag2 == '') { tag2 = 'Host 2'; }
|
||||
if (tag3 == '') { tag3 = 'Host 3'; }
|
||||
if (tag4 == '') { tag4 = 'Host 4'; }
|
||||
|
||||
let trigger_name = req.header('hx-trigger-name');
|
||||
let trigger_id = req.header('hx-trigger');
|
||||
|
||||
// If the trigger is 'submit', return the button
|
||||
if (trigger_id == 'submit'){
|
||||
res.send(`<button class="btn btn-primary" id="submit" form="settings">Update</button>`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Continues on if the trigger is 'settings'
|
||||
|
||||
// Custom link
|
||||
if (custom_link) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'custom_link'}});
|
||||
if (exists) { await ServerSettings.update({value: true}, {where: {key: 'custom_link'}}); }
|
||||
else { await ServerSettings.create({ key: 'custom_link', value: true}); }
|
||||
|
||||
let exists2 = await ServerSettings.findOne({ where: {key: 'link_url'}});
|
||||
if (exists2) { await ServerSettings.update({value: link_url}, {where: {key: 'link_url'}}); }
|
||||
else { await ServerSettings.create({ key: 'link_url', value: link_url}); }
|
||||
|
||||
|
||||
} else if (!custom_link) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'custom_link'}});
|
||||
if (exists) { await ServerSettings.update({value: false}, {where: {key: 'custom_link'}}); }
|
||||
else { await ServerSettings.create({ key: 'custom_link', value: false}); }
|
||||
|
||||
let exists2 = await ServerSettings.findOne({ where: {key: 'link_url'}});
|
||||
if (exists2) { await ServerSettings.update({value: 'http://localhost'}, {where: {key: 'link_url'}}); }
|
||||
else { await ServerSettings.create({ key: 'link_url', value: 'http://localhost'}); }
|
||||
}
|
||||
|
||||
// User registration
|
||||
if (user_registration) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||
if (exists) { const setting = await ServerSettings.update({value: true}, {where: {key: 'user_registration'}}); }
|
||||
else { const newSetting = await ServerSettings.create({ key: 'user_registration', value: true}); }
|
||||
|
||||
let exists2 = await ServerSettings.findOne({ where: {key: 'registration_secret'}});
|
||||
if (exists2) { await ServerSettings.update({value: registration_secret}, {where: {key: 'registration_secret'}}); }
|
||||
else { await ServerSettings.create({ key: 'registration_secret', value: registration_secret}); }
|
||||
|
||||
|
||||
} else if (!user_registration) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||
if (exists) { await ServerSettings.update({value: false}, {where: {key: 'user_registration'}}); }
|
||||
else { await ServerSettings.create({ key: 'user_registration', value: false}); }
|
||||
|
||||
let exists2 = await ServerSettings.findOne({ where: {key: 'registration_secret'}});
|
||||
if (exists2) { await ServerSettings.update({value: ''}, {where: {key: 'registration_secret'}}); }
|
||||
else { await ServerSettings.create({ key: 'registration_secret', value: ''}); }
|
||||
}
|
||||
|
||||
// Authentication
|
||||
if (authentication) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'authentication'}});
|
||||
if (exists) { await ServerSettings.update({value: authentication}, {where: {key: 'authentication'}}); }
|
||||
else { await ServerSettings.create({ key: 'authentication', value: authentication}); }
|
||||
} else if (!authentication) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'authentication'}});
|
||||
if (exists) { await ServerSettings.update({value: 'default'}, {where: {key: 'authentication'}}); }
|
||||
else { await ServerSettings.create({ key: 'authentication', value: 'off'}); }
|
||||
}
|
||||
|
||||
|
||||
// Host 2
|
||||
if (host2) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host2'}});
|
||||
if (exists) { await ServerSettings.update({value: `${tag2},${ip2},${port2}`}, {where: {key: 'host2'}}); }
|
||||
else { await ServerSettings.create({ key: 'host2', value: `${tag2},${ip2},${port2}`}); }
|
||||
configureHost(2, ip2, port2);
|
||||
} else if (!host2) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host2'}});
|
||||
if (exists) { await ServerSettings.update({value: ''}, {where: {key: 'host2'}}); }
|
||||
else { await ServerSettings.create({ key: 'host2', value: ''}); }
|
||||
}
|
||||
|
||||
// Host 3
|
||||
if (host3) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host3'}});
|
||||
if (exists) { await ServerSettings.update({value: `${tag3},${ip3},${port3}`}, {where: {key: 'host3'}}); }
|
||||
else { await ServerSettings.create({ key: 'host3', value: `${tag3},${ip3},${port3}`}); }
|
||||
configureHost(3, ip3, port3);
|
||||
} else if (!host3) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host3'}});
|
||||
if (exists) { await ServerSettings.update({value: ''}, {where: {key: 'host3'}}); }
|
||||
else { await ServerSettings.create({ key: 'host3', value: ''}); }
|
||||
}
|
||||
|
||||
// Host 4
|
||||
if (host4) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host4'}});
|
||||
if (exists) { await ServerSettings.update({value: `${tag4},${ip4},${port4}`}, {where: {key: 'host4'}}); }
|
||||
else { await ServerSettings.create({ key: 'host4', value: `${tag4},${ip4},${port4}`}); }
|
||||
configureHost(4, ip4, port4);
|
||||
} else if (!host4) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host4'}});
|
||||
if (exists) { await ServerSettings.update({value: ''}, {where: {key: 'host4'}}); }
|
||||
else { await ServerSettings.create({ key: 'host4', value: ''}); }
|
||||
}
|
||||
|
||||
console.log('Settings updated');
|
||||
res.send(`<button class="btn btn-success" hx-post="/settings/action/update_settings/0" hx-trigger="load delay:2s" hx-swap="outerHTML" id="submit" hx-target="#submit">Updated</button>`);
|
||||
}
|
||||
|
||||
|
||||
|
||||
let inProgress = false;
|
||||
export const updateLanguages = async function(req,res){
|
||||
|
||||
let trigger_id = req.header('hx-trigger');
|
||||
|
||||
if (inProgress == true) {
|
||||
console.log('Language update still in progress');
|
||||
res.send('<button class="btn" aria-label="button" id="checking" hx-post="/update_languages" hx-swap="outerHTML" hx-target="#checking" hx-trigger="every 2s">Checking For Updates<div class="mx-2 spinner-border spinner-border-sm"></div></button>');
|
||||
return;
|
||||
}
|
||||
|
||||
if (trigger_id == 'check_languages') {
|
||||
|
||||
inProgress = true;
|
||||
res.send('<button class="btn" aria-label="button" id="checking" hx-post="/update_languages" hx-swap="outerHTML" hx-target="#checking" hx-trigger="every 2s">Checking For Updates<div class="mx-2 spinner-border spinner-border-sm"></div></button>');
|
||||
|
||||
const resp = await fetch(`https://api.github.com/repos/lllllllillllllillll/DweebUI/contents/languages?ref=dev`);
|
||||
const data = await resp.json();
|
||||
let languages = [];
|
||||
data.forEach((lang) => {
|
||||
languages.push({ language: lang.name, download_url: lang.download_url });
|
||||
});
|
||||
|
||||
for (let i = 0; i < languages.length; i++) {
|
||||
let language_dev = await fetch(languages[i].download_url);
|
||||
language_dev = await language_dev.text();
|
||||
|
||||
let language_local = readFileSync(`./languages/${languages[i].language}`, 'utf8');
|
||||
|
||||
if (language_dev != language_local) {
|
||||
console.log(`\x1b[31mLanguage: ${languages[i].language} is out of date.\x1b[0m`);
|
||||
console.log(`\x1b[31mUpdating ${languages[i].language}...\x1b[0m`);
|
||||
writeFileSync(`./languages/${languages[i].language}`, language_dev);
|
||||
console.log(`\x1b[32mLanguage: ${languages[i].language} has been updated.\x1b[0m`);
|
||||
} else {
|
||||
console.log(`\x1b[32mLanguage: ${languages[i].language} is up to date.\x1b[0m`);
|
||||
}
|
||||
}
|
||||
|
||||
inProgress = false;
|
||||
console.log('Language update complete');
|
||||
|
||||
return;
|
||||
} else {
|
||||
if ((trigger_id == "checking") && (inProgress == false)) {
|
||||
res.send('<button class="btn" aria-label="button" name="check_languages" id="check_languages" value="true" hx-post="/update_languages" hx-swap="outerHTML" hx-target="#check_languages">Update Language Files</button>');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const searchSettings = async function (req, res) {
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import { ServerSettings, User } from '../db/config.js';
|
||||
import { Alert, getLanguage, Navbar, Sidebar, Footer, Capitalize } from '../utils/system.js';
|
||||
import { readdirSync, readFileSync } from 'fs';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
export const Sponsors = async function (req, res) {
|
||||
|
||||
let language = await getLanguage(req.session.userID);
|
||||
let Language = Capitalize(language);
|
||||
let selected = `<option value="${language}" selected hidden>${Language}</option>`;
|
||||
|
||||
let user = await User.findOne({ where: { userID: req.session.userID }});
|
||||
let preferences = JSON.parse(user.preferences);
|
||||
let hide_profile = preferences.hide_profile;
|
||||
|
||||
let checked = ''; if (hide_profile == true) { checked = 'checked'; }
|
||||
|
||||
res.render("sponsors",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
sidebar: await Sidebar(req),
|
||||
footer: await Footer(req),
|
||||
selected: selected,
|
||||
hide_profile: checked,
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const searchSponsors = async function (req, res) {
|
||||
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
|
||||
let sponsored = await ServerSettings.findOne({ where: { key: 'sponsored' }});
|
||||
if (!sponsored) {
|
||||
let secret_hash = '$2b$10$2EDoqM10LbNMmSVdbrOV/.eLFlYrxBk4An02prZeqRSqRVktNi3m.';
|
||||
let correct_key = bcrypt.compareSync(req.body.search, secret_hash);
|
||||
if (correct_key) {
|
||||
await ServerSettings.create({ key: 'sponsored', value: 'true' });
|
||||
console.log('Sponsored. Thank you for your support!');
|
||||
}
|
||||
}
|
||||
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
||||
|
||||
|
31
controllers/supporters.js
Normal file
31
controllers/supporters.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { User } from "../database/models.js";
|
||||
|
||||
export const Supporters = async (req, res) => {
|
||||
|
||||
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: req.session.user.charAt(0).toUpperCase(),
|
||||
alert: '',
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
import { Syslog } from '../db/config.js';
|
||||
import { Alert, getLanguage, Navbar, Footer } from '../utils/system.js';
|
||||
import { Syslog } from '../database/models.js';
|
||||
|
||||
export const Syslogs = async function(req, res) {
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let logs = '';
|
||||
|
||||
const syslogs = await Syslog.findAll({
|
||||
|
@ -13,74 +10,28 @@ export const Syslogs = async function(req, res) {
|
|||
]
|
||||
});
|
||||
|
||||
|
||||
for (const log of syslogs) {
|
||||
let date = (log.createdAt).toDateString();
|
||||
let time = (log.createdAt).toLocaleTimeString();
|
||||
let datetime = `${time} ${date}`;
|
||||
|
||||
|
||||
// get the last 12 characters of the uniqueID
|
||||
let uniqueID = log.uniqueID;
|
||||
// if (uniqueID.length > 12) {
|
||||
// uniqueID = uniqueID.substring(uniqueID.length - 12);
|
||||
// }
|
||||
|
||||
let message = log.message;
|
||||
// if (message.length > 50) {
|
||||
// message = message.substring(0, 50) + '...';
|
||||
// }
|
||||
|
||||
logs += `<tr>
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" type="checkbox" aria-label="Select"></td>
|
||||
<td class="sort-id">${log.id}</td>
|
||||
<td class="sort-username">${log.username}</td>
|
||||
<td class="sort-uniqueid">${uniqueID}</td>
|
||||
<td class="sort-user">${log.user}</td>
|
||||
<td class="sort-email">${log.email}</td>
|
||||
<td class="sort-event">${log.event}</td>
|
||||
<td class="sort-message">${message}</td>
|
||||
<td class="sort-message">${log.message}</td>
|
||||
<td class="sort-ip">${log.ip}</td>
|
||||
<td class="sort-timestamp">${datetime}</td>
|
||||
<td class=""><a class="" href="#"><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></a></td>
|
||||
|
||||
<td class="sort-datetime">${datetime}</td>
|
||||
</tr>`
|
||||
}
|
||||
|
||||
res.render("syslogs",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
|
||||
res.render("syslogs", {
|
||||
name: req.session.user || 'Dev',
|
||||
role: req.session.role || 'Dev',
|
||||
avatar: req.session.user.charAt(0).toUpperCase(),
|
||||
logs: logs,
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const submitSyslogs = async function(req,res){
|
||||
|
||||
// console.log(req.body);
|
||||
|
||||
let trigger_name = req.header('hx-trigger-name');
|
||||
let trigger_id = req.header('hx-trigger');
|
||||
|
||||
console.log(`trigger_name: ${trigger_name} - trigger_id: ${trigger_id}`);
|
||||
|
||||
|
||||
res.render("syslogs",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const searchSyslogs = async function (req, res) {
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
|
@ -1,12 +1,21 @@
|
|||
import { User, Permission, ContainerLists, Container, ServerSettings } from '../db/config.js';
|
||||
import { Alert, Navbar, Footer } from '../utils/system.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import { User } from '../database/models.js';
|
||||
|
||||
export const Users = async function(req,res){
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let user_list = '';
|
||||
export const Users = async (req, res) => {
|
||||
|
||||
let user_list = `
|
||||
<tr>
|
||||
<th><input class="form-check-input" type="checkbox"></th>
|
||||
<th>ID</th>
|
||||
<th>Avatar</th>
|
||||
<th>Name</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>UUID</th>
|
||||
<th>Role</th>
|
||||
<th>Last Login</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>`
|
||||
|
||||
let allUsers = await User.findAll();
|
||||
allUsers.forEach((account) => {
|
||||
|
@ -21,128 +30,33 @@ export const Users = async function(req,res){
|
|||
active = '<span class="badge badge-outline text-grey" title="User has not logged-in within the last 30 days.">Inactive</span>';
|
||||
}
|
||||
|
||||
|
||||
|
||||
let info = `
|
||||
<tr>
|
||||
<td><input class="form-check-input" type="checkbox" name="select"></td>
|
||||
|
||||
<td class="sort-id">${account.id}</td>
|
||||
<td class="sort-avatar p-1"><span class="avatar avatar-sm bg-green-lt">${avatar}</span></span>
|
||||
<td class="sort-name">${account.name}</td>
|
||||
<td class="sort-username">${account.username}</td>
|
||||
<td class="sort-email">${account.email}</td>
|
||||
<td class="sort-userid">${account.userID}</td>
|
||||
<td class="sort-role">${account.role}</td>
|
||||
<td class="sort-lastlogin">${account.lastLogin}</td>
|
||||
<td class="sort-active">${active}</td>
|
||||
<td class="sort-action"><button class="badge badge-outline text-grey" id="${account.username}" data-hx-get="/users/view/user/${account.userID}" hx-target="#modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#scrolling_modal">View</button></td>
|
||||
<td><input class="form-check-input" type="checkbox"></td>
|
||||
<td>${account.id}</td>
|
||||
<td><span class="avatar avatar-sm bg-green-lt">${avatar}</span></span>
|
||||
<td>${account.name}</td>
|
||||
<td>${account.username}</td>
|
||||
<td>${account.email}</td>
|
||||
<td>${account.UUID}</td>
|
||||
<td>${account.role}</td>
|
||||
<td>${account.lastLogin}</td>
|
||||
<td>${active}</td>
|
||||
<td><a href="#" class="btn">View</a></td>
|
||||
</tr>`
|
||||
|
||||
user_list += info;
|
||||
});
|
||||
|
||||
res.render("users",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
|
||||
res.render("users", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
avatar: req.session.user.charAt(0).toUpperCase(),
|
||||
user_list: user_list,
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const submitUsers = async function(req,res){
|
||||
|
||||
// console.log(req.body);
|
||||
|
||||
let trigger_name = req.header('hx-trigger-name');
|
||||
let trigger_id = req.header('hx-trigger');
|
||||
|
||||
console.log(`trigger_name: ${trigger_name} - trigger_id: ${trigger_id}`);
|
||||
|
||||
|
||||
// [HTMX Triggered] Changes the update button.
|
||||
if(trigger_id == 'settings'){
|
||||
res.send(`<button class="btn btn-success" hx-post="/settings" hx-trigger="load delay:2s" hx-swap="outerHTML" id="submit" hx-target="#submit">Updated</button>`);
|
||||
return;
|
||||
} else if (trigger_id == 'submit'){
|
||||
res.send(`<button class="btn btn-primary" id="submit" form="settings">Update</button>`);
|
||||
return;
|
||||
}
|
||||
|
||||
res.render("users",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
alert: ''
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const searchUsers = async function (req, res) {
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const UsersView = async (req, res) => {
|
||||
|
||||
let view = req.params.view;
|
||||
let userID = req.params.id;
|
||||
let username = req.header('hx-trigger');
|
||||
|
||||
// console.log(`[view] ${view} - [userID] ${userID} - [username] ${username}`);
|
||||
|
||||
if (view == 'user') {
|
||||
let user = await User.findOne({ where: { userID: userID } });
|
||||
let modal = readFileSync('./views/partials/user.html', 'utf8');
|
||||
modal = modal.replace(/Username/g, username);
|
||||
modal = modal.replace(/USERID/g, user.userID);
|
||||
modal = modal.replace(/FullName/g, user.name);
|
||||
modal = modal.replace(/EmailAddress/g, user.email);
|
||||
modal = modal.replace(/LastLogin/g, user.lastLogin);
|
||||
modal = modal.replace(/CreatedAt/g, user.createdAt);
|
||||
res.send(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
export const UsersAction = async (req, res) => {
|
||||
|
||||
let action = req.params.action;
|
||||
let userID = req.params.id;
|
||||
let change = req.body.change;
|
||||
|
||||
console.log(`[action] ${action} [change] ${change} - [userID] ${userID}`);
|
||||
|
||||
if (change == 'remove') {
|
||||
|
||||
let container_lists = await ContainerLists.findAll({ where: { userID: userID } });
|
||||
container_lists.destroy();
|
||||
|
||||
let permissions = await Permission.findAll({ where: { userID: userID } });
|
||||
permissions.forEach(async (permission) => {
|
||||
await permission.destroy();
|
||||
});
|
||||
|
||||
let user = await User.findOne({ where: { userID: userID } });
|
||||
await user.destroy();
|
||||
|
||||
console.log(`User removed.`);
|
||||
}
|
||||
|
||||
res.redirect('/users');
|
||||
|
||||
};
|
||||
}
|
9
controllers/variables.js
Normal file
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,
|
||||
});
|
||||
}
|
|
@ -1,15 +1,26 @@
|
|||
import { Alert, getLanguage, Navbar, Footer } from '../utils/system.js';
|
||||
import { volumeList, GetContainerLists } from '../utils/docker.js';
|
||||
import { docker } from '../server.js';
|
||||
|
||||
export const Volumes = async function(req, res) {
|
||||
|
||||
req.session.host = `${req.params.host || 1}`;
|
||||
|
||||
let container_volumes = [];
|
||||
let volume_list = '';
|
||||
|
||||
// Table header
|
||||
volume_list = `<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><label class="table-sort" data-sort="sort-type">Type</label></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">`
|
||||
|
||||
// List all containers
|
||||
let containers = await GetContainerLists();
|
||||
let containers = await docker.listContainers({ all: true });
|
||||
|
||||
// Get the first 6 volumes from each container
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
|
@ -22,7 +33,7 @@ export const Volumes = async function(req, res) {
|
|||
}
|
||||
|
||||
// List ALL volumes
|
||||
let list = await volumeList();
|
||||
let list = await docker.listVolumes({ all: true });
|
||||
let volumes = list.Volumes;
|
||||
|
||||
// Create a table row for each volume
|
||||
|
@ -46,50 +57,63 @@ export const Volumes = async function(req, res) {
|
|||
<td class="sort-score text-green">${status}</td>
|
||||
<td class="sort-date" data-date="1628122643">${volume.CreatedAt}</td>
|
||||
<td class="sort-quantity">MB</td>
|
||||
<td class=""><button class="badge badge-outline text-grey" id="" data-hx-get="/users/usersModals/user/" hx-target="#modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Details</button></td>
|
||||
<td class="text-end"><a class="btn" href="#">Details</a></td>
|
||||
</tr>`
|
||||
|
||||
volume_list += row;
|
||||
}
|
||||
|
||||
res.render("volumes",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
volume_list += `</tbody>`
|
||||
|
||||
|
||||
res.render("volumes", {
|
||||
name: req.session.user,
|
||||
role: req.session.role,
|
||||
volume_count: '',
|
||||
avatar: req.session.user.charAt(0).toUpperCase(),
|
||||
volume_list: volume_list,
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const submitVolumes = async function(req,res){
|
||||
|
||||
// console.log(req.body);
|
||||
|
||||
let trigger_name = req.header('hx-trigger-name');
|
||||
let trigger_id = req.header('hx-trigger');
|
||||
|
||||
console.log(`trigger_name: ${trigger_name} - trigger_id: ${trigger_id}`);
|
||||
|
||||
|
||||
res.render("volumes",{
|
||||
volume_count: volumes.length,
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
volume_count: '',
|
||||
volume_list: '',
|
||||
navbar: await Navbar(req),
|
||||
footer: await Footer(req),
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export const addVolume = async function(req, res) {
|
||||
|
||||
let volume = req.body.volume;
|
||||
|
||||
export const searchVolumes = async function (req, res) {
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
||||
docker.createVolume({
|
||||
Name: volume
|
||||
});
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
// docker.df(volume.Name).then((data) => {
|
||||
// for (let key in data) {
|
||||
// console.log(data[key]);
|
||||
// }
|
||||
// });
|
||||
|
|
256
database/models.js
Normal file
256
database/models.js
Normal file
|
@ -0,0 +1,256 @@
|
|||
import { Sequelize, DataTypes } from 'sequelize';
|
||||
|
||||
|
||||
export const sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: './database/db.sqlite',
|
||||
logging: false,
|
||||
});
|
||||
|
||||
export const User = sequelize.define('User', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
group: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
avatar: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
lastLogin: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
UUID: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
}
|
||||
});
|
||||
|
||||
export const Container = sequelize.define('Container', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
visibility: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
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: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
containerName: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
containerID: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
user: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
userID: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
install: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
uninstall: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
edit: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
upgrade: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
start: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
stop: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
restart: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
pause: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
logs: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
hide: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
reset_view: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
view: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export const Syslog = sequelize.define('Syslog', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
user: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
event: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
ip : {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export const Notification = sequelize.define('Notification', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
icon: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
color: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
read: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
createdAt : {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
createdBy : {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export const Settings = sequelize.define('Settings', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
key: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export const Variables = sequelize.define('Variables', {
|
||||
find: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
replace: {
|
||||
type: DataTypes.STRING,
|
||||
}
|
||||
});
|
382
db/config.js
382
db/config.js
|
@ -1,382 +0,0 @@
|
|||
import session from 'express-session';
|
||||
import SessionSequelize from 'connect-session-sequelize';
|
||||
import { Sequelize, DataTypes} from 'sequelize';
|
||||
import { readFileSync } from 'fs';
|
||||
import { check_configured_hosts } from '../utils/docker.js';
|
||||
|
||||
const SECURE = process.env.HTTPS || false;
|
||||
|
||||
// Session store
|
||||
const SequelizeStore = SessionSequelize(session.Store);
|
||||
const sessionData = new Sequelize('database', 'username', 'password', {
|
||||
dialect: 'sqlite',
|
||||
storage: 'data/sessions.sqlite',
|
||||
logging: false,
|
||||
});
|
||||
const SessionStore = new SequelizeStore({ db: sessionData });
|
||||
|
||||
export const sessionMiddleware = session({
|
||||
secret: 'not keyboard cat',
|
||||
store: SessionStore,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
secure: SECURE,
|
||||
httpOnly: SECURE,
|
||||
maxAge: 3600000 * 8,
|
||||
},
|
||||
});
|
||||
|
||||
// Server settings
|
||||
const settings = new Sequelize('database', 'username', 'password', {
|
||||
dialect: 'sqlite',
|
||||
storage: 'data/settings.sqlite',
|
||||
logging: false,
|
||||
});
|
||||
const SettingsDB = new SequelizeStore({ db: settings });
|
||||
|
||||
// Display package information
|
||||
let package_info = readFileSync(`package.json`, 'utf8');
|
||||
package_info = JSON.parse(package_info);
|
||||
console.log('\n');
|
||||
console.log(`\x1b[33mDweebUI v${package_info.version}\x1b[0m`);
|
||||
console.log(`\x1b[33mAuthor: ${package_info.author}\x1b[0m`);
|
||||
console.log(`\x1b[33mLicense: ${package_info.license}\x1b[0m`);
|
||||
console.log(`\x1b[33mDescription: ${package_info.description}\x1b[0m`);
|
||||
console.log('');
|
||||
// console.log in red
|
||||
console.log('\x1b[31m * Only Docker volumes are supported. No bind mounts.\n \x1b[0m');
|
||||
console.log('\x1b[31m * Breaking changes may require you to remove the DweebUI volume and start fresh. \n \x1b[0m');
|
||||
|
||||
// Test database connection
|
||||
try {
|
||||
await sessionData.authenticate();
|
||||
await settings.authenticate();
|
||||
sessionData.sync();
|
||||
settings.sync().then(() => {
|
||||
check_configured_hosts();
|
||||
});
|
||||
console.log(`\x1b[32mDatabase connection established.\x1b[0m`);
|
||||
} catch (error) {
|
||||
console.error('\x1b[31mDatabase connection failed:', error, '\x1b[0m');
|
||||
}
|
||||
|
||||
// Models
|
||||
export const User = settings.define('User', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
userID: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
group: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
avatar: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
lastLogin: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
language: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'english'
|
||||
},
|
||||
preferences : {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
});
|
||||
|
||||
export const Permission = settings.define('Permission', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
containerName: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
containerID: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
userID: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
install: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
uninstall: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
edit: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
upgrade: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
start: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
stop: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
restart: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
pause: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
logs: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
hide: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
view: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
options: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: false
|
||||
},
|
||||
});
|
||||
|
||||
export const Syslog = settings.define('Syslog', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
uniqueID: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
event: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
ip : {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
options : {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
});
|
||||
|
||||
export const Notification = settings.define('Notification', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
icon: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
color: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
read: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
createdAt : {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
createdBy : {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
options : {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
});
|
||||
|
||||
export const ServerSettings = settings.define('ServerSettings', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
key: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export const Container = settings.define('Container', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
containerName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
containerID: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
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: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
ram: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
link: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
update: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
group: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
options: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
host: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
});
|
||||
|
||||
export const Variables = settings.define('Variables', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
key: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
options: {
|
||||
type: DataTypes.STRING
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export const ContainerLists = settings.define('ContainerLists', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
userID: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
allowNull: false
|
||||
},
|
||||
containers: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
hidden: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
visable: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
new: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
updates: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
sent: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
{
|
||||
"Dashboard": "仪表盘",
|
||||
"Images": "镜像",
|
||||
"Volumes": "存储卷",
|
||||
"Networks": "网络",
|
||||
"Apps": "应用商店",
|
||||
"Users": "用户",
|
||||
"Syslogs": "系统日志",
|
||||
"Search": "搜索",
|
||||
"Account": "账户",
|
||||
"Notifications": "通知",
|
||||
"Preferences": "偏好设置",
|
||||
"Settings": "设置",
|
||||
"Logout": "登出",
|
||||
"Sponsors": "赞助商",
|
||||
"Credits": "积分",
|
||||
"admin": "管理员",
|
||||
"user": "",
|
||||
"Start": "",
|
||||
"Stop": "",
|
||||
"Pause": "",
|
||||
"Restart": "",
|
||||
"Starting": "",
|
||||
"Stopping": "",
|
||||
"Pausing": "",
|
||||
"Restarting": "",
|
||||
"Running": "",
|
||||
"Stopped": "",
|
||||
"Paused": "",
|
||||
"Details": "",
|
||||
"Logs": "",
|
||||
"Edit": "",
|
||||
"Update": "",
|
||||
"Uninstall": "",
|
||||
"Hide": "",
|
||||
"Reset_View": "",
|
||||
"Permissions": "",
|
||||
"Copyright": "",
|
||||
"Documentation": "文档",
|
||||
"License": "",
|
||||
"Source_Code": "",
|
||||
"Support": ""
|
||||
}
|
366
package-lock.json
generated
366
package-lock.json
generated
|
@ -1,27 +1,27 @@
|
|||
{
|
||||
"name": "dweebui",
|
||||
"version": "0.70.474",
|
||||
"version": "0.60",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dweebui",
|
||||
"version": "0.70.474",
|
||||
"version": "0.60",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.16",
|
||||
"adm-zip": "^0.5.12",
|
||||
"bcrypt": "^5.1.1",
|
||||
"connect-session-sequelize": "^7.1.7",
|
||||
"dockerode": "^4.0.2",
|
||||
"dockerode-compose": "^1.4.0",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.1",
|
||||
"express-session": "^1.18.1",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.18.0",
|
||||
"memorystore": "^1.6.7",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"sequelize": "^6.37.5",
|
||||
"sequelize": "^6.37.3",
|
||||
"sqlite3": "^5.1.7",
|
||||
"systeminformation": "^5.23.5",
|
||||
"yaml": "^2.6.0"
|
||||
"systeminformation": "^5.22.9",
|
||||
"yaml": "^2.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@balena/dockerignore": {
|
||||
|
@ -48,7 +48,7 @@
|
|||
"npmlog": "^5.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tar": "^6.1.11"
|
||||
"tar": "^6.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
|
@ -113,17 +113,17 @@
|
|||
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz",
|
||||
"integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==",
|
||||
"version": "20.12.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
|
||||
"integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/validator": {
|
||||
"version": "13.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz",
|
||||
"integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA=="
|
||||
"version": "13.11.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.10.tgz",
|
||||
"integrity": "sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg=="
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
|
@ -143,11 +143,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/adm-zip": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
|
||||
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
|
||||
"version": "0.5.12",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.12.tgz",
|
||||
"integrity": "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==",
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
|
@ -250,9 +250,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
|
@ -318,9 +318,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
|
@ -330,7 +330,7 @@
|
|||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.13.0",
|
||||
"qs": "6.11.0",
|
||||
"raw-body": "2.5.2",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
|
@ -447,6 +447,18 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/cacache/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/cacache/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
|
@ -459,6 +471,12 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/cacache/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
|
@ -579,20 +597,6 @@
|
|||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/connect-session-sequelize": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/connect-session-sequelize/-/connect-session-sequelize-7.1.7.tgz",
|
||||
"integrity": "sha512-Wqq7rg0w+9bOVs6jC0nhZnssXJ3+iKNlDVWn2JfBuBPoY7oYaxzxfBKeUYrX6dHt3OWEWbZV6LJvapwi76iBQQ==",
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"sequelize": ">= 6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
|
@ -618,9 +622,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
|
@ -650,11 +654,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
|
@ -816,9 +820,9 @@
|
|||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
|
@ -908,36 +912,36 @@
|
|||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.21.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
||||
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.3",
|
||||
"body-parser": "1.20.2",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.7.1",
|
||||
"cookie": "0.6.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.3.1",
|
||||
"finalhandler": "1.2.0",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.10",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.13.0",
|
||||
"qs": "6.11.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.19.0",
|
||||
"serve-static": "1.16.2",
|
||||
"send": "0.18.0",
|
||||
"serve-static": "1.15.0",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
|
@ -949,11 +953,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/express-session": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
|
||||
"integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==",
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
|
||||
"integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==",
|
||||
"dependencies": {
|
||||
"cookie": "0.7.2",
|
||||
"cookie": "0.6.0",
|
||||
"cookie-signature": "1.0.7",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
|
@ -966,14 +970,6 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie-signature": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||
|
@ -1038,12 +1034,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~2.0.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
|
@ -1159,7 +1155,6 @@
|
|||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
|
@ -1371,7 +1366,6 @@
|
|||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
|
@ -1434,9 +1428,9 @@
|
|||
"optional": true
|
||||
},
|
||||
"node_modules/jake": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
|
||||
"integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz",
|
||||
"integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==",
|
||||
"dependencies": {
|
||||
"async": "^3.2.3",
|
||||
"chalk": "^4.0.2",
|
||||
|
@ -1473,15 +1467,12 @@
|
|||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"optional": true,
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
|
||||
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"pseudomap": "^1.0.2",
|
||||
"yallist": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
|
@ -1533,6 +1524,24 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-fetch-happen/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-fetch-happen/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
|
@ -1541,14 +1550,23 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
"node_modules/memorystore": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/memorystore/-/memorystore-1.6.7.tgz",
|
||||
"integrity": "sha512-OZnmNY/NDrKohPQ+hxp0muBcBKrzKNtHr55DbqSx9hLsYVNnomSAMRAtI7R64t3gf3ID7tHQA7mG4oL3Hu9hdw==",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.0",
|
||||
"lru-cache": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
|
@ -1693,6 +1711,11 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
|
@ -1705,6 +1728,11 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
|
@ -1741,9 +1769,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/multer": {
|
||||
"version": "1.4.5-lts.1",
|
||||
|
@ -1763,9 +1791,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
|
||||
"integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
|
||||
"integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
|
@ -1782,9 +1810,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.67.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.67.0.tgz",
|
||||
"integrity": "sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==",
|
||||
"version": "3.62.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz",
|
||||
"integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
|
@ -1925,12 +1953,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
|
||||
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
|
@ -1994,9 +2019,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"node_modules/pg-connection-string": {
|
||||
"version": "2.6.4",
|
||||
|
@ -2064,21 +2089,26 @@
|
|||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
"integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
|
@ -2162,7 +2192,6 @@
|
|||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
|
@ -2198,9 +2227,9 @@
|
|||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
||||
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
@ -2209,9 +2238,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
|
@ -2244,18 +2273,15 @@
|
|||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/send/node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/sequelize": {
|
||||
"version": "6.37.5",
|
||||
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.5.tgz",
|
||||
"integrity": "sha512-10WA4poUb3XWnUROThqL2Apq9C2NhyV1xHPMZuybNMCucDsbbFuKg51jhmyvvAUyUqCiimwTZamc3AHhMoBr2Q==",
|
||||
"version": "6.37.3",
|
||||
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.3.tgz",
|
||||
"integrity": "sha512-V2FTqYpdZjPy3VQrZvjTPnOoLm0KudCRXfGWp48QwhyPPp2yW8z0p0sCYZd/em847Tl2dVxJJ1DR+hF+O77T7A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -2322,14 +2348,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||
"dependencies": {
|
||||
"encodeurl": "~2.0.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.19.0"
|
||||
"send": "0.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
|
@ -2499,14 +2525,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/sqlite3/node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz",
|
||||
"integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==",
|
||||
"engines": {
|
||||
"node": "^16 || ^18 || >= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/ssh2": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz",
|
||||
"integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==",
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz",
|
||||
"integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"asn1": "^0.2.6",
|
||||
|
@ -2516,8 +2545,8 @@
|
|||
"node": ">=10.16.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"cpu-features": "~0.0.10",
|
||||
"nan": "^2.20.0"
|
||||
"cpu-features": "~0.0.9",
|
||||
"nan": "^2.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ssri": {
|
||||
|
@ -2600,9 +2629,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/systeminformation": {
|
||||
"version": "5.23.5",
|
||||
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.23.5.tgz",
|
||||
"integrity": "sha512-PEpJwhRYxZgBCAlWZhWIgfMTjXLqfcaZ1pJsJn9snWNfBW/Z1YQg1mbIUSWrEV3ErAHF7l/OoVLQeaZDlPzkpA==",
|
||||
"version": "5.22.9",
|
||||
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.22.9.tgz",
|
||||
"integrity": "sha512-qUWJhQ9JSBhdjzNUQywpvc0icxUAjMY3sZqUoS0GOtaJV9Ijq8s9zEP8Gaqmymn1dOefcICyPXK1L3kgKxlUpg==",
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux",
|
||||
|
@ -2690,6 +2719,11 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
|
@ -2753,9 +2787,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||
},
|
||||
"node_modules/unique-filename": {
|
||||
"version": "1.1.1",
|
||||
|
@ -2879,14 +2913,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
|
||||
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
|
||||
"integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
|
|
18
package.json
18
package.json
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"name": "dweebui",
|
||||
"version": "0.70.474",
|
||||
"version": "0.60",
|
||||
"description": "Free and Open-Source WebUI For Managing Your Containers.",
|
||||
"main": "server.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
@ -10,20 +11,19 @@
|
|||
"keywords": [],
|
||||
"author": "lllllllillllllillll",
|
||||
"license": "MIT",
|
||||
"description": "DweebUI is a WebUI for managing your containers. https://dweebui.com",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.16",
|
||||
"adm-zip": "^0.5.12",
|
||||
"bcrypt": "^5.1.1",
|
||||
"connect-session-sequelize": "^7.1.7",
|
||||
"dockerode": "^4.0.2",
|
||||
"dockerode-compose": "^1.4.0",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.1",
|
||||
"express-session": "^1.18.1",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.18.0",
|
||||
"memorystore": "^1.6.7",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"sequelize": "^6.37.5",
|
||||
"sequelize": "^6.37.3",
|
||||
"sqlite3": "^5.1.7",
|
||||
"systeminformation": "^5.23.5",
|
||||
"yaml": "^2.6.0"
|
||||
"systeminformation": "^5.22.9",
|
||||
"yaml": "^2.4.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,276 +0,0 @@
|
|||
/*!
|
||||
* Tabler v1.0.0-beta20 (https://tabler.io)
|
||||
* @version 1.0.0-beta20
|
||||
* @link https://tabler.io
|
||||
* Copyright 2018-2023 The Tabler Authors
|
||||
* Copyright 2018-2023 codecalm.net Paweł Kuna
|
||||
* Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)
|
||||
*/
|
||||
/* prettier-ignore */
|
||||
/* prettier-ignore */
|
||||
pre.highlight,
|
||||
.highlight pre {
|
||||
max-height: 30rem;
|
||||
margin: 1.5rem 0;
|
||||
overflow: auto;
|
||||
border-radius: var(--tblr-border-radius);
|
||||
}
|
||||
pre.highlight,
|
||||
.highlight pre {
|
||||
scrollbar-color: rgba(var(--tblr-scrollbar-color, var(--tblr-body-color-rgb)), 0.16) transparent;
|
||||
}
|
||||
pre.highlight::-webkit-scrollbar,
|
||||
.highlight pre::-webkit-scrollbar {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
-webkit-transition: background 0.3s;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
pre.highlight::-webkit-scrollbar,
|
||||
.highlight pre::-webkit-scrollbar {
|
||||
-webkit-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
pre.highlight::-webkit-scrollbar-thumb,
|
||||
.highlight pre::-webkit-scrollbar-thumb {
|
||||
border-radius: 1rem;
|
||||
border: 5px solid transparent;
|
||||
box-shadow: inset 0 0 0 1rem rgba(var(--tblr-scrollbar-color, var(--tblr-body-color-rgb)), 0.16);
|
||||
}
|
||||
pre.highlight::-webkit-scrollbar-track,
|
||||
.highlight pre::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
pre.highlight:hover::-webkit-scrollbar-thumb,
|
||||
.highlight pre:hover::-webkit-scrollbar-thumb {
|
||||
box-shadow: inset 0 0 0 1rem rgba(var(--tblr-scrollbar-color, var(--tblr-body-color-rgb)), 0.32);
|
||||
}
|
||||
pre.highlight::-webkit-scrollbar-corner,
|
||||
.highlight pre::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
margin: 0;
|
||||
}
|
||||
.highlight code > * {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.highlight .c, .highlight .c1 {
|
||||
color: #a0aec0;
|
||||
}
|
||||
.highlight .nt, .highlight .nc, .highlight .nx {
|
||||
color: #ff8383;
|
||||
}
|
||||
.highlight .na, .highlight .p {
|
||||
color: #ffe484;
|
||||
}
|
||||
.highlight .s, .highlight .dl, .highlight .s2 {
|
||||
color: #b5f4a5;
|
||||
}
|
||||
.highlight .k {
|
||||
color: #93ddfd;
|
||||
}
|
||||
.highlight .s1, .highlight .mi {
|
||||
color: #d9a9ff;
|
||||
}
|
||||
|
||||
.example {
|
||||
padding: 2rem;
|
||||
margin: 1rem 0 2rem;
|
||||
border: var(--tblr-border-width) var(--tblr-border-style) var(--tblr-border-color);
|
||||
border-radius: 3px 3px 0 0;
|
||||
position: relative;
|
||||
min-height: 12rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.example-centered {
|
||||
justify-content: center;
|
||||
}
|
||||
.example-centered .example-content {
|
||||
flex: 0 auto;
|
||||
}
|
||||
|
||||
.example-content {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4285714286;
|
||||
color: var(--tblr-body-color);
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
.example-content .page-header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.example-bg {
|
||||
background: #f6f8fb;
|
||||
}
|
||||
|
||||
.example-code {
|
||||
margin: 2rem 0;
|
||||
border: var(--tblr-border-width) var(--tblr-border-style) var(--tblr-border-color);
|
||||
border-top: none;
|
||||
}
|
||||
.example-code pre {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
border-radius: 0 0 3px 3px;
|
||||
}
|
||||
.example + .example-code {
|
||||
margin-top: -2rem;
|
||||
}
|
||||
|
||||
.example-column {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.example-column > .card:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.example-column-1 {
|
||||
max-width: 26rem;
|
||||
}
|
||||
|
||||
.example-column-2 {
|
||||
max-width: 52rem;
|
||||
}
|
||||
|
||||
.example-modal-backdrop {
|
||||
background: #182433;
|
||||
opacity: 0.24;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
border-radius: 2px 2px 0 0;
|
||||
}
|
||||
|
||||
.card-sponsor {
|
||||
background: var(--tblr-primary-lt) no-repeat center/100% 100%;
|
||||
border-color: var(--tblr-primary);
|
||||
min-height: 316px;
|
||||
}
|
||||
|
||||
.dropdown-menu-demo {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
top: 0;
|
||||
margin-bottom: 1rem !important;
|
||||
}
|
||||
|
||||
.demo-icon-preview {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
.demo-icon-preview svg,
|
||||
.demo-icon-preview i {
|
||||
width: 15rem;
|
||||
height: 15rem;
|
||||
font-size: 15rem;
|
||||
stroke-width: 1.5;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.demo-icon-preview svg,
|
||||
.demo-icon-preview i {
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
font-size: 10rem;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-icon-preview-icon pre {
|
||||
margin: 0;
|
||||
-webkit-user-select: all;
|
||||
-moz-user-select: all;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
.demo-dividers > p {
|
||||
opacity: 0.2;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.demo-icons-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0;
|
||||
margin: 0 -2px -1px 0;
|
||||
list-style: none;
|
||||
}
|
||||
.demo-icons-list > * {
|
||||
flex: 1 0 4rem;
|
||||
}
|
||||
|
||||
.demo-icons-list-wrap {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.demo-icons-list-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 1;
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
border-right: var(--tblr-border-width) var(--tblr-border-style) var(--tblr-border-color);
|
||||
border-bottom: var(--tblr-border-width) var(--tblr-border-style) var(--tblr-border-color);
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
.demo-icons-list-item .icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.demo-icons-list-item:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.settings-btn {
|
||||
position: fixed;
|
||||
right: -1px;
|
||||
top: 10rem;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
box-shadow: rgba(var(--tblr-body-color-rgb), 0.04) 0 2px 4px 0;
|
||||
}
|
||||
|
||||
.settings-scheme {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
position: relative;
|
||||
border: var(--tblr-border-width) var(--tblr-border-style) var(--tblr-border-color);
|
||||
box-shadow: rgba(var(--tblr-body-color-rgb), 0.04) 0 2px 4px 0;
|
||||
}
|
||||
.settings-scheme-light {
|
||||
background: linear-gradient(135deg, #ffffff 50%, #fcfdfe 50%);
|
||||
}
|
||||
.settings-scheme-mixed {
|
||||
background-image: linear-gradient(135deg, #182433 50%, #fff 50%);
|
||||
}
|
||||
.settings-scheme-transparent {
|
||||
background: #fcfdfe;
|
||||
}
|
||||
.settings-scheme-dark {
|
||||
background: #182433;
|
||||
}
|
||||
.settings-scheme-colored {
|
||||
background-image: linear-gradient(135deg, var(--tblr-primary) 50%, #fcfdfe 50%);
|
||||
}
|
4
public/css/demo.min.css
vendored
4
public/css/demo.min.css
vendored
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Tabler v1.0.0-beta20 (https://tabler.io)
|
||||
* @version 1.0.0-beta20
|
||||
* Tabler v1.0.0-beta19 (https://tabler.io)
|
||||
* @version 1.0.0-beta19
|
||||
* @link https://tabler.io
|
||||
* Copyright 2018-2023 The Tabler Authors
|
||||
* Copyright 2018-2023 codecalm.net Paweł Kuna
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
|
||||
@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";
|
||||
}
|
||||
|
||||
.meter {
|
||||
box-sizing: content-box;
|
||||
height: 15px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
position: relative;
|
||||
background: #a7a7a752;
|
||||
border-radius: 25px;
|
||||
padding: 3px;
|
||||
box-shadow: inset 0 -1px 1px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.meter > span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-top-right-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
background-color: rgb(43, 194, 83);
|
||||
background-image: linear-gradient(
|
||||
center bottom,
|
||||
rgb(43, 194, 83) 37%,
|
||||
rgb(84, 240, 84) 69%
|
||||
);
|
||||
box-shadow: inset 0 2px 9px rgba(255, 255, 255, 0.3),
|
||||
inset 0 -2px 6px rgba(0, 0, 0, 0.4);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.meter > span:after,
|
||||
.animate > span > span {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-image: linear-gradient(
|
||||
-45deg,
|
||||
rgba(255, 255, 255, 0.2) 25%,
|
||||
transparent 25%,
|
||||
transparent 50%,
|
||||
rgba(255, 255, 255, 0.2) 50%,
|
||||
rgba(255, 255, 255, 0.2) 75%,
|
||||
transparent 75%,
|
||||
transparent
|
||||
);
|
||||
z-index: 1;
|
||||
background-size: 50px 50px;
|
||||
animation: move 2s linear infinite;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.animate > span:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 50px 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.orange > span {
|
||||
background-image: linear-gradient(#f1a165, #f36d0a);
|
||||
}
|
||||
|
||||
.red > span {
|
||||
background-image: linear-gradient(#f0a3a3, #f42323);
|
||||
}
|
||||
|
||||
.blue > span {
|
||||
background-image: linear-gradient(#2478f5, #22017e);
|
||||
}
|
||||
|
||||
.purple > span {
|
||||
background-image: linear-gradient(#bd14d3, #670370);
|
||||
}
|
||||
|
||||
.nostripes > span > span,
|
||||
.nostripes > span::after {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.container-stamp {
|
||||
--tblr-stamp-size: 8rem;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: calc(var(--tblr-stamp-size) * 1);
|
||||
height: calc(var(--tblr-stamp-size) * 1);
|
||||
max-height: 100%;
|
||||
border-top-right-radius: 4px;
|
||||
opacity: 0.2;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.container-action {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
color: var(--tblr-secondary);
|
||||
display: inline-flex;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--tblr-border-radius);
|
||||
background: transparent;
|
||||
}
|
||||
.container-action:after {
|
||||
content: none;
|
||||
}
|
||||
.container-action:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.container-action:hover, .container-action.show {
|
||||
color: var(--tblr-body-color);
|
||||
background: var(--tblr-active-bg);
|
||||
}
|
||||
.container-action.show {
|
||||
color: var(--tblr-primary);
|
||||
}
|
||||
.container-action .icon {
|
||||
margin: 0;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
font-size: 1.25rem;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.container-actions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border: 1px solid grey;
|
||||
}
|
||||
|
||||
.accordion-user {
|
||||
border: 1px solid grey;
|
||||
}
|
||||
|
||||
.slim-modal {
|
||||
--tblr-modal-width: 450px;
|
||||
}
|
||||
|
||||
.medium-modal {
|
||||
--tblr-modal-width: 850px;
|
||||
}
|
||||
|
||||
|
||||
.wide-modal {
|
||||
--tblr-modal-width: 1450px;
|
||||
}
|
||||
|
||||
|
||||
.avatar-3xl {
|
||||
--tblr-avatar-size: 9rem;
|
||||
--tblr-avatar-status-size: 1rem;
|
||||
--tblr-avatar-font-size: 3rem;
|
||||
--tblr-avatar-icon-size: 5rem;
|
||||
}
|
||||
|
||||
|
||||
.description {
|
||||
max-height: 6em; /* Adjust based on font size and line height */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis " [..]";
|
||||
line-height: 1.5em; /* Adjust based on font size */
|
||||
}
|
124
public/css/meters.css
Normal file
124
public/css/meters.css
Normal file
|
@ -0,0 +1,124 @@
|
|||
|
||||
|
||||
.meter {
|
||||
box-sizing: content-box;
|
||||
height: 15px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
position: relative;
|
||||
background: #a7a7a752;
|
||||
border-radius: 25px;
|
||||
padding: 3px;
|
||||
box-shadow: inset 0 -1px 1px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.meter > span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-top-right-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
background-color: rgb(43, 194, 83);
|
||||
background-image: linear-gradient(
|
||||
center bottom,
|
||||
rgb(43, 194, 83) 37%,
|
||||
rgb(84, 240, 84) 69%
|
||||
);
|
||||
box-shadow: inset 0 2px 9px rgba(255, 255, 255, 0.3),
|
||||
inset 0 -2px 6px rgba(0, 0, 0, 0.4);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.meter > span:after,
|
||||
.animate > span > span {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-image: linear-gradient(
|
||||
-45deg,
|
||||
rgba(255, 255, 255, 0.2) 25%,
|
||||
transparent 25%,
|
||||
transparent 50%,
|
||||
rgba(255, 255, 255, 0.2) 50%,
|
||||
rgba(255, 255, 255, 0.2) 75%,
|
||||
transparent 75%,
|
||||
transparent
|
||||
);
|
||||
z-index: 1;
|
||||
background-size: 50px 50px;
|
||||
animation: move 2s linear infinite;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.animate > span:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 50px 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.orange > span {
|
||||
background-image: linear-gradient(#f1a165, #f36d0a);
|
||||
}
|
||||
|
||||
.red > span {
|
||||
background-image: linear-gradient(#f0a3a3, #f42323);
|
||||
}
|
||||
|
||||
.blue > span {
|
||||
background-image: linear-gradient(#2478f5, #22017e);
|
||||
}
|
||||
|
||||
.purple > span {
|
||||
background-image: linear-gradient(#bd14d3, #670370);
|
||||
}
|
||||
|
||||
.nostripes > span > span,
|
||||
.nostripes > span::after {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.border {
|
||||
--tblr-card-spacer-y: 1rem;
|
||||
--tblr-card-spacer-x: 1.5rem;
|
||||
--tblr-card-title-spacer-y: 1.25rem;
|
||||
--tblr-card-border-width: var(--tblr-border-width);
|
||||
--tblr-card-border-color: var(--tblr-border-color);
|
||||
--tblr-card-border-radius: var(--tblr-border-radius);
|
||||
--tblr-card-box-shadow: var(--tblr-shadow-card);
|
||||
--tblr-card-inner-border-radius: calc(var(--tblr-border-radius) - (var(--tblr-border-width)));
|
||||
--tblr-card-cap-padding-y: 1rem;
|
||||
--tblr-card-cap-padding-x: 1.5rem;
|
||||
--tblr-card-cap-bg: var(--tblr-bg-surface-tertiary);
|
||||
--tblr-card-cap-color: inherit;
|
||||
--tblr-card-color: inherit;
|
||||
--tblr-card-bg: var(--tblr-bg-surface);
|
||||
--tblr-card-img-overlay-padding: 1rem;
|
||||
--tblr-card-group-margin: 1.5rem;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
height: var(--tblr-card-height);
|
||||
word-wrap: break-word;
|
||||
background-color: var(--tblr-card-bg);
|
||||
background-clip: border-box;
|
||||
border: var(--tblr-card-border-width) solid var(--tblr-card-border-color);
|
||||
border-radius: var(--tblr-card-border-radius);
|
||||
|
||||
}
|
25813
public/css/tabler.css
25813
public/css/tabler.css
File diff suppressed because it is too large
Load diff
28590
public/css/tabler.min.css
vendored
28590
public/css/tabler.min.css
vendored
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 413 B |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
35
public/js/demo-theme.js
Normal file
35
public/js/demo-theme.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*!
|
||||
* Tabler v1.0.0-beta19 (https://tabler.io)
|
||||
* @version 1.0.0-beta19
|
||||
* @link https://tabler.io
|
||||
* Copyright 2018-2023 The Tabler Authors
|
||||
* Copyright 2018-2023 codecalm.net Paweł Kuna
|
||||
* Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)
|
||||
*/
|
||||
(function (factory) {
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
factory();
|
||||
})((function () { 'use strict';
|
||||
|
||||
var themeStorageKey = "tablerTheme";
|
||||
var defaultTheme = "dark";
|
||||
var selectedTheme;
|
||||
var params = new Proxy(new URLSearchParams(window.location.search), {
|
||||
get: function get(searchParams, prop) {
|
||||
return searchParams.get(prop);
|
||||
}
|
||||
});
|
||||
if (!!params.theme) {
|
||||
localStorage.setItem(themeStorageKey, params.theme);
|
||||
selectedTheme = params.theme;
|
||||
} else {
|
||||
var storedTheme = localStorage.getItem(themeStorageKey);
|
||||
selectedTheme = storedTheme ? storedTheme : defaultTheme;
|
||||
}
|
||||
if (selectedTheme === 'dark') {
|
||||
document.body.setAttribute("data-bs-theme", selectedTheme);
|
||||
} else {
|
||||
document.body.removeAttribute("data-bs-theme");
|
||||
}
|
||||
|
||||
}));
|
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Tabler v1.0.0-beta20 (https://tabler.io)
|
||||
* @version 1.0.0-beta20
|
||||
* Tabler v1.0.0-beta19 (https://tabler.io)
|
||||
* @version 1.0.0-beta19
|
||||
* @link https://tabler.io
|
||||
* Copyright 2018-2023 The Tabler Authors
|
||||
* Copyright 2018-2023 codecalm.net Paweł Kuna
|
||||
|
|
4
public/js/demo.min.js
vendored
4
public/js/demo.min.js
vendored
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Tabler v1.0.0-beta20 (https://tabler.io)
|
||||
* @version 1.0.0-beta20
|
||||
* Tabler v1.0.0-beta19 (https://tabler.io)
|
||||
* @version 1.0.0-beta19
|
||||
* @link https://tabler.io
|
||||
* Copyright 2018-2023 The Tabler Authors
|
||||
* Copyright 2018-2023 codecalm.net Paweł Kuna
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
var themeStorageKey = "tablerTheme";
|
||||
var defaultTheme = "dark";
|
||||
var selectedTheme;
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var storedTheme = localStorage.getItem(themeStorageKey);
|
||||
selectedTheme = storedTheme ? storedTheme : defaultTheme;
|
||||
|
||||
if (selectedTheme === 'dark') {
|
||||
document.body.setAttribute("data-bs-theme", selectedTheme);
|
||||
} else {
|
||||
document.body.removeAttribute("data-bs-theme");
|
||||
}
|
||||
})();
|
||||
|
||||
function toggleTheme(button) {
|
||||
if (button.value == 'dark-theme') {
|
||||
document.body.setAttribute("data-bs-theme", 'dark');
|
||||
localStorage.setItem(themeStorageKey, 'dark');
|
||||
}
|
||||
else if (button.value == 'light-theme') {
|
||||
document.body.removeAttribute("data-bs-theme");
|
||||
localStorage.setItem(themeStorageKey, 'light');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function topScroll() {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
|
||||
function bottomScroll() {
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
|
@ -6,285 +6,350 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
|
|||
*/
|
||||
|
||||
(function() {
|
||||
/** @type {import("../htmx").HtmxInternalApi} */
|
||||
var api
|
||||
|
||||
htmx.defineExtension('sse', {
|
||||
/** @type {import("../htmx").HtmxInternalApi} */
|
||||
var api;
|
||||
|
||||
/**
|
||||
* 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
|
||||
htmx.defineExtension("sse", {
|
||||
|
||||
// set a function in the public API for creating new EventSource objects
|
||||
if (htmx.createEventSource == undefined) {
|
||||
htmx.createEventSource = createEventSource
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 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;
|
||||
|
||||
getSelectors: function() {
|
||||
return ['[sse-connect]', '[data-sse-connect]', '[sse-swap]', '[data-sse-swap]']
|
||||
},
|
||||
// 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) {
|
||||
var parent = evt.target || evt.detail.elt
|
||||
switch (name) {
|
||||
case 'htmx:beforeCleanupElement':
|
||||
var internalData = api.getInternalData(parent)
|
||||
// Try to remove remove an EventSource when elements are removed
|
||||
var source = internalData.sseEventSource
|
||||
if (source) {
|
||||
api.triggerEvent(parent, 'htmx:sseClose', {
|
||||
source,
|
||||
type: 'nodeReplaced',
|
||||
})
|
||||
internalData.sseEventSource.close()
|
||||
}
|
||||
/**
|
||||
* onEvent handles all events passed to this extension.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Event} evt
|
||||
* @returns void
|
||||
*/
|
||||
onEvent: function(name, evt) {
|
||||
|
||||
return
|
||||
switch (name) {
|
||||
|
||||
// Try to create EventSources when elements are processed
|
||||
case 'htmx:afterProcessNode':
|
||||
ensureEventSourceOnElement(parent)
|
||||
}
|
||||
}
|
||||
})
|
||||
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();
|
||||
}
|
||||
|
||||
/// ////////////////////////////////////////////
|
||||
// HELPER FUNCTIONS
|
||||
/// ////////////////////////////////////////////
|
||||
return;
|
||||
|
||||
/**
|
||||
* 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 })
|
||||
}
|
||||
// Try to create EventSources when elements are processed
|
||||
case "htmx:afterProcessNode":
|
||||
ensureEventSourceOnElement(evt.target);
|
||||
registerSSE(evt.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// Add message handlers for every `sse-swap` attribute
|
||||
if (api.getAttributeValue(elt, 'sse-swap')) {
|
||||
// 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
|
||||
|
||||
var sseSwapAttr = api.getAttributeValue(elt, 'sse-swap')
|
||||
var sseEventNames = sseSwapAttr.split(',')
|
||||
|
||||
for (var i = 0; i < sseEventNames.length; i++) {
|
||||
const sseEventName = sseEventNames[i].trim()
|
||||
const 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(elt)) {
|
||||
source.removeEventListener(sseEventName, listener)
|
||||
return
|
||||
}
|
||||
|
||||
// swap the response into the DOM and trigger a notification
|
||||
if (!api.triggerEvent(elt, 'htmx:sseBeforeMessage', event)) {
|
||||
return
|
||||
}
|
||||
swap(elt, event.data)
|
||||
api.triggerEvent(elt, 'htmx:sseMessage', event)
|
||||
}
|
||||
|
||||
// Register the new listener
|
||||
api.getInternalData(elt).sseEventListener = listener
|
||||
source.addEventListener(sseEventName, listener)
|
||||
}
|
||||
}
|
||||
|
||||
// Add message handlers for every `hx-trigger="sse:*"` attribute
|
||||
if (api.getAttributeValue(elt, 'hx-trigger')) {
|
||||
// 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
|
||||
|
||||
var triggerSpecs = api.getTriggerSpecs(elt)
|
||||
triggerSpecs.forEach(function(ts) {
|
||||
if (ts.trigger.slice(0, 4) !== 'sse:') {
|
||||
return
|
||||
}
|
||||
|
||||
var listener = function (event) {
|
||||
if (maybeCloseSSESource(sourceElement)) {
|
||||
return
|
||||
}
|
||||
if (!api.bodyContains(elt)) {
|
||||
source.removeEventListener(ts.trigger.slice(4), listener)
|
||||
}
|
||||
// Trigger events to be handled by the rest of htmx
|
||||
htmx.trigger(elt, ts.trigger, event)
|
||||
htmx.trigger(elt, 'htmx:sseMessage', event)
|
||||
}
|
||||
|
||||
// Register the new listener
|
||||
api.getInternalData(elt).sseEventListener = listener
|
||||
source.addEventListener(ts.trigger.slice(4), 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
|
||||
if (api.getAttributeValue(elt, 'sse-connect')) {
|
||||
var sseURL = api.getAttributeValue(elt, 'sse-connect')
|
||||
if (sseURL == null) {
|
||||
return
|
||||
}
|
||||
|
||||
ensureEventSource(elt, sseURL, retryCount)
|
||||
}
|
||||
|
||||
registerSSE(elt)
|
||||
}
|
||||
|
||||
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 })
|
||||
|
||||
// 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
|
||||
retryCount = Math.max(Math.min(retryCount * 2, 128), 1)
|
||||
var timeout = retryCount * 500
|
||||
window.setTimeout(function() {
|
||||
ensureEventSourceOnElement(elt, retryCount)
|
||||
}, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
source.onopen = function(evt) {
|
||||
api.triggerEvent(elt, 'htmx:sseOpen', { source })
|
||||
|
||||
if (retryCount && retryCount > 0) {
|
||||
const childrenToFix = elt.querySelectorAll("[sse-swap], [data-sse-swap], [hx-trigger], [data-hx-trigger]")
|
||||
for (let i = 0; i < childrenToFix.length; i++) {
|
||||
registerSSE(childrenToFix[i])
|
||||
}
|
||||
// We want to increase the reconnection delay for consecutive failed attempts only
|
||||
retryCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
api.getInternalData(elt).sseEventSource = source
|
||||
///////////////////////////////////////////////
|
||||
// HELPER FUNCTIONS
|
||||
///////////////////////////////////////////////
|
||||
|
||||
|
||||
var closeAttribute = api.getAttributeValue(elt, "sse-close");
|
||||
if (closeAttribute) {
|
||||
// close eventsource when this message is received
|
||||
source.addEventListener(closeAttribute, function() {
|
||||
api.triggerEvent(elt, 'htmx:sseClose', {
|
||||
source,
|
||||
type: 'message',
|
||||
})
|
||||
source.close()
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
api.triggerEvent(elt, 'htmx:sseClose', {
|
||||
source,
|
||||
type: 'nodeMissing',
|
||||
})
|
||||
source.close()
|
||||
// source = null
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} elt
|
||||
* @param {string} content
|
||||
*/
|
||||
function swap(elt, content) {
|
||||
api.withExtensions(elt, function(extension) {
|
||||
content = extension.transformResponse(content, null, elt)
|
||||
})
|
||||
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;
|
||||
}
|
||||
|
||||
var swapSpec = api.getSwapSpecification(elt)
|
||||
var target = api.getTarget(elt)
|
||||
api.swap(target, content, swapSpec)
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
|
||||
function hasEventSource(node) {
|
||||
return api.getInternalData(node).sseEventSource != null
|
||||
}
|
||||
})()
|
||||
// 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;
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
2
public/js/htmx.min.js
vendored
2
public/js/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
7543
public/js/tabler.js
7543
public/js/tabler.js
File diff suppressed because it is too large
Load diff
8
public/js/tabler.min.js
vendored
8
public/js/tabler.min.js
vendored
File diff suppressed because one or more lines are too long
32956
public/libs/apexcharts/dist/apexcharts.js
vendored
32956
public/libs/apexcharts/dist/apexcharts.js
vendored
File diff suppressed because one or more lines are too long
122
router.js
122
router.js
|
@ -1,122 +0,0 @@
|
|||
import express from 'express';
|
||||
export const router = express.Router();
|
||||
|
||||
import { Login, submitLogin, Logout } from './controllers/login.js';
|
||||
import { Register, submitRegister } from './controllers/register.js';
|
||||
import { Dashboard, searchDashboard, ServerMetrics, SSE, DashboardView, DashboardAction } from './controllers/dashboard.js';
|
||||
import { Images, submitImages, searchImages } from './controllers/images.js';
|
||||
import { Volumes, submitVolumes, searchVolumes } from './controllers/volumes.js';
|
||||
import { Networks, NetworkAction, searchNetworks } from './controllers/networks.js';
|
||||
import { Apps, submitApps, searchApps, appsModals, } from './controllers/apps.js';
|
||||
import { Users, submitUsers, searchUsers, UsersView, UsersAction } from './controllers/users.js';
|
||||
import { Syslogs, searchSyslogs } from './controllers/syslogs.js';
|
||||
import { Account, searchAccount } from './controllers/account.js';
|
||||
import { Preferences, submitPreferences, searchPreferences } from './controllers/preferences.js';
|
||||
import { Settings, SettingsAction, updateLanguages, searchSettings } from './controllers/settings.js';
|
||||
import { Sponsors, searchSponsors } from './controllers/sponsors.js';
|
||||
import { Credits } from './controllers/credits.js';
|
||||
|
||||
import { Install } from './utils/install.js';
|
||||
import { Uninstall } from './utils/uninstall.js';
|
||||
|
||||
import { sessionCheck, adminOnly, permissionCheck } from './utils/permissions.js';
|
||||
|
||||
// router.get('*', (req, res, next) => { console.log(`[GET] ${req.url}`); next(); });
|
||||
// router.post('*', (req, res, next) => { console.log(`[POST] ${req.url}`); next(); });
|
||||
|
||||
router.get('/login', Login);
|
||||
router.post('/login', submitLogin);
|
||||
router.get('/logout', Logout);
|
||||
router.get('/register', Register);
|
||||
router.post('/register', submitRegister);
|
||||
|
||||
router.get("/", sessionCheck, Dashboard);
|
||||
|
||||
router.get("/dashboard", sessionCheck, Dashboard);
|
||||
router.get("/dashboard/view/:view/:id?", sessionCheck, DashboardView);
|
||||
router.post("/dashboard/action/:action/:id?", sessionCheck, DashboardAction);
|
||||
router.get("/server_metrics", sessionCheck, ServerMetrics);
|
||||
router.get("/sse", permissionCheck, SSE);
|
||||
|
||||
router.get("/images", adminOnly, Images);
|
||||
router.post('/images', adminOnly, submitImages);
|
||||
|
||||
router.get("/volumes", adminOnly, Volumes);
|
||||
router.post('/volumes', adminOnly, submitVolumes);
|
||||
|
||||
router.get("/networks", adminOnly, Networks);
|
||||
router.post('/network/:action/:containerid?', adminOnly, NetworkAction);
|
||||
|
||||
router.get("/apps/:page?/:template?", adminOnly, Apps);
|
||||
router.post("/apps/:action?", adminOnly, submitApps);
|
||||
|
||||
router.get("/users", adminOnly, Users);
|
||||
router.get("/users/view/:view/:id?", adminOnly, UsersView);
|
||||
router.post("/users/action/:action/:id?", adminOnly, UsersAction);
|
||||
|
||||
router.get('/syslogs', adminOnly, Syslogs);
|
||||
|
||||
router.get('/settings', adminOnly, Settings);
|
||||
router.post('/settings/action/:action?/:id?', adminOnly, SettingsAction);
|
||||
|
||||
router.get('/preferences', sessionCheck, Preferences);
|
||||
router.post('/preferences', sessionCheck, submitPreferences);
|
||||
|
||||
router.get('/account', sessionCheck, Account);
|
||||
|
||||
router.get('/sponsors', sessionCheck, Sponsors);
|
||||
|
||||
router.get('/credits', sessionCheck, Credits);
|
||||
|
||||
|
||||
|
||||
|
||||
router.get("/appsModals/:modal?", adminOnly, appsModals);
|
||||
|
||||
router.post("/install", adminOnly, Install);
|
||||
router.post("/uninstall", adminOnly, Uninstall);
|
||||
|
||||
router.post('/update_languages', adminOnly, updateLanguages);
|
||||
|
||||
|
||||
router.post("/search", function (req, res) {
|
||||
// req.header('hx-current-url') == http://localhost:8000/dashboard
|
||||
let page = (req.header('hx-current-url')).split("/").pop();
|
||||
switch(page) {
|
||||
case "dashboard":
|
||||
searchDashboard(req, res);
|
||||
break;
|
||||
case "images":
|
||||
searchImages(req, res);
|
||||
break;
|
||||
case "volumes":
|
||||
searchVolumes(req, res);
|
||||
break;
|
||||
case "networks":
|
||||
searchNetworks(req, res);
|
||||
break;
|
||||
case "apps":
|
||||
searchApps(req, res);
|
||||
break;
|
||||
case "users":
|
||||
searchUsers(req, res);
|
||||
break;
|
||||
case "syslogs":
|
||||
searchSyslogs(req, res);
|
||||
break;
|
||||
case "preferences":
|
||||
searchPreferences(req, res);
|
||||
break;
|
||||
case "settings":
|
||||
searchSettings(req, res);
|
||||
break;
|
||||
case "account":
|
||||
searchAccount(req, res);
|
||||
case "sponsors":
|
||||
searchSponsors(req, res);
|
||||
break;
|
||||
default:
|
||||
console.log(`[Search] ${req.body.search}`);
|
||||
res.send('ok');
|
||||
}
|
||||
});
|
106
router/index.js
Normal file
106
router/index.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
import express from "express";
|
||||
import { Permission } from '../database/models.js';
|
||||
export const router = express.Router();
|
||||
|
||||
// Controllers
|
||||
import { Login, submitLogin, Logout } from "../controllers/login.js";
|
||||
import { Register, submitRegister } from "../controllers/register.js";
|
||||
import { Dashboard, DashboardAction, Stats, Chart, SSE, UpdatePermissions } from "../controllers/dashboard.js";
|
||||
import { Apps, appSearch, InstallModal, ImportModal, LearnMore, Upload, removeTemplate } from "../controllers/apps.js";
|
||||
import { Users } from "../controllers/users.js";
|
||||
import { Images } from "../controllers/images.js";
|
||||
import { Networks, removeNetwork } from "../controllers/networks.js";
|
||||
import { Volumes, addVolume, removeVolume } from "../controllers/volumes.js";
|
||||
import { Account } from "../controllers/account.js";
|
||||
import { Variables } from "../controllers/variables.js";
|
||||
import { Settings } from "../controllers/settings.js";
|
||||
import { Supporters, Thanks } from "../controllers/supporters.js";
|
||||
import { Syslogs } from "../controllers/syslogs.js";
|
||||
import { Install } from "../utils/install.js"
|
||||
import { Uninstall } from "../utils/uninstall.js"
|
||||
|
||||
// Permission Middleware
|
||||
const adminOnly = async (req, res, next) => {
|
||||
if (req.session.role == 'admin') { next(); }
|
||||
else { res.redirect('/dashboard'); }
|
||||
}
|
||||
|
||||
const sessionCheck = async (req, res, next) => {
|
||||
if (req.session.user) { next(); }
|
||||
else { res.redirect('/login'); }
|
||||
}
|
||||
|
||||
const permissionCheck = async (req, res, next) => {
|
||||
if (req.session.role == 'admin') { next(); return; }
|
||||
let user = req.session.user;
|
||||
let action = req.path.split("/")[2];
|
||||
let trigger = req.header('hx-trigger-name');
|
||||
const userAction = ['start', 'stop', 'restart', 'pause', 'uninstall', 'upgrade', 'edit', 'logs', 'view'];
|
||||
const userPaths = ['card', 'updates', 'hide', 'reset', 'alert'];
|
||||
if (userAction.includes(action)) {
|
||||
let permission = await Permission.findOne({ where: { containerName: trigger, user: user }, attributes: [`${action}`] });
|
||||
if (permission) {
|
||||
if (permission[action] == true) {
|
||||
console.log(`User ${user} has permission to ${action} ${trigger}`);
|
||||
next();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
console.log(`User ${user} does not have permission to ${action} ${trigger}`);
|
||||
}
|
||||
}
|
||||
} else if (userPaths.includes(action)) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Utils
|
||||
router.post("/install", adminOnly, Install);
|
||||
router.post("/uninstall", adminOnly, Uninstall);
|
||||
|
||||
// Routes
|
||||
router.get("/login", Login);
|
||||
router.post("/login", submitLogin);
|
||||
router.get("/logout", Logout);
|
||||
router.get("/register", Register);
|
||||
router.post("/register", submitRegister);
|
||||
|
||||
router.get("/", sessionCheck, Dashboard);
|
||||
router.get("/dashboard", sessionCheck, Dashboard);
|
||||
router.post("/dashboard/:action", sessionCheck, permissionCheck, DashboardAction);
|
||||
router.get("/sse", sessionCheck, SSE);
|
||||
router.post("/updatePermissions", adminOnly, UpdatePermissions);
|
||||
router.get("/stats", sessionCheck, Stats);
|
||||
router.get("/chart", sessionCheck, Chart);
|
||||
|
||||
router.get("/images", adminOnly, Images);
|
||||
router.post("/images/:action", adminOnly, Images);
|
||||
|
||||
router.get("/volumes", adminOnly, Volumes);
|
||||
router.post("/addVolume", adminOnly, addVolume);
|
||||
router.post("/removeVolume", adminOnly, removeVolume);
|
||||
|
||||
router.get("/networks", adminOnly, Networks);
|
||||
router.post("/removeNetwork", adminOnly, removeNetwork);
|
||||
|
||||
router.get("/apps/:page?/:template?", adminOnly, Apps);
|
||||
router.post("/apps", adminOnly, appSearch);
|
||||
router.get("/remove_template/:template", adminOnly, removeTemplate);
|
||||
|
||||
router.get("/install_modal", adminOnly, InstallModal)
|
||||
router.get("/import_modal", adminOnly, ImportModal)
|
||||
router.get("/learn_more", adminOnly, LearnMore)
|
||||
router.post("/upload", adminOnly, Upload);
|
||||
|
||||
router.get("/users", adminOnly, Users);
|
||||
router.get("/syslogs", adminOnly, Syslogs);
|
||||
|
||||
router.get("/variables", adminOnly, Variables);
|
||||
router.get("/settings", adminOnly, Settings);
|
||||
|
||||
|
||||
router.get("/account", sessionCheck, Account);
|
||||
router.get("/supporters", sessionCheck, Supporters);
|
||||
router.post("/thank", sessionCheck, Thanks);
|
||||
|
40
server.js
40
server.js
|
@ -1,22 +1,48 @@
|
|||
import express from 'express';
|
||||
import session from 'express-session';
|
||||
import memorystore from 'memorystore';
|
||||
import ejs from 'ejs';
|
||||
import { router } from './router.js';
|
||||
import { sessionMiddleware } from './db/config.js';
|
||||
import Docker from 'dockerode';
|
||||
import { router } from './router/index.js';
|
||||
import { sequelize } from './database/models.js';
|
||||
export const docker = new Docker();
|
||||
|
||||
// Session middleware
|
||||
const MemoryStore = memorystore(session);
|
||||
const sessionMiddleware = session({
|
||||
store: new MemoryStore({ checkPeriod: 86400000 }), // Prune expired entries every 24h
|
||||
secret: "keyboard cat",
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie:{
|
||||
secure: false,
|
||||
httpOnly: false,
|
||||
maxAge: 3600000 * 8 // Session max age in milliseconds. 3600000 = 1 hour.
|
||||
}
|
||||
});
|
||||
|
||||
// Express middleware
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 8000;
|
||||
|
||||
app.set('view engine', 'html');
|
||||
app.set('trust proxy', true);
|
||||
app.engine('html', ejs.renderFile);
|
||||
app.use([
|
||||
express.static('public'),
|
||||
express.urlencoded({ extended: true }),
|
||||
sessionMiddleware,
|
||||
router,
|
||||
router
|
||||
]);
|
||||
|
||||
// Initialize server
|
||||
app.listen(PORT, async () => {
|
||||
console.log(`\x1b[32mListening on http://localhost:${PORT}\x1b[0m`);
|
||||
console.log('');
|
||||
async function init() {// I made sure the console.logs and emojis lined up
|
||||
try { await sequelize.authenticate().then(
|
||||
() => { console.log('DB Connection: ✔️') }); }
|
||||
catch { console.log('DB Connection: ❌'); }
|
||||
try { await sequelize.sync().then(
|
||||
() => { console.log('Synced Models: ✔️') }); }
|
||||
catch { console.log('Synced Models: ❌'); } }
|
||||
await init().then(() => {
|
||||
console.log(`Listening on http://localhost:${PORT}`);
|
||||
});
|
||||
});
|
2
templates/compose/.gitignore
vendored
Normal file
2
templates/compose/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
|
@ -40,24 +40,6 @@
|
|||
"label": "TZ",
|
||||
"default": "America/Los_Angeles"
|
||||
}
|
||||
],
|
||||
"labels": [
|
||||
{
|
||||
"name": "traefik.enable",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "traefik.http.services.heimdall.loadbalancer.server.port",
|
||||
"value": "80"
|
||||
},
|
||||
{
|
||||
"name": "traefik.http.routers.heimdall.entrypoints",
|
||||
"value": "websecure"
|
||||
},
|
||||
{
|
||||
"name": "traefik.http.routers.heimdall.tls.certresolver",
|
||||
"value": "mydnschallenge"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -1952,7 +1934,7 @@
|
|||
"title": "Fail2ban",
|
||||
"name": "fail2ban",
|
||||
"note": "",
|
||||
"description": "Fail2Ban is an intrusion prevention software framework, designed to prevent brute-force attacks. It is able to run on POSIX systems that have an interface to a packet-control system or firewall installed locally, such as iptables or TCP Wrapper.",
|
||||
"description": "Fail2ban is a daemon to ban hosts that cause multiple authentication errors.",
|
||||
"platform": "linux",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/fail2ban.png",
|
||||
"image": "linuxserver/fail2ban:latest",
|
||||
|
@ -3141,7 +3123,7 @@
|
|||
"Networking",
|
||||
"Tools"
|
||||
],
|
||||
"description": "Guacamole is a clientless remote desktop gateway. It supports standard protocols like VNC and RDP. It is called clientless because no plugins or client software are required.",
|
||||
"description": "A clientless remote desktop gateway.",
|
||||
"image": "oznu/guacamole:latest",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/guacamole.png",
|
||||
"name": "guacamole",
|
||||
|
@ -5039,6 +5021,67 @@
|
|||
],
|
||||
"privileged": true
|
||||
},
|
||||
{
|
||||
"name": "nocodb",
|
||||
"title": "NocoDB",
|
||||
"note": "",
|
||||
"description": "NocoDB is a free, open-source, self-hosted, no-code platform to make database driven application. <a href='https://www.nocodb.com/' target='_blank'>Website</a>. <a href='https://hub.docker.com/r/nocodb/nocodb' target='_blank'>Docker Hub</a>",
|
||||
"logo": "https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/png/nocodb.png",
|
||||
"image": "nocodb/nocodb",
|
||||
"categories": [
|
||||
"Database"
|
||||
],
|
||||
"volumes": [
|
||||
{
|
||||
"bind": "/home/docker/nocodb",
|
||||
"container": "/var/lib/nocodb",
|
||||
"mode": "rw"
|
||||
}
|
||||
],
|
||||
"ports" : [
|
||||
{
|
||||
"host": "8080",
|
||||
"container": "8080"
|
||||
}
|
||||
],
|
||||
"env": [
|
||||
{
|
||||
"name": "NOCODB_DB_HOST",
|
||||
"label": "Database Host",
|
||||
"description": "Database host",
|
||||
"type": "text",
|
||||
"default": "mysql"
|
||||
},
|
||||
{
|
||||
"name": "NOCODB_DB_PORT",
|
||||
"label": "Database Port",
|
||||
"description": "Database port",
|
||||
"type": "text",
|
||||
"default": "3306"
|
||||
},
|
||||
{
|
||||
"name": "NOCODB_DB_USER",
|
||||
"label": "Database User",
|
||||
"description": "Database user",
|
||||
"type": "text",
|
||||
"default": "root"
|
||||
},
|
||||
{
|
||||
"name": "NOCODB_DB_PASSWORD",
|
||||
"label": "Database Password",
|
||||
"description": "Database password",
|
||||
"type": "password",
|
||||
"default": "password"
|
||||
},
|
||||
{
|
||||
"name": "NOCODB_DB_NAME",
|
||||
"label": "Database Name",
|
||||
"description": "Database name",
|
||||
"type": "text",
|
||||
"default": "nocodb"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"categories": [
|
||||
"Tools"
|
||||
|
@ -5063,7 +5106,7 @@
|
|||
],
|
||||
"note": "DNS-over-HTTPS: [80:80/TCP] [443:443/TCP] [443:443/UDP] [3000:3000/TCP] [DEFAULT]. DNS: [53:53/TCP] [53:53/UDP]. Admin Panel: [3000:3000/TCP]. DHCP: [67:67/UDP] [68:68/TCP] [68:68/UDP]. DNS-over-TLS: [853:853/TCP]. DNS-over-QUIC: [784:784/UDP] [853:853/UDP] [8853:8853/UDP]. DNSCrypt: [5443:5443/TCP] [5443:5443/UDP]",
|
||||
"image": "adguard/adguardhome:latest",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/Dashboard-Icons/main/png/adguard.png",
|
||||
"logo": "https://raw.githubusercontent.com/Qballjos/portainer_templates/master/Images/adguard.png",
|
||||
"name": "adguard",
|
||||
"platform": "linux",
|
||||
"ports": [
|
||||
|
@ -5314,7 +5357,7 @@
|
|||
"CMS"
|
||||
],
|
||||
"platform": "linux",
|
||||
"logo": "https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/drupal.png",
|
||||
"logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/drupal.png",
|
||||
"image": "drupal:latest",
|
||||
"ports": [
|
||||
"80/tcp"
|
||||
|
@ -5476,7 +5519,7 @@
|
|||
{
|
||||
"type": 1,
|
||||
"name": "nvidia-hwa-test",
|
||||
"title": "NVIDIA-HWA-Test",
|
||||
"title": "NVIDIA HWA Test",
|
||||
"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",
|
2
templates/tmp/.gitignore
vendored
Normal file
2
templates/tmp/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
241
utils/docker.js
241
utils/docker.js
|
@ -1,241 +0,0 @@
|
|||
import Docker from 'dockerode';
|
||||
import { dockerContainerStats } from 'systeminformation';
|
||||
import { Container, ServerSettings } from '../db/config.js'
|
||||
import stream from 'stream';
|
||||
|
||||
export var docker;
|
||||
var docker2;
|
||||
var docker3;
|
||||
var docker4;
|
||||
|
||||
if (process.env.DOCKER_HOST && process.env.DOCKER_PORT) {
|
||||
console.log('Connecting to Docker with environment variables.');
|
||||
docker = new Docker({ host: process.env.DOCKER_HOST, port: process.env.DOCKER_PORT });
|
||||
console.log('Docker host connected.');
|
||||
} else {
|
||||
console.log('Connecting to default Docker host.');
|
||||
docker = new Docker();
|
||||
console.log('Docker host connected.');
|
||||
}
|
||||
|
||||
export async function GetContainerLists(hostid) {
|
||||
|
||||
let host = hostid || 1;
|
||||
|
||||
let containers;
|
||||
|
||||
if (host == 0) {
|
||||
containers = await docker.listContainers({ all: true });
|
||||
}
|
||||
|
||||
if ((host == 0) && docker2) {
|
||||
let containers2 = await docker2.listContainers({ all: true });
|
||||
containers = containers.concat(containers2);
|
||||
}
|
||||
|
||||
if ((host == 0) && docker3) {
|
||||
let containers3 = await docker3.listContainers({ all: true });
|
||||
containers = containers.concat(containers3);
|
||||
}
|
||||
|
||||
if ((host == 0) && docker4) {
|
||||
let containers4 = await docker4.listContainers({ all: true });
|
||||
containers = containers.concat(containers4);
|
||||
}
|
||||
|
||||
if (host == 1) {
|
||||
containers = await docker.listContainers({ all: true });
|
||||
}
|
||||
|
||||
if (host == 2 && docker2) {
|
||||
containers = await docker2.listContainers({ all: true });
|
||||
}
|
||||
|
||||
if (host == 3 && docker3) {
|
||||
containers = await docker3.listContainers({ all: true });
|
||||
}
|
||||
|
||||
if (host == 4 && docker4) {
|
||||
containers = await docker4.listContainers({ all: true });
|
||||
}
|
||||
|
||||
return containers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function configureHost(hostid, ip, port) {
|
||||
|
||||
if (hostid == 2) {
|
||||
docker2 = new Docker({ host: ip, port: port });
|
||||
try {
|
||||
let containers = await docker2.listContainers({ all: true });
|
||||
console.log(`Host 2 connected. ${containers.length} containers found.`);
|
||||
}
|
||||
catch {
|
||||
console.log('Host 2 connection failed.');
|
||||
docker2;
|
||||
}
|
||||
} else if (hostid == 3) {
|
||||
docker3 = new Docker({ host: ip, port: port });
|
||||
try {
|
||||
let containers = await docker3.listContainers({ all: true });
|
||||
console.log(`Host 3 connected. ${containers.length} containers found.`);
|
||||
}
|
||||
catch {
|
||||
console.log('Host 3 connection failed.');
|
||||
docker3;
|
||||
}
|
||||
} else if (hostid == 4) {
|
||||
docker4 = new Docker({ host: ip, port: port });
|
||||
try {
|
||||
let containers = await docker4.listContainers({ all: true });
|
||||
console.log(`Host 4 connected. ${containers.length} containers found.`);
|
||||
}
|
||||
catch {
|
||||
console.log('Host 4 connection failed.');
|
||||
docker4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function imageList() {
|
||||
let images = await docker.listImages({ all: true });
|
||||
return images;
|
||||
}
|
||||
|
||||
export async function volumeList() {
|
||||
let volumes = await docker.listVolumes();
|
||||
return volumes;
|
||||
}
|
||||
|
||||
export async function networkList() {
|
||||
let networks = await docker.listNetworks();
|
||||
return networks;
|
||||
}
|
||||
|
||||
export async function GetContainer(containerID) {
|
||||
let container = docker.getContainer(containerID);
|
||||
return container;
|
||||
}
|
||||
|
||||
export async function containerInfo (containerID) {
|
||||
|
||||
// get the container info
|
||||
let info = docker.getContainer(containerID);
|
||||
let container = await info.inspect();
|
||||
|
||||
let container_name = container.Name.slice(1);
|
||||
let container_image = container.Config.Image;
|
||||
let container_service = container.Config.Labels['com.docker.compose.service'];
|
||||
|
||||
let ports_list = [];
|
||||
let external = 0;
|
||||
let internal = 0;
|
||||
|
||||
try {
|
||||
for (const [key, value] of Object.entries(container.HostConfig.PortBindings)) {
|
||||
let ports = {
|
||||
check: 'checked',
|
||||
external: value[0].HostPort,
|
||||
internal: key.split('/')[0],
|
||||
protocol: key.split('/')[1]
|
||||
}
|
||||
ports_list.push(ports);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
try { external = ports_list[0].external; internal = ports_list[0].internal; } catch { }
|
||||
|
||||
let container_info = {
|
||||
containerName: container_name,
|
||||
containerID: containerID,
|
||||
containerImage: container_image,
|
||||
containerService: container_service,
|
||||
containerState: container.State.Status,
|
||||
external_port: external,
|
||||
internal_port: internal,
|
||||
ports: ports_list,
|
||||
volumes: container.Mounts,
|
||||
env: container.Config.Env,
|
||||
labels: container.Config.Labels,
|
||||
link: '',
|
||||
}
|
||||
|
||||
return container_info;
|
||||
}
|
||||
|
||||
|
||||
export async function containerLogs(containerID) {
|
||||
let container = docker.getContainer(containerID);
|
||||
const logs = await container.logs({ stdout: true, stderr: true, tail: 'all', });
|
||||
const logsString = logs.toString('utf8');
|
||||
return logsString;
|
||||
}
|
||||
|
||||
|
||||
let available_versions = '';
|
||||
async function version_check () {
|
||||
const resp = await fetch('https://registry.hub.docker.com/v2/namespaces/lllllllillllllillll/repositories/dweebui/tags/?page_size=10000');
|
||||
let hub = await resp.json();
|
||||
for (let i = 0; i < hub.results.length; i++) {
|
||||
available_versions += '| ' + hub.results[i].name + ' ';
|
||||
}
|
||||
console.log('Available versions:');
|
||||
console.log(available_versions);
|
||||
}
|
||||
version_check();
|
||||
|
||||
|
||||
// Creates then destroys a docker volume to trigger a docker event.
|
||||
export async function trigger_docker_event () {
|
||||
let volume = await docker.createVolume({ Name: 'dweebui_test_volume' });
|
||||
setTimeout(async() => {
|
||||
await volume.remove();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
||||
export async function containerStats (containerID) {
|
||||
const stats = await dockerContainerStats(containerID);
|
||||
let info = {
|
||||
containerID: containerID,
|
||||
cpu: Math.round(stats[0].cpuPercent),
|
||||
ram: Math.round(stats[0].memPercent)
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function removeNetwork(networkID) {
|
||||
let network = docker.getNetwork(networkID);
|
||||
await network.remove();
|
||||
console.log(`Network ${networkID} removed.`);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function check_configured_hosts () {
|
||||
|
||||
let [host2, created] = await ServerSettings.findOrCreate({ where: {key: 'host2'}, defaults: { key: 'host2', value: '' } });
|
||||
if (host2.value != '') {
|
||||
let [tag2, ip2, port2] = host2.value.split(',');
|
||||
configureHost(2, ip2, port2);
|
||||
console.log('Host 2 configured.');
|
||||
}
|
||||
|
||||
let [host3, created3] = await ServerSettings.findOrCreate({ where: {key: 'host3'}, defaults: { key: 'host3', value: '' } });
|
||||
if (host3.value != '') {
|
||||
let [tag3, ip3, port3] = host3.value.split(',');
|
||||
configureHost(3, ip3, port3);
|
||||
console.log('Host 3 configured.');
|
||||
}
|
||||
|
||||
let [host4, created4] = await ServerSettings.findOrCreate({ where: {key: 'host4'}, defaults: { key: 'host4', value: '' } });
|
||||
if (host4.value != '') {
|
||||
let [tag4, ip4, port4] = host4.value.split(',');
|
||||
configureHost(4, ip4, port4);
|
||||
console.log('Host 4 configured.');
|
||||
}
|
||||
}
|
457
utils/install.js
457
utils/install.js
|
@ -1,239 +1,250 @@
|
|||
import { writeFileSync, mkdirSync, readFileSync, readdirSync, writeFile } from "fs";
|
||||
import { execSync } from "child_process";
|
||||
import { Syslog } from "../db/config.js";
|
||||
import { docker } from "./docker.js";
|
||||
import DockerodeCompose from "dockerode-compose";
|
||||
import yaml from 'js-yaml';
|
||||
import { Alert } from "./system.js";
|
||||
|
||||
import { execSync } from "child_process";
|
||||
import { docker } from "../server.js";
|
||||
import DockerodeCompose from "dockerode-compose";
|
||||
import { Syslog } from "../database/models.js";
|
||||
import { addAlert } from "../controllers/dashboard.js";
|
||||
|
||||
// This entire page hurts to look at.
|
||||
export const Install = async (req, res) => {
|
||||
|
||||
let data = req.body;
|
||||
|
||||
let { name, service_name, image, command_check, command, net_mode, restart_policy } = data;
|
||||
let { port0, port1, port2, port3, port4, port5 } = data;
|
||||
let { volume0, volume1, volume2, volume3, volume4, volume5 } = data;
|
||||
let { env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11 } = data;
|
||||
let { label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11 } = data;
|
||||
let data = req.body;
|
||||
let name = data.name;
|
||||
|
||||
let ports = [ port0, port1, port2, port3, port4, port5 ];
|
||||
let volumes = [volume0, volume1, volume2, volume3, volume4, volume5];
|
||||
let env_vars = [env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11];
|
||||
let labels = [label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11];
|
||||
|
||||
let docker_volumes = [];
|
||||
|
||||
// Make sure there isn't a container already running that has the same name
|
||||
let containers = await docker.listContainers({ all: true });
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
if (containers[i].Names[0].includes(name)) {
|
||||
console.log(`App '${name}' already exists. Please choose a different name.`);
|
||||
let alert = Alert('danger', `App '${name}' already exists. Please choose a different name.`);
|
||||
res.send(alert);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// async function composeInstall (name, compose, req) {
|
||||
|
||||
// console.log('[composeInstall]');
|
||||
|
||||
// // try {
|
||||
// // await compose.pull().then(() => {
|
||||
|
||||
// // compose.up();
|
||||
|
||||
|
||||
// // Syslog.create({
|
||||
// // user: req.session.user,
|
||||
// // email: null,
|
||||
// // event: "App Installation",
|
||||
// // message: `${name} installed successfully`,
|
||||
// // ip: req.socket.remoteAddress
|
||||
// // });
|
||||
|
||||
// // });
|
||||
// // } catch (err) {
|
||||
// // await Syslog.create({
|
||||
// // user: req.session.user,
|
||||
// // email: null,
|
||||
// // event: "App Installation",
|
||||
// // message: `${name} installation failed: ${err}`,
|
||||
// // ip: req.socket.remoteAddress
|
||||
// // });
|
||||
// // }
|
||||
|
||||
// await compose.pull();
|
||||
// await compose.up();
|
||||
// console.log('compose.up');
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// Compose file installation
|
||||
if (req.body.compose) {
|
||||
// Create the directory
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
// Write the form data to the compose file
|
||||
writeFileSync(`./templates/compose/${name}/compose.yaml`, req.body.compose, function (err) { console.log(err) });
|
||||
var compose = new DockerodeCompose(docker, `./templates/compose/${name}/compose.yaml`, `${name}`);
|
||||
composeInstall(name, compose, req);
|
||||
res.redirect('/');
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert a JSON template into a compose file
|
||||
let compose_file = `version: '3'`;
|
||||
compose_file += `\nservices:`
|
||||
compose_file += `\n ${service_name}:`
|
||||
compose_file += `\n container_name: ${name}`;
|
||||
compose_file += `\n image: ${image}`;
|
||||
|
||||
// Command
|
||||
if (command_check == 'on') { compose_file += `\n command: ${command}` }
|
||||
|
||||
// Network mode
|
||||
if (net_mode == 'host') { compose_file += `\n network_mode: 'host'` }
|
||||
else if (net_mode != 'host' && net_mode != 'docker') { compose_file += `\n network_mode: '${net_mode}'` }
|
||||
|
||||
// Restart policy
|
||||
if (restart_policy != '') { compose_file += `\n restart: ${restart_policy}` }
|
||||
|
||||
// Ports
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
if ((ports[i] == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n ports:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
if ((ports[i] == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n - ${data[`port_${i}_external`]}:${data[`port_${i}_internal`]}/${data[`port_${i}_protocol`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Volumes
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
if (volumes[i] == 'on') {
|
||||
compose_file += `\n volumes:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
// if volume is on and neither bind or container is empty, it's a bind mount (ex /mnt/user/appdata/config:/config )
|
||||
if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] != '') && (data[`volume_${i}_container`] != '')) {
|
||||
compose_file += `\n - ${data[`volume_${i}_bind`]}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
||||
}
|
||||
// if bind is empty create a docker volume (ex container_name_config:/config) convert any '/' in container name to '_'
|
||||
else if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] == '') && (data[`volume_${i}_container`] != '')) {
|
||||
let volume_name = data[`volume_${i}_container`].replace(/\//g, '_');
|
||||
compose_file += `\n - ${name}_${volume_name}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
||||
docker_volumes.push(`${name}_${volume_name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Environment variables
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if (env_vars[i] == 'on') {
|
||||
compose_file += `\n environment:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if (env_vars[i] == 'on') {
|
||||
compose_file += `\n - ${data[`env_${i}_name`]}=${data[`env_${i}_default`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Labels
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
if (labels[i] == 'on') {
|
||||
compose_file += `\n labels:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (data[`label${i}`] == 'on') {
|
||||
compose_file += `\n - ${data[`label_${i}_name`]}=${data[`label_${i}_value`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Privileged mode
|
||||
if (data.privileged == 'on') { compose_file += `\n privileged: true` }
|
||||
|
||||
// Hardware acceleration
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if ((env_vars[i] == 'on') && (data[`env_${i}_name`] == 'DRINODE')) {
|
||||
compose_file += `\n deploy:`
|
||||
compose_file += `\n resources:`
|
||||
compose_file += `\n reservations:`
|
||||
compose_file += `\n devices:`
|
||||
compose_file += `\n - driver: nvidia`
|
||||
compose_file += `\n count: 1`
|
||||
compose_file += `\n capabilities: [gpu]`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add volumes to the compose file
|
||||
if ( docker_volumes.length > 0 ) {
|
||||
compose_file += `\n`
|
||||
compose_file += `\nvolumes:`
|
||||
// Removed any duplicates from docker_volumes
|
||||
docker_volumes = docker_volumes.filter((item, index) => docker_volumes.indexOf(item) === index)
|
||||
for (let i = 0; i < docker_volumes.length; i++) {
|
||||
if ( docker_volumes[i] != '') {
|
||||
compose_file += `\n ${docker_volumes[i]}:`
|
||||
let containers = await docker.listContainers({ all: true });
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
if (containers[i].Names[0].includes(name)) {
|
||||
addAlert(req.session, 'danger', `App ${name} already exists. Please remove it first.`);
|
||||
res.redirect('/');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
|
||||
writeFileSync(`./appdata/${name}/compose.yaml`, compose_file, function (err) { console.log(err) });
|
||||
if (req.body.compose) {
|
||||
|
||||
console.log(`Installing ${name}. It should appear on the dashboard shortly.`);
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
writeFileSync(`./templates/compose/${name}/compose.yaml`, req.body.compose, function (err) { console.log(err) });
|
||||
let compose = new DockerodeCompose(docker, `./templates/compose/${name}/compose.yaml`, `${name}`);
|
||||
addAlert(req.session, 'success', `Installing ${name}. It should appear on the dashboard shortly.`);
|
||||
try {
|
||||
(async () => {
|
||||
await compose.pull();
|
||||
await compose.up();
|
||||
|
||||
// composeInstall(name, compose, req);
|
||||
await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${app} installed successfully`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
})();
|
||||
} catch (err) {
|
||||
await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${app} installation failed: ${err}`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
var compose = new DockerodeCompose(docker, `./appdata/${name}/compose.yaml`, `${name}`);
|
||||
let { service_name, image, command_check, command, net_mode, restart_policy } = data;
|
||||
let { port0, port1, port2, port3, port4, port5 } = data;
|
||||
let { volume0, volume1, volume2, volume3, volume4, volume5 } = data;
|
||||
let { env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11 } = data;
|
||||
let { label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11 } = data;
|
||||
|
||||
(async () => {
|
||||
console.log('Pulling image');
|
||||
await compose.pull();
|
||||
console.log('Starting container');
|
||||
await compose.up();
|
||||
})();
|
||||
let ports = [port0, port1, port2, port3, port4, port5]
|
||||
|
||||
let alert = Alert('success', `Installing ${name}. It should appear on the dashboard shortly.`);
|
||||
let docker_volumes = [];
|
||||
|
||||
res.send(alert);
|
||||
addAlert(req.session, 'success', `Installing ${name}. It should appear on the dashboard shortly.`);
|
||||
|
||||
if (image.startsWith('https://')){
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
execSync(`curl -o ./appdata/${name}/${name}_stack.yml -L ${image}`);
|
||||
console.log(`Downloaded stackfile: ${image}`);
|
||||
let stackfile = yaml.load(readFileSync(`./appdata/${name}/${name}_stack.yml`, 'utf8'));
|
||||
let services = Object.keys(stackfile.services);
|
||||
|
||||
for ( let i = 0; i < services.length; i++ ) {
|
||||
try {
|
||||
console.log(stackfile.services[Object.keys(stackfile.services)[i]].environment);
|
||||
} catch { console.log('no env') }
|
||||
}
|
||||
} else {
|
||||
|
||||
let compose_file = `version: '3'`;
|
||||
compose_file += `\nservices:`
|
||||
compose_file += `\n ${service_name}:`
|
||||
compose_file += `\n container_name: ${name}`;
|
||||
compose_file += `\n image: ${image}`;
|
||||
|
||||
// Command
|
||||
if (command_check == 'on') {
|
||||
compose_file += `\n command: ${command}`
|
||||
}
|
||||
|
||||
// Network mode
|
||||
if (net_mode == 'host') {
|
||||
compose_file += `\n network_mode: 'host'`
|
||||
}
|
||||
else if (net_mode != 'host' && net_mode != 'docker') {
|
||||
compose_file += `\n network_mode: '${net_mode}'`
|
||||
}
|
||||
|
||||
// Restart policy
|
||||
if (restart_policy != '') {
|
||||
compose_file += `\n restart: ${restart_policy}`
|
||||
}
|
||||
|
||||
// Ports
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
if ((ports[i] == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n ports:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
if ((ports[i] == 'on') && (net_mode != 'host')) {
|
||||
compose_file += `\n - ${data[`port_${i}_external`]}:${data[`port_${i}_internal`]}/${data[`port_${i}_protocol`]}`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Volumes
|
||||
let volumes = [volume0, volume1, volume2, volume3, volume4, volume5]
|
||||
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
if (volumes[i] == 'on') {
|
||||
compose_file += `\n volumes:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
|
||||
// if volume is on and neither bind or container is empty, it's a bind mount (ex /mnt/user/appdata/config:/config )
|
||||
if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] != '') && (data[`volume_${i}_container`] != '')) {
|
||||
compose_file += `\n - ${data[`volume_${i}_bind`]}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
||||
}
|
||||
|
||||
// if bind is empty create a docker volume (ex container_name_config:/config) convert any '/' in container name to '_'
|
||||
else if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] == '') && (data[`volume_${i}_container`] != '')) {
|
||||
let volume_name = data[`volume_${i}_container`].replace(/\//g, '_');
|
||||
compose_file += `\n - ${name}_${volume_name}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
|
||||
docker_volumes.push(`${name}_${volume_name}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Environment variables
|
||||
let env_vars = [env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11]
|
||||
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if (env_vars[i] == 'on') {
|
||||
compose_file += `\n environment:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if (env_vars[i] == 'on') {
|
||||
compose_file += `\n - ${data[`env_${i}_name`]}=${data[`env_${i}_default`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Labels
|
||||
let labels = [label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11]
|
||||
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
if (labels[i] == 'on') {
|
||||
compose_file += `\n labels:`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (data[`label${i}`] == 'on') {
|
||||
compose_file += `\n - ${data[`label_${i}_name`]}=${data[`label_${i}_value`]}`
|
||||
}
|
||||
}
|
||||
|
||||
// Privileged mode
|
||||
if (data.privileged == 'on') {
|
||||
compose_file += `\n privileged: true`
|
||||
}
|
||||
|
||||
// Hardware acceleration
|
||||
for (let i = 0; i < env_vars.length; i++) {
|
||||
if ((env_vars[i] == 'on') && (data[`env_${i}_name`] == 'DRINODE')) {
|
||||
compose_file += `\n deploy:`
|
||||
compose_file += `\n resources:`
|
||||
compose_file += `\n reservations:`
|
||||
compose_file += `\n devices:`
|
||||
compose_file += `\n - driver: nvidia`
|
||||
compose_file += `\n count: 1`
|
||||
compose_file += `\n capabilities: [gpu]`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// add any docker volumes to the docker-compose file
|
||||
if ( docker_volumes.length > 0 ) {
|
||||
compose_file += `\n`
|
||||
compose_file += `\nvolumes:`
|
||||
|
||||
// check docker_volumes for duplicates and remove them completely
|
||||
docker_volumes = docker_volumes.filter((item, index) => docker_volumes.indexOf(item) === index)
|
||||
|
||||
for (let i = 0; i < docker_volumes.length; i++) {
|
||||
if ( docker_volumes[i] != '') {
|
||||
compose_file += `\n ${docker_volumes[i]}:`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
writeFileSync(`./appdata/${name}/docker-compose.yml`, compose_file, function (err) { console.log(err) });
|
||||
var compose = new DockerodeCompose(docker, `./appdata/${name}/docker-compose.yml`, `${name}`);
|
||||
} catch {
|
||||
await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${name} installation failed - error creating directory or compose file`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
(async () => {
|
||||
await compose.pull();
|
||||
await compose.up();
|
||||
|
||||
await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${name} installed successfully`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
})();
|
||||
} catch (err) {
|
||||
await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Installation",
|
||||
message: `${name} installation failed: ${err}`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
res.redirect('/');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// im just going to leave this old stackfile snippet here for now
|
||||
|
||||
// if (image.startsWith('https://')){
|
||||
// mkdirSync(`./appdata/${name}`, { recursive: true });
|
||||
// execSync(`curl -o ./appdata/${name}/${name}_stack.yml -L ${image}`);
|
||||
// console.log(`Downloaded stackfile: ${image}`);
|
||||
// let stackfile = yaml.load(readFileSync(`./appdata/${name}/${name}_stack.yml`, 'utf8'));
|
||||
// let services = Object.keys(stackfile.services);
|
||||
|
||||
// for ( let i = 0; i < services.length; i++ ) {
|
||||
// try {
|
||||
// console.log(stackfile.services[Object.keys(stackfile.services)[i]].environment);
|
||||
// } catch { console.log('no env') }
|
||||
// }
|
||||
// }
|
|
@ -1,60 +0,0 @@
|
|||
import { Permission, User, Syslog } from "../db/config.js";
|
||||
import { readFileSync } from 'fs';
|
||||
import { Capitalize } from '../utils/system.js';
|
||||
|
||||
|
||||
export const adminOnly = async (req, res, next) => {
|
||||
let path = req.path;
|
||||
// console.log(`\x1b[90m ${req.session.username} ${path} \x1b[0m`);
|
||||
if (req.session.role == 'admin') { next(); return; }
|
||||
console.log(`User ${req.session.username} does not have permission to access ${path}`);
|
||||
res.redirect('/dashboard');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
export const sessionCheck = async (req, res, next) => {
|
||||
if (req.session.userID) { next(); }
|
||||
else { res.redirect('/login'); }
|
||||
}
|
||||
|
||||
|
||||
export const permissionCheck = async (req, res, next) => {
|
||||
if (req.session.role == 'admin') { next(); return; }
|
||||
|
||||
let path = req.path;
|
||||
let containerID = req.params.containerid;
|
||||
let action = req.params.action;
|
||||
let AltIDState = 'a' + containerID + 'State';
|
||||
|
||||
const userAction = ['start', 'stop', 'pause', 'restart', 'uninstall', 'upgrade', 'edit', 'logs', 'view'];
|
||||
const userPaths = ['/card_list', '/update_card', 'hide', 'reset', 'alert', '/sse', `/update_card/${containerID}` ];
|
||||
|
||||
if (userAction.includes(action)) {
|
||||
let permission = await Permission.findOne({ where: { containerID: containerID, userID: req.session.userID }, attributes: [`${action}`] });
|
||||
if (permission) {
|
||||
if (permission[action] == true) {
|
||||
// console.log(`User ${req.session.username} has permission for ${path}`);
|
||||
await Syslog.create({ username: req.session.username, uniqueID: req.session.userID, event: "User Action", message: `User ${req.session.username} has permission to ${action} ${containerID}`, ip: req.socket.remoteAddress });
|
||||
next();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
console.log(`User ${req.session.username} does NOT have permission for ${path}`);
|
||||
await Syslog.create({ username: req.session.username, uniqueID: req.session.userID, event: "User Action", message: `User ${req.session.username} does not have permission to ${action} ${containerID}`, ip: req.socket.remoteAddress });
|
||||
let denied =`<div class="text-yellow d-inline-flex align-items-center lh-1 ms-auto" id="${AltIDState}">
|
||||
<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>
|
||||
<strong>Denied</strong>
|
||||
</div>`;
|
||||
res.send(denied);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (userPaths.includes(path)) {
|
||||
// console.log(`User ${req.session.username} has permission for ${path}`);
|
||||
next();
|
||||
return;
|
||||
} else {
|
||||
console.log(`User ${req.session.username} does NOT have permission for ${path}`);
|
||||
}
|
||||
}
|
174
utils/system.js
174
utils/system.js
|
@ -1,174 +0,0 @@
|
|||
import { User, ServerSettings } from '../db/config.js';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
|
||||
|
||||
// Navbar
|
||||
export async function Navbar (req) {
|
||||
|
||||
let userID = req.session.userID;
|
||||
let username = req.session.username;
|
||||
let role = req.session.role;
|
||||
let host = req.session.host;
|
||||
|
||||
let language = await getLanguage(userID);
|
||||
|
||||
// Check if the user wants to hide their profile name.
|
||||
if (userID != '00000000-0000-0000-0000-000000000000') {
|
||||
let user = await User.findOne({ where: { userID: userID }});
|
||||
let preferences = JSON.parse(user.preferences);
|
||||
if (preferences.hide_profile == true) { username = 'Anon'; }
|
||||
}
|
||||
|
||||
let sponsored = await ServerSettings.findOne({ where: { key: 'sponsored' }});
|
||||
if (sponsored) { username = `<label class="text-yellow">${username}</label>`; }
|
||||
|
||||
let [host0_active, host0_toggle, host0_tag, host0_ip, host0_port] = ['', '', '', '', ''];
|
||||
let [host1_active, host1_toggle, host1_tag, host1_ip, host1_port] = ['', '', '', '', ''];
|
||||
let [host2_active, host2_toggle, host2_tag, host2_ip, host2_port] = ['', '', '', '', ''];
|
||||
let [host3_active, host3_toggle, host3_tag, host3_ip, host3_port] = ['', '', '', '', ''];
|
||||
let [host4_active, host4_toggle, host4_tag, host4_ip, host4_port] = ['', '', '', '', ''];
|
||||
|
||||
const [host2, created2] = await ServerSettings.findOrCreate({ where: { key: 'host2' }, defaults: { key: 'host2', value: '' }});
|
||||
const [host3, created3] = await ServerSettings.findOrCreate({ where: { key: 'host3' }, defaults: { key: 'host3', value: '' }});
|
||||
const [host4, created4] = await ServerSettings.findOrCreate({ where: { key: 'host4' }, defaults: { key: 'host4', value: '' }});
|
||||
|
||||
if (host2.value) { host2_toggle = 'checked'; [host2_tag, host2_ip, host2_port] = host2.value.split(','); }
|
||||
if (host3.value) { host3_toggle = 'checked'; [host3_tag, host3_ip, host3_port] = host3.value.split(','); }
|
||||
if (host4.value) { host4_toggle = 'checked'; [host4_tag, host4_ip, host4_port] = host4.value.split(','); }
|
||||
|
||||
let host_buttons = '<form action="/dashboard/action/switch_host/hostid" method="post">';
|
||||
let nav_link = '';
|
||||
|
||||
if (host == '0') { host0_active = 'text-yellow'; nav_link = '/0'; }
|
||||
if (host == '1') { host1_active = 'text-yellow'; }
|
||||
if (host == '2') { host2_active = 'text-yellow'; nav_link = '/2'; }
|
||||
if (host == '3') { host3_active = 'text-yellow'; nav_link = '/3'; }
|
||||
if (host == '4') { host4_active = 'text-yellow'; nav_link = '/4'; }
|
||||
|
||||
if (host2_toggle || host3_toggle || host4_toggle) { host_buttons += `<button type="submit" name="host" value="0" class="btn ${host0_active}" title="All">All</button> <button type="submit" name="host" value="1" hx-swap="none" class="btn ${host1_active}" title="Host 1">Host 1</button>`; }
|
||||
if (host2_toggle) { host_buttons += `<button type="submit" name="host" value="2" class="btn ${host2_active}" title="${host2_tag}">${host2_tag}</button>`; }
|
||||
if (host3_toggle) { host_buttons += `<button type="submit" name="host" value="3" hx-swap="none" class="btn ${host3_active}" title="${host3_tag}">${host3_tag}</button>`; }
|
||||
if (host4_toggle) { host_buttons += `<button type="submit" name="host" value="4" hx-swap="none" class="btn ${host4_active}" title="${host4_tag}">${host4_tag}</button>`; }
|
||||
|
||||
host_buttons += '</form>';
|
||||
|
||||
let navbar = readFileSync('./views/partials/navbar.html', 'utf8');
|
||||
|
||||
if (language == 'english') {
|
||||
navbar = navbar.replace(/Username/g, username);
|
||||
navbar = navbar.replace(/Userrole/g, role);
|
||||
navbar = navbar.replace(/HostButtons/g, host_buttons);
|
||||
navbar = navbar.replace(/HOSTID/g, nav_link);
|
||||
return navbar;
|
||||
} else {
|
||||
let lang = readFileSync(`./languages/${language}.json`, 'utf8');
|
||||
lang = JSON.parse(lang);
|
||||
|
||||
navbar = navbar.replace(/Dashboard/g, lang.Dashboard);
|
||||
navbar = navbar.replace(/Images/g, lang.Images);
|
||||
navbar = navbar.replace(/Volumes/g, lang.Volumes);
|
||||
navbar = navbar.replace(/Networks/g, lang.Networks);
|
||||
navbar = navbar.replace(/Apps/g, lang.Apps);
|
||||
navbar = navbar.replace(/Users/g, lang.Users);
|
||||
navbar = navbar.replace(/Syslogs/g, lang.Syslogs);
|
||||
navbar = navbar.replace(/HOSTID/g, nav_link);
|
||||
|
||||
navbar = navbar.replace(/Search/g, lang.Search);
|
||||
navbar = navbar.replace(/Account/g, lang.Account);
|
||||
navbar = navbar.replace(/Notifications/g, lang.Notifications);
|
||||
navbar = navbar.replace(/Preferences/g, lang.Preferences);
|
||||
navbar = navbar.replace(/Settings/g, lang.Settings);
|
||||
navbar = navbar.replace(/Logout/g, lang.Logout);
|
||||
|
||||
navbar = navbar.replace(/Username/g, username);
|
||||
navbar = navbar.replace(/Userrole/g, role);
|
||||
navbar = navbar.replace(/HostButtons/g, host_buttons);
|
||||
return navbar;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sidebar
|
||||
export async function Sidebar (req) {
|
||||
|
||||
let language = await getLanguage(req.session.userID);
|
||||
|
||||
let sidebar = readFileSync('./views/partials/sidebar.html', 'utf8');
|
||||
|
||||
if (language == 'english') {
|
||||
return sidebar;
|
||||
} else {
|
||||
let lang = readFileSync(`./languages/${language}.json`, 'utf8');
|
||||
lang = JSON.parse(lang);
|
||||
|
||||
sidebar = sidebar.replace(/Account/g, lang.Account);
|
||||
sidebar = sidebar.replace(/Notifications/g, lang.Notifications);
|
||||
sidebar = sidebar.replace(/Preferences/g, lang.Preferences);
|
||||
sidebar = sidebar.replace(/Settings/g, lang.Settings);
|
||||
sidebar = sidebar.replace(/Sponsors/g, lang.Sponsors);
|
||||
sidebar = sidebar.replace(/Credits/g, lang.Credits);
|
||||
|
||||
return sidebar;
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
export async function Footer (req) {
|
||||
|
||||
let language = await getLanguage(req.session.userID);
|
||||
|
||||
let footer = readFileSync('./views/partials/footer.html', 'utf8');
|
||||
|
||||
let package_info = readFileSync(`package.json`, 'utf8');
|
||||
package_info = JSON.parse(package_info);
|
||||
let build_version = package_info.version.split('.').pop();
|
||||
|
||||
footer = footer.replace(/BuildVersion/g, build_version);
|
||||
|
||||
if (language == 'english') {
|
||||
return footer;
|
||||
} else {
|
||||
let lang = readFileSync(`./languages/${language}.json`, 'utf8');
|
||||
lang = JSON.parse(lang);
|
||||
|
||||
footer = footer.replace(/Documentation/g, lang.Documentation);
|
||||
|
||||
return footer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Header Alert
|
||||
export function Alert (type, message) {
|
||||
return `
|
||||
<div class="alert alert-${type} alert-dismissible" role="alert" style="margin-bottom: 0;">
|
||||
<div class="d-flex">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon alert-icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 12l5 5l10 -10"></path></svg>
|
||||
</div>
|
||||
<div>
|
||||
${message}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn-close" data-bs-dismiss="alert" aria-label="close"></a>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
export async function getLanguage (userID) {
|
||||
|
||||
// Use the admin's language if authentication is disabled.
|
||||
if (userID == '00000000-0000-0000-0000-000000000000') {
|
||||
let user = await User.findOne({ where: { role: 'admin' }});
|
||||
return user.language;
|
||||
} else {
|
||||
let user = await User.findOne({ where: { userID: userID }});
|
||||
return user.language;
|
||||
}
|
||||
}
|
||||
|
||||
export function Capitalize (string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
|
@ -1,34 +1,32 @@
|
|||
import { docker } from "../utils/docker.js";
|
||||
import { Syslog } from "../db/config.js";
|
||||
import { docker } from "../server.js";
|
||||
import { Syslog } from "../database/models.js";
|
||||
|
||||
|
||||
export const Uninstall = async (req, res) => {
|
||||
|
||||
let { confirm, service_id } = req.body;
|
||||
let { confirm, service_name } = req.body;
|
||||
|
||||
console.log(req.body);
|
||||
|
||||
console.log(`Uninstalling ${service_id}...`);
|
||||
console.log(`Uninstalling ${service_name}...`);
|
||||
|
||||
if (confirm == 'Yes') {
|
||||
|
||||
let containerName = docker.getContainer(service_id);
|
||||
console.log(`Stopping ${service_id}...`)
|
||||
let containerName = docker.getContainer(service_name);
|
||||
console.log(`Stopping ${service_name}...`)
|
||||
try {
|
||||
await containerName.stop();
|
||||
} catch {
|
||||
console.log(`Error stopping ${service_id} container`);
|
||||
console.log(`Error stopping ${service_name} container`);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Removing ${service_id}...`);
|
||||
console.log(`Removing ${service_name}...`);
|
||||
containerName.remove();
|
||||
|
||||
const syslog = await Syslog.create({
|
||||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Removal",
|
||||
message: `${service_id} uninstalled successfully`,
|
||||
message: `${service_name} uninstalled successfully`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
|
||||
|
@ -38,12 +36,12 @@ export const Uninstall = async (req, res) => {
|
|||
user: req.session.user,
|
||||
email: null,
|
||||
event: "App Removal",
|
||||
message: `${service_id} uninstallation failed`,
|
||||
message: `${service_name} uninstallation failed`,
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(`Didn't confirm uninstallation of ${service_id}...`);
|
||||
console.log(`Didn't confirm uninstallation of ${service_name}...`);
|
||||
}
|
||||
|
||||
res.redirect('/');
|
||||
|
|
|
@ -1,108 +1,131 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Account - DweebUI</title>
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- sidebar %>
|
||||
|
||||
<div class="col-12 col-md-9 d-flex flex-column">
|
||||
<div class="card-body">
|
||||
<h1 class="mb-4">My Account</h1>
|
||||
<h3 class="card-title">Profile Details</h3>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto"><span class="avatar avatar-xl bg-green-lt">A</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"> </h3>
|
||||
<div class="row g-3">
|
||||
<div class="col-md">
|
||||
<div class="form-label">Name</div>
|
||||
<input type="text" class="form-control" value="<%= name %>">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Username</div>
|
||||
<input type="text" class="form-control" value="<%= username %>">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-label">Role</div>
|
||||
<input type="text" class="form-control" value="<%= role %>">
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="card-title mt-4">Email</h3>
|
||||
<p class="card-subtitle"> </p>
|
||||
<div>
|
||||
<div class="row g-2">
|
||||
<div class="col-auto">
|
||||
<input type="text" class="form-control w-auto" value="<%= 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"> </p>
|
||||
<div>
|
||||
<a href="#" class="btn">
|
||||
Set new password
|
||||
</a>
|
||||
</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>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
</body>
|
||||
<!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('partials/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('partials/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('partials/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,21 +1,29 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Apps - DweebUI</title>
|
||||
<title>DweebUI - Apps</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
<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">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
<!-- Navbar -->
|
||||
|
||||
<%- include('partials/navbar.html') %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
@ -27,7 +35,7 @@
|
|||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="me-auto btn"><%= app_count %></div>
|
||||
<div class="me-auto btn"><%= list_start %> - <%= list_end %> of <%= app_count %> Apps</div>
|
||||
<%- remove_button %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -85,7 +93,7 @@
|
|||
<%- json_templates %>
|
||||
</ul>
|
||||
</dropdown>
|
||||
<button class="btn" name="import" data-bs-toggle="modal" data-bs-target="#import_modal">Import</button>
|
||||
<button class="btn" name="Import" id="Import" data-hx-get="/import_modal" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Import</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -113,37 +121,28 @@
|
|||
<div class="container-xl">
|
||||
<div class="row row-cards">
|
||||
|
||||
|
||||
<%- apps_list %>
|
||||
|
||||
|
||||
<div id="import_modal" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="modal-title">Import Template(s)</div>
|
||||
<div class="text-muted mb-2">Individual template(s) can be *.json, *.yml, or *.yaml.</div>
|
||||
<div class="text-muted mb-3">Multiple compose files can be imported from a zip file. Each folder should contain a single compose.yaml.</div>
|
||||
<img src = "/img/add to zip.jpg" alt = "Add to zip" class = "img-fluid" />
|
||||
<div class="mt-3">
|
||||
<form method="post" action="/upload" enctype="multipart/form-data" id="upload">
|
||||
<input class="form-control" type="file" name="files" multiple />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link link-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" data-bs-dismiss="modal" form="upload">Upload</button>
|
||||
</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-sm modal-dialog-centered modal-dialog-scrollables">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Loading</h5>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<div class="spinner-border"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="d-flex mt-4">
|
||||
<ul class="pagination ms-auto">
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<%- prev %>" tabindex="-1" aria-disabled="true">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-left -->
|
||||
<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="M15 6l-6 6l6 6" /></svg>
|
||||
prev
|
||||
</a>
|
||||
|
@ -153,7 +152,7 @@
|
|||
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="<%- next %>">
|
||||
next
|
||||
next <!-- Download SVG icon from http://tabler-icons.io/i/chevron-right -->
|
||||
<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="M9 6l6 6l-6 6" /></svg>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -162,34 +161,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
<%- include('partials/footer.html') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal medium-modal modal-blur fade" id="wide_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="wide_modal_content">
|
||||
<!-- modal content inserted with htmx -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal slim-modal modal-blur fade" id="info_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="info_modal_content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,89 +0,0 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Credits - DweebUI.</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- sidebar %>
|
||||
|
||||
<div class="col-12 col-md-9 d-flex flex-column">
|
||||
<div class="card-body">
|
||||
|
||||
<!-- HTMX - Submits the form and replaces the target with the response. Replaces the submit button with "Updated" -->
|
||||
<form id="preferences" hx-post="/preferences" hx-target="#submit" hx-swap="outerHTML">
|
||||
|
||||
<h1 class="">Credits</h1>
|
||||
<label class="text-muted">DweebUI wouldn't be possible without:</label>
|
||||
|
||||
|
||||
<div class="table-group-divider mt-3"></div>
|
||||
|
||||
|
||||
<ul>
|
||||
<li class="m-2">Dockerode and Dockerode-Compose by Apocas </li>
|
||||
<li class="m-2">Tabler</li>
|
||||
<li class="m-2">Walkxcode</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- <div class="card-footer bg-transparent mt-auto">
|
||||
<div class="btn-list justify-content-end">
|
||||
<a href="#" class="btn">
|
||||
Cancel
|
||||
</a>
|
||||
<button class="btn btn-primary" id="submit">
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,188 +1,280 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Dashboard - DweebUI.</title>
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<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>
|
||||
<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">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
|
||||
<div class="page-wrapper" hx-ext="sse" sse-connect="/sse">
|
||||
<div class="page-body" style="margin-top: 16px;">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
<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="/server_metrics" data-hx-trigger="load, every 1s" hx-swap="innerHTML">
|
||||
<div class="font-weight-medium">
|
||||
<label class="cpu-text mb-1">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="/server_metrics" data-hx-trigger="load, every 2s" hx-swap="innerHTML">
|
||||
<div class="font-weight-medium">
|
||||
<label class="ram-text mb-1">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>
|
||||
<div class="col" name="NET" id="purple" data-hx-get="/server_metrics" data-hx-trigger="load, every 3s">
|
||||
<div class="font-weight-medium">NET 0%</div>
|
||||
<div class="cpu-bar meter animate purple mt-1">
|
||||
<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="/server_metrics" data-hx-trigger="load, every 3s">
|
||||
<div class="font-weight-medium">
|
||||
<label class="disk-text mb-1">DISK 0%</label>
|
||||
</div>
|
||||
<div class="meter animate orange">
|
||||
<span style="width:20%"><span></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-12">
|
||||
<div class="row row-cards" id="containers">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HTMX -->
|
||||
<div class="col-12">
|
||||
|
||||
<div class="row row-cards" name="card_list" hx-get="/dashboard/view/card_list" data-hx-trigger="load, sse:update" data-hx-swap="afterbegin" hx-target="#containers"></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="modal slim-modal modal-blur fade" id="scrolling_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="modal_content">
|
||||
<!-- modal content inserted with htmx -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal medium-modal modal-blur fade" id="medium_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="medium_modal_content">
|
||||
<!-- modal content inserted with htmx -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal wide-modal modal-blur fade" id="wide_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="wide_modal_content">
|
||||
<!-- modal content inserted with htmx -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script defer>
|
||||
var modalScrollable = document.getElementById('wide_modal');
|
||||
modalScrollable.addEventListener('shown.bs.modal', function () {
|
||||
modalScrollable.querySelector('.modal-body').scrollTop = modalScrollable.querySelector('.modal-body').scrollHeight;
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="/libs/apexcharts/dist/apexcharts.min.js?1692870487"></script>
|
||||
|
||||
<script src="/js/dweebui.js"></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
<script src="/js/htmx-sse.js"></script>
|
||||
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
|
||||
<%- include('partials/navbar.html') %>
|
||||
|
||||
</body>
|
||||
</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">
|
||||
|
||||
<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" data-hx-get="/stats" data-hx-trigger="load, every 2s">
|
||||
<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>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="row row-cards" id="containers">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HTMX -->
|
||||
<div class="col-12">
|
||||
<div class="row row-cards" data-hx-post="/dashboard/updates" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HTMX Modal Target -->
|
||||
<div id="modals-here" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered modal-dialog-scrollables">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Loading</h5>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<div class="spinner-border"></div>
|
||||
</div>
|
||||
</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('partials/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: [3, 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>
|
||||
|
||||
<!-- SelectAll for the permissions modal -->
|
||||
<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>
|
||||
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
<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">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
|
||||
<!-- Navbar -->
|
||||
<%- include('partials/navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body" style="margin-top: 16px;">
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
|
||||
|
@ -44,23 +49,9 @@
|
|||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
<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('select')"></th>
|
||||
<th><label class="table-sort" data-sort="sort-name">Name</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-type">Tag</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-quantity">Size</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">
|
||||
|
||||
<%- image_list %>
|
||||
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
@ -124,21 +115,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
<%- include('partials/footer.html') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
|
@ -154,5 +140,20 @@
|
|||
})
|
||||
</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>
|
126
views/login.html
126
views/login.html
|
@ -1,117 +1,89 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Login - DweebUI</title>
|
||||
<title>DweebUI - Login</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
<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 class=" d-flex flex-column">
|
||||
|
||||
<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.png" height="100" alt="Dweeb">
|
||||
</a>
|
||||
<div class="text-center">
|
||||
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||
<img src="/img/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="/img/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>
|
||||
<% } %>
|
||||
|
||||
<form action="/login" method="post" autocomplete="off" novalidate>
|
||||
<div class="mb-3">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Email address</label>
|
||||
<input name="email" type="email" class="form-control" autocomplete="off">
|
||||
<input type="email" class="form-control" id="email" name="email">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">
|
||||
Password
|
||||
<span class="form-label-description">
|
||||
<!-- <a href="./forgot-password.html">I forgot password</a> -->
|
||||
</span>
|
||||
</label>
|
||||
<div class="input-group input-group-flat">
|
||||
<input type="password" name="password" class="form-control" autocomplete="off">
|
||||
|
||||
<!-- <span class="input-group-text">
|
||||
<a href="#" class="link-secondary" title="Show password" data-bs-toggle="tooltip">
|
||||
<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 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>
|
||||
</span> -->
|
||||
|
||||
<input type="password" class="form-control" id="password" name="password" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="mb-2">
|
||||
<div class="mb-2 d-flex justify-content-between">
|
||||
<label class="form-check">
|
||||
<input type="checkbox" class="form-check-input"/>
|
||||
<span class="form-check-label">Remember me on this device</span>
|
||||
<input type="checkbox" class="form-check-input" checked=""/>
|
||||
<span class="form-check-label">
|
||||
Remember me
|
||||
</span>
|
||||
</label>
|
||||
</div> -->
|
||||
|
||||
<span>
|
||||
<a href="#">Forgot password</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary w-100">Login</button>
|
||||
<button type="submit" class="btn btn-primary w-100">Sign in</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="hr-text">or</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="#" class="btn w-100 text-secondary" title="not available">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-github" 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="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5" /></svg>
|
||||
Login with Github
|
||||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<a href="#" class="btn w-100 text-secondary" title="not available">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-twitter" 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="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c0 -.249 1.51 -2.772 1.818 -4.013z" /></svg>
|
||||
Login with Twitter
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div>
|
||||
<button class="nav-link hide-theme-dark" title="Enable dark mode" data-bs-toggle="tooltip" data-bs-placement="bottom" value="dark-theme" onclick="toggleTheme(this)">
|
||||
<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>
|
||||
</button>
|
||||
<button class="nav-link hide-theme-light" title="Enable light mode" data-bs-toggle="tooltip" data-bs-placement="bottom" value="light-theme" onclick="toggleTheme(this)">
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-center text-secondary">
|
||||
Don't have an account? <a href="/register" tabindex="-1">Register</a>
|
||||
</div>
|
||||
<div class="text-center text-muted mt-2">
|
||||
Don't have account? <a href="/register" tabindex="-1">Register</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
|
||||
|
||||
<!-- Libs JS -->
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,83 +1,75 @@
|
|||
|
||||
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<form id="details">
|
||||
|
||||
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-lg-5">
|
||||
<label class="form-label">Container Name: </label>
|
||||
<input type="text" class="form-control" name="service_name" value="AppName" hidden/>
|
||||
<input type="text" class="form-control" name="name" value="AppName"/>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label class="form-label">Image: </label>
|
||||
<input type="text" class="form-control" name="image" value="AppImage"/>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Restart Policy: </label>
|
||||
<select class="form-select" name="restart_policy">
|
||||
<option value="RestartPolicy" selected hidden>RestartPolicy</option>
|
||||
<option value="unless-stopped">unless-stopped</option>
|
||||
<option value="on-failure">on-failure</option>
|
||||
<option value="no">never</option>
|
||||
<option value="always">always</option>
|
||||
</select>
|
||||
</div>
|
||||
<form action="" id="details_modal" method="POST">
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-lg-5">
|
||||
<label class="form-label">Container Name: </label>
|
||||
<input type="text" class="form-control" name="service_name" value="AppName" hidden/>
|
||||
<input type="text" class="form-control" name="name" value="AppName"/>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label class="form-label">Image: </label>
|
||||
<input type="text" class="form-control" name="image" value="AppImage"/>
|
||||
</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="net_mode" value="host" class="form-selectgroup-input" NetHost>
|
||||
<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="net_mode" value="NetName" class="form-selectgroup-input" NetBridge>
|
||||
<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="net_mode" value="docker" class="form-selectgroup-input" NetDocker>
|
||||
<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">Docker Network</span>
|
||||
<span class="d-block text-secondary">Isolated on the docker network. ex.172.0.34.2</span>
|
||||
<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>
|
||||
</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">
|
||||
|
@ -90,21 +82,20 @@
|
|||
<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="port0" type="checkbox" Port0Check>
|
||||
<input class="form-check-input" name="port_0_check" type="checkbox" Port0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">External Port</label>
|
||||
<label class="form-label">External Port</label>
|
||||
<input type="text" class="form-control" name="port_0_external" value="Port0External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Internal Port</label>
|
||||
<label class="form-label">Internal Port</label>
|
||||
<input type="text" class="form-control" name="port_0_internal" value="Port0Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<label class="form-label">Protocol</label>
|
||||
<label class="form-label">Protocol</label>
|
||||
<select class="form-select" name="port_0_protocol">
|
||||
<option value="Port0Protocol" selected hidden>Port0Protocol</option>
|
||||
<option value="tcp">tcp</option>
|
||||
|
@ -115,7 +106,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port1" type="checkbox" Port1Check>
|
||||
<input class="form-check-input" name="port_1_check" type="checkbox" Port1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_external" value="Port1External"/>
|
||||
|
@ -134,7 +125,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port2" type="checkbox" Port2Check>
|
||||
<input class="form-check-input" name="port_2_check" type="checkbox" Port2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_external" value="Port2External"/>
|
||||
|
@ -154,7 +145,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port3" type="checkbox" Port3Check>
|
||||
<input class="form-check-input" name="port_3_check" type="checkbox" Port3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_external" value="Port3External"/>
|
||||
|
@ -174,7 +165,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port4" type="checkbox" Port4Check>
|
||||
<input class="form-check-input" name="port_4_check" type="checkbox" Port4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_external" value="Port4External"/>
|
||||
|
@ -193,7 +184,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="port5" type="checkbox" Port5Check>
|
||||
<input class="form-check-input" name="port_5_check" type="checkbox" Port5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_5_external" value="Port5External"/>
|
||||
|
@ -225,7 +216,7 @@
|
|||
<div class="accordion-body pt-0">
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume0" type="checkbox" Vol0Check>
|
||||
<input class="form-check-input" name="volume_0_check" type="checkbox" Vol0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_bind" value="Vol0Source"/>
|
||||
|
@ -244,7 +235,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume1" type="checkbox" Vol1Check>
|
||||
<input class="form-check-input" name="volume_1_check" type="checkbox" Vol1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_bind" value="Vol1Source"/>
|
||||
|
@ -263,7 +254,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume2" type="checkbox" Vol2Check>
|
||||
<input class="form-check-input" name="volume_2_check" type="checkbox" Vol2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_bind" value="Vol2Source"/>
|
||||
|
@ -282,7 +273,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume3" type="checkbox" Vol3Check>
|
||||
<input class="form-check-input" name="volume_3_check" type="checkbox" Vol3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_bind" value="Vol3Source"/>
|
||||
|
@ -301,7 +292,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume4" type="checkbox" Vol4Check>
|
||||
<input class="form-check-input" name="volume_4_check" type="checkbox" Vol4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_bind" value="Vol4Source"/>
|
||||
|
@ -320,7 +311,7 @@
|
|||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="volume5" type="checkbox" Vol5Check>
|
||||
<input class="form-check-input" name="volume_5_check" type="checkbox" Vol5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_bind" value="Vol5Source"/>
|
||||
|
@ -579,52 +570,52 @@
|
|||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_1_check" Label1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_name" value="Label1Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_value" value="Label1Value"/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_1_check" Label1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_name" value="Label1Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_value" value="Label1Value"/>
|
||||
</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" Label2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_name" value="Label2Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_value" value="Label2Value"/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_2_check" Label2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_name" value="Label2Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_value" value="Label2Value"/>
|
||||
</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" Label3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_name" value="Label3Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_value" value="Label3Value"/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_3_check" Label3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_name" value="Label3Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_value" value="Label3Value"/>
|
||||
</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" Label4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_name" value="Label4Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_value" value="Label4Value"/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label_4_check" Label4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_name" value="Label4Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_value" value="Label4Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
|
@ -808,35 +799,158 @@
|
|||
</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>
|
||||
</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>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary" form="install_info">Install</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
20
views/modals/import.html
Normal file
20
views/modals/import.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="modal-title">Import Template(s)</div>
|
||||
<div class="text-muted mb-2">Individual template(s) can be *.json, *.yml, or *.yaml.</div>
|
||||
<div class="text-muted mb-3">Multiple compose files can be imported from a zip file. Each folder should contain a single compose.yaml.</div>
|
||||
<img src = "/img/add to zip.jpg" alt = "Add to zip" class = "img-fluid" />
|
||||
<div class="mt-3">
|
||||
<form method="post" action="/upload" enctype="multipart/form-data" id="upload">
|
||||
<input class="form-control" type="file" name="files" multiple />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link link-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" data-bs-dismiss="modal" form="upload">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
728
views/modals/json.html
Normal file
728
views/modals/json.html
Normal file
|
@ -0,0 +1,728 @@
|
|||
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Install AppName</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<pre class="text-secondary">AppNote</pre>
|
||||
|
||||
<form action="/install" name="FormId_install" id="FormId_install" 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="AppName" hidden/>
|
||||
<input type="text" class="form-control" name="name" value="AppName"/>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Image: </label>
|
||||
<input type="text" class="form-control" name="image" value="AppImage"/>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Restart Policy: </label>
|
||||
<select class="form-select" name="restart_policy">
|
||||
<option value="RestartPolicy" selected hidden>RestartPolicy</option>
|
||||
<option value="unless-stopped">unless-stopped</option>
|
||||
<option value="on-failure">on-failure</option>
|
||||
<option value="no">never</option>
|
||||
<option value="always">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="net_mode" value="host" class="form-selectgroup-input" NetHost>
|
||||
<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="net_mode" value="NetName" class="form-selectgroup-input" NetBridge>
|
||||
<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="net_mode" value="docker" class="form-selectgroup-input" NetDocker>
|
||||
<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="ModalName-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="#ModalName-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="port0" type="checkbox" Port0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">External Port</label>
|
||||
<input type="text" class="form-control" name="port_0_external" value="Port0External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Internal Port</label>
|
||||
<input type="text" class="form-control" name="port_0_internal" value="Port0Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<label class="form-label">Protocol</label>
|
||||
<select class="form-select" name="port_0_protocol">
|
||||
<option value="Port0Protocol" selected hidden>Port0Protocol</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="port1" type="checkbox" Port1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_external" value="Port1External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_internal" value="Port1Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_1_protocol">
|
||||
<option value="Port1Protocol" selected hidden>Port1Protocol</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="port2" type="checkbox" Port2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_external" value="Port2External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_internal" value="Port2Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_2_protocol">
|
||||
<option value="Port2Protocol" selected hidden>Port2Protocol</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="port3" type="checkbox" Port3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_external" value="Port3External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_internal" value="Port3Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_3_protocol">
|
||||
<option value="Port3Protocol" selected hidden>Port3Protocol</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="port4" type="checkbox" Port4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_external" value="Port4External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_internal" value="Port4Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_4_protocol">
|
||||
<option value="Port4Protocol" selected hidden>Port4Protocol</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="port5" type="checkbox" Port5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_5_external" value="Port5External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_5_internal" value="Port5Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_5_protocol">
|
||||
<option value="Port5Protocol" selected hidden>Port5Protocol</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="#ModalName-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="volume0" type="checkbox" Volume0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_bind" value="Volume0Bind"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_container" value="Volume0Container"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_0_readwrite">
|
||||
<option value="Volume0RW" selected hidden>Volume0RW</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="volume1" type="checkbox" Volume1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_bind" value="Volume1Bind"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_container" value="Volume1Container"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_1_readwrite">
|
||||
<option value="Volume1RW" selected hidden>Volume1RW</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="volume2" type="checkbox" Volume2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_bind" value="Volume2Bind"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_container" value="Volume2Container"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_2_readwrite">
|
||||
<option value="Volume2RW" selected hidden>Volume2RW</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="volume3" type="checkbox" Volume3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_bind" value="Volume3Bind"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_container" value="Volume3Container"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_3_readwrite">
|
||||
<option value="Volume3RW" selected hidden>Volume3RW</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="volume4" type="checkbox" Volume4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_bind" value="Volume4Bind"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_container" value="Volume4Container"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_4_readwrite">
|
||||
<option value="Volume4RW" selected hidden>Volume4RW</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="volume5" type="checkbox" Volume5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_bind" value="Volume5Bind"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_container" value="Volume5Container"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_5_readwrite">
|
||||
<option value="Volume5RW" selected hidden>Volume5RW</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="#ModalName-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="env0" Env0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Variable</label>
|
||||
<input type="text" class="form-control" name="env_0_name" value="Env0Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Value</label>
|
||||
<input type="text" class="form-control" name="env_0_default" value="Env0Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env1" Env1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_1_name" value="Env1Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_1_default" value="Env1Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env2" Env2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_2_name" value="Env2Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_2_default" value="Env2Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env3" Env3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_3_name" value="Env3Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_3_default" value="Env3Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env4" Env4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_4_name" value="Env4Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_4_default" value="Env4Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env5" Env5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_5_name" value="Env5Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_5_default" value="Env5Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env6" Env6Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_6_name" value="Env6Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_6_default" value="Env6Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env7" Env7Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_7_name" value="Env7Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_7_default" value="Env7Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env8" Env8Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_8_name" value="Env8Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_8_default" value="Env8Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env9" Env9Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_9_name" value="Env9Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_9_default" value="Env9Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env10" Env10Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_10_name" value="Env10Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_10_default" value="Env10Default"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env11" Env11Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_11_name" value="Env11Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_11_default" value="Env11Default"/>
|
||||
</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="#ModalName-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="label0" Label0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Variable</label>
|
||||
<input type="text" class="form-control" name="label_0_name" value="Label0Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Value</label>
|
||||
<input type="text" class="form-control" name="label_0_value" value="Label0Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label1" Label1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_name" value="Label1Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_value" value="Label1Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label2" Label2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_name" value="Label2Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_value" value="Label2Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label3" Label3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_name" value="Label3Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_value" value="Label3Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label4" Label4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_name" value="Label4Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_value" value="Label4Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label5" Label5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_5_name" value="Label5Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_5_value" value="Label5Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label6" Label6Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_6_name" value="Label6Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_6_value" value="Label6Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label7" Label7Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_7_name" value="Label7Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_7_value" value="Label7Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label8" Label8Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_8_name" value="Label8Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_8_value" value="Label8Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label9" Label9Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_9_name" value="Label9Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_9_value" value="Label9Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label10" Label10Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_10_name" value="Label10Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_10_value" value="Label10Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="label11" Label11Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label11Name"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label11Value"/>
|
||||
</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="#ModalName-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="command_check" type="checkbox" CommandCheck>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Command</label>
|
||||
<input type="text" class="form-control" name="command" value="CommandValue"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" name="privileged" type="checkbox" PrivilegedCheck>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Privileged Mode</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||
<input type="submit" form="FormId_install" class="btn btn-success" value="Install"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
13
views/modals/learnmore.html
Normal file
13
views/modals/learnmore.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="modal-title">AppName</div>
|
||||
<div>AppDesc</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link link-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Okay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
22
views/modals/permissions.html
Normal file
22
views/modals/permissions.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<div class="modal-dialog modal-sm modal-dialog-centered modal-dialog-scrollables">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">PermissionsTitle Permissions</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="accordion" id="modal-accordion">
|
||||
PermissionsList
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<form id="reset_permissions">
|
||||
<input type="hidden" name="container" value="PermissionsContainer">
|
||||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" name="reset_permissions" value="reset_permissions" id="submit" hx-post="/updatePermissions" hx-trigger="click" hx-confirm="Are you sure you want to reset permissions for this container?">Reset</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
77
views/modals/uninstall.html
Normal file
77
views/modals/uninstall.html
Normal file
|
@ -0,0 +1,77 @@
|
|||
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="modal-status bg-danger"></div>
|
||||
<div class="modal-body text-center py-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon mb-2 text-danger icon-lg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M12 9v2m0 4v.01"></path><path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path></svg>
|
||||
<h3>Uninstall AppName?</h3>
|
||||
<form action="/uninstall" id="AppName_uninstall" method="POST">
|
||||
<input type="text" class="form-control" name="service_name" value="AppName" hidden/>
|
||||
<div class="mb-3"> </div>
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="divide-y">
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">Remove Volumes</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="remove_volumes" disabled="">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Remove Image
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="remove_image" disabled="">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Remove Backups
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="remove_backups" disabled="">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1"> </div>
|
||||
<div class="text-muted">Enter "Yes" below to remove the container.</div>
|
||||
<input type="text" class="form-control mb-2" name="confirm" autocomplete="off">
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="w-100">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="#" class="btn w-100" data-bs-dismiss="modal">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="submit" form="AppName_uninstall" class="btn btn-danger w-100" value="Uninstall"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,27 +1,31 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
<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">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
|
||||
<!-- Navbar -->
|
||||
<%- include('partials/navbar.html') %>
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body" style="margin-top: 16px;">
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-deck row-cards">
|
||||
|
||||
|
@ -44,28 +48,16 @@
|
|||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
<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('select')"></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">
|
||||
|
||||
|
||||
<%- network_list %>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
|
||||
|
||||
<button class="btn" type="submit" formaction="/network/remove/">Remove</button>
|
||||
<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>
|
||||
|
@ -95,21 +87,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
<%- include('partials/footer.html') %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
|
@ -125,6 +112,20 @@
|
|||
})
|
||||
</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>
|
22
views/partials/appCard.html
Normal file
22
views/partials/appCard.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<div class="col-md-6 col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<span class="avatar avatar-xlplus mb-3 rounded"><img src='AppLogo' width="144px" height="144px" loading="lazy"/></span>
|
||||
<h3 class="m-0 mb-1"><a href="#">AppShortName</a></h3>
|
||||
<div class="text-secondary">AppDesc</div>
|
||||
<div class="mt-3">
|
||||
AppCategories
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<a href="#" class="card-btn" name="AppName" id="AppType" data-hx-get="/learn_more" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-article" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"></path> <path d="M7 8h10"></path> <path d="M7 12h10"></path> <path d="M7 16h10"></path></svg>
|
||||
Learn More
|
||||
</a>
|
||||
<a href="" class="card-btn" name="AppName" id="AppType" data-hx-get="/install_modal" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-bar-to-down" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M4 20l16 0"></path> <path d="M12 14l0 -10"></path> <path d="M12 14l4 -4"></path> <path d="M12 14l-4 -4"></path></svg>
|
||||
Install
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,46 +0,0 @@
|
|||
<div class="col-md-6 col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body py-2">
|
||||
<div class="row">
|
||||
|
||||
<h1 class="m-0 p-0">AppTitle</h1>
|
||||
|
||||
<div class="col-auto p-0">
|
||||
<span class="avatar avatar-xl mt-1" style="background-image: url(AppIcon)"></span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card-subtitle description m-1">AppDescription</div>
|
||||
</div>
|
||||
|
||||
<div class="py-1 mb-1">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto m-0 p-0">
|
||||
AppCategories
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-0">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<a href="" class="btn" name="AppName" id="AppType" hx-get="/appsModals/info" hx-swap="innerHTML" data-hx-trigger="mousedown" hx-target="#info_modal_content" data-bs-toggle="modal" data-bs-target="#info_modal">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-article mx-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"></path> <path d="M7 8h10"></path> <path d="M7 12h10"></path> <path d="M7 16h10"></path></svg>
|
||||
More Info
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-auto ms-auto">
|
||||
|
||||
<button class="btn" name="AppName" id="AppType" hx-get="/appsModals/view_install" hx-swap="innerHTML" data-hx-trigger="mousedown" hx-target="#wide_modal_content" data-bs-toggle="modal" data-bs-target="#wide_modal">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-bar-to-down mx-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M4 20l16 0"></path> <path d="M12 14l0 -10"></path> <path d="M12 14l4 -4"></path> <path d="M12 14l-4 -4"></path></svg>
|
||||
Install
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
80
views/partials/containerFull.html
Normal file
80
views/partials/containerFull.html
Normal file
|
@ -0,0 +1,80 @@
|
|||
<div class="col-sm-6 col-lg-3 pt-1" hx-post="/dashboard/card" hx-trigger="sse:AppName" hx-swap="outerHTML" name="AppName">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-stamp card-stamp-sm">
|
||||
<img width="100px" src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/AppIcon.png" onerror="this.onerror=null;this.src='https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/docker.png';"></img>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subheader text-yellow">ExternalPort:InternalPort</div>
|
||||
<div class="ms-auto lh-1">
|
||||
<div class="card-actions btn-actions">
|
||||
<div class="card-actions btn-actions">
|
||||
<button class="btn-action" title="Start" data-hx-post="/dashboard/start" data-hx-trigger="mousedown" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
|
||||
<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 class="btn-action" title="Stop" data-hx-post="/dashboard/stop" data-hx-trigger="mousedown" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
|
||||
<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 class="btn-action" title="Pause" data-hx-post="/dashboard/pause" data-hx-trigger="mousedown" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
|
||||
<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 class="btn-action" title="Restart" data-hx-post="/dashboard/restart" data-hx-trigger="mousedown" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-reload" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path><path d="M20 4v5h-5"></path></svg>
|
||||
</button>
|
||||
<div class="dropdown">
|
||||
<a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-post="/dashboard/details" hx-swap="innerHTML" data-hx-trigger="mousedown" data-hx-target="#modals-here" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="logs" data-hx-post="/dashboard/logs" hx-swap="innerHTML" hx-trigger="mousedown" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="edit">Edit</button>
|
||||
<button class="dropdown-item text-primary" name="AppName" id="update" disabled="">Update</button>
|
||||
<button class="dropdown-item text-danger" name="AppName" id="uninstall" hx-trigger="mousedown" data-hx-post="/dashboard/uninstall" hx-swap="innerHTML" data-bs-toggle="modal" data-hx-target="#modals-here" data-bs-target="#modals-here">Uninstall</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /> </svg>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item text-secondary" data-hx-post="/dashboard/hide" data-hx-trigger="mousedown" data-hx-swap="none" name="AppName" id="hide" value="hide">Hide</button>
|
||||
<button class="dropdown-item text-secondary" data-hx-post="/dashboard/reset" data-hx-trigger="mousedown" data-hx-swap="none" name="AppName" id="reset" value="reset">Reset View</button>
|
||||
<button class="dropdown-item text-secondary" data-hx-post="/dashboard/permissions" name="AppName" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="mousedown" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-baseline">
|
||||
<div class="h1 me-2" title="AppName" style="margin-bottom: 0;">
|
||||
<a href="http://${link}:${external_port}" target="_blank">
|
||||
AppShortName
|
||||
</a>
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<label id="AppNameState">
|
||||
<span class="text-StateColor 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>
|
||||
AppState
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var ChartNamechart = new ApexCharts(document.querySelector("#ChartName_chart"), options);
|
||||
</script>
|
||||
|
||||
<div class="chart-sm">
|
||||
<div id="ChartName_chart" data-trigger="" data-hx-get="/chart" name="ChartName" hx-swap="innerHTML">
|
||||
<script>
|
||||
ChartNamechart.render();
|
||||
</script>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
71
views/partials/containerSimple.html
Normal file
71
views/partials/containerSimple.html
Normal file
|
@ -0,0 +1,71 @@
|
|||
<div class="col-sm-6 col-lg-3 pt-1" hx-get="" hx-trigger="" hx-swap="none" name="AppName">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-stamp card-stamp-sm">
|
||||
<img width="100px" src="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/AppIcon.png" onerror="this.onerror=null;this.src='https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/docker.png';"></img>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subheader text-yellow">ExternalPort:InternalPort</div>
|
||||
<div class="ms-auto lh-1">
|
||||
<div class="card-actions btn-actions">
|
||||
<div class="card-actions btn-actions">
|
||||
<button class="btn-action" title="Start" data-hx-post="/dashboard/start" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
|
||||
<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 class="btn-action" title="Stop" data-hx-post="/dashboard/stop" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
|
||||
<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 class="btn-action" title="Pause" data-hx-post="/dashboard/pause" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
|
||||
<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 class="btn-action" title="Restart" data-hx-post="/dashboard/restart" data-hx-trigger="click" data-hx-target="#AppNameState" name="AppName" id="AppState" ${disable}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-reload" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path><path d="M20 4v5h-5"></path></svg>
|
||||
</button>
|
||||
<div class="dropdown">
|
||||
<a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="details" data-hx-get="/dashboard/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Details</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="logs" data-hx-get="/dashboard/logs" hx-swap="innerHTML" data-hx-target="#logView" data-bs-toggle="modal" data-bs-target="#log_view">Logs</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="edit">Edit</button>
|
||||
<button class="dropdown-item text-primary" name="AppName" id="update" disabled="">Update</button>
|
||||
<button class="dropdown-item text-danger" name="AppName" id="uninstall" hx-trigger="click" data-hx-get="/modals" hx-swap="innerHTML" data-bs-toggle="modal" data-hx-target="#modals-here" data-bs-target="#modals-here">Uninstall</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /> </svg>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item text-secondary" data-hx-post="/dashboard/hide" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="hide" value="hide">Hide</button>
|
||||
<button class="dropdown-item text-secondary" data-hx-post="/dashboard/reset" data-hx-trigger="click" data-hx-swap="none" name="AppName" id="reset" value="reset">Reset View</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" id="permissions" data-hx-get="/modals" data-hx-target="#modals-here" hx-swap="innerHTML" data-hx-trigger="click" data-bs-toggle="modal" data-bs-target="#modals-here">Permissions</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-baseline">
|
||||
<div class="h1 me-2" title="AppName">
|
||||
<a href="http://${link}:${external_port}" target="_blank">
|
||||
AppShortName
|
||||
</a>
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<label id="AppNameState">
|
||||
<span class="text-StateColor 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>
|
||||
AppState
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,127 +0,0 @@
|
|||
<div class="col-sm-6 col-lg-3" hx-get="/dashboard/view/update_card/ContainerID" hx-trigger="sse:ContainerID" id="AltID" hx-swap="outerHTML" hx-target="#AltID" name="AppName">
|
||||
<div class="card p-2">
|
||||
<div class="container-stamp">
|
||||
<img class="rounded-4 pt-1 px-1" width="95px" src="https://raw.githubusercontent.com/lllllllillllllillll/Dashboard-Icons/main/png/AppService.png" onerror="this.onerror=null;this.src='https://raw.githubusercontent.com/lllllllillllllillll/DweebUI-Icons/main/docker.png';"></img>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<label style="font-size: smaller; font-weight: bold;" class="text-yellow">AppPorts</label>
|
||||
<div class="ms-auto lh-1">
|
||||
<button class="container-action" title="Start" data-hx-post="/dashboard/action/start/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Start" id="AppName" hx-swap="outerHTML">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="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 class="container-action" title="Stop" data-hx-post="/dashboard/action/stop/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Stop" id="AppName" hx-swap="outerHTML">
|
||||
<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 class="container-action" title="Pause" data-hx-post="/dashboard/action/pause/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Pause" id="AppName" hx-swap="outerHTML">
|
||||
<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 class="container-action" title="Restart" data-hx-post="/dashboard/action/restart/ContainerID" data-hx-trigger="mousedown" data-hx-target="#AltIDState" name="Restart" id="AppName" hx-swap="outerHTML">
|
||||
<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>
|
||||
|
||||
<a href="#" class="container-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-get="/dashboard/view/details/ContainerID" data-hx-trigger="mousedown" hx-target="#medium_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#medium_modal">Details</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-get="/dashboard/view/logs/ContainerID" data-hx-trigger="mousedown" hx-target="#wide_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#wide_modal">Logs</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-get="/dashboard/view/edit/ContainerID" data-hx-trigger="mousedown" hx-target="#wide_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#wide_modal">Edit</button>
|
||||
<button class="dropdown-item text-primary" name="AppName" id="update" disabled="">Update</button>
|
||||
<button class="dropdown-item text-danger" name="AppName" hx-trigger="mousedown" data-hx-get="/dashboard/view/uninstall/ContainerID" hx-swap="innerHTML" data-hx-target="#modal_content" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Uninstall</button>
|
||||
</div>
|
||||
|
||||
<a href="#" class="container-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" /> </svg>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-post="/dashboard/action/hide/ContainerID" data-hx-trigger="mousedown" data-hx-swap="delete" data-hx-target="#AltID">Hide</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-get="/dashboard/view/link_modal/ContainerID" hx-trigger="mousedown" hx-swap="innerHTML" data-hx-target="#modal_content" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Edit Link</button>
|
||||
<button class="dropdown-item text-secondary" name="AppName" data-hx-get="/dashboard/view/permissions/ContainerID" hx-target="#modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#scrolling_modal">Permissions</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<label style="font-size: x-large; font-weight: bold; line-height: 1;">TitleLink</label>
|
||||
|
||||
<div class="text-StateColor d-inline-flex align-items-center ms-auto" id="AltIDState">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon-tabler icon-tabler-point-filled" width="20" height="24" viewBox="0 0 20 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>
|
||||
<strong>AppState</strong>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="AltIDchart" class="chart-sm"></div>
|
||||
|
||||
<script>
|
||||
var options = {
|
||||
chart: {
|
||||
type: "line",
|
||||
fontFamily: 'inherit',
|
||||
height: 40.0,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
animations: {
|
||||
enabled: false
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
opacity: 1,
|
||||
},
|
||||
stroke: {
|
||||
width: [3, 1],
|
||||
dashArray: [0, 3],
|
||||
lineCap: "round",
|
||||
curve: "smooth",
|
||||
},
|
||||
series: [{
|
||||
name: "CPU",
|
||||
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
},{
|
||||
name: "RAM",
|
||||
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
}],
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
theme: 'dark'
|
||||
},
|
||||
grid: {
|
||||
strokeDashArray: 4,
|
||||
},
|
||||
xaxis: {
|
||||
labels: {
|
||||
padding: 0,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false
|
||||
},
|
||||
type: 'datetime',
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
labels: {
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
labels: [
|
||||
'2020-06-20', '2020-06-21', '2020-06-22', '2020-06-23', '2020-06-24', '2020-06-25', '2020-06-26', '2020-06-27', '2020-06-28', '2020-06-29'
|
||||
],
|
||||
colors: [tabler.getColor("primary"), tabler.getColor("gray-600")],
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
var AltIDchart = new ApexCharts(document.querySelector("#AltIDchart"), options);
|
||||
AltIDchart.render();
|
||||
|
||||
</script>
|
||||
|
||||
ChartTrigger
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -3,11 +3,11 @@
|
|||
<div class="row text-center align-items-center flex-row-reverse">
|
||||
<div class="col-lg-auto ms-lg-auto">
|
||||
<ul class="list-inline list-inline-dots mb-0">
|
||||
<li class="list-inline-item"><a href="https://github.com/lllllllillllllillll/DweebUI/wiki" target="_blank" class="link-secondary" rel="noopener">Documentation</a></li>
|
||||
<li class="list-inline-item"><a href="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI/main/LICENSE" class="link-secondary">License</a></li>
|
||||
<li class="list-inline-item"><a href="https://github.com/lllllllillllllillll/DweebUI" target="_blank" class="link-secondary" rel="noopener">Source code</a></li>
|
||||
<li class="list-inline-item"><a href="https://github.com/lllllllillllllillll/DweebUI/blob/main/README.md" target="_blank" class="link-secondary" rel="noopener">Documentation</a></li>
|
||||
<li class="list-inline-item"><a href="https://github.com/lllllllillllllillll/DweebUI/blob/main/LICENSE" class="link-secondary">License</a></li>
|
||||
<li class="list-inline-item"><a href="https://github.com/lllllllillllllillll/DweebUI/tree/main" target="_blank" class="link-secondary" rel="noopener">Source code</a></li>
|
||||
<li class="list-inline-item">
|
||||
<a href="https://buymeacoffee.com/lllllllillllllillll" target="_blank" class="link-secondary" rel="noopener">
|
||||
<a href="https://www.buymeacoffee.com/lllllllillllllillll" target="_blank" class="link-secondary" rel="noopener">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-pink icon-filled icon-inline" 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="M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" /></svg>
|
||||
Sponsor
|
||||
</a>
|
||||
|
@ -17,13 +17,13 @@
|
|||
<div class="col-12 col-lg-auto mt-3 mt-lg-0">
|
||||
<ul class="list-inline list-inline-dots mb-0">
|
||||
<li class="list-inline-item">
|
||||
Copyright © 2024
|
||||
Copyright © 2023 - 2024
|
||||
<a href="https://dweebui.com" class="link-secondary">DweebUI</a>.
|
||||
All rights reserved.
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a href="https://raw.githubusercontent.com/lllllllillllllillll/DweebUI/main/CHANGELOG.md" class="link-secondary" rel="noopener">
|
||||
v0.7X (Extra Broken) [Build BuildVersion]
|
||||
<a href="https://github.com/lllllllillllllillll/DweebUI/releases" class="link-secondary" rel="noopener">
|
||||
v0.60
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
<div class="modal-body">
|
||||
<div class="modal-title">AppTitle</div>
|
||||
<div>AppDescription</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link link-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Okay</button>
|
||||
</div>
|
|
@ -1,842 +0,0 @@
|
|||
|
||||
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<form id="install_info" hx-post="/install" hx-swap="innerHTML" hx-target="#alert">
|
||||
|
||||
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-lg-5">
|
||||
<label class="form-label">Container Name: </label>
|
||||
<input type="text" class="form-control" name="service_name" value="AppName" hidden/>
|
||||
<input type="text" class="form-control" name="name" value="AppName"/>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label class="form-label">Image: </label>
|
||||
<input type="text" class="form-control" name="image" value="AppImage"/>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label">Restart Policy: </label>
|
||||
<select class="form-select" name="restart_policy">
|
||||
<option value="RestartPolicy" selected hidden>RestartPolicy</option>
|
||||
<option value="unless-stopped">unless-stopped</option>
|
||||
<option value="on-failure">on-failure</option>
|
||||
<option value="no">never</option>
|
||||
<option value="always">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="net_mode" value="host" class="form-selectgroup-input" NetHost>
|
||||
<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="net_mode" value="NetName" class="form-selectgroup-input" NetBridge>
|
||||
<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="net_mode" value="docker" class="form-selectgroup-input" NetDocker>
|
||||
<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="port0" type="checkbox" Port0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">External Port</label>
|
||||
<input type="text" class="form-control" name="port_0_external" value="Port0External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Internal Port</label>
|
||||
<input type="text" class="form-control" name="port_0_internal" value="Port0Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<label class="form-label">Protocol</label>
|
||||
<select class="form-select" name="port_0_protocol">
|
||||
<option value="Port0Protocol" selected hidden>Port0Protocol</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="port1" type="checkbox" Port1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_external" value="Port1External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_1_internal" value="Port1Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_1_protocol">
|
||||
<option value="Port1Protocol" selected hidden>Port1Protocol</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="port2" type="checkbox" Port2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_external" value="Port2External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_2_internal" value="Port2Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_2_protocol">
|
||||
<option value="Port2Protocol" selected hidden>Port2Protocol</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="port3" type="checkbox" Port3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_external" value="Port3External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_3_internal" value="Port3Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_3_protocol">
|
||||
<option value="Port3Protocol" selected hidden>Port3Protocol</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="port4" type="checkbox" Port4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_external" value="Port4External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_4_internal" value="Port4Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_4_protocol">
|
||||
<option value="Port4Protocol" selected hidden>Port4Protocol</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="port5" type="checkbox" Port5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_5_external" value="Port5External"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="port_5_internal" value="Port5Internal"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="port_5_protocol">
|
||||
<option value="Port5Protocol" selected hidden>Port5Protocol</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="volume0" type="checkbox" Vol0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_bind" value="Vol0Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_0_container" value="Vol0Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_0_readwrite">
|
||||
<option value="Vol0RW" selected hidden>Vol0RW</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="volume1" type="checkbox" Vol1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_bind" value="Vol1Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_1_container" value="Vol1Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_1_readwrite">
|
||||
<option value="Vol1RW" selected hidden>Vol1RW</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="volume2" type="checkbox" Vol2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_bind" value="Vol2Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_2_container" value="Vol2Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_2_readwrite">
|
||||
<option value="Vol2RW" selected hidden>Vol2RW</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="volume3" type="checkbox" Vol3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_bind" value="Vol3Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_3_container" value="Vol3Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_3_readwrite">
|
||||
<option value="Vol3RW" selected hidden>Vol3RW</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="volume4" type="checkbox" Vol4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_bind" value="Vol4Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_4_container" value="Vol4Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_4_readwrite">
|
||||
<option value="Vol4RW" selected hidden>Vol4RW</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="volume5" type="checkbox" Vol5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_bind" value="Vol5Source"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="volume_5_container" value="Vol5Destination"/>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="form-select" name="volume_5_readwrite">
|
||||
<option value="Vol5RW" selected hidden>Vol5RW</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" Env0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Variable</label>
|
||||
<input type="text" class="form-control" name="env_0_name" value="Env0Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Value</label>
|
||||
<input type="text" class="form-control" name="env_0_default" value="Env0Value"/>
|
||||
</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" Env1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_1_name" value="Env1Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_1_default" value="Env1Value"/>
|
||||
</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" Env2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_2_name" value="Env2Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_2_default" value="Env2Value"/>
|
||||
</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" Env3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_3_name" value="Env3Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_3_default" value="Env3Value"/>
|
||||
</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" Env4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_4_name" value="Env4Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_4_default" value="Env4Value"/>
|
||||
</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" Env5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_5_name" value="Env5Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_5_default" value="Env5Value"/>
|
||||
</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" Env6Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_6_name" value="Env6Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_6_default" value="Env6Value"/>
|
||||
</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" Env7Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_7_name" value="Env7Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_7_default" value="Env7Value"/>
|
||||
</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" Env8Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_8_name" value="Env8Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_8_default" value="Env8Value"/>
|
||||
</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" Env9Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_9_name" value="Env9Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_9_default" value="Env9Value"/>
|
||||
</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" Env10Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_10_name" value="Env10Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_10_default" value="Env10Value"/>
|
||||
</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" Env11Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_11_name" value="Env11Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_11_default" value="Env11Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_12_check" Env12Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_name" value="Env12Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_default" value="Env12Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_12_check" Env13Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_name" value="Env13Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_default" value="Env13Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_12_check" Env14Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_name" value="Env14Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_default" value="Env14Value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-auto">
|
||||
<input class="form-check-input" type="checkbox" name="env_12_check" Env15Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_name" value="Env15Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="env_12_default" value="Env15Value"/>
|
||||
</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" Label0Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Variable</label>
|
||||
<input type="text" class="form-control" name="label_0_name" value="Label0Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Value</label>
|
||||
<input type="text" class="form-control" name="label_0_value" value="Label0Value"/>
|
||||
</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" Label1Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_name" value="Label1Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_1_value" value="Label1Value"/>
|
||||
</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" Label2Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_name" value="Label2Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_2_value" value="Label2Value"/>
|
||||
</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" Label3Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_name" value="Label3Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_3_value" value="Label3Value"/>
|
||||
</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" Label4Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_name" value="Label4Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_4_value" value="Label4Value"/>
|
||||
</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" Label5Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_5_name" value="Label5Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_5_value" value="Label5Value"/>
|
||||
</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" Label6Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_6_name" value="Label6Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_6_value" value="Label6Value"/>
|
||||
</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" Label7Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_7_name" value="Label7Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_7_value" value="Label7Value"/>
|
||||
</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" Label8Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_8_name" value="Label8Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_8_value" value="Label8Value"/>
|
||||
</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" Label9Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_9_name" value="Label9Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_9_value" value="Label9Value"/>
|
||||
</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" Label10Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_10_name" value="Label10Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_10_value" value="Label10Value"/>
|
||||
</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" Label11Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label11Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label11Value"/>
|
||||
</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" Label12Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label12Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label12Value"/>
|
||||
</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" Label13Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label13Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label13Value"/>
|
||||
</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" Label14Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label14Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label14Value"/>
|
||||
</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" Label15Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label15Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label15Value"/>
|
||||
</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" Label16Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label16Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label16Value"/>
|
||||
</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" Label17Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label17Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label17Value"/>
|
||||
</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" Label18Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label18Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label18Value"/>
|
||||
</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" Label19Check>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_name" value="Label19Key"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" name="label_11_value" value="Label19Value"/>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary" form="install_info" data-bs-dismiss="modal" onclick="topScroll()">Install</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
|
||||
<div class="modal-content" id="modal_content">
|
||||
|
||||
<form action="/container/update_link/ContainerID" method="post">
|
||||
|
||||
<input type="hidden" name="service_id" value="ContainerID">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
|
||||
<div class="modal-body text-center py-3">
|
||||
<h3 class="mb-3">AppName Link</h3>
|
||||
<div class="text-muted mb-2">URL needs to start with http:// or https://</div>
|
||||
<input type="text" class="form-control mb-2" name="url" value="AppLink">
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="w-100">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="#" class="btn w-100" data-bs-dismiss="modal">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="submit" value="Update" class="btn btn-primary w-100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
|
@ -1,43 +0,0 @@
|
|||
|
||||
<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" id="modalBody">
|
||||
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-lg-5">
|
||||
<label class="form-label mb-1">Container Name: </label>
|
||||
<input type="text" class="form-control" name="service_name" value="AppName" hidden/>
|
||||
<input type="text" class="form-control" name="name" value="AppName"/>
|
||||
</div>
|
||||
|
||||
<!-- <div class="col-lg-4">
|
||||
<label class="form-label mb-1">Image: </label>
|
||||
<input type="text" class="form-control" name="image" value="AppImage"/>
|
||||
</div> -->
|
||||
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label mb-1">Filter: </label>
|
||||
<select class="form-select" name="restart_policy">
|
||||
<option value="All" selected hidden>All</option>
|
||||
<option value="All">All</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="row mb-1 align-items-end">
|
||||
<div class="col-12">
|
||||
<textarea class="form-control" id="logs" style="height: 65vh; resize: none;" readonly>ContainerLogs</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-success" name="AppName" data-hx-get="/dashboard/view/logs/ContainerID" data-hx-trigger="mousedown" hx-target="#wide_modal_content" hx-swap="innerHTML" data-bs-toggle="modal" data-bs-target="#wide_modal">Refresh</button>
|
||||
</div>
|
|
@ -1,251 +1,278 @@
|
|||
<header class="navbar navbar-expand-md d-print-none" >
|
||||
<div class="container-xl">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu" 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 p-0">
|
||||
<a href="#">
|
||||
<img src="/static/logo.png" alt="DweebUI" title="DweebUI" height="40px">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img src="/static/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
|
||||
</a>
|
||||
</h1>
|
||||
<script>
|
||||
var themeStorageKey = "tablerTheme";
|
||||
var defaultTheme = "dark";
|
||||
var selectedTheme;
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var storedTheme = localStorage.getItem(themeStorageKey);
|
||||
selectedTheme = storedTheme ? storedTheme : defaultTheme;
|
||||
|
||||
if (selectedTheme === 'dark') {
|
||||
document.body.setAttribute("data-bs-theme", selectedTheme);
|
||||
} else {
|
||||
document.body.removeAttribute("data-bs-theme");
|
||||
}
|
||||
})();
|
||||
|
||||
function toggleTheme(button) {
|
||||
if (button.value == 'dark-theme') {
|
||||
document.body.setAttribute("data-bs-theme", 'dark');
|
||||
localStorage.setItem(themeStorageKey, 'dark');
|
||||
}
|
||||
else if (button.value == 'light-theme') {
|
||||
document.body.removeAttribute("data-bs-theme");
|
||||
localStorage.setItem(themeStorageKey, 'light');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<header class="navbar navbar-expand-md d-print-none py-0">
|
||||
<div class="container-xl">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu"
|
||||
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">
|
||||
<a href="#">
|
||||
<img src="/img/logo.png" alt="DweebUI" title="DweebUI" height="40px">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img src="/img/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
<% if(alert) { %>
|
||||
<%- alert %>
|
||||
<% } %>
|
||||
|
||||
<div class="navbar-nav flex-row order-md-last">
|
||||
<div class="nav-item d-none d-md-flex me-3">
|
||||
|
||||
|
||||
|
||||
<!-- <div class="btn-list">
|
||||
<a href="#" class="btn text-green">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-lock" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6z"></path> <path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path> <path d="M8 11v-4a4 4 0 1 1 8 0v4"></path> </svg>
|
||||
VPN
|
||||
</a>
|
||||
<a href="#" class="btn text-green">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-shield" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"></path> </svg>
|
||||
Firewall
|
||||
</a>
|
||||
<a href="#" class="btn text-green">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-screen-share" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M21 12v3a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1v-10a1 1 0 0 1 1 -1h9"></path> <path d="M7 20l10 0"></path> <path d="M9 16l0 4"></path> <path d="M15 16l0 4"></path> <path d="M17 4h4v4"></path> <path d="M16 9l5 -5"></path> </svg>
|
||||
VNC
|
||||
</a>
|
||||
</div> -->
|
||||
|
||||
<!-- <% if(role == 'admin') { %>
|
||||
<div class="btn-list">
|
||||
<a href="#" class="btn text-red">
|
||||
Admin
|
||||
</a>
|
||||
</div>
|
||||
<% } %> -->
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="d-none d-md-flex">
|
||||
|
||||
<button class="nav-link px-0 hide-theme-dark" title="Enable dark mode" data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom" value="dark-theme" onclick="toggleTheme(this)">
|
||||
<!-- 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>
|
||||
</button>
|
||||
|
||||
<button class="nav-link px-0 hide-theme-light" title="Enable light mode" data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom" value="light-theme" onclick="toggleTheme(this)">
|
||||
<!-- 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>
|
||||
</button>
|
||||
|
||||
<div class="nav-item dropdown d-none d-md-flex me-2">
|
||||
<a href="#" class="nav-link px-0" data-bs-toggle="dropdown" tabindex="-1" aria-label="Show notifications">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/bell -->
|
||||
<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 id="alert"></div>
|
||||
|
||||
|
||||
<div class="navbar-nav flex-row order-md-last">
|
||||
|
||||
<div class="nav-item d-none d-md-flex">
|
||||
|
||||
<div class="btn-list">
|
||||
HostButtons
|
||||
|
||||
<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>
|
||||
|
||||
<div class="d-none d-md-flex mx-2">
|
||||
|
||||
<button class="nav-link px-0 hide-theme-dark" title="Enable dark mode" data-bs-toggle="tooltip" data-bs-placement="bottom" value="dark-theme" onclick="toggleTheme(this)">
|
||||
<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>
|
||||
</button>
|
||||
|
||||
<button class="nav-link px-0 hide-theme-light" title="Enable light mode" data-bs-toggle="tooltip" data-bs-placement="bottom" value="light-theme" onclick="toggleTheme(this)">
|
||||
<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>
|
||||
</button>
|
||||
<div class="nav-item dropdown d-none d-md-flex">
|
||||
|
||||
<a class="nav-link px-0 text-muted" data-bs-toggle="dropdown" tabindex="-1" aria-label="Show notifications">
|
||||
|
||||
<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">Last updates</h3>
|
||||
|
||||
|
||||
<div class="list-group list-group-flush list-group-hoverable">
|
||||
<div class="list-group-item">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto"><span class="status-dot status-dot-animated bg-green d-block"></span></div>
|
||||
<div class="col text-truncate">
|
||||
<a href="#" class="text-body d-block">App Installed</a>
|
||||
<div class="d-block text-muted text-truncate mt-n1">
|
||||
Just an example of an app install notification.
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group list-group-flush list-group-hoverable">
|
||||
|
||||
<div class="list-group-item">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto"><span class="status-dot status-dot-animated bg-red d-block"></span></div>
|
||||
<div class="col text-truncate">
|
||||
<a href="#" class="text-body d-block">Example 1</a>
|
||||
<div class="d-block text-secondary text-truncate mt-n1">
|
||||
Change deprecated html tags to text decoration classes (#29604)
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="list-group-item-actions">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-muted" 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 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-group-item">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto"><span class="status-dot d-block"></span></div>
|
||||
<div class="col text-truncate">
|
||||
<a href="#" class="text-body d-block">Example 2</a>
|
||||
<div class="d-block text-secondary text-truncate mt-n1">
|
||||
justify-content:between ⇒ justify-content:space-between (#29734)
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="list-group-item-actions show">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-yellow" 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 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<a href="#" class="list-group-item-actions">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/star -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-muted" 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 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /> </svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <button class="nav-link text-orange px-0" title="">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" fill="orange" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>
|
||||
</button> -->
|
||||
|
||||
<!-- <button class="nav-link text-pink px-0" title="">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="pink" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-heart"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" /></svg>
|
||||
</button> -->
|
||||
|
||||
</div>
|
||||
<div class="nav-item dropdown">
|
||||
<a href="#" class="nav-link d-flex lh-1 text-reset p-0" data-bs-toggle="dropdown" aria-label="Open user menu">
|
||||
<span class="avatar avatar-sm bg-green-lt">A</span>
|
||||
<div class="d-none d-xl-block ps-2">
|
||||
|
||||
<label class="">Username</label>
|
||||
|
||||
<div class="mt-1 small text-secondary">Userrole</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
|
||||
<a href="/account" class="dropdown-item">Account</a>
|
||||
<a class="dropdown-item text-muted">Notifications</a>
|
||||
<!-- <div class="dropdown-divider"></div> -->
|
||||
<a href="/preferences" class="dropdown-item">Preferences</a>
|
||||
<a href="/settings" class="dropdown-item">Settings</a>
|
||||
<a href="/logout" class="dropdown-item">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<header class="navbar-expand-md">
|
||||
<div class="collapse navbar-collapse" id="navbar-menu">
|
||||
<div class="navbar">
|
||||
<div class="container-xl">
|
||||
<ul class="navbar-nav">
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/dashboard">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/dashboard -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-dashboard" 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 13m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path> <path d="M13.45 11.55l2.05 -2.05"></path> <path d="M6.4 20a9 9 0 1 1 11.2 0z"></path> </svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Dashboard
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/images">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-augmented-reality" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 8v-2a2 2 0 0 1 2 -2h2" /><path d="M4 16v2a2 2 0 0 0 2 2h2" /><path d="M16 4h2a2 2 0 0 1 2 2v2" /><path d="M16 20h2a2 2 0 0 0 2 -2v-2" /><path d="M12 12.5l4 -2.5" /><path d="M8 10l4 2.5v4.5l4 -2.5v-4.5l-4 -2.5z" /><path d="M8 10v4.5l4 2.5" /></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Images
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/volumes">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-database" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 6m-8 0a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"></path> <path d="M4 6v6a8 3 0 0 0 16 0v-6"></path> <path d="M4 12v6a8 3 0 0 0 16 0v-6"></path></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Volumes
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/networks">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-world" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /><path d="M3.6 9h16.8" /><path d="M3.6 15h16.8" /><path d="M11.5 3a17 17 0 0 0 0 18" /><path d="M12.5 3a17 17 0 0 1 0 18" /></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Networks
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/apps">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/apps -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-apps" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path> <path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path> <path d="M14 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path> <path d="M14 7l6 0"></path> <path d="M17 4l0 6"></path> </svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Apps
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/users">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" 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="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0"></path> <path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path> </svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Users
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/syslogs">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-text" 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="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" /><path d="M9 9l1 0" /><path d="M9 13l6 0" /><path d="M9 17l6 0" /></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Syslogs
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
|
||||
<div class="my-2 my-md-0 flex-grow-1 flex-md-grow-0 order-first order-md-last">
|
||||
<div class="card-actions btn-actions">
|
||||
|
||||
<input type="search" class="form-control mx-2" placeholder="Search..." aria-label="search" name="search" hx-post="/search" hx-trigger="input changed delay:500ms, search" hx-swap="none">
|
||||
|
||||
<button class="btn-action mx-1 text-muted" title="Grid View" data-hx-post="/dashboard/view" data-hx-trigger="mousedown" data-hx-target="#" name="grid" id="AppState" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler icons-tabler-outline icon-tabler-layout-grid"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M14 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M14 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /></svg>
|
||||
</button>
|
||||
|
||||
<button class="btn-action mx-1 text-muted" title="List View" data-hx-post="/dashboard/view" data-hx-trigger="mousedown" data-hx-target="#" name="list" id="AppState" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler icons-tabler-outline icon-tabler-layout-grid"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 6l11 0" /><path d="M9 12l11 0" /><path d="M9 18l11 0" /><path d="M5 6l0 .01" /><path d="M5 12l0 .01" /><path d="M5 18l0 .01" /></svg>
|
||||
</button>
|
||||
|
||||
<div class="dropdown">
|
||||
<a href="#" class="btn-action dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle><circle cx="12" cy="5" r="1"></circle></svg>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<form action="/dashboard/action/reset/000" method="post">
|
||||
<button class="dropdown-item text-secondary" name="reset">Reset View</button>
|
||||
</form>
|
||||
<div class="list-group-item">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto"><span class="status-dot status-dot-animated bg-red d-block"></span></div>
|
||||
<div class="col text-truncate">
|
||||
<a href="#" class="text-body d-block">App Uninstalled</a>
|
||||
<div class="d-block text-muted text-truncate mt-n1">
|
||||
Just an example of an app uninstall notification.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="list-group-item-actions">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/star -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon text-muted" 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 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /> </svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
</div>
|
||||
<div class="nav-item dropdown">
|
||||
<a href="#" class="nav-link d-flex lh-1 text-reset p-0" data-bs-toggle="dropdown" aria-label="Open user menu">
|
||||
<span class="avatar avatar-sm bg-green-lt"><%= avatar %></span></span>
|
||||
<div class="d-none d-xl-block ps-2">
|
||||
<div>
|
||||
<%= name %>
|
||||
</div>
|
||||
<div class="mt-1 small text-muted">
|
||||
<%= role %>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
|
||||
<a href="/account" class="dropdown-item">Account</a>
|
||||
<a href="/settings" class="dropdown-item">Settings</a>
|
||||
<!-- <div class="dropdown-divider"></div> -->
|
||||
|
||||
<a href="/logout" class="dropdown-item">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<header class="navbar-expand-md">
|
||||
<div class="collapse navbar-collapse" id="navbar-menu">
|
||||
<div class="navbar">
|
||||
<div class="container-xl">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/dashboard">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/dashboard -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-dashboard" 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 13m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path> <path d="M13.45 11.55l2.05 -2.05"></path> <path d="M6.4 20a9 9 0 1 1 11.2 0z"></path> </svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Dashboard
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/images">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-augmented-reality" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 8v-2a2 2 0 0 1 2 -2h2" /><path d="M4 16v2a2 2 0 0 0 2 2h2" /><path d="M16 4h2a2 2 0 0 1 2 2v2" /><path d="M16 20h2a2 2 0 0 0 2 -2v-2" /><path d="M12 12.5l4 -2.5" /><path d="M8 10l4 2.5v4.5l4 -2.5v-4.5l-4 -2.5z" /><path d="M8 10v4.5l4 2.5" /></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Images
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/volumes">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-database" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 6m-8 0a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"></path> <path d="M4 6v6a8 3 0 0 0 16 0v-6"></path> <path d="M4 12v6a8 3 0 0 0 16 0v-6"></path></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Volumes
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/networks">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-world" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /><path d="M3.6 9h16.8" /><path d="M3.6 15h16.8" /><path d="M11.5 3a17 17 0 0 0 0 18" /><path d="M12.5 3a17 17 0 0 1 0 18" /></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Networks
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/apps">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/apps -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-apps" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path> <path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path> <path d="M14 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path> <path d="M14 7l6 0"></path> <path d="M17 4l0 6"></path> </svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Apps
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/users">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" 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="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0"></path> <path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path> </svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Users
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/syslogs">
|
||||
<span
|
||||
class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from https://tabler-icons.io/i/user -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-text" 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="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" /><path d="M9 9l1 0" /><path d="M9 13l6 0" /><path d="M9 17l6 0" /></svg>
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
Syslogs
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<div class="my-2 my-md-0 flex-grow-1 flex-md-grow-0 order-first order-md-last">
|
||||
|
||||
<ul class="navbar-nav">
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
|
@ -1,155 +0,0 @@
|
|||
|
||||
<div class="accordion-user mb-3">
|
||||
<h2 class="accordion-header" id="heading-Entry">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-Entry" 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;">Username</div>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-Entry" class="accordion-collapse collapse" data-bs-parent="#accordion-example">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
<form id="updatePermissionsEntry">
|
||||
<div class="col">
|
||||
|
||||
<input type="hidden" name="containerName" value="container_name">
|
||||
<input type="hidden" name="containerID" value="container_id">
|
||||
<input type="hidden" name="userID" value="user_id">
|
||||
<input type="hidden" name="username" value="Username">
|
||||
<input type="hidden" name="select" value="selectEntry">
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="row">
|
||||
<span class="col">All</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntry" onclick="selectAll('selectEntry')" data-AllCheck>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="divide-y-2">
|
||||
|
||||
<div>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col">Uninstall</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntry" value="uninstall" data-UninstallCheck>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col">Edit</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntry" value="edit" data-EditCheck>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col">Upgrade</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntry" value="upgrade" data-UpgradeCheck>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col">Start</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntry" value="start" data-StartCheck>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col">Stop</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntry" value="stop" data-StopCheck>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col">Pause</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntry" value="pause" data-PauseCheck>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col">Restart</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntry" value="restart" data-RestartCheck>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col">Logs</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntry" value="logs" data-LogsCheck>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="row">
|
||||
<span class="col">View</span>
|
||||
<span class="col-auto">
|
||||
<label class="form-check form-check-single form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="selectEntry" value="view" data-ViewCheck>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2 pt-2">
|
||||
<button class="btn" type="button" id="submit" hx-post="/dashboard/action/update_permissions/container_id" hx-swap="outerHTML">Update </button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,16 +1,13 @@
|
|||
<div class="col-12 col-md-3 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">Account</a>
|
||||
<a class="list-group-item list-group-item-action d-flex align-items-center text-muted">Notifications</a>
|
||||
<a href="/preferences" class="list-group-item list-group-item-action d-flex align-items-center">Preferences</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">Special Thanks</h4>
|
||||
<div class="list-group list-group-transparent">
|
||||
<a href="/sponsors" class="list-group-item list-group-item-action">Sponsors</a>
|
||||
<a href="/credits" class="list-group-item list-group-item-action">Credits</a>
|
||||
</div>
|
||||
</div>
|
||||
<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="/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>
|
|
@ -1,100 +0,0 @@
|
|||
|
||||
<div class="modal-content" id="modal_content">
|
||||
|
||||
<form action="/uninstall" method="POST" id="uninstall">
|
||||
<input type="hidden" name="service_id" value="ContainerID">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="modal-status bg-danger">
|
||||
</div>
|
||||
|
||||
<div class="modal-body text-center py-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon mb-2 text-danger icon-lg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M12 9v2m0 4v.01"></path><path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path></svg>
|
||||
<h3 class="mb-3">Uninstall AppName?</h3>
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="divide-y">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Remove Volumes
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="remove_volumes" disabled="">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Remove Image
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="remove_image" disabled="">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Remove Networks
|
||||
</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" disabled="">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<label class="row text-start">
|
||||
<span class="col">
|
||||
Remove Backups
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-check form-check-single form-switch text-end">
|
||||
<input class="form-check-input" type="checkbox" name="remove_backups" disabled="">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1"> </div>
|
||||
<div class="text-muted">Enter "Yes" below to remove the container.</div>
|
||||
<input type="text" class="form-control mb-2" name="confirm" autocomplete="off">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="w-100">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="#" class="btn w-100" data-bs-dismiss="modal">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="submit" form="uninstall" class="btn btn-danger w-100" value="Uninstall"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
|
@ -1,84 +0,0 @@
|
|||
|
||||
<form action="/users/action/change/USERID" method="post">
|
||||
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title px-2">Username</h5>
|
||||
<div class="me-auto badge badge-outline text-green">Active</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body py-2">
|
||||
|
||||
<div class="row mb-3 align-items-end">
|
||||
<div class="col-auto">
|
||||
<!-- <a href="#" class="avatar avatar-upload rounded">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M12 5l0 14"></path><path d="M5 12l14 0"></path></svg>
|
||||
<span class="avatar-upload-text">Add</span>
|
||||
</a> -->
|
||||
<span class="avatar avatar-upload rounded bg-green-lt">J</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label">Full Name</label>
|
||||
<input type="text" class="form-control" name="name" value="FullName">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mb-3">
|
||||
<label class="mb-1">Email: </label>
|
||||
<input type="text" class="form-control" name="email" value="EmailAddress"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mb-3">
|
||||
<label class="mb-1">UserID: </label>
|
||||
<input type="text" class="form-control text-secondary" name="userID" value="USERID" readonly/>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-6">
|
||||
<label class="mb-1">Last Login: </label>
|
||||
<input type="text" class="form-control" name="last_login" value="LastLogin"/>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<label class="mb-1">Created: </label>
|
||||
<input type="text" class="form-control" name="created" value="CreatedAt"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col mb-2">
|
||||
<a href="#" class="btn w-100" data-bs-dismiss="modal">
|
||||
Reset Permissions
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="w-100">
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
<a href="#" class="btn w-100" data-bs-dismiss="modal">
|
||||
Close
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<button type="submit" name="change" value="remove" class="btn btn-danger w-100" hx-confirm="Are you sure you want to remove this account?">Remove</button>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<button type="submit" name="change" value="disable" class="btn btn-secondary w-100" hx-confirm="Are you sure you want to disable this account?">Disable</button>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<button type="submit" name="change" value="update" class="btn btn-primary w-100" hx-confirm="Update account?">Update</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
182
views/partials/user_permissions.html
Normal file
182
views/partials/user_permissions.html
Normal file
|
@ -0,0 +1,182 @@
|
|||
<div class="accordion-item mb-3" style="border: 1px solid grey;">
|
||||
<h2 class="accordion-header" id="heading-EntryNumber">
|
||||
<button class="accordion-button collapsed row" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-EntryNumber" 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;">PermissionsUsername</div>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-EntryNumber" class="accordion-collapse collapse" data-bs-parent="#modal-accordion">
|
||||
<div class="accordion-body pt-0">
|
||||
|
||||
|
||||
<div class="">
|
||||
<div class="">
|
||||
<form id="updatePermissionsEntryNumber">
|
||||
<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="selectEntryNumber" onclick="selectAll('selectEntryNumber')">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="user" value="PermissionsUsername">
|
||||
<input type="hidden" name="container" value="PermissionsContainer">
|
||||
|
||||
<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="selectEntryNumber" value="uninstall" data-UninstallCheck>
|
||||
</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="selectEntryNumber" value="edit" data-EditCheck>
|
||||
</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="selectEntryNumber" value="upgrade" data-UpgradeCheck>
|
||||
</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="selectEntryNumber" value="start" data-StartCheck>
|
||||
</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="selectEntryNumber" value="stop" data-StopCheck>
|
||||
</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="selectEntryNumber" value="pause" data-PauseCheck>
|
||||
</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="selectEntryNumber" value="restart" data-RestartCheck>
|
||||
</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="selectEntryNumber" value="logs" data-LogsCheck>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-4">
|
||||
<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="selectEntryNumber" value="view" data-ViewCheck>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="row mb-2">
|
||||
<button class="btn" type="button" id="submit" hx-post="/updatePermissions" hx-vals="#updatePermissionsEntryNumber" hx-swap="outerHTML">Update </button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
275
views/portal.html
Normal file
275
views/portal.html
Normal file
|
@ -0,0 +1,275 @@
|
|||
<!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('partials/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">
|
||||
<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">
|
||||
<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">
|
||||
<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="containers" data-hx-get="/user_containers" data-hx-trigger="load" data-hx-swap="innerHTML">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HTMX -->
|
||||
<div class="col-12">
|
||||
<div class="row row-cards" data-hx-get="/new_user_cards" data-hx-trigger="sse:update" data-hx-swap="afterbegin" hx-target="#containers">
|
||||
</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-sm modal-dialog-centered modal-dialog-scrollables">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Loading</h5>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<div class="spinner-border"></div>
|
||||
</div>
|
||||
</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('partials/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: [3, 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>
|
||||
|
||||
<!-- SelectAll for the permissions modal -->
|
||||
<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>
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Preferences - DweebUI.</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- sidebar %>
|
||||
|
||||
<div class="col-12 col-md-9 d-flex flex-column">
|
||||
<div class="card-body">
|
||||
|
||||
<!-- HTMX - Submits the form and replaces the target with the response. Replaces the submit button with "Updated" -->
|
||||
<form id="preferences" action="/preferences" method="POST">
|
||||
|
||||
<h1 class="">Preferences</h1>
|
||||
<label class="text-muted mb-3">User Preferences.</label>
|
||||
|
||||
<div class="table-group-divider mt-6"></div>
|
||||
|
||||
<h3 class="card-title mt-4">Profile Visibility</h3>
|
||||
<p class="card-subtitle">Hide your username from the dashboard.</p>
|
||||
<div>
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" name="hidden_input" <%= hide_profile %>>
|
||||
<span class="form-check-label form-check-label-on">Hidden</span>
|
||||
<span class="form-check-label form-check-label-off"><%= username %></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="table-group-divider mt-4"></div>
|
||||
|
||||
<h3 class="card-title mt-4">Language</h3>
|
||||
<p class="card-subtitle">In early development.</p>
|
||||
<div class="row g-2">
|
||||
<div class="col-3">
|
||||
<select class="form-control form-select" name="language_input">
|
||||
<option value="english" selected="" hidden="">English</option>
|
||||
<%- selected %>
|
||||
<option value="english">English</option>
|
||||
<option value="chinese">Chinese</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="card-footer bg-transparent mt-auto">
|
||||
<div class="btn-list justify-content-end">
|
||||
<a href="#" class="btn">
|
||||
Cancel
|
||||
</a>
|
||||
<button class="btn btn-primary" id="submit" type="submit">
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,27 +1,47 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Register - DweebUI</title>
|
||||
<title>DweebUI - Register</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
<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 class="d-flex flex-column">
|
||||
<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.png" height="100" alt="Dweeb">
|
||||
</a>
|
||||
|
||||
|
||||
<form class="container container-tight py-4" action="/register" method="POST" novalidate>
|
||||
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||
<img src="/img/logo.png" alt="DweebUI" title="DweebUI" height="100px">
|
||||
</h1>
|
||||
</div>
|
||||
<form class="card card-md" action="/register" method="post" autocomplete="off" novalidate>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-center mb-4">Create new account</h2>
|
||||
<div class="text-center mb-4">
|
||||
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||
<img src="/img/dweebui.svg" alt="DweebUI" title="DweebUI" class="navbar-brand-image">
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body text-center py-4">
|
||||
<h1 class="mt-1">Welcome to DweebUI</h1>
|
||||
<p class="text-muted">Account information is stored in a local sqlite database.</p>
|
||||
|
||||
<% if(error) { %>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
|
@ -29,73 +49,91 @@
|
|||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-control" name="name">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Username</label>
|
||||
<input type="text" class="form-control" name="username">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email address</label>
|
||||
<input type="email" class="form-control" name="email">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="row row-cards">
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Email address</label>
|
||||
<input type="email" class="form-control" id="email" name="email">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Password</label>
|
||||
<div class="input-group input-group-flat">
|
||||
<input type="password" class="form-control" autocomplete="off" name="password">
|
||||
<input type="password" class="form-control" id="password" name="password" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Confirm Password</label>
|
||||
<div class="input-group input-group-flat">
|
||||
<input type="password" class="form-control" autocomplete="off" name="confirm">
|
||||
<input type="password" class="form-control" id="confirmPassword" name="confirmPassword" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label" title="Enter the value of 'SECRET' from the DweebUI docker-compose.yaml">SECRET</label>
|
||||
<input type="text" class="form-control" id="secret" name="secret">
|
||||
</div>
|
||||
|
||||
|
||||
<%- reg_secret %>
|
||||
|
||||
|
||||
<!-- <div class="mb-3">
|
||||
|
||||
<!-- <div class="mb-2">
|
||||
<label class="form-check">
|
||||
<input type="checkbox" class="form-check-input"/>
|
||||
<span class="form-check-label">Agree the <a href="./terms-of-service.html" tabindex="-1">terms and policy</a>.</span>
|
||||
<input type="checkbox" class="form-check-input" name="warning"/>
|
||||
<span class="form-check-label">
|
||||
I understand that<a href="https://github.com/lllllllillllllillll/DweebUI/wiki/Exposing-DweebUI-to-the-Internet"> exposing DweebUI directly to the internet</a> is a bad idea.
|
||||
</span>
|
||||
</label>
|
||||
</div> -->
|
||||
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary w-100">Create new account</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div>
|
||||
<button class="nav-link px-0 hide-theme-dark" title="Enable dark mode" data-bs-toggle="tooltip" data-bs-placement="bottom" value="dark-theme" onclick="toggleTheme(this)">
|
||||
<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>
|
||||
</button>
|
||||
<button class="nav-link px-0 hide-theme-light" title="Enable light mode" data-bs-toggle="tooltip" data-bs-placement="bottom" value="light-theme" onclick="toggleTheme(this)">
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-center text-secondary">
|
||||
Already have an account? <a href="/login" tabindex="-1">Login</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row align-items-center mt-2">
|
||||
|
||||
<div class="col">
|
||||
<a href="/login">Login</a>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="btn-list justify-content-end">
|
||||
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Register</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,292 +1,120 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Settings - DweebUI.</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
<!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('partials/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('partials/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>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- sidebar %>
|
||||
|
||||
<div class="col-12 col-md-9 d-flex flex-column">
|
||||
<div class="card-body">
|
||||
|
||||
<!-- HTMX - Submits the form and replaces the target with the response. Replaces the submit button with "Updated" -->
|
||||
<form id="settings" hx-post="/settings/action/update" hx-target="#submit" hx-swap="outerHTML">
|
||||
|
||||
<h1 class="">Settings</h1>
|
||||
<label class="text-muted mb-3">Configure server settings. Admin only.</label>
|
||||
|
||||
<div class="table-group-divider mt-6"></div>
|
||||
|
||||
|
||||
<h3 class="card-title mt-4">User Registration</h3>
|
||||
<p class="card-subtitle mb-3">Enable registration and choose a secret.</p>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" name="user_registration" <%= user_registration %>>
|
||||
<span class="form-check-label form-check-label-on text-success">
|
||||
Enabled
|
||||
</span>
|
||||
<span class="form-check-label form-check-label-off text-danger">
|
||||
Disabled
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<input type="text" class="form-control" name="registration_secret" placeholder="multiple-words-strong-passphrase" value="<%= registration_secret %>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-group-divider mt-3"></div>
|
||||
|
||||
<h3 class="card-title mt-4">Container Port Links</h3>
|
||||
<p class="card-subtitle mb-3">Choose the base URL for the container card port links. Link can include 'http://' or 'https://'.</p>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" name="custom_link" <%= custom_link %>>
|
||||
<span class="form-check-label form-check-label-on text-warning">
|
||||
Custom
|
||||
</span>
|
||||
<span class="form-check-label form-check-label-off text-success">
|
||||
Localhost
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<input type="text" class="form-control" name="link_url" placeholder="IP Address or Domain" value="<%= link_url %>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-group-divider mt-3"></div>
|
||||
|
||||
<h3 class="card-title mt-4">Authentication</h3>
|
||||
<p class="card-subtitle mb-3">Change authentication settings. Only the default, Username and Password, supports multiple users.</p>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<select class="form-select" name="authentication">
|
||||
<option value="default">Username and Password - Default</option>
|
||||
<option value="localhost">Localhost</option>
|
||||
<option value="no_auth">Disabled - No Authentication</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-group-divider mt-3"></div>
|
||||
|
||||
<h3 class="card-title mt-4">Default Language</h3>
|
||||
<p class="card-subtitle">Default language for the server.</p>
|
||||
<div class="row g-2">
|
||||
<div class="col-3">
|
||||
<select class="form-control form-select" name="language_input">
|
||||
<option value="english" selected="" hidden="">English</option>
|
||||
<%- selected %>
|
||||
<option value="english">English</option>
|
||||
<option value="chinese">Chinese</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn" aria-label="button" name="check_languages" id="check_languages" value="true" hx-post="/update_languages" hx-swap="outerHTML" hx-target="#check_languages">Update Language Files</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="table-group-divider mt-3"></div>
|
||||
|
||||
<h3 class="card-title mt-4">HTTP / HTTPS</h3>
|
||||
<p class="card-subtitle">Requires restarting DweebUI.</p>
|
||||
<div class="row g-2">
|
||||
<div class="col-3">
|
||||
<select class="form-control form-select" name="language_input">
|
||||
<option value="HTTP" selected="" hidden="">HTTP</option>
|
||||
<option value="HTTP">HTTP</option>
|
||||
<option value="HTTPS">HTTPS</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn" aria-label="button">Restart DweebUI</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="table-group-divider mt-3"></div>
|
||||
|
||||
<h3 class="card-title mt-4">Port</h3>
|
||||
<p class="card-subtitle">Requires restarting DweebUI.</p>
|
||||
<div class="row g-2">
|
||||
<div class="col-3">
|
||||
<input type="text" class="form-control" name="port" placeholder="Port" value="8000">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn" aria-label="button">Restart DweebUI</button>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
<div class="table-group-divider mt-4"></div>
|
||||
|
||||
|
||||
<h3 class="mt-4">Hosts</h3>
|
||||
|
||||
|
||||
<label class="text-muted mb-2">Host #1</label>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" name="host1" checked disabled>
|
||||
<span class="form-check-label form-check-label-on text-success">
|
||||
Enabled
|
||||
</span>
|
||||
<span class="form-check-label form-check-label-off text-danger">
|
||||
Disabled
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="text" class="form-control" placeholder="Host 1" readonly>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="text" class="form-control" placeholder="/var/run/docker.sock" readonly>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="text" class="form-control" readonly>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<label class="text-muted mb-2">Host #2</label>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" name="host2" <%= host2_toggle %> >
|
||||
<span class="form-check-label form-check-label-on text-success">
|
||||
Enabled
|
||||
</span>
|
||||
<span class="form-check-label form-check-label-off text-danger">
|
||||
Disabled
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="text" class="form-control" name="tag2" value="<%= host2_tag %>" placeholder="Tag">
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="text" class="form-control" name="ip2" value="<%= host2_ip %>" placeholder="Host IP">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="text" class="form-control" name="port2" value="<%= host2_port %>" placeholder="PORT">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<label class="text-muted mb-2">Host #3</label>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" name="host3" <%= host3_toggle %> >
|
||||
<span class="form-check-label form-check-label-on text-success">
|
||||
Enabled
|
||||
</span>
|
||||
<span class="form-check-label form-check-label-off text-danger">
|
||||
Disabled
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="text" class="form-control" name="tag3" value="<%= host3_tag %>" placeholder="Tag">
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="text" class="form-control" name="ip3" value="<%= host3_ip %>" placeholder="Host IP">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="text" class="form-control" name="port3" value="<%= host3_port %>" placeholder="PORT">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<label class="text-muted mb-2">Host #4</label>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<label class="form-check form-switch form-switch-lg">
|
||||
<input class="form-check-input" type="checkbox" name="host4" <%= host4_toggle %> >
|
||||
<span class="form-check-label form-check-label-on text-success">
|
||||
Enabled
|
||||
</span>
|
||||
<span class="form-check-label form-check-label-off text-danger">
|
||||
Disabled
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="text" class="form-control" name="tag4" value="<%= host4_tag %>" placeholder="Tag">
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="text" class="form-control" name="ip4" value="<%= host4_ip %>" placeholder="Host IP">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="text" class="form-control" name="port4" value="<%= host4_port %>" placeholder="PORT">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="card-footer bg-transparent mt-auto">
|
||||
<div class="btn-list justify-content-end">
|
||||
<a href="#" class="btn">
|
||||
Cancel
|
||||
</a>
|
||||
<button class="btn btn-primary" id="submit">
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
<%- include('partials/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,87 +0,0 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Sponsors - DweebUI.</title>
|
||||
<!-- CSS files -->
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body >
|
||||
<div class="page">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- sidebar %>
|
||||
|
||||
<div class="col-12 col-md-9 d-flex flex-column">
|
||||
<div class="card-body">
|
||||
|
||||
<!-- HTMX - Submits the form and replaces the target with the response. Replaces the submit button with "Updated" -->
|
||||
<form id="preferences" hx-post="/preferences" hx-target="#submit" hx-swap="outerHTML">
|
||||
|
||||
<h1 class="">Sponsors</h1>
|
||||
<label class="text-muted mb-3">Some awesome people who have donated to DweebUI.</label>
|
||||
|
||||
<div class="table-group-divider mb-3"></div>
|
||||
|
||||
|
||||
<span type="button" class="btn avatar avatar-xl 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="btn avatar avatar-xl bg-azure-lt" hx-trigger="click" hx-post="/thank" hx-target="#count" name="PD" title="PD" style="margin-right: 5px;">PD</span>
|
||||
|
||||
<span type="button" class="btn avatar avatar-xl bg-teal-lt" hx-trigger="click" hx-post="/thank" hx-target="#count" name="C" title="C" style="margin-right: 5px;">C</span>
|
||||
|
||||
<span type="button" class="btn avatar avatar-xl bg-indigo-lt" hx-trigger="click" hx-post="/thank" hx-target="#count" name="AD" title="AD" style="margin-right: 5px;">AD</span>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- <div class="card-footer bg-transparent mt-auto">
|
||||
<div class="btn-list justify-content-end">
|
||||
<a href="#" class="btn">
|
||||
Cancel
|
||||
</a>
|
||||
<button class="btn btn-primary" id="submit">
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/dweebui.js" defer></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
</body>
|
||||
</html>
|
85
views/supporters.html
Normal file
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('partials/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('partials/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('partials/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,159 +1,93 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Syslogs - DweebUI</title>
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
<title>DweebUI - Syslogs</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">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
|
||||
<%- include('partials/navbar.html') %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body" style="margin-top: 16px;">
|
||||
|
||||
<div class="page-header d-print-none">
|
||||
<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">Syslogs</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="#modals-here">
|
||||
<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>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
<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('select')"></th>
|
||||
<th><label class="table-sort" data-sort="sort-id">ID</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-username">Username</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-uniqueid">Unique ID</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-event">Event</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-message">Message</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-ip">IP</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-timestamp">Timestamp</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-action">Action</label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">
|
||||
|
||||
<%- logs %>
|
||||
|
||||
</tbody>
|
||||
</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">Events</p>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<div id="modals-here" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered modal-dialog-scrollables">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">New Volume</h5>
|
||||
</div>
|
||||
|
||||
<div class="modal-body text-center">
|
||||
<form method="post" action="/">
|
||||
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-9">
|
||||
<label class="form-label text-muted">Volume Name</label>
|
||||
<input type="text" class="form-control" name="volume">
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<button type="submit" class="btn mt-2">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="mt-3 text-muted"><label class="text-danger">*</label>Name cannot contain spaces or special characters.</label>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Syslogs
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
|
||||
|
||||
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><button class="table-sort" data-sort="sort-id">id</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-user">user</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-email">email</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-event">event</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-message">message</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-ip">ip</button></th>
|
||||
<th><button class="table-sort" data-sort="sort-datetime">date/time</button></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">
|
||||
|
||||
<%- logs %>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<%- include('partials/footer.html') %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js" defer></script>
|
||||
|
||||
<script src="/js/dweebui.js" ></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
<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-id', 'sort-username', 'sort-uniqueid', 'sort-event', 'sort-message', 'sort-ip', { attr: 'data-date', name: 'sort-timestamp' }, 'sort-quantity' ]
|
||||
});
|
||||
const list = new List('table-default', {
|
||||
sortClass: 'table-sort',
|
||||
listClass: 'table-tbody',
|
||||
valueNames: [ 'sort-id', 'sort-email', 'sort-user', 'sort-event',
|
||||
{ attr: 'data-date', name: 'sort-message' },
|
||||
{ attr: 'data-progress', name: 'sort-datetime' },
|
||||
'sort-ip'
|
||||
]
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
188
views/users.html
188
views/users.html
|
@ -1,164 +1,70 @@
|
|||
<!doctype html>
|
||||
<!--Tabler - version 1.0.0-beta20 - Copyright 2018-2023 The Tabler Authors - Copyright 2018-2023 codecalm.net Paweł Kuna - Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)-->
|
||||
<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>Users - DweebUI</title>
|
||||
<link href="/css/tabler.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/demo.min.css?1692870487" rel="stylesheet"/>
|
||||
<link href="/css/dweebui.css" rel="stylesheet"/>
|
||||
<title>DweebUI - Users</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">
|
||||
|
||||
<!-- EJS -->
|
||||
<%- navbar %>
|
||||
|
||||
<%- include('partials/navbar.html') %>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
|
||||
<!-- Page body -->
|
||||
<div class="page-body" style="margin-top: 16px;">
|
||||
|
||||
<div class="page-header d-print-none">
|
||||
<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">Users</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="#modals-here">
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="table-default" class="table-responsive">
|
||||
<table class="table">
|
||||
<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('select')"></th>
|
||||
<th><label class="table-sort" data-sort="sort-id">ID</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-avatar">Avatar</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-name">Name</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-username">Username</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-email">Email</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-userid">UserID</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-role">Role</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-lastlogin">Last Login</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-status">Status</label></th>
|
||||
<th><label class="table-sort" data-sort="sort-action">Action</label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">
|
||||
|
||||
|
||||
<%- user_list %>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
|
||||
<button class="btn" type="submit" formaction="/">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">Users</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- New User Modal -->
|
||||
<!-- <div id="modals-here" class="modal modal-blur fade" style="display: none" aria-hidden="false" tabindex="-1">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered modal-dialog-scrollables">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">New User</h5>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-9">
|
||||
<label class="form-label text-muted">User Name</label>
|
||||
<input type="text" class="form-control" name="name">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<button type="submit" class="btn mt-2">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="mt-3 text-muted"><label class="text-danger">*</label>Name cannot contain spaces or special characters.</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
|
||||
<div class="modal slim-modal modal-blur fade" id="scrolling_modal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content" id="modal_content">
|
||||
<!-- modal content inserted with htmx -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Users
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EJS -->
|
||||
<%- footer %>
|
||||
|
||||
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-cards">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-vcenter table-mobile-md card-table">
|
||||
<tbody>
|
||||
|
||||
<%- user_list %>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('partials/footer.html') %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Libs JS -->
|
||||
<script src="/libs/list.js/dist/list.min.js"></script>
|
||||
|
||||
<script src="/js/dweebui.js"></script>
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<!-- Tabler Core -->
|
||||
<script src="/js/tabler.min.js?1692870487" defer></script>
|
||||
<script src="/js/demo.min.js?1692870487" defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const list = new List('table-default', {
|
||||
sortClass: 'table-sort',
|
||||
listClass: 'table-tbody',
|
||||
valueNames: [ 'sort-id', 'sort-avatar', 'sort-name', 'sort-username', 'sort-email', 'sort-userid', 'sort-role', 'sort-lastlogin', 'sort-status']
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<script src="/js/tabler.min.js" defer></script>
|
||||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
120
views/variables.html
Normal file
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('partials/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('partials/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('partials/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>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue