Almost a complete rewrite, part 1.
This commit is contained in:
parent
33e45a8bbf
commit
622318c461
386 changed files with 310761 additions and 40629 deletions
|
@ -1,54 +1,36 @@
|
|||
import { User } from "../database/models.js";
|
||||
import { User, ServerSettings } from '../database/config.js';
|
||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||
|
||||
const no_auth = process.env.NO_AUTH || false;
|
||||
export const Account = async function(req,res){
|
||||
|
||||
export const Account = async (req, res) => {
|
||||
let container_links = await ServerSettings.findOne({ where: {key: 'container_links'}});
|
||||
let user_registration = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||
|
||||
if (no_auth && req.hostname == 'localhost') {
|
||||
res.render("account", {
|
||||
first_name: 'Localhost',
|
||||
last_name: 'Localhost',
|
||||
username: 'Localhost',
|
||||
id: 0,
|
||||
email: 'admin@localhost',
|
||||
role: 'admin',
|
||||
avatar: 'L',
|
||||
alert: '',
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let user = await User.findOne({ where: {userID: req.session.userID}});
|
||||
|
||||
res.render("account",{
|
||||
first_name: user.name,
|
||||
last_name: user.name,
|
||||
username: req.session.username,
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
avatar: req.session.username.charAt(0).toUpperCase(),
|
||||
alert: '',
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
name: user.name,
|
||||
username: req.session.username,
|
||||
email: user.email,
|
||||
avatar: user.avatar,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const submitAccount = async function(req,res){
|
||||
|
||||
console.log(req.body);
|
||||
|
||||
res.render("account",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
|
||||
|
||||
}
|
|
@ -1,605 +1,69 @@
|
|||
import { readFileSync, readdirSync, renameSync, mkdirSync, unlinkSync, existsSync } from 'fs';
|
||||
import { parse } from 'yaml';
|
||||
import multer from 'multer';
|
||||
import AdmZip from 'adm-zip';
|
||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||
|
||||
const upload = multer({storage: multer.diskStorage({
|
||||
destination: function (req, file, cb) { cb(null, 'templates/tmp/') },
|
||||
filename: function (req, file, cb) { cb(null, file.originalname) },
|
||||
})});
|
||||
export const Apps = async function(req,res){
|
||||
|
||||
let alert = '';
|
||||
let templates_global = '';
|
||||
let json_templates = '';
|
||||
let remove_button = '';
|
||||
let { page, template } = req.params;
|
||||
|
||||
export const Apps = async (req, res) => {
|
||||
let file = '';
|
||||
let templates = [];
|
||||
|
||||
let page = Number(req.params.page) || 1;
|
||||
let template_param = req.params.template || 'default';
|
||||
if (!page) { page = 1; }
|
||||
if (!template) { 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 = '';
|
||||
}
|
||||
|
||||
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/');
|
||||
|
||||
app_count = compose_files.length;
|
||||
last_page = Math.ceil(compose_files.length/28);
|
||||
|
||||
compose_files.forEach(file => {
|
||||
if (file == '.gitignore') { return; }
|
||||
|
||||
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;
|
||||
try { // Try to read the template file
|
||||
file = readFileSync(`./appdata/templates/${template}.json`);
|
||||
templates = JSON.parse(file).templates;
|
||||
// Sort the templates by name
|
||||
templates = templates.sort((a, b) => { if (a.name < b.name) { return -1; } });
|
||||
app_count = templates.length;
|
||||
|
||||
templates_global = templates;
|
||||
|
||||
apps_list = '';
|
||||
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, 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;
|
||||
let logo = templates[i].logo;
|
||||
let categories = '';
|
||||
// set data.catagories to 'other' if data.catagories is empty or undefined
|
||||
if (templates[i].categories == null || templates[i].categories == undefined || templates[i].categories == '') {
|
||||
templates[i].categories = ['Other'];
|
||||
}
|
||||
// loop through the categories and add the badge to the card
|
||||
for (let j = 0; j < templates[i].categories.length; j++) {
|
||||
categories += CatagoryColor(templates[i].categories[j]);
|
||||
catch {
|
||||
console.log(`Template ${template} not found`);
|
||||
}
|
||||
appCard = appCard.replace(/AppName/g, name);
|
||||
appCard = appCard.replace(/AppTitle/g, title);
|
||||
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", {
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
avatar: req.session.username.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,
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
});
|
||||
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();
|
||||
let app_card = readFileSync('./views/partials/app_card.html', 'utf8');
|
||||
app_card = app_card.replace(/AppShortName/g, templates[i].name);
|
||||
app_card = app_card.replace(/AppIcon/g, templates[i].logo);
|
||||
apps_list += app_card;
|
||||
}
|
||||
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');
|
||||
let app_count = `1 - 28 of ${templates.length} Apps`;
|
||||
|
||||
apps_list += appCard;
|
||||
}
|
||||
res.render("apps",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
avatar: req.session.username.charAt(0).toUpperCase(),
|
||||
list_start: list_start + 1,
|
||||
list_end: list_end,
|
||||
app_count: results.length,
|
||||
prev: prev,
|
||||
next: next,
|
||||
app_count: app_count,
|
||||
remove_button: '',
|
||||
json_templates: '',
|
||||
apps_list: apps_list,
|
||||
alert: alert,
|
||||
template_list: '',
|
||||
json_templates: json_templates,
|
||||
pages: pages,
|
||||
remove_button: remove_button,
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
prev: '',
|
||||
next: '',
|
||||
pages: '',
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function CatagoryColor(category) {
|
||||
switch (category) {
|
||||
case 'Other':
|
||||
return '<span class="badge bg-blue-lt">Other</span> ';
|
||||
case 'Productivity':
|
||||
return '<span class="badge bg-blue-lt">Productivity</span> ';
|
||||
case 'Tools':
|
||||
return '<span class="badge bg-blue-lt">Tools</span> ';
|
||||
case 'Dashboard':
|
||||
return '<span class="badge bg-blue-lt">Dashboard</span> ';
|
||||
case 'Communication':
|
||||
return '<span class="badge bg-azure-lt">Communication</span> ';
|
||||
case 'Media':
|
||||
return '<span class="badge bg-azure-lt">Media</span> ';
|
||||
case 'CMS':
|
||||
return '<span class="badge bg-azure-lt">CMS</span> ';
|
||||
case 'Monitoring':
|
||||
return '<span class="badge bg-indigo-lt">Monitoring</span> ';
|
||||
case 'LDAP':
|
||||
return '<span class="badge bg-purple-lt">LDAP</span> ';
|
||||
case 'Arr':
|
||||
return '<span class="badge bg-purple-lt">Arr</span> ';
|
||||
case 'Database':
|
||||
return '<span class="badge bg-red-lt">Database</span> ';
|
||||
case 'Paid':
|
||||
return '<span class="badge bg-red-lt" title="This is a paid product or contains paid features.">Paid</span> ';
|
||||
case 'Gaming':
|
||||
return '<span class="badge bg-pink-lt">Gaming</span> ';
|
||||
case 'Finance':
|
||||
return '<span class="badge bg-orange-lt">Finance</span> ';
|
||||
case 'Networking':
|
||||
return '<span class="badge bg-yellow-lt">Networking</span> ';
|
||||
case 'Authentication':
|
||||
return '<span class="badge bg-lime-lt">Authentication</span> ';
|
||||
case 'Development':
|
||||
return '<span class="badge bg-green-lt">Development</span> ';
|
||||
case 'Media Server':
|
||||
return '<span class="badge bg-teal-lt">Media Server</span> ';
|
||||
case 'Downloaders':
|
||||
return '<span class="badge bg-cyan-lt">Downloaders</span> ';
|
||||
default:
|
||||
return ''; // default to other if the category is not recognized
|
||||
}
|
||||
}
|
||||
|
||||
export const InstallModal = async (req, res) => {
|
||||
let input = req.header('hx-trigger-name');
|
||||
let type = req.header('hx-trigger');
|
||||
export const submitApps = async function(req,res){
|
||||
|
||||
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';
|
||||
// console.log(req.body);
|
||||
|
||||
switch (result.network) {
|
||||
case 'host':
|
||||
net_host = 'checked';
|
||||
break;
|
||||
case 'bridge':
|
||||
net_bridge = 'checked';
|
||||
net_name = result.network;
|
||||
break;
|
||||
default:
|
||||
net_docker = 'checked';
|
||||
}
|
||||
let trigger_name = req.header('hx-trigger-name');
|
||||
let trigger_id = req.header('hx-trigger');
|
||||
|
||||
if (repository != "") {
|
||||
image = (`${repository.url}/raw/master/${repository.stackfile}`);
|
||||
}
|
||||
console.log(`trigger_name: ${trigger_name} - trigger_id: ${trigger_id}`);
|
||||
|
||||
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
|
||||
res.render("apps",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
} 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,199 +1,29 @@
|
|||
import { Readable } from 'stream';
|
||||
import { currentLoad, mem, networkStats, fsSize } from 'systeminformation';
|
||||
import { containerList, containerInspect } from '../utils/docker.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import { currentLoad, mem, networkStats, fsSize, dockerContainerStats } from 'systeminformation';
|
||||
import { Permission, User, ServerSettings } from '../database/models.js';
|
||||
import { Op } from 'sequelize';
|
||||
import { docker, containerList, containerInspect } from '../utils/docker.js';
|
||||
import { User } from '../database/config.js';
|
||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||
|
||||
let [ hidden, alert, newCards, stats ] = [ '', '', '', {} ];
|
||||
let logString = '';
|
||||
export const Dashboard = async function(req,res){
|
||||
|
||||
let container_list = '';
|
||||
|
||||
export const Dashboard = async (req, res) => {
|
||||
const { host } = req.params;
|
||||
let { link1, link2, link3, link4, link5, link6, link7, link8, link9 } = ['', '', '', '', '', '', '', '', ''];
|
||||
// if (host) { console.log(`Viewing Host: ${host}`); } else { console.log('Viewing Host: 1'); }
|
||||
let containers = await containerList();
|
||||
for (let container of containers) {
|
||||
let details = await containerInspect(container.containerID);
|
||||
let container_card = readFileSync('./views/partials/container_card.html', 'utf8');
|
||||
|
||||
res.render("dashboard", {
|
||||
username: req.session.username,
|
||||
avatar: req.session.username.charAt(0).toUpperCase(),
|
||||
role: req.session.role,
|
||||
alert: req.session.alert,
|
||||
link1: link1,
|
||||
link2: link2,
|
||||
link3: link3,
|
||||
link4: link4,
|
||||
link5: link5,
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
});
|
||||
if (details.name.length > 17) {
|
||||
details.name = details.name.substring(0, 17) + '...';
|
||||
}
|
||||
|
||||
|
||||
export const DashboardAction = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let value = req.header('hx-trigger');
|
||||
const { action } = req.params;
|
||||
let modal = '';
|
||||
console.log(`Action: ${action} Name: ${name} Value: ${value}`);
|
||||
|
||||
if (req.body.search) {
|
||||
console.log(req.body.search);
|
||||
res.send('search');
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == 'get_containers') {
|
||||
res.send(newCards);
|
||||
newCards = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Creates the permissions modal
|
||||
if (action == 'permissions') {
|
||||
// To capitalize the title
|
||||
let title = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
// Empty the permissions list
|
||||
let permissions_list = '';
|
||||
// Get the container ID
|
||||
let container = docker.getContainer(name);
|
||||
let containerInfo = await container.inspect();
|
||||
let container_id = containerInfo.Id;
|
||||
// Get the body of the permissions modal
|
||||
let permissions_modal = readFileSync('./views/modals/permissions.html', 'utf8');
|
||||
// Replace the title and container name in the modal
|
||||
permissions_modal = permissions_modal.replace(/PermissionsTitle/g, title);
|
||||
permissions_modal = permissions_modal.replace(/PermissionsContainer/g, name);
|
||||
permissions_modal = permissions_modal.replace(/ContainerID/g, container_id);
|
||||
// Get a list of all users
|
||||
let users = await User.findAll({ attributes: ['username', 'userID']});
|
||||
// Loop through each user to check what permissions they have
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
// Get the user_permissions form
|
||||
let user_permissions = readFileSync('./views/partials/user_permissions.html', 'utf8');
|
||||
// Check if the user has any permissions for the container
|
||||
let exists = await Permission.findOne({ where: { containerID: container_id, userID: users[i].userID }});
|
||||
// Create an entry if one doesn't exist
|
||||
if (!exists) { const newPermission = await Permission.create({ containerName: name, containerID: container_id, username: users[i].username, userID: users[i].userID }); }
|
||||
// Get the permissions for the user
|
||||
let permissions = await Permission.findOne({ where: { containerID: container_id, userID: users[i].userID }});
|
||||
// Fill in the form values
|
||||
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);
|
||||
user_permissions = user_permissions.replace(/PermissionsUserID/g, users[i].userID);
|
||||
user_permissions = user_permissions.replace(/PermissionsID/g, container_id);
|
||||
// Add the user entry to the permissions list
|
||||
permissions_list += user_permissions;
|
||||
}
|
||||
// Insert the user list into the permissions modal
|
||||
permissions_modal = permissions_modal.replace(/PermissionsList/g, permissions_list);
|
||||
// Send the permissions modal
|
||||
res.send(permissions_modal);
|
||||
return;
|
||||
}
|
||||
// Capitalize the first letter of the name
|
||||
details.name = details.name.charAt(0).toUpperCase() + details.name.slice(1);
|
||||
|
||||
|
||||
switch (action) {
|
||||
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 'card':
|
||||
// Check which cards the user has permissions for
|
||||
await userCards(req.session);
|
||||
// Remove the container if it isn't in the user's list
|
||||
if (!req.session.container_list.find(c => c.container === name)) {
|
||||
res.send('');
|
||||
return;
|
||||
} else {
|
||||
// Get the container information and send the updated card
|
||||
let details = await containerInfo(value);
|
||||
let card = await createCard(details);
|
||||
res.send(card);
|
||||
return;
|
||||
}
|
||||
case 'logs':
|
||||
logString = '';
|
||||
let options = { follow: false, stdout: true, stderr: false, timestamps: true };
|
||||
console.log(`Getting logs for ${name}`);
|
||||
docker.getContainer(name).logs(options, function (err, stream) {
|
||||
if (err) { console.log(`some error getting logs`); 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 'alert':
|
||||
req.session.alert = '';
|
||||
res.send('');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function createCard (details) {
|
||||
let { containerName, containerID, containerState } = details;
|
||||
// console.log(`Creating card for ${containerName} ID: ${containerID} Service: ${containerService} State: ${containerState}`);
|
||||
let container = await containerInspect(containerID);
|
||||
|
||||
let shortname = containerName.slice(0, 10) + '...';
|
||||
let trigger = 'data-hx-trigger="load, every 3s"';
|
||||
let state = containerState;
|
||||
let state = details.state;
|
||||
let state_color = '';
|
||||
let app_icon = container.service;
|
||||
let links = await ServerSettings.findOne({ where: {key: 'links'}});
|
||||
if (!links) { links = { value: 'localhost' }; }
|
||||
|
||||
switch (state) {
|
||||
case 'running':
|
||||
state_color = 'green';
|
||||
|
@ -201,148 +31,44 @@ async function createCard (details) {
|
|||
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;
|
||||
}
|
||||
let card = readFileSync('./views/partials/containerFull.html', 'utf8');
|
||||
card = card.replace(/AppName/g, containerName);
|
||||
card = card.replace(/AppID/g, containerID);
|
||||
card = card.replace(/AppShortName/g, shortname);
|
||||
card = card.replace(/AppIcon/g, app_icon);
|
||||
card = card.replace(/AppState/g, state);
|
||||
card = card.replace(/StateColor/g, state_color);
|
||||
card = card.replace(/AppLink/g, links.value);
|
||||
card = card.replace(/ChartName/g, containerName.replace(/-/g, ''));
|
||||
card = card.replace(/AppNameState/g, `${containerName}State`);
|
||||
card = card.replace(/data-trigger=""/, trigger);
|
||||
|
||||
// Show nothing if there are no ports exposed.
|
||||
if ((container.external_port == 0) && (container.internal_port == 0)) { card = card.replace(/AppPorts/g, ''); }
|
||||
else { card = card.replace(/AppPorts/g, `${container.external_port}:${container.internal_port}`); }
|
||||
container_card = container_card.replace(/AppName/g, details.name);
|
||||
container_card = container_card.replace(/AppService/g, details.service);
|
||||
container_card = container_card.replace(/AppState/g, state);
|
||||
container_card = container_card.replace(/StateColor/g, state_color);
|
||||
|
||||
return card;
|
||||
if (details.external_port == 0 && details.internal_port == 0) {
|
||||
container_card = container_card.replace(/AppPorts/g, ``);
|
||||
} else {
|
||||
container_card = container_card.replace(/AppPorts/g, `${details.external_port}:${details.internal_port}`);
|
||||
}
|
||||
|
||||
|
||||
// Creates a list of containers that the user should be able to see.
|
||||
async function updateLists(session, host) {
|
||||
// Create an empty container list.
|
||||
session.container_list = [];
|
||||
// Check what containers the user has hidden.
|
||||
let hidden = await Permission.findAll({ where: { userID: session.userID, hide: true }, attributes: ['containerID'], raw: true });
|
||||
// Check which containers the user has permissions for.
|
||||
let visable = await Permission.findAll({ where: { userID: session.userID, [Op.or]: [{ uninstall: true }, { edit: true }, { upgrade: true }, { start: true }, { stop: true }, { pause: true }, { restart: true }, { logs: true }, { view: true }] }, attributes: ['containerID'], raw: true});
|
||||
let containers = await containerList(host);
|
||||
// Loop through the list of containers.
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
// Get the container ID.
|
||||
let containerID = containers[i].containerID;
|
||||
// Skip the container if it's ID is in the hidden list.
|
||||
if (hidden.includes(containerID)) { console.log('skipped hidden container'); continue; }
|
||||
// If the user is admin and they don't have it hidden, add it to the list.
|
||||
if (session.role == 'admin') { session.container_list.push({ containerName: containers[i].containerName, containerID: containerID, containerState: containers[i].containerState }); }
|
||||
// Add the container if it's ID is in the visable list.
|
||||
else if (visable.includes(containerID)){ session.container_list.push({ containerName: containers[i].containerName, containerID: containerID, containerState: containers[i].containerState }); }
|
||||
container_list += container_card;
|
||||
}
|
||||
// Create the lists if they don't exist.
|
||||
if (!session.sent_list) { session.sent_list = []; }
|
||||
if (!session.update_list) { session.update_list = []; }
|
||||
if (!session.new_cards) { session.new_cards = []; }
|
||||
|
||||
session.new_cards = [];
|
||||
session.update_list = [];
|
||||
// Loop through the containers list
|
||||
session.container_list.forEach(info => {
|
||||
// Get the containerID and state
|
||||
let { containerName, containerID, containerState } = info;
|
||||
// Check if the containerID is in the sent list
|
||||
let sent = session.sent_list.find(c => c.containerID === containerID);
|
||||
// If it's not in the sent list, add it to the new cards list.
|
||||
if (!sent) { session.new_cards.push({ containerName, containerID, containerState }); }
|
||||
// If it is in the sent list, check if the state has changed.
|
||||
else if (sent.containerState !== containerState) { session.update_list.push({ containerName, containerID, containerState }); }
|
||||
});
|
||||
// Loop through the sent list to see if any containers have been removed
|
||||
session.sent_list.forEach(info => {
|
||||
let { containerName, containerID, containerState } = info;
|
||||
let exists = session.container_list.find(c => c.containerID === containerID);
|
||||
if (!exists) { session.update_list.push({ containerName, containerID, containerState }); }
|
||||
|
||||
res.render("dashboard",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
container_list: container_list,
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// HTMX server-side events
|
||||
export const SSE = async (req, res) => {
|
||||
let running = false;
|
||||
let skipped_events = 0;
|
||||
|
||||
// Set the headers
|
||||
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
|
||||
|
||||
// Updates req.session.container_list with the containers the user can see.
|
||||
await newEvent();
|
||||
|
||||
// Event trigger with debounce
|
||||
|
||||
async function newEvent() {
|
||||
if (!running) {
|
||||
console.log('[Docker event]');
|
||||
running = true;
|
||||
// Update the container lists
|
||||
await updateLists(req.session, 'host');
|
||||
// Check if the container_list is the same as the sent_list
|
||||
if ((JSON.stringify(req.session.container_list) != JSON.stringify(req.session.sent_list))) {
|
||||
console.log('Updating dashboard');
|
||||
// New card
|
||||
for (let i = 0; i < req.session.new_cards.length; i++) {
|
||||
console.log('SSE event: new card');
|
||||
let card = await createCard(req.session.new_cards[i]);
|
||||
newCards += card;
|
||||
req.session.alert = '';
|
||||
}
|
||||
// Card needs to be updated
|
||||
for (let i = 0; i < req.session.update_list.length; i++) {
|
||||
console.log(`SSE event: update card ${req.session.update_list[i].containerName} ${req.session.update_list[i].containerID}`);
|
||||
res.write(`event: ${req.session.update_list[i].containerID}\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();
|
||||
}
|
||||
|
||||
// res.write(`event: update\n`);
|
||||
// res.write(`data: 'update cards'\n\n`);
|
||||
|
||||
setTimeout(() => {
|
||||
running = false;
|
||||
// console.log(`Skipped ${skipped_events} events`);
|
||||
skipped_events = 0;
|
||||
}, 300);
|
||||
} else { skipped_events++; }
|
||||
}
|
||||
|
||||
// Listens for docker events
|
||||
docker.getEvents({}, function (err, data) {
|
||||
data.on('data', function () {
|
||||
newEvent();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Server metrics (CPU, RAM, TX, RX, DISK)
|
||||
export const Stats = async (req, res) => {
|
||||
export const ServerMetrics = async (req, res) => {
|
||||
let name = req.header('hx-trigger-name');
|
||||
let color = req.header('hx-trigger');
|
||||
let value = 0;
|
||||
|
@ -369,124 +95,9 @@ export const Stats = async (req, res) => {
|
|||
res.send(info);
|
||||
}
|
||||
|
||||
// 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>
|
||||
${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>`;
|
||||
}
|
||||
|
||||
// Update container permissions.
|
||||
export const UpdatePermissions = async (req, res) => {
|
||||
let { userID, container, containerID, reset_permissions } = req.body;
|
||||
let id = req.header('hx-trigger');
|
||||
// console.log(`User: ${userID} Container: ${container} ContainerID: ${containerID} Reset: ${reset_permissions}`);
|
||||
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} });
|
||||
return;
|
||||
}
|
||||
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 } });
|
||||
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: { containerID: containerID, userID: userID}}); }
|
||||
if (permissions.includes('edit')) { await Permission.update({ edit: true }, { where: { containerID: containerID, userID: userID}}); }
|
||||
if (permissions.includes('upgrade')) { await Permission.update({ upgrade: true }, { where: { containerID: containerID, userID: userID}}); }
|
||||
if (permissions.includes('start')) { await Permission.update({ start: true }, { where: { containerID: containerID, userID: userID}}); }
|
||||
if (permissions.includes('stop')) { await Permission.update({ stop: true }, { where: { containerID: containerID, userID: userID}}); }
|
||||
if (permissions.includes('pause')) { await Permission.update({ pause: true }, { where: { containerID: containerID, userID: userID}}); }
|
||||
if (permissions.includes('restart')) { await Permission.update({ restart: true }, { where: { containerID: containerID, userID: userID}}); }
|
||||
if (permissions.includes('logs')) { await Permission.update({ logs: true }, { where: { containerID: containerID, userID: userID}}); }
|
||||
if (permissions.includes('view')) { await Permission.update({ view: true }, { where: { containerID: containerID, userID: userID}}); }
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Container actions (start, stop, pause, restart, hide)
|
||||
export const ContainerAction = async (req, res) => {
|
||||
// Assign values
|
||||
let container_name = req.header('hx-trigger-name');
|
||||
let container_id = req.header('hx-trigger');
|
||||
let action = req.params.action;
|
||||
|
||||
console.log(`Container: ${container_name} ID: ${container_id} Action: ${action}`);
|
||||
|
||||
// Reset the view
|
||||
if (container_id == 'reset') {
|
||||
console.log('Resetting view');
|
||||
await Permission.update({ hide: false }, { where: { userID: req.session.userID } });
|
||||
export const submitDashboard = async function(req,res){
|
||||
console.log(req.body);
|
||||
res.send('ok');
|
||||
return;
|
||||
}
|
||||
// Inspect the container
|
||||
let container = docker.getContainer(container_id);
|
||||
let containerInfo = await container.inspect();
|
||||
let state = containerInfo.State.Status;
|
||||
// console.log(`Container: ${container_name} ID: ${container_id} State: ${state} Action: ${action}`);
|
||||
// Displays container state (starting, stopping, restarting, pausing)
|
||||
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>`);
|
||||
}
|
||||
// Perform the action
|
||||
if ((action == 'start') && (state == 'exited')) {
|
||||
await container.start();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'start') && (state == 'paused')) {
|
||||
await container.unpause();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'stop') && (state != 'exited')) {
|
||||
await container.stop();
|
||||
res.send(status('stopping'));
|
||||
} else if ((action == 'pause') && (state == 'paused')) {
|
||||
await container.unpause();
|
||||
res.send(status('starting'));
|
||||
} else if ((action == 'pause') && (state == 'running')) {
|
||||
await container.pause();
|
||||
res.send(status('pausing'));
|
||||
} else if (action == 'restart') {
|
||||
await container.restart();
|
||||
res.send(status('restarting'));
|
||||
} else if (action == 'hide') {
|
||||
let exists = await Permission.findOne({ where: { containerID: container_id, userID: req.session.userID }});
|
||||
if (!exists) { const newPermission = await Permission.create({ containerName: container_name, containerID: container_id, username: req.session.username, userID: req.session.userID, hide: true }); }
|
||||
else { exists.update({ hide: true }); }
|
||||
// Array of hidden containers
|
||||
hidden = await Permission.findAll({ where: { userID: req.session.userID, hide: true}}, { attributes: ['containerID'] });
|
||||
// Map the container IDs
|
||||
hidden = hidden.map((container) => container.containerID);
|
||||
res.send("ok");
|
||||
}
|
||||
}
|
|
@ -1,54 +1,18 @@
|
|||
import { docker } from '../utils/docker.js';
|
||||
import { addAlert } from './dashboard.js';
|
||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||
import { containerList, imageList } from '../utils/docker.js';
|
||||
|
||||
export const Images = async function(req,res){
|
||||
|
||||
let action = req.params.action;
|
||||
|
||||
console.log(req.params.host);
|
||||
|
||||
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 containers = await containerList();
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
container_images.push(containers[i].Image);
|
||||
}
|
||||
|
||||
let images = await docker.listImages({ all: true });
|
||||
let images = await imageList();
|
||||
|
||||
// Top of the table
|
||||
let image_list = `
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -101,23 +65,33 @@ export const Images = async function(req, res) {
|
|||
|
||||
image_list += `</tbody>`
|
||||
|
||||
|
||||
res.render("images",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
avatar: req.session.username.charAt(0).toUpperCase(),
|
||||
image_count: '',
|
||||
image_list: image_list,
|
||||
image_count: images.length,
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
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",{
|
||||
alert: '',
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
|
||||
}
|
|
@ -1,96 +1,37 @@
|
|||
import bcrypt from 'bcrypt';
|
||||
import { User, Syslog } from '../database/models.js';
|
||||
|
||||
// Environment variable to disable authentication.
|
||||
const no_auth = process.env.NO_AUTH || false;
|
||||
import { User, Syslog } from '../database/config.js';
|
||||
|
||||
|
||||
export const Login = function(req,res){
|
||||
if (req.session.username) { res.redirect("/dashboard"); }
|
||||
else { res.render("login",{ "error":"", }); }
|
||||
if (req.session.userID) { res.redirect("/dashboard"); }
|
||||
else { res.render("login",{
|
||||
"error":"",
|
||||
}); }
|
||||
}
|
||||
|
||||
export const submitLogin = async function(req,res){
|
||||
const { password } = req.body;
|
||||
let email = req.body.email.toLowerCase();
|
||||
|
||||
let error = '';
|
||||
if (!email || !password) { error = "Invalid credentials."; }
|
||||
|
||||
let user = await User.findOne({ where: { email: email }});
|
||||
|
||||
if (!user || !await bcrypt.compare(password, user.password)) { error = "Invalid credentials."; }
|
||||
|
||||
if (error) { res.render("login",{ "error":error }); return; }
|
||||
else {
|
||||
req.session.username = user.username;
|
||||
req.session.userID = user.userID;
|
||||
req.session.role = user.role;
|
||||
res.redirect("/dashboard");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const Logout = function(req,res){
|
||||
req.session.destroy(() => {
|
||||
res.redirect("/login");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const submitLogin = async function(req,res){
|
||||
|
||||
// Grab values from the form.
|
||||
let { email, password } = req.body;
|
||||
|
||||
// Convert the email to lowercase.
|
||||
email = email.toLowerCase();
|
||||
|
||||
// Create an admin session if NO_AUTH is enabled and the user is on localhost.
|
||||
if (no_auth && req.hostname == 'localhost') {
|
||||
req.session.username = 'Localhost';
|
||||
req.session.userID = '';
|
||||
req.session.role = 'admin';
|
||||
res.redirect("/dashboard");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that all fields are filled out.
|
||||
if (!email || !password) {
|
||||
res.render("login",{
|
||||
"error":"Please fill in all fields.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the user exists.
|
||||
let user = await User.findOne({ where: { email: email }});
|
||||
if (!user) {
|
||||
res.render("login",{
|
||||
"error":"Invalid credentials.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the password is correct.
|
||||
let password_check = await bcrypt.compare( password, user.password);
|
||||
|
||||
// If the password is incorrect, log the failed login attempt.
|
||||
if (!password_check) {
|
||||
res.render("login",{
|
||||
"error":"Invalid credentials.",
|
||||
});
|
||||
const syslog = await Syslog.create({
|
||||
user: null,
|
||||
email: email,
|
||||
event: "Bad Login",
|
||||
message: "Invalid password",
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Successful login. Create the user session.
|
||||
req.session.username = user.username;
|
||||
req.session.userID = user.userID;
|
||||
req.session.role = user.role;
|
||||
|
||||
// Update the last login time.
|
||||
let date = new Date();
|
||||
let new_login = date.toLocaleString();
|
||||
await User.update({ lastLogin: new_login }, { where: { userID: user.userID}});
|
||||
|
||||
// Create a login entry.
|
||||
const syslog = await Syslog.create({
|
||||
user: req.session.username,
|
||||
email: email,
|
||||
event: "Successful Login",
|
||||
message: "User logged in successfully",
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
|
||||
// Redirect to the dashboard.
|
||||
res.redirect("/dashboard");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,90 +1,38 @@
|
|||
import { docker } from '../utils/docker.js';
|
||||
|
||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||
|
||||
export const Networks = async function(req,res){
|
||||
let container_networks = [];
|
||||
let network_name = '';
|
||||
|
||||
console.log(req.params.host);
|
||||
|
||||
// List all containers
|
||||
let containers = await docker.listContainers({ all: true });
|
||||
// Loop through the containers to find out which networks are being used
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
// console.log(Object.keys(containers[i].NetworkSettings.Networks)[0]);
|
||||
try { network_name += containers[i].HostConfig.NetworkMode; } catch {}
|
||||
try { container_networks.push(containers[i].NetworkSettings.Networks[network_name].NetworkID); } catch {}
|
||||
}
|
||||
// List all networks
|
||||
let networks = await docker.listNetworks({ all: true });
|
||||
// Uses template literals to build the networks table
|
||||
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 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
|
||||
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>
|
||||
<td class="sort-name">${networks[i].Name}</td>
|
||||
<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="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,
|
||||
role: req.session.role,
|
||||
avatar: req.session.username.charAt(0).toUpperCase(),
|
||||
network_list: network_list,
|
||||
network_count: networks.length,
|
||||
alert: '',
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
network_count: '',
|
||||
network_list: '',
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
}
|
||||
|
||||
export const removeNetwork = async function(req, res) {
|
||||
// Grab the list of networks
|
||||
let networks = req.body.select;
|
||||
// Make sure the value is an array
|
||||
if (typeof(networks) == 'string') { networks = [networks]; }
|
||||
// Loop through the array
|
||||
for (let i = 0; i < networks.length; i++) {
|
||||
if (networks[i] != 'on') {
|
||||
try {
|
||||
let network = docker.getNetwork(networks[i]);
|
||||
await network.remove();
|
||||
}
|
||||
catch {
|
||||
console.log(`Unable to remove network: ${networks[i]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
res.redirect("/networks");
|
||||
|
||||
|
||||
export const submitNetworks = 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("networks",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
network_count: '',
|
||||
network_list: '',
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
|
||||
}
|
71
controllers/preferences.js
Normal file
71
controllers/preferences.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { ServerSettings, User } from '../database/config.js';
|
||||
import { Alert, getLanguage, Navbar, Capitalize } from '../utils/system.js';
|
||||
|
||||
export const Preferences = async function(req,res){
|
||||
|
||||
let language = await getLanguage(req);
|
||||
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),
|
||||
selected: selected,
|
||||
hide_profile: checked,
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const submitPreferences = async function(req,res){
|
||||
|
||||
let { language_input, hidden_input } = 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}`);
|
||||
|
||||
if (hidden_input == 'on') { hidden_input = true; } else { hidden_input = false; }
|
||||
|
||||
let user_preferences = {
|
||||
language: language_input,
|
||||
hide_profile: hidden_input,
|
||||
};
|
||||
|
||||
if (language_input != undefined && hidden_input != undefined) {
|
||||
await User.update({ preferences: JSON.stringify(user_preferences) }, { where: { userID: req.session.userID }});
|
||||
}
|
||||
|
||||
// [HTMX Triggered] Changes the update button.
|
||||
if(trigger_id == 'preferences'){
|
||||
res.send(`<button class="btn btn-success" hx-post="/preferences" 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="preferences">Update</button>`);
|
||||
return;
|
||||
}
|
||||
|
||||
let language = await getLanguage(req);
|
||||
let Language = Capitalize(language);
|
||||
let selected = `<option value="${language}" selected hidden>${Language}</option>`;
|
||||
|
||||
res.render("preferences",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
selected: selected,
|
||||
});
|
||||
|
||||
}
|
|
@ -1,135 +1,83 @@
|
|||
import bcrypt from 'bcrypt';
|
||||
import { User, Syslog, Permission, ServerSettings } from '../database/models.js';
|
||||
import bcrypt from "bcrypt";
|
||||
import { Op } from "sequelize";
|
||||
import { User, ServerSettings } from "../database/config.js";
|
||||
|
||||
|
||||
export const Register = async function(req,res){
|
||||
|
||||
// Redirect to dashboard if user is already logged in.
|
||||
if(req.session.user){ res.redirect("/dashboard"); return; }
|
||||
if (req.session.username) { res.redirect("/dashboard"); }
|
||||
|
||||
// Continue to registration page if no users have been created.
|
||||
let users = await User.count();
|
||||
if (users == 0) {
|
||||
const disable_passphrase = await ServerSettings.create({ key: 'registration', value: ''});
|
||||
res.render("register",{
|
||||
"error": "Creating admin account. Leave passphrase blank.",
|
||||
});
|
||||
} else {
|
||||
// Check if registration is enabled.
|
||||
let registration = await ServerSettings.findOne({ where: {key: 'registration'}});
|
||||
if (registration.value == 'off') {
|
||||
res.render("login",{
|
||||
"error":"User registration is disabled.",
|
||||
});
|
||||
} else {
|
||||
let secret_input = '';
|
||||
let registration_secret = await ServerSettings.findOne({ where: { key: 'registration' }}).value;
|
||||
|
||||
// Input field for secret if one has been set.
|
||||
if (registration_secret) {
|
||||
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="secret">
|
||||
</div>
|
||||
</div>`}
|
||||
|
||||
// If there are no users, or a registration secret has not been set, display the registration page.
|
||||
if ((await User.count() == 0) || (registration_secret == '')) {
|
||||
res.render("register",{
|
||||
"error": "",
|
||||
"reg_secret": secret_input,
|
||||
});
|
||||
} else {
|
||||
res.render("login", {
|
||||
"error": "User registration is disabled."
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const submitRegister = async function(req,res){
|
||||
|
||||
// Grab values from the form.
|
||||
let { name, username, email, password1, password2, passphrase } = req.body;
|
||||
const { name, username, password, confirm, secret } = req.body;
|
||||
let email = req.body.email.toLowerCase();
|
||||
|
||||
// Convert the email to lowercase.
|
||||
email = email.toLowerCase();
|
||||
let registration_secret = await ServerSettings.findOne({ where: { key: 'registration' }}).value;
|
||||
|
||||
// Get the registration passphrase.
|
||||
let registration_passphrase = await ServerSettings.findOne({ where: { key: 'registration' }});
|
||||
registration_passphrase = registration_passphrase.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"; }
|
||||
else if (await User.findOne({ where: { [Op.or]: [{ username: username }, { email: email }] }})) { error = "Username or email already exists"; }
|
||||
|
||||
// Create a log entry if the form is submitted with an invalid passphrase.
|
||||
if (passphrase != registration_passphrase) {
|
||||
const syslog = await Syslog.create({
|
||||
user: username,
|
||||
email: email,
|
||||
event: "Failed Registration",
|
||||
message: "Invalid secret",
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
res.render("register",{
|
||||
"error":"Invalid passphrase",
|
||||
});
|
||||
if (error) {
|
||||
res.render("register", { "error": error });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that all fields are filled out correctly.
|
||||
if ((!name || !username || !email || !password1 || !password2) || (password1 != password2)) {
|
||||
res.render("register",{
|
||||
"error":"Missing field or password mismatch.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the username and email are unique.
|
||||
let existing_username = await User.findOne({ where: {username:username}});
|
||||
let existing_email = await User.findOne({ where: {email:email}});
|
||||
if (existing_username || existing_email) {
|
||||
res.render("register",{
|
||||
"error":"Username or email already exists.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Make the user an admin and disable registration if there are no other users.
|
||||
async function userRole () {
|
||||
let userCount = await User.count();
|
||||
if (userCount == 0) {
|
||||
await ServerSettings.update({ value: 'off' }, { where: { key: 'registration' }});
|
||||
return "admin";
|
||||
} else {
|
||||
return "user";
|
||||
}
|
||||
// Returns 'admin' if no users have been created.
|
||||
async function Role() {
|
||||
if (await User.count() == 0) { return "admin"; }
|
||||
else { return "user"; }
|
||||
}
|
||||
|
||||
// Create the user.
|
||||
const user = await User.create({
|
||||
await User.create({
|
||||
name: name,
|
||||
username: username,
|
||||
email: email,
|
||||
password: bcrypt.hashSync(password1,10),
|
||||
role: await userRole(),
|
||||
group: 'all',
|
||||
password: bcrypt.hashSync(password, 10),
|
||||
role: await Role(),
|
||||
preferences: JSON.stringify({ language: "english", 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( password1, 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) {
|
||||
// Create the user session.
|
||||
req.session.username = newUser.username;
|
||||
req.session.userID = newUser.userID;
|
||||
req.session.role = newUser.role;
|
||||
|
||||
// Create an entry in the permissions table.
|
||||
await Permission.create({ username: req.session.username, userID: req.session.userID });
|
||||
|
||||
// Create a log entry.
|
||||
const syslog = await Syslog.create({
|
||||
user: req.session.username,
|
||||
email: email,
|
||||
event: "Successful Registration",
|
||||
message: "User registered successfully",
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
console.log(`User ${username} created`);
|
||||
req.session.username = user.username;
|
||||
req.session.userID = user.userID;
|
||||
req.session.role = user.role;
|
||||
res.redirect("/dashboard");
|
||||
} else {
|
||||
// Create a log entry.
|
||||
const syslog = await Syslog.create({
|
||||
user: req.session.username,
|
||||
email: email,
|
||||
event: "Failed Registration",
|
||||
message: "User not created",
|
||||
ip: req.socket.remoteAddress
|
||||
});
|
||||
res.render("register",{
|
||||
"error":"User not created",
|
||||
});
|
||||
res.render("register", { "error": "Error. User not created" });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,192 +1,50 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { ServerSettings } from '../database/models.js';
|
||||
import { ServerSettings } from '../database/config.js';
|
||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||
|
||||
export const Settings = async function(req,res){
|
||||
|
||||
export const Settings = async (req, res) => {
|
||||
|
||||
let settings = readFileSync('views/partials/settings.html', 'utf8');
|
||||
|
||||
let links = await ServerSettings.findOne({ where: {key: 'links'}});
|
||||
try {
|
||||
if (links.value != 'localhost' && links.value != '') {
|
||||
settings = settings.replaceAll('data-LinkMode', 'checked');
|
||||
settings = settings.replaceAll('data-LinkValue', `value="${links.value}"`);
|
||||
}
|
||||
} catch {
|
||||
console.log(`Container Links: No Value Set`)
|
||||
}
|
||||
|
||||
let registration = await ServerSettings.findOne({ where: {key: 'registration'}});
|
||||
try {
|
||||
if (registration.value != 'off' && registration.value != '') {
|
||||
settings = settings.replaceAll('data-UserReg', 'checked');
|
||||
settings = settings.replaceAll('data-Passphrase', `value="${registration.value}"`);
|
||||
}
|
||||
} catch {
|
||||
console.log(`User Registration: No Value Set`);
|
||||
}
|
||||
|
||||
async function hostInfo(host) {
|
||||
let info = await ServerSettings.findOne({ where: {key: host}});
|
||||
try {
|
||||
if (info.value != 'off' && info.value != '') {
|
||||
let values = info.value.split(',');
|
||||
return { tag: values[0], ip: values[1], port: values[2] };
|
||||
}
|
||||
} catch {
|
||||
console.log(`${host}: No Value Set`);
|
||||
}
|
||||
}
|
||||
|
||||
let host2 = await hostInfo('host2');
|
||||
if (host2) {
|
||||
settings = settings.replaceAll('data-Host2', 'checked');
|
||||
settings = settings.replaceAll('data-Tag2', `value="${host2.tag}"`);
|
||||
settings = settings.replaceAll('data-Ip2', `value="${host2.ip}"`);
|
||||
settings = settings.replaceAll('data-Port2', `value="${host2.port}"`);
|
||||
}
|
||||
|
||||
let host3 = await hostInfo('host3');
|
||||
if (host3) {
|
||||
settings = settings.replaceAll('data-Host3', 'checked');
|
||||
settings = settings.replaceAll('data-Tag3', `value="${host3.tag}"`);
|
||||
settings = settings.replaceAll('data-Ip3', `value="${host3.ip}"`);
|
||||
settings = settings.replaceAll('data-Port3', `value="${host3.port}"`);
|
||||
}
|
||||
|
||||
let host4 = await hostInfo('host4');
|
||||
if (host4) {
|
||||
settings = settings.replaceAll('data-Host4', 'checked');
|
||||
settings = settings.replaceAll('data-Tag4', `value="${host4.tag}"`);
|
||||
settings = settings.replaceAll('data-Ip4', `value="${host4.ip}"`);
|
||||
settings = settings.replaceAll('data-Port4', `value="${host4.port}"`);
|
||||
}
|
||||
let container_links = await ServerSettings.findOne({ where: {key: 'container_links'}});
|
||||
let user_registration = await ServerSettings.findOne({ where: {key: 'user_registration'}});
|
||||
|
||||
|
||||
res.render("settings",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
avatar: req.session.username.charAt(0).toUpperCase(),
|
||||
alert: '',
|
||||
settings: settings,
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
user_registration: 'checked',
|
||||
registration_secret: 'some-long-secret',
|
||||
container_links: 'checked',
|
||||
link_url: 'mydomain.com',
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const updateSettings = async (req, res) => {
|
||||
|
||||
let trigger = req.header('hx-trigger');
|
||||
if (trigger == 'updated') {
|
||||
let update = `<button class="btn btn-primary" id="submit" hx-trigger="click" hx-post="/settings" hx-swap="outerHTML" hx-target="#submit">
|
||||
Update
|
||||
</button>`
|
||||
res.send(update);
|
||||
export const submitSettings = 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;
|
||||
}
|
||||
|
||||
// Container links
|
||||
let { link_mode, link } = req.body;
|
||||
if (link_mode) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'links'}});
|
||||
if (exists) {
|
||||
const setting = await ServerSettings.update({value: link}, {where: {key: 'links'}});
|
||||
} else {
|
||||
const newSetting = await ServerSettings.create({ key: 'links', value: link});
|
||||
}
|
||||
console.log('Custom links on');
|
||||
} else if (!link_mode) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'links'}});
|
||||
if (exists) {
|
||||
const setting = await ServerSettings.update({value: 'localhost'}, {where: {key: 'links'}});
|
||||
}
|
||||
console.log('Custom links off');
|
||||
}
|
||||
res.render("settings",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
|
||||
// User registration
|
||||
let { user_registration, passphrase} = req.body;
|
||||
if (user_registration) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'registration'}});
|
||||
if (exists) {
|
||||
const setting = await ServerSettings.update({value: passphrase}, {where: {key: 'registration'}});
|
||||
} else {
|
||||
const newSetting = await ServerSettings.create({ key: 'registration', value: passphrase});
|
||||
}
|
||||
console.log('registration on');
|
||||
} else if (!user_registration) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'registration'}});
|
||||
if (exists) {
|
||||
const setting = await ServerSettings.update({value: 'off'}, {where: {key: 'registration'}});
|
||||
}
|
||||
console.log('registration off');
|
||||
}
|
||||
|
||||
// Host 2
|
||||
let { host2, tag2, ip2, port2 } = req.body;
|
||||
if (host2) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host2'}});
|
||||
if (exists) {
|
||||
const setting = await ServerSettings.update({value: `${tag2},${ip2},${port2}`}, {where: {key: 'host2'}});
|
||||
} else {
|
||||
const newSetting = await ServerSettings.create({ key: 'host2', value: `${tag2},${ip2},${port2}`});
|
||||
}
|
||||
console.log('host2 on');
|
||||
} else if (!host2) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host2'}});
|
||||
if (exists) {
|
||||
const setting = await ServerSettings.update({value: 'off'}, {where: {key: 'host2'}});
|
||||
}
|
||||
console.log('host2 off');
|
||||
}
|
||||
|
||||
// Host 3
|
||||
let { host3, tag3, ip3, port3 } = req.body;
|
||||
if (host3) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host3'}});
|
||||
if (exists) {
|
||||
const setting = await ServerSettings.update({value: `${tag3},${ip3},${port3}`}, {where: {key: 'host3'}});
|
||||
} else {
|
||||
const newSetting = await ServerSettings.create({ key: 'host3', value: `${tag3},${ip3},${port3}`});
|
||||
}
|
||||
console.log('host3 on');
|
||||
} else if (!host3) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host3'}});
|
||||
if (exists) {
|
||||
const setting = await ServerSettings.update({value: 'off'}, {where: {key: 'host3'}});
|
||||
}
|
||||
console.log('host3 off');
|
||||
}
|
||||
|
||||
// Host 4
|
||||
let { host4, tag4, ip4, port4 } = req.body;
|
||||
if (host4) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host4'}});
|
||||
if (exists) {
|
||||
const setting = await ServerSettings.update({value: `${tag4},${ip4},${port4}`}, {where: {key: 'host4'}});
|
||||
} else {
|
||||
const newSetting = await ServerSettings.create({ key: 'host4', value: `${tag4},${ip4},${port4}`});
|
||||
}
|
||||
console.log('host4 on');
|
||||
} else if (!host4) {
|
||||
let exists = await ServerSettings.findOne({ where: {key: 'host4'}});
|
||||
if (exists) {
|
||||
const setting = await ServerSettings.update({value: 'off'}, {where: {key: 'host4'}});
|
||||
}
|
||||
console.log('host4 off');
|
||||
}
|
||||
|
||||
|
||||
let success = `<button class="btn btn-success" id="updated" hx-trigger="load delay:2s" hx-post="/settings" hx-swap="outerHTML" hx-target="#updated">
|
||||
Update
|
||||
</button>`
|
||||
|
||||
res.send(success);
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
export const Supporters = async (req, res) => {
|
||||
|
||||
|
||||
|
||||
res.render("supporters", {
|
||||
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
avatar: req.session.username.charAt(0).toUpperCase(),
|
||||
alert: '',
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
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,46 +1,34 @@
|
|||
import { Syslog } from '../database/models.js';
|
||||
import { ServerSettings } from '../database/config.js';
|
||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||
|
||||
export const Syslogs = async function(req,res){
|
||||
|
||||
let logs = '';
|
||||
|
||||
const syslogs = await Syslog.findAll({
|
||||
order: [
|
||||
['id', 'DESC']
|
||||
]
|
||||
});
|
||||
|
||||
for (const log of syslogs) {
|
||||
let date = (log.createdAt).toDateString();
|
||||
let time = (log.createdAt).toLocaleTimeString();
|
||||
let datetime = `${time} ${date}`;
|
||||
|
||||
logs += `<tr>
|
||||
<td class="sort-id">${log.id}</td>
|
||||
<td class="sort-user">${log.user}</td>
|
||||
<td class="sort-email">${log.email}</td>
|
||||
<td class="sort-event">${log.event}</td>
|
||||
<td class="sort-message">${log.message}</td>
|
||||
<td class="sort-ip">${log.ip}</td>
|
||||
<td class="sort-datetime">${datetime}</td>
|
||||
</tr>`
|
||||
}
|
||||
|
||||
res.render("syslogs",{
|
||||
username: req.session.username || 'Dev',
|
||||
role: req.session.role || 'Dev',
|
||||
avatar: req.session.username.charAt(0).toUpperCase(),
|
||||
logs: logs,
|
||||
alert: '',
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(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),
|
||||
});
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { User } from '../database/models.js';
|
||||
import { ServerSettings, User } from '../database/config.js';
|
||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||
|
||||
export const Users = async (req, res) => {
|
||||
export const Users = async function(req,res){
|
||||
|
||||
let user_list = `
|
||||
<tr>
|
||||
|
@ -30,8 +31,6 @@ export const Users = async (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"></td>
|
||||
|
@ -50,22 +49,41 @@ export const Users = async (req, res) => {
|
|||
user_list += info;
|
||||
});
|
||||
|
||||
|
||||
res.render("users",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
avatar: req.session.username.charAt(0).toUpperCase(),
|
||||
user_list: user_list,
|
||||
navbar: await Navbar(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: '',
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
|
||||
}
|
|
@ -1,123 +1,37 @@
|
|||
import { docker } from '../utils/docker.js';
|
||||
import { Alert, getLanguage, Navbar } from '../utils/system.js';
|
||||
|
||||
export const Volumes = async function(req,res){
|
||||
let container_volumes = [];
|
||||
let volume_list = '';
|
||||
|
||||
console.log(req.params.host);
|
||||
|
||||
// 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 docker.listContainers({ all: true });
|
||||
|
||||
// Get the first 6 volumes from each container
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
try { container_volumes.push({type: containers[i].Mounts[0].Type, source: containers[i].Mounts[0].Source}); } catch { }
|
||||
try { container_volumes.push({type: containers[i].Mounts[1].Type, source: containers[i].Mounts[1].Source}); } catch { }
|
||||
try { container_volumes.push({type: containers[i].Mounts[2].Type, source: containers[i].Mounts[2].Source}); } catch { }
|
||||
try { container_volumes.push({type: containers[i].Mounts[3].Type, source: containers[i].Mounts[3].Source}); } catch { }
|
||||
try { container_volumes.push({type: containers[i].Mounts[4].Type, source: containers[i].Mounts[4].Source}); } catch { }
|
||||
try { container_volumes.push({type: containers[i].Mounts[5].Type, source: containers[i].Mounts[5].Source}); } catch { }
|
||||
}
|
||||
|
||||
// List ALL volumes
|
||||
let list = await docker.listVolumes({ all: true });
|
||||
let volumes = list.Volumes;
|
||||
|
||||
// Create a table row for each volume
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
let volume = volumes[i];
|
||||
let name = "" + volume.Name;
|
||||
let mount = "" + volume.Mountpoint;
|
||||
let type = "Bind";
|
||||
|
||||
// Check if the volume is being used by any of the containers
|
||||
let status = '';
|
||||
if (container_volumes.some(volume => volume.source === mount)) { status = "In use"; }
|
||||
if (container_volumes.some(volume => volume.source === mount && volume.type === 'volume')) { type = "Volume"; }
|
||||
|
||||
let row = `
|
||||
<tr>
|
||||
<td><input class="form-check-input m-0 align-middle" name="select" value="${name}" type="checkbox" aria-label="Select"></td>
|
||||
<td class="sort-type">${type}</td>
|
||||
<td class="sort-name">${name}</td>
|
||||
<td class="sort-city">${mount}</td>
|
||||
<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="text-end"><a class="btn" href="#">Details</a></td>
|
||||
</tr>`
|
||||
|
||||
volume_list += row;
|
||||
}
|
||||
|
||||
volume_list += `</tbody>`
|
||||
|
||||
|
||||
res.render("volumes",{
|
||||
alert: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
avatar: req.session.username.charAt(0).toUpperCase(),
|
||||
volume_list: volume_list,
|
||||
volume_count: volumes.length,
|
||||
volume_count: '',
|
||||
volume_list: '',
|
||||
navbar: await Navbar(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",{
|
||||
alert: '',
|
||||
link1: '',
|
||||
link2: '',
|
||||
link3: '',
|
||||
link4: '',
|
||||
link5: '',
|
||||
link6: '',
|
||||
link7: '',
|
||||
link8: '',
|
||||
link9: '',
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
volume_count: '',
|
||||
volume_list: '',
|
||||
navbar: await Navbar(req),
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export const addVolume = async function(req, res) {
|
||||
|
||||
let volume = req.body.volume;
|
||||
|
||||
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");
|
||||
}
|
249
database/config.js
Normal file
249
database/config.js
Normal file
|
@ -0,0 +1,249 @@
|
|||
import session from 'express-session';
|
||||
import SessionSequelize from 'connect-session-sequelize';
|
||||
import { Sequelize, DataTypes} from 'sequelize';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
const SECURE = process.env.HTTPS || false;
|
||||
|
||||
// Session store
|
||||
const SequelizeStore = SessionSequelize(session.Store);
|
||||
const sessionData = new Sequelize('database', 'username', 'password', {
|
||||
dialect: 'sqlite',
|
||||
storage: 'database/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: 'database/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('');
|
||||
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('');
|
||||
|
||||
// Test database connection
|
||||
try {
|
||||
await sessionData.authenticate();
|
||||
await settings.authenticate();
|
||||
sessionData.sync();
|
||||
settings.sync();
|
||||
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
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
group: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
avatar: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
lastLogin: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
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
|
||||
},
|
||||
reset_view: {
|
||||
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
|
||||
},
|
||||
email: {
|
||||
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
|
||||
}
|
||||
});
|
|
@ -1,272 +0,0 @@
|
|||
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
|
||||
},
|
||||
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
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
group: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
avatar: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
lastLogin: {
|
||||
type: DataTypes.STRING
|
||||
}
|
||||
});
|
||||
|
||||
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: {
|
||||
// stores the last 15 values from dockerContainerStats
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
ram: {
|
||||
// stores 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,
|
||||
},
|
||||
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
|
||||
},
|
||||
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 ServerSettings = sequelize.define('ServerSettings', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
key: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
});
|
||||
|
||||
export const UserSettings = sequelize.define('UserSettings', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
userID: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
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,
|
||||
}
|
||||
});
|
BIN
database/sessions.sqlite
Normal file
BIN
database/sessions.sqlite
Normal file
Binary file not shown.
BIN
database/settings.sqlite
Normal file
BIN
database/settings.sqlite
Normal file
Binary file not shown.
14
languages/chinese.json
Normal file
14
languages/chinese.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"Dashboard": "仪表盘",
|
||||
"Images": "镜像",
|
||||
"Volumes": "存储卷",
|
||||
"Networks": "网络",
|
||||
"Apps": "应用商店",
|
||||
"Users": "用户",
|
||||
"Syslogs": "系统日志",
|
||||
"Account": "设置",
|
||||
"Notifications": "",
|
||||
"Preferences": "",
|
||||
"Settings": "设置",
|
||||
"Logout": ""
|
||||
}
|
380
package-lock.json
generated
380
package-lock.json
generated
|
@ -1,27 +1,23 @@
|
|||
{
|
||||
"name": "dweebui",
|
||||
"version": "0.60",
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dweebui",
|
||||
"version": "0.60",
|
||||
"license": "MIT",
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.14",
|
||||
"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.19.2",
|
||||
"express-session": "^1.18.0",
|
||||
"memorystore": "^1.6.7",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"sequelize": "^6.37.3",
|
||||
"sqlite3": "^5.1.7",
|
||||
"systeminformation": "^5.22.11",
|
||||
"yaml": "^2.4.5"
|
||||
"systeminformation": "^5.22.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@balena/dockerignore": {
|
||||
|
@ -78,18 +74,6 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/move-file/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||
|
@ -113,17 +97,17 @@
|
|||
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz",
|
||||
"integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==",
|
||||
"version": "20.14.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz",
|
||||
"integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/validator": {
|
||||
"version": "13.11.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.10.tgz",
|
||||
"integrity": "sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg=="
|
||||
"version": "13.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.0.tgz",
|
||||
"integrity": "sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag=="
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
|
@ -142,14 +126,6 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/adm-zip": {
|
||||
"version": "0.5.14",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.14.tgz",
|
||||
"integrity": "sha512-DnyqqifT4Jrcvb8USYjp6FHtBpEIz1mnXu6pTRHZ0RL69LbQYiO+0lDFg5+OKA7U29oWSs3a/i8fhn8ZcceIWg==",
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
|
@ -208,11 +184,6 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/append-field": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
|
||||
},
|
||||
"node_modules/aproba": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||
|
@ -231,11 +202,6 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
|
@ -385,11 +351,6 @@
|
|||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
|
||||
},
|
||||
"node_modules/buildcheck": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz",
|
||||
|
@ -399,17 +360,6 @@
|
|||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||
"dependencies": {
|
||||
"streamsearch": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
|
@ -447,36 +397,6 @@
|
|||
"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",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"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",
|
||||
|
@ -556,45 +476,18 @@
|
|||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
|
||||
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
|
||||
"engines": [
|
||||
"node >= 0.8"
|
||||
],
|
||||
"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": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^2.2.2",
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
"debug": "^4.1.1"
|
||||
},
|
||||
"node_modules/concat-stream/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"node_modules/concat-stream/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/concat-stream/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
"peerDependencies": {
|
||||
"sequelize": ">= 6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
|
@ -634,11 +527,6 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"node_modules/cpu-features": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
|
||||
|
@ -654,9 +542,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
||||
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
|
@ -764,32 +652,6 @@
|
|||
"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",
|
||||
|
@ -1418,11 +1280,6 @@
|
|||
"integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
|
@ -1430,9 +1287,9 @@
|
|||
"optional": true
|
||||
},
|
||||
"node_modules/jake": {
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz",
|
||||
"integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==",
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
|
||||
"integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
|
||||
"dependencies": {
|
||||
"async": "^3.2.3",
|
||||
"chalk": "^4.0.2",
|
||||
|
@ -1446,17 +1303,6 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsbn": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
|
||||
|
@ -1469,12 +1315,15 @@
|
|||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
|
||||
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
|
||||
"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": {
|
||||
"pseudomap": "^1.0.2",
|
||||
"yallist": "^2.1.2"
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
|
@ -1526,24 +1375,6 @@
|
|||
"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",
|
||||
|
@ -1552,18 +1383,6 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
@ -1713,11 +1532,6 @@
|
|||
"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",
|
||||
|
@ -1730,20 +1544,15 @@
|
|||
"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",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
|
@ -1775,23 +1584,6 @@
|
|||
"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",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
|
||||
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
"busboy": "^1.0.0",
|
||||
"concat-stream": "^1.5.2",
|
||||
"mkdirp": "^0.5.4",
|
||||
"object-assign": "^4.1.1",
|
||||
"type-is": "^1.6.4",
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
|
||||
|
@ -1955,9 +1747,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||
"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"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
|
@ -2055,11 +1850,6 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"node_modules/promise-inflight": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
|
||||
|
@ -2091,11 +1881,6 @@
|
|||
"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.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
|
@ -2230,9 +2015,9 @@
|
|||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
||||
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
@ -2528,12 +2313,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/sqlite3/node_modules/node-addon-api": {
|
||||
"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"
|
||||
}
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
|
||||
},
|
||||
"node_modules/ssh2": {
|
||||
"version": "1.15.0",
|
||||
|
@ -2572,14 +2354,6 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
|
@ -2711,22 +2485,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"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",
|
||||
|
@ -2773,11 +2531,6 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
|
@ -2907,29 +2660,10 @@
|
|||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"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.4.5",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz",
|
||||
"integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
package.json
12
package.json
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"name": "dweebui",
|
||||
"version": "0.60",
|
||||
"description": "Free and Open-Source WebUI For Managing Your Containers.",
|
||||
"version": "0.70.401",
|
||||
"main": "server.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
@ -11,19 +10,16 @@
|
|||
"keywords": [],
|
||||
"author": "lllllllillllllillll",
|
||||
"license": "MIT",
|
||||
"description": "DweebUI is a WebUI for managing your containers. Simple setup, a dynamically updating dashboard, and a multi-user permission system.",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.14",
|
||||
"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.19.2",
|
||||
"express-session": "^1.18.0",
|
||||
"memorystore": "^1.6.7",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"sequelize": "^6.37.3",
|
||||
"sqlite3": "^5.1.7",
|
||||
"systeminformation": "^5.22.11",
|
||||
"yaml": "^2.4.5"
|
||||
"systeminformation": "^5.22.11"
|
||||
}
|
||||
}
|
||||
|
|
276
public/css/demo.css
Normal file
276
public/css/demo.css
Normal file
|
@ -0,0 +1,276 @@
|
|||
/*!
|
||||
* 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-beta19 (https://tabler.io)
|
||||
* @version 1.0.0-beta19
|
||||
* 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
|
||||
|
|
276
public/css/demo.rtl.css
Normal file
276
public/css/demo.rtl.css
Normal file
|
@ -0,0 +1,276 @@
|
|||
/*!
|
||||
* 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%;
|
||||
right: 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 0 -1px -2px;
|
||||
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-left: 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;
|
||||
left: -1px;
|
||||
top: 10rem;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-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%);
|
||||
}
|
9
public/css/demo.rtl.min.css
vendored
Normal file
9
public/css/demo.rtl.min.css
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*!
|
||||
* 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)
|
||||
*/
|
||||
.highlight pre,pre.highlight{max-height:30rem;margin:1.5rem 0;overflow:auto;border-radius:var(--tblr-border-radius)}.highlight pre,pre.highlight{scrollbar-color:rgba(var(--tblr-scrollbar-color,var(--tblr-body-color-rgb)),.16) transparent}.highlight pre::-webkit-scrollbar,pre.highlight::-webkit-scrollbar{width:1rem;height:1rem;-webkit-transition:background .3s;transition:background .3s}@media (prefers-reduced-motion:reduce){.highlight pre::-webkit-scrollbar,pre.highlight::-webkit-scrollbar{-webkit-transition:none;transition:none}}.highlight pre::-webkit-scrollbar-thumb,pre.highlight::-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)),.16)}.highlight pre::-webkit-scrollbar-track,pre.highlight::-webkit-scrollbar-track{background:0 0}.highlight pre:hover::-webkit-scrollbar-thumb,pre.highlight:hover::-webkit-scrollbar-thumb{box-shadow:inset 0 0 0 1rem rgba(var(--tblr-scrollbar-color,var(--tblr-body-color-rgb)),.32)}.highlight pre::-webkit-scrollbar-corner,pre.highlight::-webkit-scrollbar-corner{background:0 0}.highlight{margin:0}.highlight code>*{margin:0!important;padding:0!important}.highlight .c,.highlight .c1{color:#a0aec0}.highlight .nc,.highlight .nt,.highlight .nx{color:#ff8383}.highlight .na,.highlight .p{color:#ffe484}.highlight .dl,.highlight .s,.highlight .s2{color:#b5f4a5}.highlight .k{color:#93ddfd}.highlight .mi,.highlight .s1{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:.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:.24;position:absolute;width:100%;right: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 i,.demo-icon-preview svg{width:15rem;height:15rem;font-size:15rem;stroke-width:1.5;margin:0 auto;display:block}@media (max-width:575.98px){.demo-icon-preview i,.demo-icon-preview svg{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:.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 0 -1px -2px;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:.5rem;border-left: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;left:-1px;top:10rem;border-top-left-radius:0;border-bottom-left-radius:0;box-shadow:rgba(var(--tblr-body-color-rgb),.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),.04) 0 2px 4px 0}.settings-scheme-light{background:linear-gradient(-135deg,#fff 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%)}
|
|
@ -93,32 +93,64 @@
|
|||
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);
|
||||
|
||||
|
||||
.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-item {
|
||||
border: 1px solid grey;
|
||||
}
|
10
public/css/tabler-social.css
Normal file
10
public/css/tabler-social.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*!
|
||||
* 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 */
|
8
public/css/tabler-social.min.css
vendored
Normal file
8
public/css/tabler-social.min.css
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*!
|
||||
* 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)
|
||||
*/
|
10
public/css/tabler-social.rtl.css
Normal file
10
public/css/tabler-social.rtl.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*!
|
||||
* 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 */
|
8
public/css/tabler-social.rtl.min.css
vendored
Normal file
8
public/css/tabler-social.rtl.min.css
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*!
|
||||
* 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)
|
||||
*/
|
25813
public/css/tabler.css
Normal file
25813
public/css/tabler.css
Normal file
File diff suppressed because it is too large
Load diff
28578
public/css/tabler.min.css
vendored
28578
public/css/tabler.min.css
vendored
File diff suppressed because one or more lines are too long
25777
public/css/tabler.rtl.css
Normal file
25777
public/css/tabler.rtl.css
Normal file
File diff suppressed because it is too large
Load diff
13
public/css/tabler.rtl.min.css
vendored
Normal file
13
public/css/tabler.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Tabler v1.0.0-beta19 (https://tabler.io)
|
||||
* @version 1.0.0-beta19
|
||||
* 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
|
||||
|
@ -12,7 +12,7 @@
|
|||
})((function () { 'use strict';
|
||||
|
||||
var themeStorageKey = "tablerTheme";
|
||||
var defaultTheme = "dark";
|
||||
var defaultTheme = "light";
|
||||
var selectedTheme;
|
||||
var params = new Proxy(new URLSearchParams(window.location.search), {
|
||||
get: function get(searchParams, prop) {
|
||||
|
|
9
public/js/demo-theme.min.js
vendored
Normal file
9
public/js/demo-theme.min.js
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*!
|
||||
* 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)
|
||||
*/
|
||||
!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";var e,t="tablerTheme",a=new Proxy(new URLSearchParams(window.location.search),{get:function(e,t){return e.get(t)}});if(a.theme)localStorage.setItem(t,a.theme),e=a.theme;else{var n=localStorage.getItem(t);e=n||"light"}"dark"===e?document.body.setAttribute("data-bs-theme",e):document.body.removeAttribute("data-bs-theme")}));
|
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Tabler v1.0.0-beta19 (https://tabler.io)
|
||||
* @version 1.0.0-beta19
|
||||
* 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
|
||||
|
|
4
public/js/demo.min.js
vendored
4
public/js/demo.min.js
vendored
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Tabler v1.0.0-beta19 (https://tabler.io)
|
||||
* @version 1.0.0-beta19
|
||||
* 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
|
||||
|
|
27
public/js/dweebui.js
Normal file
27
public/js/dweebui.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
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');
|
||||
}
|
||||
}
|
|
@ -6,11 +6,10 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
|
|||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
/** @type {import("../htmx").HtmxInternalApi} */
|
||||
var api;
|
||||
var api
|
||||
|
||||
htmx.defineExtension("sse", {
|
||||
htmx.defineExtension('sse', {
|
||||
|
||||
/**
|
||||
* Init saves the provided reference to the internal HTMX API.
|
||||
|
@ -20,14 +19,18 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
|
|||
*/
|
||||
init: function(apiRef) {
|
||||
// store a reference to the internal API.
|
||||
api = apiRef;
|
||||
api = apiRef
|
||||
|
||||
// set a function in the public API for creating new EventSource objects
|
||||
if (htmx.createEventSource == undefined) {
|
||||
htmx.createEventSource = createEventSource;
|
||||
htmx.createEventSource = createEventSource
|
||||
}
|
||||
},
|
||||
|
||||
getSelectors: function() {
|
||||
return ['[sse-connect]', '[data-sse-connect]', '[sse-swap]', '[data-sse-swap]']
|
||||
},
|
||||
|
||||
/**
|
||||
* onEvent handles all events passed to this extension.
|
||||
*
|
||||
|
@ -36,31 +39,33 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
|
|||
* @returns void
|
||||
*/
|
||||
onEvent: function(name, evt) {
|
||||
|
||||
var parent = evt.target || evt.detail.elt
|
||||
switch (name) {
|
||||
|
||||
case "htmx:beforeCleanupElement":
|
||||
var internalData = api.getInternalData(evt.target)
|
||||
case 'htmx:beforeCleanupElement':
|
||||
var internalData = api.getInternalData(parent)
|
||||
// Try to remove remove an EventSource when elements are removed
|
||||
if (internalData.sseEventSource) {
|
||||
internalData.sseEventSource.close();
|
||||
var source = internalData.sseEventSource
|
||||
if (source) {
|
||||
api.triggerEvent(parent, 'htmx:sseClose', {
|
||||
source,
|
||||
type: 'nodeReplaced',
|
||||
})
|
||||
internalData.sseEventSource.close()
|
||||
}
|
||||
|
||||
return;
|
||||
return
|
||||
|
||||
// Try to create EventSources when elements are processed
|
||||
case "htmx:afterProcessNode":
|
||||
ensureEventSourceOnElement(evt.target);
|
||||
registerSSE(evt.target);
|
||||
case 'htmx:afterProcessNode':
|
||||
ensureEventSourceOnElement(parent)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
/// ////////////////////////////////////////////
|
||||
// HELPER FUNCTIONS
|
||||
/// ////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* createEventSource is the default method for creating new EventSource objects.
|
||||
* it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
|
||||
|
@ -69,39 +74,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
|
|||
* @returns EventSource
|
||||
*/
|
||||
function createEventSource(url) {
|
||||
return new EventSource(url, { withCredentials: true });
|
||||
}
|
||||
|
||||
function splitOnWhitespace(trigger) {
|
||||
return trigger.trim().split(/\s+/);
|
||||
}
|
||||
|
||||
function getLegacySSEURL(elt) {
|
||||
var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
|
||||
if (legacySSEValue) {
|
||||
var values = splitOnWhitespace(legacySSEValue);
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var value = values[i].split(/:(.+)/);
|
||||
if (value[0] === "connect") {
|
||||
return value[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLegacySSESwaps(elt) {
|
||||
var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
|
||||
var returnArr = [];
|
||||
if (legacySSEValue != null) {
|
||||
var values = splitOnWhitespace(legacySSEValue);
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var value = values[i].split(/:(.+)/);
|
||||
if (value[0] === "swap") {
|
||||
returnArr.push(value[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return returnArr;
|
||||
return new EventSource(url, { withCredentials: true })
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,78 +85,86 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
|
|||
* @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);
|
||||
var sourceElement = api.getClosestMatch(elt, hasEventSource)
|
||||
if (sourceElement == null) {
|
||||
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
|
||||
return null; // no eventsource in parentage, orphaned element
|
||||
return null // no eventsource in parentage, orphaned element
|
||||
}
|
||||
|
||||
// Set internalData and source
|
||||
var internalData = api.getInternalData(sourceElement);
|
||||
var source = internalData.sseEventSource;
|
||||
var internalData = api.getInternalData(sourceElement)
|
||||
var source = internalData.sseEventSource
|
||||
|
||||
// Add message handlers for every `sse-swap` attribute
|
||||
queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) {
|
||||
|
||||
var sseSwapAttr = api.getAttributeValue(child, "sse-swap");
|
||||
if (sseSwapAttr) {
|
||||
var sseEventNames = sseSwapAttr.split(",");
|
||||
} else {
|
||||
var sseEventNames = getLegacySSESwaps(child);
|
||||
}
|
||||
var sseSwapAttr = api.getAttributeValue(elt, 'sse-swap')
|
||||
var sseEventNames = sseSwapAttr.split(',')
|
||||
|
||||
for (var i = 0; i < sseEventNames.length; i++) {
|
||||
var sseEventName = sseEventNames[i].trim();
|
||||
var listener = function(event) {
|
||||
|
||||
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(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);
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -195,61 +176,73 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
|
|||
* @returns {EventSource | null}
|
||||
*/
|
||||
function ensureEventSourceOnElement(elt, retryCount) {
|
||||
|
||||
if (elt == null) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
// handle extension source creation attribute
|
||||
queryAttributeOnThisOrChildren(elt, "sse-connect").forEach(function(child) {
|
||||
var sseURL = api.getAttributeValue(child, "sse-connect");
|
||||
if (api.getAttributeValue(elt, 'sse-connect')) {
|
||||
var sseURL = api.getAttributeValue(elt, 'sse-connect')
|
||||
if (sseURL == null) {
|
||||
return;
|
||||
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(elt, sseURL, retryCount)
|
||||
}
|
||||
|
||||
ensureEventSource(child, sseURL, retryCount);
|
||||
});
|
||||
|
||||
registerSSE(elt)
|
||||
}
|
||||
|
||||
function ensureEventSource(elt, url, retryCount) {
|
||||
var source = htmx.createEventSource(url);
|
||||
var source = htmx.createEventSource(url)
|
||||
|
||||
source.onerror = function(err) {
|
||||
|
||||
// Log an error event
|
||||
api.triggerErrorEvent(elt, "htmx:sseError", { error: err, source: source });
|
||||
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;
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, try to reconnect the EventSource
|
||||
if (source.readyState === EventSource.CLOSED) {
|
||||
retryCount = retryCount || 0;
|
||||
var timeout = Math.random() * (2 ^ retryCount) * 500;
|
||||
retryCount = retryCount || 0
|
||||
retryCount = Math.max(Math.min(retryCount * 2, 128), 1)
|
||||
var timeout = retryCount * 500
|
||||
window.setTimeout(function() {
|
||||
ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1));
|
||||
}, timeout);
|
||||
ensureEventSourceOnElement(elt, retryCount)
|
||||
}, timeout)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
source.onopen = function(evt) {
|
||||
api.triggerEvent(elt, "htmx:sseOpen", { source: source });
|
||||
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;
|
||||
api.getInternalData(elt).sseEventSource = source
|
||||
|
||||
|
||||
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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,95 +254,37 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
|
|||
*/
|
||||
function maybeCloseSSESource(elt) {
|
||||
if (!api.bodyContains(elt)) {
|
||||
var source = api.getInternalData(elt).sseEventSource;
|
||||
var source = api.getInternalData(elt).sseEventSource
|
||||
if (source != undefined) {
|
||||
source.close();
|
||||
api.triggerEvent(elt, 'htmx:sseClose', {
|
||||
source,
|
||||
type: 'nodeMissing',
|
||||
})
|
||||
source.close()
|
||||
// source = null
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false;
|
||||
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);
|
||||
});
|
||||
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)();
|
||||
}
|
||||
var swapSpec = api.getSwapSpecification(elt)
|
||||
var target = api.getTarget(elt)
|
||||
api.swap(target, content, swapSpec)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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
7482
public/js/tabler.esm.js
Normal file
7482
public/js/tabler.esm.js
Normal file
File diff suppressed because it is too large
Load diff
15
public/js/tabler.esm.min.js
vendored
Normal file
15
public/js/tabler.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7543
public/js/tabler.js
Normal file
7543
public/js/tabler.js
Normal file
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
Normal file
32956
public/libs/apexcharts/dist/apexcharts.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6314
public/libs/bootstrap/dist/js/bootstrap.bundle.js
vendored
Normal file
6314
public/libs/bootstrap/dist/js/bootstrap.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
public/libs/bootstrap/dist/js/bootstrap.bundle.js.map
vendored
Normal file
1
public/libs/bootstrap/dist/js/bootstrap.bundle.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
7
public/libs/bootstrap/dist/js/bootstrap.bundle.min.js
vendored
Normal file
7
public/libs/bootstrap/dist/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/libs/bootstrap/dist/js/bootstrap.bundle.min.js.map
vendored
Normal file
1
public/libs/bootstrap/dist/js/bootstrap.bundle.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4447
public/libs/bootstrap/dist/js/bootstrap.esm.js
vendored
Normal file
4447
public/libs/bootstrap/dist/js/bootstrap.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
public/libs/bootstrap/dist/js/bootstrap.esm.js.map
vendored
Normal file
1
public/libs/bootstrap/dist/js/bootstrap.esm.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
7
public/libs/bootstrap/dist/js/bootstrap.esm.min.js
vendored
Normal file
7
public/libs/bootstrap/dist/js/bootstrap.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/libs/bootstrap/dist/js/bootstrap.esm.min.js.map
vendored
Normal file
1
public/libs/bootstrap/dist/js/bootstrap.esm.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4494
public/libs/bootstrap/dist/js/bootstrap.js
vendored
Normal file
4494
public/libs/bootstrap/dist/js/bootstrap.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
public/libs/bootstrap/dist/js/bootstrap.js.map
vendored
Normal file
1
public/libs/bootstrap/dist/js/bootstrap.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
7
public/libs/bootstrap/dist/js/bootstrap.min.js
vendored
Normal file
7
public/libs/bootstrap/dist/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/libs/bootstrap/dist/js/bootstrap.min.js.map
vendored
Normal file
1
public/libs/bootstrap/dist/js/bootstrap.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
66
public/libs/countup.js/dist/countUp.d.ts
vendored
Normal file
66
public/libs/countup.js/dist/countUp.d.ts
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
export interface CountUpOptions {
|
||||
startVal?: number;
|
||||
decimalPlaces?: number;
|
||||
duration?: number;
|
||||
useGrouping?: boolean;
|
||||
useIndianSeparators?: boolean;
|
||||
useEasing?: boolean;
|
||||
smartEasingThreshold?: number;
|
||||
smartEasingAmount?: number;
|
||||
separator?: string;
|
||||
decimal?: string;
|
||||
easingFn?: (t: number, b: number, c: number, d: number) => number;
|
||||
formattingFn?: (n: number) => string;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
numerals?: string[];
|
||||
enableScrollSpy?: boolean;
|
||||
scrollSpyDelay?: number;
|
||||
scrollSpyOnce?: boolean;
|
||||
onCompleteCallback?: () => any;
|
||||
plugin?: CountUpPlugin;
|
||||
}
|
||||
export declare interface CountUpPlugin {
|
||||
render(elem: HTMLElement, formatted: string): void;
|
||||
}
|
||||
export declare class CountUp {
|
||||
private endVal;
|
||||
options?: CountUpOptions;
|
||||
version: string;
|
||||
private defaults;
|
||||
private rAF;
|
||||
private startTime;
|
||||
private remaining;
|
||||
private finalEndVal;
|
||||
private useEasing;
|
||||
private countDown;
|
||||
el: HTMLElement | HTMLInputElement;
|
||||
formattingFn: (num: number) => string;
|
||||
easingFn?: (t: number, b: number, c: number, d: number) => number;
|
||||
error: string;
|
||||
startVal: number;
|
||||
duration: number;
|
||||
paused: boolean;
|
||||
frameVal: number;
|
||||
once: boolean;
|
||||
constructor(target: string | HTMLElement | HTMLInputElement, endVal: number, options?: CountUpOptions);
|
||||
handleScroll(self: CountUp): void;
|
||||
/**
|
||||
* Smart easing works by breaking the animation into 2 parts, the second part being the
|
||||
* smartEasingAmount and first part being the total amount minus the smartEasingAmount. It works
|
||||
* by disabling easing for the first part and enabling it on the second part. It is used if
|
||||
* useEasing is true and the total animation amount exceeds the smartEasingThreshold.
|
||||
*/
|
||||
private determineDirectionAndSmartEasing;
|
||||
start(callback?: (args?: any) => any): void;
|
||||
pauseResume(): void;
|
||||
reset(): void;
|
||||
update(newEndVal: string | number): void;
|
||||
count: (timestamp: number) => void;
|
||||
printValue(val: number): void;
|
||||
ensureNumber(n: any): boolean;
|
||||
validateValue(value: string | number): number;
|
||||
private resetDuration;
|
||||
formatNumber: (num: number) => string;
|
||||
easeOutExpo: (t: number, b: number, c: number, d: number) => number;
|
||||
}
|
300
public/libs/countup.js/dist/countUp.js
vendored
Normal file
300
public/libs/countup.js/dist/countUp.js
vendored
Normal file
|
@ -0,0 +1,300 @@
|
|||
var __assign = (this && this.__assign) || function () {
|
||||
__assign = Object.assign || function(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
return __assign.apply(this, arguments);
|
||||
};
|
||||
// playground: stackblitz.com/edit/countup-typescript
|
||||
var CountUp = /** @class */ (function () {
|
||||
function CountUp(target, endVal, options) {
|
||||
var _this = this;
|
||||
this.endVal = endVal;
|
||||
this.options = options;
|
||||
this.version = '2.6.2';
|
||||
this.defaults = {
|
||||
startVal: 0,
|
||||
decimalPlaces: 0,
|
||||
duration: 2,
|
||||
useEasing: true,
|
||||
useGrouping: true,
|
||||
useIndianSeparators: false,
|
||||
smartEasingThreshold: 999,
|
||||
smartEasingAmount: 333,
|
||||
separator: ',',
|
||||
decimal: '.',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
enableScrollSpy: false,
|
||||
scrollSpyDelay: 200,
|
||||
scrollSpyOnce: false,
|
||||
};
|
||||
this.finalEndVal = null; // for smart easing
|
||||
this.useEasing = true;
|
||||
this.countDown = false;
|
||||
this.error = '';
|
||||
this.startVal = 0;
|
||||
this.paused = true;
|
||||
this.once = false;
|
||||
this.count = function (timestamp) {
|
||||
if (!_this.startTime) {
|
||||
_this.startTime = timestamp;
|
||||
}
|
||||
var progress = timestamp - _this.startTime;
|
||||
_this.remaining = _this.duration - progress;
|
||||
// to ease or not to ease
|
||||
if (_this.useEasing) {
|
||||
if (_this.countDown) {
|
||||
_this.frameVal = _this.startVal - _this.easingFn(progress, 0, _this.startVal - _this.endVal, _this.duration);
|
||||
}
|
||||
else {
|
||||
_this.frameVal = _this.easingFn(progress, _this.startVal, _this.endVal - _this.startVal, _this.duration);
|
||||
}
|
||||
}
|
||||
else {
|
||||
_this.frameVal = _this.startVal + (_this.endVal - _this.startVal) * (progress / _this.duration);
|
||||
}
|
||||
// don't go past endVal since progress can exceed duration in the last frame
|
||||
var wentPast = _this.countDown ? _this.frameVal < _this.endVal : _this.frameVal > _this.endVal;
|
||||
_this.frameVal = wentPast ? _this.endVal : _this.frameVal;
|
||||
// decimal
|
||||
_this.frameVal = Number(_this.frameVal.toFixed(_this.options.decimalPlaces));
|
||||
// format and print value
|
||||
_this.printValue(_this.frameVal);
|
||||
// whether to continue
|
||||
if (progress < _this.duration) {
|
||||
_this.rAF = requestAnimationFrame(_this.count);
|
||||
}
|
||||
else if (_this.finalEndVal !== null) {
|
||||
// smart easing
|
||||
_this.update(_this.finalEndVal);
|
||||
}
|
||||
else {
|
||||
if (_this.options.onCompleteCallback) {
|
||||
_this.options.onCompleteCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
// default format and easing functions
|
||||
this.formatNumber = function (num) {
|
||||
var neg = (num < 0) ? '-' : '';
|
||||
var result, x1, x2, x3;
|
||||
result = Math.abs(num).toFixed(_this.options.decimalPlaces);
|
||||
result += '';
|
||||
var x = result.split('.');
|
||||
x1 = x[0];
|
||||
x2 = x.length > 1 ? _this.options.decimal + x[1] : '';
|
||||
if (_this.options.useGrouping) {
|
||||
x3 = '';
|
||||
var factor = 3, j = 0;
|
||||
for (var i = 0, len = x1.length; i < len; ++i) {
|
||||
if (_this.options.useIndianSeparators && i === 4) {
|
||||
factor = 2;
|
||||
j = 1;
|
||||
}
|
||||
if (i !== 0 && (j % factor) === 0) {
|
||||
x3 = _this.options.separator + x3;
|
||||
}
|
||||
j++;
|
||||
x3 = x1[len - i - 1] + x3;
|
||||
}
|
||||
x1 = x3;
|
||||
}
|
||||
// optional numeral substitution
|
||||
if (_this.options.numerals && _this.options.numerals.length) {
|
||||
x1 = x1.replace(/[0-9]/g, function (w) { return _this.options.numerals[+w]; });
|
||||
x2 = x2.replace(/[0-9]/g, function (w) { return _this.options.numerals[+w]; });
|
||||
}
|
||||
return neg + _this.options.prefix + x1 + x2 + _this.options.suffix;
|
||||
};
|
||||
// t: current time, b: beginning value, c: change in value, d: duration
|
||||
this.easeOutExpo = function (t, b, c, d) {
|
||||
return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b;
|
||||
};
|
||||
this.options = __assign(__assign({}, this.defaults), options);
|
||||
this.formattingFn = (this.options.formattingFn) ?
|
||||
this.options.formattingFn : this.formatNumber;
|
||||
this.easingFn = (this.options.easingFn) ?
|
||||
this.options.easingFn : this.easeOutExpo;
|
||||
this.startVal = this.validateValue(this.options.startVal);
|
||||
this.frameVal = this.startVal;
|
||||
this.endVal = this.validateValue(endVal);
|
||||
this.options.decimalPlaces = Math.max(0 || this.options.decimalPlaces);
|
||||
this.resetDuration();
|
||||
this.options.separator = String(this.options.separator);
|
||||
this.useEasing = this.options.useEasing;
|
||||
if (this.options.separator === '') {
|
||||
this.options.useGrouping = false;
|
||||
}
|
||||
this.el = (typeof target === 'string') ? document.getElementById(target) : target;
|
||||
if (this.el) {
|
||||
this.printValue(this.startVal);
|
||||
}
|
||||
else {
|
||||
this.error = '[CountUp] target is null or undefined';
|
||||
}
|
||||
// scroll spy
|
||||
if (typeof window !== 'undefined' && this.options.enableScrollSpy) {
|
||||
if (!this.error) {
|
||||
// set up global array of onscroll functions to handle multiple instances
|
||||
window['onScrollFns'] = window['onScrollFns'] || [];
|
||||
window['onScrollFns'].push(function () { return _this.handleScroll(_this); });
|
||||
window.onscroll = function () {
|
||||
window['onScrollFns'].forEach(function (fn) { return fn(); });
|
||||
};
|
||||
this.handleScroll(this);
|
||||
}
|
||||
else {
|
||||
console.error(this.error, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
CountUp.prototype.handleScroll = function (self) {
|
||||
if (!self || !window || self.once)
|
||||
return;
|
||||
var bottomOfScroll = window.innerHeight + window.scrollY;
|
||||
var rect = self.el.getBoundingClientRect();
|
||||
var topOfEl = rect.top + window.pageYOffset;
|
||||
var bottomOfEl = rect.top + rect.height + window.pageYOffset;
|
||||
if (bottomOfEl < bottomOfScroll && bottomOfEl > window.scrollY && self.paused) {
|
||||
// in view
|
||||
self.paused = false;
|
||||
setTimeout(function () { return self.start(); }, self.options.scrollSpyDelay);
|
||||
if (self.options.scrollSpyOnce)
|
||||
self.once = true;
|
||||
}
|
||||
else if ((window.scrollY > bottomOfEl || topOfEl > bottomOfScroll) &&
|
||||
!self.paused) {
|
||||
// out of view
|
||||
self.reset();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Smart easing works by breaking the animation into 2 parts, the second part being the
|
||||
* smartEasingAmount and first part being the total amount minus the smartEasingAmount. It works
|
||||
* by disabling easing for the first part and enabling it on the second part. It is used if
|
||||
* useEasing is true and the total animation amount exceeds the smartEasingThreshold.
|
||||
*/
|
||||
CountUp.prototype.determineDirectionAndSmartEasing = function () {
|
||||
var end = (this.finalEndVal) ? this.finalEndVal : this.endVal;
|
||||
this.countDown = (this.startVal > end);
|
||||
var animateAmount = end - this.startVal;
|
||||
if (Math.abs(animateAmount) > this.options.smartEasingThreshold && this.options.useEasing) {
|
||||
this.finalEndVal = end;
|
||||
var up = (this.countDown) ? 1 : -1;
|
||||
this.endVal = end + (up * this.options.smartEasingAmount);
|
||||
this.duration = this.duration / 2;
|
||||
}
|
||||
else {
|
||||
this.endVal = end;
|
||||
this.finalEndVal = null;
|
||||
}
|
||||
if (this.finalEndVal !== null) {
|
||||
// setting finalEndVal indicates smart easing
|
||||
this.useEasing = false;
|
||||
}
|
||||
else {
|
||||
this.useEasing = this.options.useEasing;
|
||||
}
|
||||
};
|
||||
// start animation
|
||||
CountUp.prototype.start = function (callback) {
|
||||
if (this.error) {
|
||||
return;
|
||||
}
|
||||
if (callback) {
|
||||
this.options.onCompleteCallback = callback;
|
||||
}
|
||||
if (this.duration > 0) {
|
||||
this.determineDirectionAndSmartEasing();
|
||||
this.paused = false;
|
||||
this.rAF = requestAnimationFrame(this.count);
|
||||
}
|
||||
else {
|
||||
this.printValue(this.endVal);
|
||||
}
|
||||
};
|
||||
// pause/resume animation
|
||||
CountUp.prototype.pauseResume = function () {
|
||||
if (!this.paused) {
|
||||
cancelAnimationFrame(this.rAF);
|
||||
}
|
||||
else {
|
||||
this.startTime = null;
|
||||
this.duration = this.remaining;
|
||||
this.startVal = this.frameVal;
|
||||
this.determineDirectionAndSmartEasing();
|
||||
this.rAF = requestAnimationFrame(this.count);
|
||||
}
|
||||
this.paused = !this.paused;
|
||||
};
|
||||
// reset to startVal so animation can be run again
|
||||
CountUp.prototype.reset = function () {
|
||||
cancelAnimationFrame(this.rAF);
|
||||
this.paused = true;
|
||||
this.resetDuration();
|
||||
this.startVal = this.validateValue(this.options.startVal);
|
||||
this.frameVal = this.startVal;
|
||||
this.printValue(this.startVal);
|
||||
};
|
||||
// pass a new endVal and start animation
|
||||
CountUp.prototype.update = function (newEndVal) {
|
||||
cancelAnimationFrame(this.rAF);
|
||||
this.startTime = null;
|
||||
this.endVal = this.validateValue(newEndVal);
|
||||
if (this.endVal === this.frameVal) {
|
||||
return;
|
||||
}
|
||||
this.startVal = this.frameVal;
|
||||
if (this.finalEndVal == null) {
|
||||
this.resetDuration();
|
||||
}
|
||||
this.finalEndVal = null;
|
||||
this.determineDirectionAndSmartEasing();
|
||||
this.rAF = requestAnimationFrame(this.count);
|
||||
};
|
||||
CountUp.prototype.printValue = function (val) {
|
||||
var _a;
|
||||
if (!this.el)
|
||||
return;
|
||||
var result = this.formattingFn(val);
|
||||
if ((_a = this.options.plugin) === null || _a === void 0 ? void 0 : _a.render) {
|
||||
this.options.plugin.render(this.el, result);
|
||||
return;
|
||||
}
|
||||
if (this.el.tagName === 'INPUT') {
|
||||
var input = this.el;
|
||||
input.value = result;
|
||||
}
|
||||
else if (this.el.tagName === 'text' || this.el.tagName === 'tspan') {
|
||||
this.el.textContent = result;
|
||||
}
|
||||
else {
|
||||
this.el.innerHTML = result;
|
||||
}
|
||||
};
|
||||
CountUp.prototype.ensureNumber = function (n) {
|
||||
return (typeof n === 'number' && !isNaN(n));
|
||||
};
|
||||
CountUp.prototype.validateValue = function (value) {
|
||||
var newValue = Number(value);
|
||||
if (!this.ensureNumber(newValue)) {
|
||||
this.error = "[CountUp] invalid start or end value: ".concat(value);
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return newValue;
|
||||
}
|
||||
};
|
||||
CountUp.prototype.resetDuration = function () {
|
||||
this.startTime = null;
|
||||
this.duration = Number(this.options.duration) * 1000;
|
||||
this.remaining = this.duration;
|
||||
};
|
||||
return CountUp;
|
||||
}());
|
||||
export { CountUp };
|
1
public/libs/countup.js/dist/countUp.min.js
vendored
Normal file
1
public/libs/countup.js/dist/countUp.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/libs/countup.js/dist/countUp.umd.js
vendored
Normal file
1
public/libs/countup.js/dist/countUp.umd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
26
public/libs/countup.js/dist/requestAnimationFrame.polyfill.js
vendored
Normal file
26
public/libs/countup.js/dist/requestAnimationFrame.polyfill.js
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
// make sure requestAnimationFrame and cancelAnimationFrame are defined
|
||||
// polyfill for browsers without native support
|
||||
// by Opera engineer Erik Möller
|
||||
(function () {
|
||||
var lastTime = 0;
|
||||
var vendors = ['webkit', 'moz', 'ms', 'o'];
|
||||
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
||||
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
|
||||
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
|
||||
window[vendors[x] + 'CancelRequestAnimationFrame'];
|
||||
}
|
||||
if (!window.requestAnimationFrame) {
|
||||
window.requestAnimationFrame = function (callback) {
|
||||
var currTime = new Date().getTime();
|
||||
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
||||
var id = window.setTimeout(function () { return callback(currTime + timeToCall); }, timeToCall);
|
||||
lastTime = currTime + timeToCall;
|
||||
return id;
|
||||
};
|
||||
}
|
||||
if (!window.cancelAnimationFrame) {
|
||||
window.cancelAnimationFrame = function (id) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
}
|
||||
})();
|
1
public/libs/dropzone/dist/basic.css
vendored
Normal file
1
public/libs/dropzone/dist/basic.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.dropzone,.dropzone *{box-sizing:border-box}.dropzone{position:relative}.dropzone .dz-preview{position:relative;display:inline-block;width:120px;margin:.5em}.dropzone .dz-preview .dz-progress{display:block;height:15px;border:1px solid #aaa}.dropzone .dz-preview .dz-progress .dz-upload{display:block;height:100%;width:0;background:green}.dropzone .dz-preview .dz-error-message{color:red;display:none}.dropzone .dz-preview.dz-error .dz-error-message,.dropzone .dz-preview.dz-error .dz-error-mark{display:block}.dropzone .dz-preview.dz-success .dz-success-mark{display:block}.dropzone .dz-preview .dz-error-mark,.dropzone .dz-preview .dz-success-mark{position:absolute;display:none;left:30px;top:30px;width:54px;height:58px;left:50%;margin-left:-27px}/*# sourceMappingURL=basic.css.map */
|
1
public/libs/dropzone/dist/basic.css.map
vendored
Normal file
1
public/libs/dropzone/dist/basic.css.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["../src/basic.scss"],"names":[],"mappings":"AAEA,sBACE,sBAEF,UAEE,kBAEA,sBACE,kBACA,qBACA,YACA,YAEA,mCACE,cACA,YACA,sBACA,8CACE,cACA,YACA,QACA,iBAIJ,wCACE,UACA,aAGA,+FACE,cAIF,kDACE,cAIJ,4EACE,kBACA,aACA,UACA,SACA,WACA,YACA,SACA","file":"basic.css"}
|
2
public/libs/dropzone/dist/dropzone-min.js
vendored
Normal file
2
public/libs/dropzone/dist/dropzone-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/libs/dropzone/dist/dropzone-min.js.map
vendored
Normal file
1
public/libs/dropzone/dist/dropzone-min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/libs/dropzone/dist/dropzone.css
vendored
Normal file
1
public/libs/dropzone/dist/dropzone.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
@keyframes passing-through{0%{opacity:0;transform:translateY(40px)}30%,70%{opacity:1;transform:translateY(0px)}100%{opacity:0;transform:translateY(-40px)}}@keyframes slide-in{0%{opacity:0;transform:translateY(40px)}30%{opacity:1;transform:translateY(0px)}}@keyframes pulse{0%{transform:scale(1)}10%{transform:scale(1.1)}20%{transform:scale(1)}}.dropzone,.dropzone *{box-sizing:border-box}.dropzone{min-height:150px;border:1px solid rgba(0,0,0,.8);border-radius:5px;padding:20px 20px}.dropzone.dz-clickable{cursor:pointer}.dropzone.dz-clickable *{cursor:default}.dropzone.dz-clickable .dz-message,.dropzone.dz-clickable .dz-message *{cursor:pointer}.dropzone.dz-started .dz-message{display:none}.dropzone.dz-drag-hover{border-style:solid}.dropzone.dz-drag-hover .dz-message{opacity:.5}.dropzone .dz-message{text-align:center;margin:3em 0}.dropzone .dz-message .dz-button{background:none;color:inherit;border:none;padding:0;font:inherit;cursor:pointer;outline:inherit}.dropzone .dz-preview{position:relative;display:inline-block;vertical-align:top;margin:16px;min-height:100px}.dropzone .dz-preview:hover{z-index:1000}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview.dz-file-preview .dz-image{border-radius:20px;background:#999;background:linear-gradient(to bottom, #eee, #ddd)}.dropzone .dz-preview.dz-file-preview .dz-details{opacity:1}.dropzone .dz-preview.dz-image-preview{background:#fff}.dropzone .dz-preview.dz-image-preview .dz-details{transition:opacity .2s linear}.dropzone .dz-preview .dz-remove{font-size:14px;text-align:center;display:block;cursor:pointer;border:none}.dropzone .dz-preview .dz-remove:hover{text-decoration:underline}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview .dz-details{z-index:20;position:absolute;top:0;left:0;opacity:0;font-size:13px;min-width:100%;max-width:100%;padding:2em 1em;text-align:center;color:rgba(0,0,0,.9);line-height:150%}.dropzone .dz-preview .dz-details .dz-size{margin-bottom:1em;font-size:16px}.dropzone .dz-preview .dz-details .dz-filename{white-space:nowrap}.dropzone .dz-preview .dz-details .dz-filename:hover span{border:1px solid rgba(200,200,200,.8);background-color:rgba(255,255,255,.8)}.dropzone .dz-preview .dz-details .dz-filename:not(:hover){overflow:hidden;text-overflow:ellipsis}.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span{border:1px solid transparent}.dropzone .dz-preview .dz-details .dz-filename span,.dropzone .dz-preview .dz-details .dz-size span{background-color:rgba(255,255,255,.4);padding:0 .4em;border-radius:3px}.dropzone .dz-preview:hover .dz-image img{transform:scale(1.05, 1.05);filter:blur(8px)}.dropzone .dz-preview .dz-image{border-radius:20px;overflow:hidden;width:120px;height:120px;position:relative;display:block;z-index:10}.dropzone .dz-preview .dz-image img{display:block}.dropzone .dz-preview.dz-success .dz-success-mark{animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1)}.dropzone .dz-preview.dz-error .dz-error-mark{opacity:1;animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1)}.dropzone .dz-preview .dz-success-mark,.dropzone .dz-preview .dz-error-mark{pointer-events:none;opacity:0;z-index:500;position:absolute;display:block;top:50%;left:50%;margin-left:-27px;margin-top:-27px;background:rgba(0,0,0,.8);border-radius:50%}.dropzone .dz-preview .dz-success-mark svg,.dropzone .dz-preview .dz-error-mark svg{display:block;width:54px;height:54px;fill:#fff}.dropzone .dz-preview.dz-processing .dz-progress{opacity:1;transition:all .2s linear}.dropzone .dz-preview.dz-complete .dz-progress{opacity:0;transition:opacity .4s ease-in}.dropzone .dz-preview:not(.dz-processing) .dz-progress{animation:pulse 6s ease infinite}.dropzone .dz-preview .dz-progress{opacity:1;z-index:1000;pointer-events:none;position:absolute;height:20px;top:50%;margin-top:-10px;left:15%;right:15%;border:3px solid rgba(0,0,0,.8);background:rgba(0,0,0,.8);border-radius:10px;overflow:hidden}.dropzone .dz-preview .dz-progress .dz-upload{background:#fff;display:block;position:relative;height:100%;width:0;transition:width 300ms ease-in-out;border-radius:17px}.dropzone .dz-preview.dz-error .dz-error-message{display:block}.dropzone .dz-preview.dz-error:hover .dz-error-message{opacity:1;pointer-events:auto}.dropzone .dz-preview .dz-error-message{pointer-events:none;z-index:1000;position:absolute;display:block;display:none;opacity:0;transition:opacity .3s ease;border-radius:8px;font-size:13px;top:130px;left:-10px;width:140px;background:#b10606;padding:.5em 1em;color:#fff}.dropzone .dz-preview .dz-error-message:after{content:"";position:absolute;top:-6px;left:64px;width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #b10606}/*# sourceMappingURL=dropzone.css.map */
|
1
public/libs/dropzone/dist/dropzone.css.map
vendored
Normal file
1
public/libs/dropzone/dist/dropzone.css.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["../src/dropzone.scss"],"names":[],"mappings":"AAGA,2BACE,GACE,UACA,2BAGF,QACE,UACA,0BAGF,KACE,UACA,6BAKJ,oBACE,GACE,UACA,2BAEF,IACE,UACA,2BAMJ,iBACE,sBACA,yBACA,wBAKF,sBACE,sBAEF,UAmBE,iBACA,gCACA,kBACA,kBAhBA,uBACE,eAEA,yBACE,eAGA,wEACE,eAWJ,iCACE,aAIJ,wBACE,mBACA,oCACE,WAGJ,sBACE,kBACA,aAEA,iCACE,gBACA,cACA,YACA,UACA,aACA,eACA,gBAMJ,sBACE,kBACA,qBAEA,mBAEA,YACA,iBAEA,4BAEE,aACA,wCACE,UAMF,gDACE,cArEgB,KAsEhB,gBACA,kDAGF,kDACE,UAIJ,uCACE,gBACA,mDACE,8BAIJ,iCACE,eACA,kBACA,cACA,eACA,YACA,uCACE,0BAIJ,wCACE,UAEF,kCAGE,WAEA,kBACA,MACA,OAEA,UAEA,eACA,eACA,eACA,gBACA,kBACA,qBAIA,iBAEA,2CACE,kBACA,eAGF,+CAEE,mBAGE,0DACE,sCACA,sCAGJ,2DAIE,gBACA,uBAJA,gEACE,6BASJ,oGACE,sCACA,eACA,kBASF,0CACE,4BACA,iBAIN,gCACE,cAvKkB,KAwKlB,gBACA,MA3KS,MA4KT,OA5KS,MA6KT,kBACA,cACA,WAEA,oCACE,cAMF,kDACE,6DAIF,8CACE,UACA,sDASJ,4EAKE,oBAEA,UACA,YAEA,kBACA,cACA,QACA,SACA,kBACA,iBAEA,WApBiB,eAqBjB,kBAEA,oFACE,cACA,MAnBY,KAoBZ,OArBa,KAsBb,KA5BY,KAiChB,iDACE,UACA,0BAEF,+CACE,UACA,+BAIA,uDACE,iCAGJ,mCAIE,UACA,aAEA,oBACA,kBACA,YACA,QACA,iBACA,SACA,UAEA,gCACA,WA9DiB,eAgEjB,mBAEA,gBAEA,8CACE,WAtEY,KAwEZ,cACA,kBACA,YACA,QACA,mCAEA,mBAMF,iDACE,cAEF,uDACE,UACA,oBAIJ,wCAIE,oBACA,aACA,kBACA,cACA,aACA,UACA,4BACA,kBACA,eACA,UACA,WACA,MAdQ,MAeR,WAdQ,QAeR,iBACA,WAGA,8CACE,WACA,kBACA,SACA,UACA,QACA,SACA,kCACA,mCACA","file":"dropzone.css"}
|
3068
public/libs/dropzone/dist/dropzone.js
vendored
Normal file
3068
public/libs/dropzone/dist/dropzone.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
public/libs/dropzone/dist/dropzone.js.map
vendored
Normal file
1
public/libs/dropzone/dist/dropzone.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2111
public/libs/dropzone/dist/dropzone.mjs
vendored
Normal file
2111
public/libs/dropzone/dist/dropzone.mjs
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
public/libs/dropzone/dist/dropzone.mjs.map
vendored
Normal file
1
public/libs/dropzone/dist/dropzone.mjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
21
public/libs/fslightbox/LICENSE
Normal file
21
public/libs/fslightbox/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
## The MIT License (MIT) ##
|
||||
|
||||
Copyright (c) Piotr Zdziarski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
68
public/libs/fslightbox/README.md
Normal file
68
public/libs/fslightbox/README.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
# Vanilla JavaScript Fullscreen Lightbox Basic
|
||||
|
||||
## Description
|
||||
A vanilla JavaScript plug-in without production dependencies for displaying images, videos, or, through custom sources, anything you want in a clean overlying box.
|
||||
The project's website: https://fslightbox.com.
|
||||
|
||||
## Installation
|
||||
### Through an archive downloaded from the website.
|
||||
Just before the end of the <body> tag:
|
||||
```html
|
||||
<script src="fslightbox.js"></script>
|
||||
```
|
||||
### Or, through a package manager.
|
||||
```
|
||||
npm install fslightbox
|
||||
```
|
||||
And import it in your project's JavaScript file, for example through the Node.js "require" function:
|
||||
```
|
||||
require("fslightbox")
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
```html
|
||||
<a data-fslightbox="gallery" href="https://i.imgur.com/fsyrScY.jpg">
|
||||
Open the first slide (an image)
|
||||
</a>
|
||||
<a
|
||||
data-fslightbox="gallery"
|
||||
href="https://www.youtube.com/watch?v=xshEZzpS4CQ"
|
||||
>
|
||||
Open the second slide (a YouTube video)
|
||||
</a>
|
||||
<a
|
||||
data-fslightbox="gallery"
|
||||
href="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
|
||||
>
|
||||
Open the third slide (an HTML video)
|
||||
</a>
|
||||
<a data-fslightbox="gallery" href="#vimeo">
|
||||
Open the fourth slide (a Vimeo video—a custom source)
|
||||
</a>
|
||||
<iframe
|
||||
id="vimeo"
|
||||
src="https://player.vimeo.com/video/22439234"
|
||||
width="1920px"
|
||||
height="1080px"
|
||||
frameBorder="0"
|
||||
allow="autoplay; fullscreen"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
```
|
||||
|
||||
## Documentation
|
||||
Available at: https://fslightbox.com/javascript/documentation.
|
||||
|
||||
## Demo
|
||||
Available at: https://fslightbox.com/javascript.
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
| Browser | Works? |
|
||||
| --- | --- |
|
||||
| Chrome | Yes |
|
||||
| Firefox | Yes |
|
||||
| Opera | Yes |
|
||||
| Safari | Yes |
|
||||
| Edge | Yes |
|
||||
| IE 11 | Yes |
|
1
public/libs/fslightbox/index.js
Normal file
1
public/libs/fslightbox/index.js
Normal file
File diff suppressed because one or more lines are too long
38
public/libs/fslightbox/package.json
Normal file
38
public/libs/fslightbox/package.json
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "fslightbox",
|
||||
"version": "3.4.1",
|
||||
"description": "An easy to use vanilla JavaScript plug-in without production dependencies for displaying images, videos, or, through custom sources, anything you want in a clean overlying box.",
|
||||
"author": "Bantha Apps Piotr Zdziarski",
|
||||
"license": "MIT",
|
||||
"homepage": "https://fslightbox.com",
|
||||
"bugs": {
|
||||
"url": "https://github.com/banthagroup/fslightbox/issues"
|
||||
},
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"fslightbox",
|
||||
"vanilla javascript fslightbox",
|
||||
"vanilla js fslightbox",
|
||||
"vanilla javascript lightbox",
|
||||
"vanilla js lightbox",
|
||||
"lightbox"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/banthagroup/fslightbox"
|
||||
},
|
||||
"scripts": {
|
||||
"w": "webpack-dev-server --host 0.0.0.0",
|
||||
"p": "webpack --config webpack.prod.config.js && cp index.js fslightbox.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.4",
|
||||
"@babel/preset-env": "^7.4.4",
|
||||
"@babel/register": "^7.4.4",
|
||||
"babel-loader": "^8.0.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"webpack": "^4.30.0",
|
||||
"webpack-cli": "^3.3.1",
|
||||
"webpack-dev-server": "^3.3.1"
|
||||
}
|
||||
}
|
2318
public/libs/jsvectormap/dist/js/jsvectormap.js
vendored
Normal file
2318
public/libs/jsvectormap/dist/js/jsvectormap.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
public/libs/jsvectormap/dist/js/jsvectormap.min.js
vendored
Normal file
1
public/libs/jsvectormap/dist/js/jsvectormap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/libs/jsvectormap/dist/maps/world-merc.js
vendored
Normal file
1
public/libs/jsvectormap/dist/maps/world-merc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/libs/jsvectormap/dist/maps/world.js
vendored
Normal file
1
public/libs/jsvectormap/dist/maps/world.js
vendored
Normal file
File diff suppressed because one or more lines are too long
52
public/libs/litepicker/dist/bundle.js
vendored
Normal file
52
public/libs/litepicker/dist/bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
public/libs/litepicker/dist/css/litepicker.css
vendored
Normal file
13
public/libs/litepicker/dist/css/litepicker.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/libs/litepicker/dist/css/plugins/keyboardnav.js.css
vendored
Normal file
12
public/libs/litepicker/dist/css/plugins/keyboardnav.js.css
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*!
|
||||
*
|
||||
* ../css/plugins/keyboardnav.js.css
|
||||
* Litepicker v2.0.12 (https://github.com/wakirin/Litepicker)
|
||||
* Package: litepicker (https://www.npmjs.com/package/litepicker)
|
||||
* License: MIT (https://github.com/wakirin/Litepicker/blob/master/LICENCE.md)
|
||||
* Copyright 2019-2021 Rinat G.
|
||||
*
|
||||
* Hash: fc3887e0bb19d54c36db
|
||||
*
|
||||
*/
|
||||
|
133
public/libs/litepicker/dist/css/plugins/mobilefriendly.js.css
vendored
Normal file
133
public/libs/litepicker/dist/css/plugins/mobilefriendly.js.css
vendored
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*!
|
||||
*
|
||||
* ../css/plugins/mobilefriendly.js.css
|
||||
* Litepicker v2.0.12 (https://github.com/wakirin/Litepicker)
|
||||
* Package: litepicker (https://www.npmjs.com/package/litepicker)
|
||||
* License: MIT (https://github.com/wakirin/Litepicker/blob/master/LICENCE.md)
|
||||
* Copyright 2019-2021 Rinat G.
|
||||
*
|
||||
* Hash: fc3887e0bb19d54c36db
|
||||
*
|
||||
*/
|
||||
:root {
|
||||
--litepicker-mobilefriendly-backdrop-color-bg: #000;
|
||||
}
|
||||
|
||||
.litepicker-backdrop {
|
||||
display: none;
|
||||
background-color: var(--litepicker-mobilefriendly-backdrop-color-bg);
|
||||
opacity: 0.3;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.litepicker-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.litepicker.mobilefriendly[data-plugins*="mobilefriendly"] {
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 1.1rem;
|
||||
--litepicker-container-months-box-shadow-color: #616161;
|
||||
}
|
||||
.litepicker.mobilefriendly-portrait {
|
||||
--litepicker-day-width: 13.5vw;
|
||||
--litepicker-month-width: calc(var(--litepicker-day-width) * 7);
|
||||
}
|
||||
.litepicker.mobilefriendly-landscape {
|
||||
--litepicker-day-width: 5.5vw;
|
||||
--litepicker-month-width: calc(var(--litepicker-day-width) * 7);
|
||||
}
|
||||
|
||||
.litepicker[data-plugins*="mobilefriendly"] .container__months {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.litepicker.mobilefriendly[data-plugins*="mobilefriendly"] .container__months .month-item-header {
|
||||
height: var(--litepicker-day-width);
|
||||
}
|
||||
|
||||
.litepicker.mobilefriendly[data-plugins*="mobilefriendly"] .container__days > div {
|
||||
height: var(--litepicker-day-width);
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.litepicker[data-plugins*="mobilefriendly"] .container__months .month-item {
|
||||
-webkit-transform-origin: center;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.litepicker[data-plugins*="mobilefriendly"] .container__months .month-item.touch-target-next {
|
||||
-webkit-animation-name: lp-bounce-target-next;
|
||||
animation-name: lp-bounce-target-next;
|
||||
-webkit-animation-duration: .5s;
|
||||
animation-duration: .5s;
|
||||
-webkit-animation-timing-function: ease;
|
||||
animation-timing-function: ease;
|
||||
}
|
||||
|
||||
.litepicker[data-plugins*="mobilefriendly"] .container__months .month-item.touch-target-prev {
|
||||
-webkit-animation-name: lp-bounce-target-prev;
|
||||
animation-name: lp-bounce-target-prev;
|
||||
-webkit-animation-duration: .5s;
|
||||
animation-duration: .5s;
|
||||
-webkit-animation-timing-function: ease;
|
||||
animation-timing-function: ease;
|
||||
}
|
||||
|
||||
@-webkit-keyframes lp-bounce-target-next {
|
||||
from {
|
||||
-webkit-transform: translateX(100px) scale(0.5);
|
||||
transform: translateX(100px) scale(0.5);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: translateX(0px) scale(1);
|
||||
transform: translateX(0px) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes lp-bounce-target-next {
|
||||
from {
|
||||
-webkit-transform: translateX(100px) scale(0.5);
|
||||
transform: translateX(100px) scale(0.5);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: translateX(0px) scale(1);
|
||||
transform: translateX(0px) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes lp-bounce-target-prev {
|
||||
from {
|
||||
-webkit-transform: translateX(-100px) scale(0.5);
|
||||
transform: translateX(-100px) scale(0.5);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: translateX(0px) scale(1);
|
||||
transform: translateX(0px) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes lp-bounce-target-prev {
|
||||
from {
|
||||
-webkit-transform: translateX(-100px) scale(0.5);
|
||||
transform: translateX(-100px) scale(0.5);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: translateX(0px) scale(1);
|
||||
transform: translateX(0px) scale(1);
|
||||
}
|
||||
}
|
54
public/libs/litepicker/dist/css/plugins/multiselect.js.css
vendored
Normal file
54
public/libs/litepicker/dist/css/plugins/multiselect.js.css
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*!
|
||||
*
|
||||
* ../css/plugins/multiselect.js.css
|
||||
* Litepicker v2.0.12 (https://github.com/wakirin/Litepicker)
|
||||
* Package: litepicker (https://www.npmjs.com/package/litepicker)
|
||||
* License: MIT (https://github.com/wakirin/Litepicker/blob/master/LICENCE.md)
|
||||
* Copyright 2019-2021 Rinat G.
|
||||
*
|
||||
* Hash: fc3887e0bb19d54c36db
|
||||
*
|
||||
*/
|
||||
:root {
|
||||
--litepicker-multiselect-is-selected-color-bg: #2196f3;
|
||||
--litepicker-multiselect-is-selected-color: #fff;
|
||||
--litepicker-multiselect-hover-color-bg: #2196f3;
|
||||
--litepicker-multiselect-hover-color: #fff;
|
||||
}
|
||||
|
||||
.litepicker[data-plugins*="multiselect"] .container__days .day-item {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.litepicker[data-plugins*="multiselect"] .container__days .day-item:not(.is-locked):after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: -1;
|
||||
border-radius: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.litepicker[data-plugins*="multiselect"] .container__days .day-item:not(.is-locked):hover {
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
color: var(--litepicker-day-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.litepicker[data-plugins*="multiselect"] .container__days .day-item.is-selected,
|
||||
.litepicker[data-plugins*="multiselect"] .container__days .day-item.is-selected:hover {
|
||||
color: var(--litepicker-multiselect-is-selected-color);
|
||||
}
|
||||
|
||||
.litepicker[data-plugins*="multiselect"] .container__days .day-item.is-selected:after {
|
||||
color: var(--litepicker-multiselect-is-selected-color);
|
||||
background-color: var(--litepicker-multiselect-is-selected-color-bg);
|
||||
}
|
||||
|
81
public/libs/litepicker/dist/css/plugins/ranges.js.css
vendored
Normal file
81
public/libs/litepicker/dist/css/plugins/ranges.js.css
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*!
|
||||
*
|
||||
* ../css/plugins/ranges.js.css
|
||||
* Litepicker v2.0.12 (https://github.com/wakirin/Litepicker)
|
||||
* Package: litepicker (https://www.npmjs.com/package/litepicker)
|
||||
* License: MIT (https://github.com/wakirin/Litepicker/blob/master/LICENCE.md)
|
||||
* Copyright 2019-2021 Rinat G.
|
||||
*
|
||||
* Hash: fc3887e0bb19d54c36db
|
||||
*
|
||||
*/
|
||||
.litepicker[data-plugins*="ranges"] > .container__main > .container__predefined-ranges {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-align: start;
|
||||
-ms-flex-align: start;
|
||||
align-items: flex-start;
|
||||
background: var(--litepicker-container-months-color-bg);
|
||||
-webkit-box-shadow: -2px 0px 5px var(--litepicker-footer-box-shadow-color);
|
||||
box-shadow: -2px 0px 5px var(--litepicker-footer-box-shadow-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.litepicker[data-plugins*="ranges"][data-ranges-position="left"] > .container__main {
|
||||
/* */
|
||||
}
|
||||
.litepicker[data-plugins*="ranges"][data-ranges-position="right"] > .container__main{
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: reverse;
|
||||
-ms-flex-direction: row-reverse;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.litepicker[data-plugins*="ranges"][data-ranges-position="right"] > .container__main > .container__predefined-ranges {
|
||||
-webkit-box-shadow: 2px 0px 2px var(--litepicker-footer-box-shadow-color);
|
||||
box-shadow: 2px 0px 2px var(--litepicker-footer-box-shadow-color);
|
||||
}
|
||||
.litepicker[data-plugins*="ranges"][data-ranges-position="top"] > .container__main {
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.litepicker[data-plugins*="ranges"][data-ranges-position="top"] > .container__main > .container__predefined-ranges {
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-shadow: 2px 0px 2px var(--litepicker-footer-box-shadow-color);
|
||||
box-shadow: 2px 0px 2px var(--litepicker-footer-box-shadow-color);
|
||||
}
|
||||
.litepicker[data-plugins*="ranges"][data-ranges-position="bottom"] > .container__main {
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: reverse;
|
||||
-ms-flex-direction: column-reverse;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
.litepicker[data-plugins*="ranges"][data-ranges-position="bottom"] > .container__main > .container__predefined-ranges {
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-shadow: 2px 0px 2px var(--litepicker-footer-box-shadow-color);
|
||||
box-shadow: 2px 0px 2px var(--litepicker-footer-box-shadow-color);
|
||||
}
|
||||
.litepicker[data-plugins*="ranges"] > .container__main > .container__predefined-ranges button {
|
||||
padding: 5px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
.litepicker[data-plugins*="ranges"][data-ranges-position="left"] > .container__main > .container__predefined-ranges button,
|
||||
.litepicker[data-plugins*="ranges"][data-ranges-position="right"] > .container__main > .container__predefined-ranges button{
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
.litepicker[data-plugins*="ranges"] > .container__main > .container__predefined-ranges button:hover {
|
||||
cursor: default;
|
||||
opacity: .6;
|
||||
}
|
13
public/libs/litepicker/dist/js/main.js
vendored
Normal file
13
public/libs/litepicker/dist/js/main.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/libs/litepicker/dist/litepicker.amd.js
vendored
Normal file
12
public/libs/litepicker/dist/litepicker.amd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/libs/litepicker/dist/litepicker.commonjs2.js
vendored
Normal file
12
public/libs/litepicker/dist/litepicker.commonjs2.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/libs/litepicker/dist/litepicker.js
vendored
Normal file
12
public/libs/litepicker/dist/litepicker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/libs/litepicker/dist/litepicker.umd.js
vendored
Normal file
12
public/libs/litepicker/dist/litepicker.umd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/libs/litepicker/dist/nocss/litepicker.amd.js
vendored
Normal file
12
public/libs/litepicker/dist/nocss/litepicker.amd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/libs/litepicker/dist/nocss/litepicker.commonjs2.js
vendored
Normal file
12
public/libs/litepicker/dist/nocss/litepicker.commonjs2.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/libs/litepicker/dist/nocss/litepicker.js
vendored
Normal file
12
public/libs/litepicker/dist/nocss/litepicker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/libs/litepicker/dist/nocss/litepicker.umd.js
vendored
Normal file
12
public/libs/litepicker/dist/nocss/litepicker.umd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
public/libs/litepicker/dist/nocss/plugins/keyboardnav.js
vendored
Normal file
11
public/libs/litepicker/dist/nocss/plugins/keyboardnav.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
*
|
||||
* plugins/keyboardnav.js
|
||||
* Litepicker v2.0.12 (https://github.com/wakirin/Litepicker)
|
||||
* Package: litepicker (https://www.npmjs.com/package/litepicker)
|
||||
* License: MIT (https://github.com/wakirin/Litepicker/blob/master/LICENCE.md)
|
||||
* Copyright 2019-2021 Rinat G.
|
||||
*
|
||||
* Hash: fc3887e0bb19d54c36db
|
||||
*
|
||||
*/!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);n(1);function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Litepicker.add("keyboardnav",{init:function(e){Object.defineProperties(e,{isMouseDown:{value:!1,writable:!0}});function t(t,r){if(t.classList.contains("day-item")){r.preventDefault();var o=n(e.ui,t,(function(e,t){return e===(t="ArrowLeft"===r.code?t-1:t+1)}));o?o.focus():function(t){var n=e.ui.querySelector("".concat({ArrowLeft:".button-previous-month",ArrowRight:".button-next-month"}[t.code],'[tabindex="1"]'));n&&n.dispatchEvent(new Event("click"));setTimeout((function(){var n=null;switch(t.code){case"ArrowLeft":var r=e.ui.querySelectorAll('[tabindex="2"]');n=r[r.length-1];break;case"ArrowRight":n=e.ui.querySelector('[tabindex="2"]')}n.focus()}))}(r)}}function n(e,t,n){var r=Array.from(e.querySelectorAll('.day-item[tabindex="2"]')),o=r.indexOf(t);return r.filter((function(e,t){return n(t,o)&&2===e.tabIndex}))[0]}function r(t){e.isMouseDown=!0}function i(t){e.isMouseDown?e.isMouseDown=!1:this.options.inlineMode||this.isShowning()||(this.show(t.target),this.ui.querySelector('[tabindex="'.concat(e.options.keyboardnav.firstTabIndex,'"]')).focus())}function c(e){var t=this;this.options.inlineMode||setTimeout((function(){var e=document.activeElement;t.ui.contains(e)||(t.nextFocusElement=e)}))}e.options.keyboardnav=o(o({},{firstTabIndex:1}),e.options.keyboardnav),e.ui.addEventListener("keydown",function(r){var o=this,i=r.target;switch(setTimeout((function(){o.onMouseEnter({target:document.activeElement})})),r.code){case"ArrowUp":case"ArrowDown":!function(t,r){if(t.classList.contains("day-item")){r.preventDefault();var o=n(e.ui,t,(function(e,t){return e===(t="ArrowUp"===r.code?t-7:t+7)}));o&&o.focus()}}(i,r);break;case"ArrowLeft":case"ArrowRight":t(i,r);break;case"Tab":!function(t,n){setTimeout((function(){if(!document.activeElement.closest(".litepicker")){var n=e.ui.querySelector('[tabindex="1"]');if(t===n){var r=e.ui.querySelectorAll('[tabindex="2"]');n=r[r.length-1]}n.focus()}}))}(i);break;case"Enter":case"Space":!function(t,n){t.classList.contains("day-item")&&(n.preventDefault(),document.activeElement.dispatchEvent(new Event("click")),setTimeout((function(){var t=e.ui.querySelector('.is-start-date[tabindex="2"]');t||(t=e.ui.querySelector('[tabindex="2"]')),t.focus()})))}(i,r);break;case"Escape":e.hide()}}.bind(e),!0);var u=e.options;u.element instanceof HTMLElement&&(u.element.addEventListener("mousedown",r.bind(e),!0),u.element.addEventListener("focus",i.bind(e),!0)),u.elementEnd instanceof HTMLElement&&(u.elementEnd.addEventListener("mousedown",r.bind(e),!0),u.elementEnd.addEventListener("focus",i.bind(e),!0)),u.element instanceof HTMLElement&&u.element.addEventListener("blur",c.bind(e),!0),u.elementEnd instanceof HTMLElement&&u.elementEnd.addEventListener("blur",c.bind(e),!0),e.on("render",(function(e){Array.from(e.querySelectorAll([".month-item:first-child:not(.no-previous-month) .button-previous-month",".month-item:last-child:not(.no-next-month) .button-next-month",".reset-button","select"].join(","))).forEach((function(e){return e.tabIndex=1}))})),e.on("render:day",(function(e){e.tabIndex=e.classList.contains("is-locked")?-1:2}))}})},function(e,t,n){}]);
|
11
public/libs/litepicker/dist/nocss/plugins/mobilefriendly.js
vendored
Normal file
11
public/libs/litepicker/dist/nocss/plugins/mobilefriendly.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
public/libs/litepicker/dist/nocss/plugins/multiselect.js
vendored
Normal file
11
public/libs/litepicker/dist/nocss/plugins/multiselect.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
*
|
||||
* plugins/multiselect.js
|
||||
* Litepicker v2.0.12 (https://github.com/wakirin/Litepicker)
|
||||
* Package: litepicker (https://www.npmjs.com/package/litepicker)
|
||||
* License: MIT (https://github.com/wakirin/Litepicker/blob/master/LICENCE.md)
|
||||
* Copyright 2019-2021 Rinat G.
|
||||
*
|
||||
* Hash: fc3887e0bb19d54c36db
|
||||
*
|
||||
*/!function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=6)}({6:function(e,t,r){"use strict";r.r(t);r(7);function n(e){return function(e){if(Array.isArray(e))return i(e)}(e)||function(e){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return i(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);"Object"===r&&e.constructor&&(r=e.constructor.name);if("Map"===r||"Set"===r)return Array.from(e);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return i(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r<t;r++)n[r]=e[r];return n}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function l(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?o(Object(r),!0).forEach((function(t){u(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):o(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function u(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}Litepicker.add("multiselect",{init:function(e){Object.defineProperties(e,{multipleDates:{value:[],enumerable:!0,writable:!0},preMultipleDates:{value:[],writable:!0}});e.options.multiselect=l(l({},{max:null}),e.options.multiselect),e.options.autoApply=e.options.inlineMode,e.options.showTooltip=!1;var t=function(){var t=e.preMultipleDates.length,r=e.ui.querySelector(".preview-date-range");if(r&&t>0){var n=e.pluralSelector(t),i=e.options.tooltipText[n]?e.options.tooltipText[n]:"[".concat(n,"]"),o="".concat(t," ").concat(i);r.innerText=o}};e.on("before:show",(function(){e.preMultipleDates=n(e.multipleDates)})),e.on("show",(function(){t()})),e.on("before:click",(function(r){if(r.classList.contains("day-item")){if(e.preventClick=!0,r.classList.contains("is-locked"))return void r.blur();var n=Number(r.dataset.time);r.classList.contains("is-selected")?(e.preMultipleDates=e.preMultipleDates.filter((function(e){return e!==n})),e.emit("multiselect.deselect",e.DateTime(n))):(e.preMultipleDates[e.preMultipleDates.length]=n,e.emit("multiselect.select",e.DateTime(n))),e.options.autoApply&&e.emit("button:apply"),e.render(),t()}})),e.on("render:day",(function(t){var r=e.preMultipleDates.filter((function(e){return e===Number(t.dataset.time)})).length,n=Number(e.options.multiselect.max);r?t.classList.add("is-selected"):n&&e.preMultipleDates.length>=n&&t.classList.add("is-locked")})),e.on("button:cancel",(function(){e.preMultipleDates.length=0})),e.on("button:apply",(function(){e.multipleDates=n(e.preMultipleDates).sort((function(e,t){return e-t}))})),e.on("clear:selection",(function(){e.clearMultipleDates(),e.render()})),e.clearMultipleDates=function(){e.preMultipleDates.length=0,e.multipleDates.length=0},e.getMultipleDates=function(){return e.multipleDates.map((function(t){return e.DateTime(t)}))},e.multipleDatesToString=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"YYYY-MM-DD",r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:",";return e.multipleDates.map((function(r){return e.DateTime(r).format(t)})).join(r)}}})},7:function(e,t,r){e.exports={litepicker:"litepicker",containerDays:"container__days",dayItem:"day-item",isLocked:"is-locked",isSelected:"is-selected"}}});
|
11
public/libs/litepicker/dist/nocss/plugins/ranges.js
vendored
Normal file
11
public/libs/litepicker/dist/nocss/plugins/ranges.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
*
|
||||
* plugins/ranges.js
|
||||
* Litepicker v2.0.12 (https://github.com/wakirin/Litepicker)
|
||||
* Package: litepicker (https://www.npmjs.com/package/litepicker)
|
||||
* License: MIT (https://github.com/wakirin/Litepicker/blob/master/LICENCE.md)
|
||||
* Copyright 2019-2021 Rinat G.
|
||||
*
|
||||
* Hash: fc3887e0bb19d54c36db
|
||||
*
|
||||
*/!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=4)}({4:function(e,t,n){"use strict";n.r(t);n(5);function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){a(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Litepicker.add("ranges",{init:function(e){var t={position:"left",customRanges:{},customRangesLabels:["Today","Yesterday","Last 7 Days","Last 30 Days","This Month","Last Month"],force:!1,autoApply:e.options.autoApply};e.options.ranges=o(o({},t),e.options.ranges),e.options.singleMode=!1;var n=e.options.ranges;if(!Object.keys(n.customRanges).length){var r,i=e.DateTime();n.customRanges=(a(r={},n.customRangesLabels[0],[i.clone(),i.clone()]),a(r,n.customRangesLabels[1],[i.clone().subtract(1,"day"),i.clone().subtract(1,"day")]),a(r,n.customRangesLabels[2],[i.clone().subtract(6,"day"),i]),a(r,n.customRangesLabels[3],[i.clone().subtract(29,"day"),i]),a(r,n.customRangesLabels[4],function(e){var t=e.clone();return t.setDate(1),[t,new Date(e.getFullYear(),e.getMonth()+1,0)]}(i)),a(r,n.customRangesLabels[5],function(e){var t=e.clone();return t.setDate(1),t.setMonth(e.getMonth()-1),[t,new Date(e.getFullYear(),e.getMonth(),0)]}(i)),r)}e.on("render",(function(t){var r=document.createElement("div");r.className="container__predefined-ranges",e.ui.dataset.rangesPosition=n.position,Object.keys(n.customRanges).forEach((function(o){var a=n.customRanges[o],i=document.createElement("button");i.innerText=o,i.tabIndex=t.dataset.plugins.indexOf("keyboardnav")>=0?1:-1,i.dataset.start=a[0].getTime(),i.dataset.end=a[1].getTime(),i.addEventListener("click",(function(t){var r=t.target;if(r){var o=e.DateTime(Number(r.dataset.start)),a=e.DateTime(Number(r.dataset.end));n.autoApply?(e.setDateRange(o,a,n.force),e.emit("ranges.selected",o,a),e.hide()):(e.datePicked=[o,a],e.emit("ranges.preselect",o,a)),!e.options.inlineMode&&n.autoApply||e.gotoDate(o)}})),r.appendChild(i)})),t.querySelector(".container__main").prepend(r)}))}})},5:function(e,t,n){e.exports={litepicker:"litepicker",containerMain:"container__main",containerPredefinedRanges:"container__predefined-ranges"}}});
|
11
public/libs/litepicker/dist/plugins/keyboardnav.js
vendored
Normal file
11
public/libs/litepicker/dist/plugins/keyboardnav.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue