Portal page for regular users. Fix apps.js submit.

This commit is contained in:
lllllllillllllillll 2024-01-10 21:31:13 -08:00
parent a105e5fbb6
commit 6d8a919d18
13 changed files with 392 additions and 14 deletions

3
app.js
View file

@ -212,14 +212,17 @@ io.on('connection', (socket) => {
// Start intervals if not already started
if (!metricsInterval) {
serverMetrics();
metricsInterval = setInterval(serverMetrics, 1000);
console.log('Metrics interval started');
}
if (!cardsInterval) {
containerCards();
cardsInterval = setInterval(containerCards, 1000);
console.log('Cards interval started');
}
if (!graphsInterval) {
containerStats();
graphsInterval = setInterval(containerStats, 1000);
console.log('Graphs interval started');
}

View file

@ -219,7 +219,7 @@ export const appCard = (data) => {
<div class="col-md-6 col-lg-3">
<div class="card">
<div class="card-body p-4 text-center">
<span class="avatar avatar-xlplus mb-3 rounded"><img src='${data.logo}' width="144px" height="144px" loading="lazy"></img></span>
<span class="avatar avatar-xlplus mb-3 rounded"><img src='${data.logo}' width="144px" height="144px" loading="lazy"/></span>
<h3 class="m-0 mb-1"><a href="#">${shortened_name}</a></h3>
<div class="text-secondary">${shortened_desc}</div>
<div class="mt-3">

View file

@ -11,6 +11,8 @@ templates = templates.sort((a, b) => {
});
export const Apps = (req, res) => {
// console.log(req.body);
let page = Number(req.params.page) || 1;
let list_start = (page-1)*28;
let list_end = (page*28);
@ -50,6 +52,7 @@ export const Apps = (req, res) => {
export const searchApps = async (req, res) => {
// console.log(req.body);
let page = Number(req.query.page) || 1;
let list_start = (page - 1) * 28;
let list_end = (page * 28);
@ -67,13 +70,14 @@ export const searchApps = async (req, res) => {
let apps_list = '';
let search_results = [];
console.log(req.body);
let search = req.body.search;
// split value of search into an array of words
search = search.split(' ');
try {console.log(search[0]);} catch (error) {}
try {console.log(search[1]);} catch (error) {}
try {console.log(search[2]);} catch (error) {}
// try {console.log(search[0]);} catch (error) {}
// try {console.log(search[1]);} catch (error) {}
// try {console.log(search[2]);} catch (error) {}
function searchTemplates(word) {

12
controllers/portal.js Normal file
View file

@ -0,0 +1,12 @@
export const Portal = (req, res) => {
res.render("portal", {
name: req.session.user,
role: req.session.role,
avatar: req.session.avatar,
});
}

View file

@ -159,4 +159,31 @@ export const Syslog = sequelize.define('Syslog', {
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,
},
createdAt : {
type: DataTypes.STRING
},
createdBy : {
type: DataTypes.STRING
},
});

213
functions/install.js Normal file
View file

@ -0,0 +1,213 @@
import { writeFileSync, mkdirSync, readFileSync } from "fs";
import yaml from 'js-yaml';
import { execSync } from "child_process";
import { docker } from "../app.js";
import DockerodeCompose from "dockerode-compose";
export const Install = async (req, res) => {
console.log('getInstall:')
console.log(req.body);
res.render("/apps", {
name: req.session.user,
role: req.session.role,
avatar: req.session.avatar,
list_start: 0,
list_end: 28,
app_count: 0,
prev: 0,
next: 0,
apps_list: 0,
});
}
// module.exports.install = async function (data) {
// console.log(`[Start of install function]`);
// let { service_name, name, image, command_check, command, net_mode, restart_policy } = data;
// let { port0, port1, port2, port3, port4, port5 } = data;
// let { volume0, volume1, volume2, volume3, volume4, volume5 } = data;
// let { env0, env1, env2, env3, env4, env5, env6, env7, env8, env9, env10, env11 } = data;
// let { label0, label1, label2, label3, label4, label5, label6, label7, label8, label9, label10, label11 } = data;
// let docker_volumes = [];
// if (image.startsWith('https://')){
// mkdirSync(`./appdata/${name}`, { recursive: true });
// execSync(`curl -o ./appdata/${name}/${name}_stack.yml -L ${image}`);
// console.log(`Downloaded stackfile: ${image}`);
// let stackfile = yaml.load(readFileSync(`./appdata/${name}/${name}_stack.yml`, 'utf8'));
// let services = Object.keys(stackfile.services);
// for ( let i = 0; i < services.length; i++ ) {
// try {
// console.log(stackfile.services[Object.keys(stackfile.services)[i]].environment);
// } catch { console.log('no env') }
// }
// } else {
// let compose_file = `version: '3'`;
// compose_file += `\nservices:`
// compose_file += `\n ${service_name}:`
// compose_file += `\n container_name: ${name}`;
// compose_file += `\n image: ${image}`;
// // Command
// if (command_check == 'on') {
// compose_file += `\n command: ${command}`
// }
// // Network mode
// if (net_mode == 'host') {
// compose_file += `\n network_mode: 'host'`
// }
// else if (net_mode != 'host' && net_mode != 'docker') {
// compose_file += `\n network_mode: '${net_mode}'`
// }
// // Restart policy
// if (restart_policy != '') {
// compose_file += `\n restart: ${restart_policy}`
// }
// // Ports
// if ((port0 == 'on' || port1 == 'on' || port2 == 'on' || port3 == 'on' || port4 == 'on' || port5 == 'on') && (net_mode != 'host')) {
// compose_file += `\n ports:`
// for (let i = 0; i < 6; i++) {
// if (data[`port${i}`] == 'on') {
// compose_file += `\n - ${data[`port_${i}_external`]}:${data[`port_${i}_internal`]}/${data[`port_${i}_protocol`]}`
// }
// }
// }
// // Volumes
// if (volume0 == 'on' || volume1 == 'on' || volume2 == 'on' || volume3 == 'on' || volume4 == 'on' || volume5 == 'on') {
// compose_file += `\n volumes:`
// for (let i = 0; i < 6; i++) {
// // if volume is on and neither bind or container is empty, it's a bind mount (ex /mnt/user/appdata/config:/config )
// if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] != '') && (data[`volume_${i}_container`] != '')) {
// compose_file += `\n - ${data[`volume_${i}_bind`]}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
// }
// // if bind is empty create a docker volume (ex container_name_config:/config) convert any '/' in container name to '_'
// else if ((data[`volume${i}`] == 'on') && (data[`volume_${i}_bind`] == '') && (data[`volume_${i}_container`] != '')) {
// let volume_name = data[`volume_${i}_container`].replace(/\//g, '_');
// compose_file += `\n - ${name}_${volume_name}:${data[`volume_${i}_container`]}:${data[`volume_${i}_readwrite`]}`
// docker_volumes.push(`${name}_${volume_name}`);
// }
// }
// }
// // Environment variables
// if (env0 == 'on' || env1 == 'on' || env2 == 'on' || env3 == 'on' || env4 == 'on' || env5 == 'on' || env6 == 'on' || env7 == 'on' || env8 == 'on' || env9 == 'on' || env10 == 'on' || env11 == 'on') {
// compose_file += `\n environment:`
// }
// for (let i = 0; i < 12; i++) {
// if (data[`env${i}`] == 'on') {
// compose_file += `\n - ${data[`env_${i}_name`]}=${data[`env_${i}_default`]}`
// }
// }
// // Add labels
// if (label0 == 'on' || label1 == 'on' || label2 == 'on' || label3 == 'on' || label4 == 'on' || label5 == 'on' || label6 == 'on' || label7 == 'on' || label8 == 'on' || label9 == 'on' || label10 == 'on' || label11 == 'on') {
// compose_file += `\n labels:`
// }
// for (let i = 0; i < 12; i++) {
// if (data[`label${i}`] == 'on') {
// compose_file += `\n - ${data[`label_${i}_name`]}=${data[`label_${i}_value`]}`
// }
// }
// // Add privileged mode
// if (data.privileged == 'on') {
// compose_file += `\n privileged: true`
// }
// // Add hardware acceleration to the docker-compose file if one of the environment variables has the label DRINODE
// if (env0 == 'on' || env1 == 'on' || env2 == 'on' || env3 == 'on' || env4 == 'on' || env5 == 'on' || env6 == 'on' || env7 == 'on' || env8 == 'on' || env9 == 'on' || env10 == 'on' || env11 == 'on') {
// for (let i = 0; i < 12; i++) {
// if (data[`env${i}`] == 'on') {
// if (data[`env_${i}_name`] == 'DRINODE') {
// compose_file += `\n deploy:`
// compose_file += `\n resources:`
// compose_file += `\n reservations:`
// compose_file += `\n devices:`
// compose_file += `\n - driver: nvidia`
// compose_file += `\n count: 1`
// compose_file += `\n capabilities: [gpu]`
// }
// }
// }
// }
// // add any docker volumes to the docker-compose file
// if ( docker_volumes.length > 0 ) {
// compose_file += `\n`
// compose_file += `\nvolumes:`
// // check docker_volumes for duplicates and remove them completely
// docker_volumes = docker_volumes.filter((item, index) => docker_volumes.indexOf(item) === index)
// for (let i = 0; i < docker_volumes.length; i++) {
// if ( docker_volumes[i] != '') {
// compose_file += `\n ${docker_volumes[i]}:`
// }
// }
// }
// try {
// mkdirSync(`./appdata/${name}`, { recursive: true });
// writeFileSync(`./appdata/${name}/docker-compose.yml`, compose_file, function (err) { console.log(err) });
// } catch { console.log('error creating directory or compose file') }
// try {
// var compose = new DockerodeCompose(docker, `./appdata/${name}/docker-compose.yml`, `${name}`);
// (async () => {
// await compose.pull();
// await compose.up();
// })();
// } catch { console.log('error running compose file')}
// }
// }
// module.exports.uninstall = async function (data) {
// if (data.confirm == 'Yes') {
// console.log(`Uninstalling ${data.service_name}: ${data}`);
// var containerName = docker.getContainer(`${data.service_name}`);
// try {
// await containerName.stop();
// console.log(`Stopped ${data.service_name} container`);
// } catch {
// console.log(`Error stopping ${data.service_name} container`);
// }
// try {
// await containerName.remove();
// console.log(`Removed ${data.service_name} container`);
// } catch {
// console.log(`Error removing ${data.service_name} container`);
// }
// }
// }

28
functions/uninstall.js Normal file
View file

@ -0,0 +1,28 @@
import { writeFileSync, mkdirSync, readFileSync } from "fs";
import yaml from 'js-yaml';
import { execSync } from "child_process";
import { docker } from "../app.js";
import DockerodeCompose from "dockerode-compose";
export const Uninstall = async (req, res) => {
console.log('Uninstall')
console.log(req.body);
res.render("/apps", {
name: req.session.user,
role: req.session.role,
avatar: req.session.avatar,
list_start: 0,
list_end: 28,
app_count: 0,
prev: 0,
next: 0,
apps_list: 0,
});
}

28
package-lock.json generated
View file

@ -17,11 +17,13 @@
"compression": "^1.7.4",
"cors": "^2.8.5",
"dockerode": "^4.0.1",
"dockerode-compose": "^1.4.0",
"ejs": "^3.1.9",
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"express-session": "^1.17.3",
"helmet": "^7.1.0",
"js-yaml": "^4.1.0",
"mocha": "^10.2.0",
"sequelize": "^6.35.2",
"sinon": "^17.0.1",
@ -1672,6 +1674,32 @@
"node": ">= 8.0"
}
},
"node_modules/dockerode-compose": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/dockerode-compose/-/dockerode-compose-1.4.0.tgz",
"integrity": "sha512-6x5ZlK06H+cgoTR4ffucqN5kWVvxNvxwTLcHQUZcegCJBEDGrdzXMOEGDMsxbHwiLtLo2dNwG0eZK7B2RfEWSw==",
"dependencies": {
"dockerode": "^4.0.0",
"js-yaml": "^4.0.0",
"tar-fs": "^2.1.1"
}
},
"node_modules/dockerode-compose/node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"node_modules/dockerode-compose/node_modules/tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/dottie": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz",

View file

@ -19,11 +19,13 @@
"compression": "^1.7.4",
"cors": "^2.8.5",
"dockerode": "^4.0.1",
"dockerode-compose": "^1.4.0",
"ejs": "^3.1.9",
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"express-session": "^1.17.3",
"helmet": "^7.1.0",
"js-yaml": "^4.1.0",
"mocha": "^10.2.0",
"sequelize": "^6.35.2",
"sinon": "^17.0.1",

View file

@ -1,6 +1,7 @@
import express from "express";
import { io } from "../app.js";
// Controllers
import { Login, submitLogin } from "../controllers/login.js";
import { Register, submitRegister } from "../controllers/register.js";
import { Dashboard, searchDashboard } from "../controllers/dashboard.js";
@ -11,19 +12,24 @@ import { Account } from "../controllers/account.js";
import { Settings } from "../controllers/settings.js";
import { Networks } from "../controllers/networks.js";
import { Volumes } from "../controllers/volumes.js";
import { Syslogs } from "../controllers/syslogs.js";
import { Syslogs } from "../controllers/syslogs.js";
import { Portal } from "../controllers/portal.js"
export const router = express.Router();
/// Functions
import { Install } from "../functions/install.js"
import { Uninstall } from "../functions/uninstall.js"
// Auth middleware
const auth = (req, res, next) => {
if (req.session.role == "admin") {
next();
} else {
res.redirect("/login");
res.redirect("/portal");
}
};
router.get("/login", Login);
router.post("/login", submitLogin);
@ -32,7 +38,8 @@ router.post("/register", submitRegister);
router.get("/", auth, Dashboard);
router.post("/", auth, searchDashboard);
router.post("/:search", auth, searchDashboard);
router.get("/portal", auth, Portal)
router.get("/apps", auth, Apps);
router.get("/apps/:page", auth, Apps);
@ -57,4 +64,8 @@ router.get("/logout", (req, res) => {
io.to(sessionId).disconnectSockets();
res.redirect("/login");
});
});
});
router.post("/install", auth, Install);
router.post("/uninstall", auth, Uninstall);

View file

@ -43,7 +43,7 @@
<form action="/apps" id="search" name="search" method="POST">
<input type="search" class="form-control" name="search" placeholder="Search apps…">
</form>
&nbsp;<input type="submit" form="search" class="btn btn-outline-success h-50" value="search">
<input type="submit" form="search" class="btn btn-outline-success h-50" value="search">
<div class="card-actions btn-actions">
<div class="card-actions btn-actions">

View file

@ -255,17 +255,17 @@
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<a class="nav-link" href="/portal">
<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-tool" 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="M7 10h3v-3l-3.5 -3.5a6 6 0 0 1 8 8l6 6a2 2 0 0 1 -3 3l-6 -6a6 6 0 0 1 -8 -8l3.5 3.5"></path> </svg> </span>
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-star" 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> </span>
<span class="nav-link-title">
Tools
Portal
</span>
</a>
</li>
</ul>
<div class="my-2 my-md-0 flex-grow-1 flex-md-grow-0 order-first order-md-last">

50
views/portal.ejs Normal file
View file

@ -0,0 +1,50 @@
<!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"/>
<style>
@import url('fonts/inter.css');
:root {
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
}
body {
font-feature-settings: "cv03", "cv04", "cv11";
}
</style>
</head>
<body >
<div class="page">
<%- include('navbar.ejs') %>
<div class="page-wrapper">
<div class="page-body">
<div class="container-xl">
<div class="row row-deck row-cards">
</div>
</div>
</div>
<%- include('footer.ejs') %>
</div>
</div>
<!-- Libs JS -->
<script src="libs/apexcharts/dist/apexcharts.min.js" defer></script>
<!-- Tabler Core -->
<script src="js/tabler.min.js"></script>
</body>
</html>