Almost a complete rewrite, part 1.

This commit is contained in:
lllllllillllllillll 2024-07-29 17:49:04 -07:00
parent 33e45a8bbf
commit 622318c461
386 changed files with 310761 additions and 40629 deletions

View file

@ -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) => {
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: '',
let container_links = await ServerSettings.findOne({ where: {key: 'container_links'}});
let user_registration = await ServerSettings.findOne({ where: {key: 'user_registration'}});
});
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(),
let user = await User.findOne({ where: {userID: req.session.userID}});
res.render("account",{
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),
});
}
}

View file

@ -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 page = Number(req.params.page) || 1;
let template_param = req.params.template || 'default';
let file = '';
let templates = [];
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/');
if (!page) { page = 1; }
if (!template) { template = 'default'; }
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;
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]);
}
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;
}
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; } });
}
catch {
console.log(`Template ${template} not found`);
}
let apps_list = '';
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();
}
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]);
}
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;
}
}
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", {
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,
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: '',
});
res.render("apps",{
alert: '',
username: req.session.username,
role: req.session.role,
app_count: app_count,
remove_button: '',
json_templates: '',
apps_list: apps_list,
prev: '',
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';
switch (result.network) {
case 'host':
net_host = 'checked';
break;
case 'bridge':
net_bridge = 'checked';
net_name = result.network;
break;
default:
net_docker = 'checked';
}
// console.log(req.body);
if (repository != "") {
image = (`${repository.url}/raw/master/${repository.stackfile}`);
}
let trigger_name = req.header('hx-trigger-name');
let trigger_id = req.header('hx-trigger');
let [ports_data, volumes_data, env_data, label_data] = [[], [], [], []];
for (let i = 0; i < 12; i++) {
// Get port details
try {
let ports = result.ports[i];
let port_check = ports ? "checked" : "";
let port_external = ports.split(":")[0] ? ports.split(":")[0] : ports.split("/")[0];
let port_internal = ports.split(":")[1] ? ports.split(":")[1].split("/")[0] : ports.split("/")[0];
let port_protocol = ports.split("/")[1] ? ports.split("/")[1] : "";
// remove /tcp or /udp from port_external if it exists
if (port_external.includes("/")) {
port_external = port_external.split("/")[0];
}
ports_data.push({
check: port_check,
external: port_external,
internal: port_internal,
protocol: port_protocol
});
} catch {
ports_data.push({
check: "",
external: "",
internal: "",
protocol: ""
});
}
// Get volume details
try {
let volumes = result.volumes[i];
let volume_check = volumes ? "checked" : "";
let volume_bind = volumes.bind ? volumes.bind : "";
let volume_container = volumes.container ? volumes.container.split(":")[0] : "";
let volume_readwrite = "rw";
if (volumes.readonly == true) {
volume_readwrite = "ro";
}
volumes_data.push({
check: volume_check,
bind: volume_bind,
container: volume_container,
readwrite: volume_readwrite
});
} catch {
volumes_data.push({
check: "",
bind: "",
container: "",
readwrite: ""
});
}
// Get environment details
try {
let env = result.env[i];
let env_check = "";
let env_default = env.default ? env.default : "";
if (env.set) { env_default = env.set;}
let env_description = env.description ? env.description : "";
let env_label = env.label ? env.label : "";
let env_name = env.name ? env.name : "";
env_data.push({
check: env_check,
default: env_default,
description: env_description,
label: env_label,
name: env_name
});
} catch {
env_data.push({
check: "",
default: "",
description: "",
label: "",
name: ""
});
}
// Get label details
try {
let label = result.labels[i];
let label_check = "";
let label_name = label.name ? label.name : "";
let label_value = label.value ? label.value : "";
label_data.push({
check: label_check,
name: label_name,
value: label_value
});
} catch {
label_data.push({
check: "",
name: "",
value: ""
});
}
}
let modal = readFileSync('./views/modals/json.html', 'utf8');
modal = modal.replace(/AppName/g, name);
modal = modal.replace(/AppNote/g, note);
modal = modal.replace(/AppImage/g, image);
modal = modal.replace(/RestartPolicy/g, restart_policy);
modal = modal.replace(/NetHost/g, net_host);
modal = modal.replace(/NetBridge/g, net_bridge);
modal = modal.replace(/NetDocker/g, net_docker);
modal = modal.replace(/NetName/g, net_name);
modal = modal.replace(/ModalName/g, modal_name);
modal = modal.replace(/FormId/g, form_id);
modal = modal.replace(/CommandCheck/g, command_check);
modal = modal.replace(/CommandValue/g, command);
modal = modal.replace(/PrivilegedCheck/g, privileged_check);
console.log(`trigger_name: ${trigger_name} - trigger_id: ${trigger_id}`);
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);
res.render("apps",{
alert: '',
username: req.session.username,
role: req.session.role,
navbar: await Navbar(req),
});
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');
});
};
}

View file

@ -1,348 +1,74 @@
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: '',
});
}
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;
if (details.name.length > 17) {
details.name = details.name.substring(0, 17) + '...';
}
// 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);
let state = details.state;
let state_color = '';
switch (state) {
case 'running':
state_color = 'green';
break;
case 'exited':
state = 'stopped';
state_color = 'red';
break;
case 'paused':
state_color = 'orange';
break;
case 'installing':
state_color = 'blue';
break;
}
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);
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}`);
}
container_list += container_card;
}
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_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';
break;
case 'exited':
state = 'stopped';
state_color = 'red';
trigger = 'data-hx-trigger="load"';
break;
case 'paused':
state_color = 'orange';
trigger = 'data-hx-trigger="load"';
break;
case 'installing':
state_color = 'blue';
trigger = 'data-hx-trigger="load"';
break;
}
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}`); }
return card;
}
// 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 }); }
}
// 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 }); }
});
}
// 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();
});
res.render("dashboard",{
alert: '',
username: req.session.username,
role: req.session.role,
container_list: container_list,
navbar: await Navbar(req),
});
};
}
// 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 } });
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");
}
export const submitDashboard = async function(req,res){
console.log(req.body);
res.send('ok');
return;
}

View file

@ -1,68 +1,32 @@
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) {
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>
<th class="w-1"><input class="form-check-input m-0 align-middle" name="select" type="checkbox" aria-label="Select all" onclick="selectAll()"></th>
<th><label class="table-sort" data-sort="sort-name">Name</label></th>
<th><label class="table-sort" data-sort="sort-type">Tag</label></th>
<th><label class="table-sort" data-sort="sort-city">ID</label></th>
<th><label class="table-sort" data-sort="sort-score">Status</label></th>
<th><label class="table-sort" data-sort="sort-date">Created</label></th>
<th><label class="table-sort" data-sort="sort-quantity">Size</label></th>
<th><label class="table-sort" data-sort="sort-progress">Action</label></th>
</tr>
</thead>
<tbody class="table-tbody">`
<thead>
<tr>
<th class="w-1"><input class="form-check-input m-0 align-middle" name="select" type="checkbox" aria-label="Select all" onclick="selectAll()"></th>
<th><label class="table-sort" data-sort="sort-name">Name</label></th>
<th><label class="table-sort" data-sort="sort-type">Tag</label></th>
<th><label class="table-sort" data-sort="sort-city">ID</label></th>
<th><label class="table-sort" data-sort="sort-score">Status</label></th>
<th><label class="table-sort" data-sort="sort-date">Created</label></th>
<th><label class="table-sort" data-sort="sort-quantity">Size</label></th>
<th><label class="table-sort" data-sort="sort-progress">Action</label></th>
</tr>
</thead>
<tbody class="table-tbody">`
for (let i = 0; i < images.length; i++) {
@ -101,23 +65,33 @@ export const Images = async function(req, res) {
image_list += `</tbody>`
res.render("images", {
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),
});
}

View file

@ -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");
}
}

View file

@ -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){
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", {
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),
});
}

View 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,
});
}

View file

@ -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) {
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.",
});
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 {
// 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 {
res.render("register",{
"error":"",
});
}
res.render("login", {
"error": "User registration is disabled."
});
}
}
export const submitRegister = async function(req,res){
export const submitRegister = async function (req,res) {
const { name, username, password, confirm, secret } = req.body;
let email = req.body.email.toLowerCase();
// Grab values from the form.
let { name, username, email, password1, password2, passphrase } = req.body;
let registration_secret = await ServerSettings.findOne({ where: { key: 'registration' }}).value;
// Convert the email to lowercase.
email = email.toLowerCase();
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"; }
// Get the registration passphrase.
let registration_passphrase = await ServerSettings.findOne({ where: { key: 'registration' }});
registration_passphrase = registration_passphrase.value;
// 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);
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
});
// 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) {
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" });
}
}
}

View file

@ -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){
let container_links = await ServerSettings.findOne({ where: {key: 'container_links'}});
let user_registration = await ServerSettings.findOne({ where: {key: 'user_registration'}});
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}"`);
}
res.render("settings", {
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);
}

View file

@ -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);
}

View file

@ -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) {
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,
res.render("syslogs",{
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),
});
}

View file

@ -1,7 +1,8 @@
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 function(req,res){
export const Users = async (req, res) => {
let user_list = `
<tr>
<th><input class="form-check-input" type="checkbox"></th>
@ -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", {
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),
});
}

View file

@ -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 = '';
export const Volumes = async function(req,res){
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", {
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
View 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
}
});

View file

@ -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

Binary file not shown.

BIN
database/settings.sqlite Normal file

Binary file not shown.

14
languages/chinese.json Normal file
View file

@ -0,0 +1,14 @@
{
"Dashboard": "仪表盘",
"Images": "镜像",
"Volumes": "存储卷",
"Networks": "网络",
"Apps": "应用商店",
"Users": "用户",
"Syslogs": "系统日志",
"Account": "设置",
"Notifications": "",
"Preferences": "",
"Settings": "设置",
"Logout": ""
}

384
package-lock.json generated
View file

@ -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"
}
},
"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"
}
},
"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"
"debug": "^4.1.1"
},
"engines": {
"node": ">= 10"
},
"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=="
}
}
}

View file

@ -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
View 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%);
}

View file

@ -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
View 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
View 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%)}

View file

@ -92,33 +92,65 @@
.nostripes > span::after {
background-image: none;
}
.border {
--tblr-card-spacer-y: 1rem;
--tblr-card-spacer-x: 1.5rem;
--tblr-card-title-spacer-y: 1.25rem;
--tblr-card-border-width: var(--tblr-border-width);
--tblr-card-border-color: var(--tblr-border-color);
--tblr-card-border-radius: var(--tblr-border-radius);
--tblr-card-box-shadow: var(--tblr-shadow-card);
--tblr-card-inner-border-radius: calc(var(--tblr-border-radius) - (var(--tblr-border-width)));
--tblr-card-cap-padding-y: 1rem;
--tblr-card-cap-padding-x: 1.5rem;
--tblr-card-cap-bg: var(--tblr-bg-surface-tertiary);
--tblr-card-cap-color: inherit;
--tblr-card-color: inherit;
--tblr-card-bg: var(--tblr-bg-surface);
--tblr-card-img-overlay-padding: 1rem;
--tblr-card-group-margin: 1.5rem;
position: relative;
display: flex;
flex-direction: column;
min-width: 0;
height: var(--tblr-card-height);
word-wrap: break-word;
background-color: var(--tblr-card-bg);
background-clip: border-box;
border: var(--tblr-card-border-width) solid var(--tblr-card-border-color);
border-radius: var(--tblr-card-border-radius);
}
.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;
}

View 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
View 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)
*/

View 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
View 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

File diff suppressed because it is too large Load diff

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

File diff suppressed because it is too large Load diff

13
public/css/tabler.rtl.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -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
View 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")}));

View file

@ -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

View file

@ -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
View 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');
}
}

View file

@ -6,350 +6,285 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
*/
(function() {
/** @type {import("../htmx").HtmxInternalApi} */
var api
/** @type {import("../htmx").HtmxInternalApi} */
var api;
htmx.defineExtension('sse', {
htmx.defineExtension("sse", {
/**
* Init saves the provided reference to the internal HTMX API.
*
* @param {import("../htmx").HtmxInternalApi} api
* @returns void
*/
init: function(apiRef) {
// store a reference to the internal API.
api = apiRef
/**
* Init saves the provided reference to the internal HTMX API.
*
* @param {import("../htmx").HtmxInternalApi} api
* @returns void
*/
init: function(apiRef) {
// store a reference to the internal API.
api = apiRef;
// set a function in the public API for creating new EventSource objects
if (htmx.createEventSource == undefined) {
htmx.createEventSource = createEventSource
}
},
// set a function in the public API for creating new EventSource objects
if (htmx.createEventSource == undefined) {
htmx.createEventSource = createEventSource;
}
},
getSelectors: function() {
return ['[sse-connect]', '[data-sse-connect]', '[sse-swap]', '[data-sse-swap]']
},
/**
* onEvent handles all events passed to this extension.
*
* @param {string} name
* @param {Event} evt
* @returns void
*/
onEvent: function(name, evt) {
/**
* onEvent handles all events passed to this extension.
*
* @param {string} name
* @param {Event} evt
* @returns void
*/
onEvent: function(name, evt) {
var parent = evt.target || evt.detail.elt
switch (name) {
case 'htmx:beforeCleanupElement':
var internalData = api.getInternalData(parent)
// Try to remove remove an EventSource when elements are removed
var source = internalData.sseEventSource
if (source) {
api.triggerEvent(parent, 'htmx:sseClose', {
source,
type: 'nodeReplaced',
})
internalData.sseEventSource.close()
}
switch (name) {
return
case "htmx:beforeCleanupElement":
var internalData = api.getInternalData(evt.target)
// Try to remove remove an EventSource when elements are removed
if (internalData.sseEventSource) {
internalData.sseEventSource.close();
}
// Try to create EventSources when elements are processed
case 'htmx:afterProcessNode':
ensureEventSourceOnElement(parent)
}
}
})
return;
/// ////////////////////////////////////////////
// HELPER FUNCTIONS
/// ////////////////////////////////////////////
// Try to create EventSources when elements are processed
case "htmx:afterProcessNode":
ensureEventSourceOnElement(evt.target);
registerSSE(evt.target);
}
}
});
/**
* createEventSource is the default method for creating new EventSource objects.
* it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
*
* @param {string} url
* @returns EventSource
*/
function createEventSource(url) {
return new EventSource(url, { withCredentials: true })
}
///////////////////////////////////////////////
// HELPER FUNCTIONS
///////////////////////////////////////////////
/**
* registerSSE looks for attributes that can contain sse events, right
* now hx-trigger and sse-swap and adds listeners based on these attributes too
* the closest event source
*
* @param {HTMLElement} elt
*/
function registerSSE(elt) {
// Add message handlers for every `sse-swap` attribute
if (api.getAttributeValue(elt, 'sse-swap')) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(elt, hasEventSource)
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null // no eventsource in parentage, orphaned element
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement)
var source = internalData.sseEventSource
var sseSwapAttr = api.getAttributeValue(elt, 'sse-swap')
var sseEventNames = sseSwapAttr.split(',')
for (var i = 0; i < sseEventNames.length; i++) {
const sseEventName = sseEventNames[i].trim()
const listener = function(event) {
// If the source is missing then close SSE
if (maybeCloseSSESource(sourceElement)) {
return
}
// If the body no longer contains the element, remove the listener
if (!api.bodyContains(elt)) {
source.removeEventListener(sseEventName, listener)
return
}
// swap the response into the DOM and trigger a notification
if (!api.triggerEvent(elt, 'htmx:sseBeforeMessage', event)) {
return
}
swap(elt, event.data)
api.triggerEvent(elt, 'htmx:sseMessage', event)
}
// Register the new listener
api.getInternalData(elt).sseEventListener = listener
source.addEventListener(sseEventName, listener)
}
}
// Add message handlers for every `hx-trigger="sse:*"` attribute
if (api.getAttributeValue(elt, 'hx-trigger')) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(elt, hasEventSource)
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null // no eventsource in parentage, orphaned element
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement)
var source = internalData.sseEventSource
var triggerSpecs = api.getTriggerSpecs(elt)
triggerSpecs.forEach(function(ts) {
if (ts.trigger.slice(0, 4) !== 'sse:') {
return
}
var listener = function (event) {
if (maybeCloseSSESource(sourceElement)) {
return
}
if (!api.bodyContains(elt)) {
source.removeEventListener(ts.trigger.slice(4), listener)
}
// Trigger events to be handled by the rest of htmx
htmx.trigger(elt, ts.trigger, event)
htmx.trigger(elt, 'htmx:sseMessage', event)
}
// Register the new listener
api.getInternalData(elt).sseEventListener = listener
source.addEventListener(ts.trigger.slice(4), listener)
})
}
}
/**
* ensureEventSourceOnElement creates a new EventSource connection on the provided element.
* If a usable EventSource already exists, then it is returned. If not, then a new EventSource
* is created and stored in the element's internalData.
* @param {HTMLElement} elt
* @param {number} retryCount
* @returns {EventSource | null}
*/
function ensureEventSourceOnElement(elt, retryCount) {
if (elt == null) {
return null
}
// handle extension source creation attribute
if (api.getAttributeValue(elt, 'sse-connect')) {
var sseURL = api.getAttributeValue(elt, 'sse-connect')
if (sseURL == null) {
return
}
ensureEventSource(elt, sseURL, retryCount)
}
registerSSE(elt)
}
function ensureEventSource(elt, url, retryCount) {
var source = htmx.createEventSource(url)
source.onerror = function(err) {
// Log an error event
api.triggerErrorEvent(elt, 'htmx:sseError', { error: err, source })
// If parent no longer exists in the document, then clean up this EventSource
if (maybeCloseSSESource(elt)) {
return
}
// Otherwise, try to reconnect the EventSource
if (source.readyState === EventSource.CLOSED) {
retryCount = retryCount || 0
retryCount = Math.max(Math.min(retryCount * 2, 128), 1)
var timeout = retryCount * 500
window.setTimeout(function() {
ensureEventSourceOnElement(elt, retryCount)
}, timeout)
}
}
source.onopen = function(evt) {
api.triggerEvent(elt, 'htmx:sseOpen', { source })
if (retryCount && retryCount > 0) {
const childrenToFix = elt.querySelectorAll("[sse-swap], [data-sse-swap], [hx-trigger], [data-hx-trigger]")
for (let i = 0; i < childrenToFix.length; i++) {
registerSSE(childrenToFix[i])
}
// We want to increase the reconnection delay for consecutive failed attempts only
retryCount = 0
}
}
api.getInternalData(elt).sseEventSource = source
/**
* createEventSource is the default method for creating new EventSource objects.
* it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
*
* @param {string} url
* @returns EventSource
*/
function createEventSource(url) {
return new EventSource(url, { withCredentials: true });
}
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()
});
}
}
function splitOnWhitespace(trigger) {
return trigger.trim().split(/\s+/);
}
/**
* maybeCloseSSESource confirms that the parent element still exists.
* If not, then any associated SSE source is closed and the function returns true.
*
* @param {HTMLElement} elt
* @returns boolean
*/
function maybeCloseSSESource(elt) {
if (!api.bodyContains(elt)) {
var source = api.getInternalData(elt).sseEventSource
if (source != undefined) {
api.triggerEvent(elt, 'htmx:sseClose', {
source,
type: 'nodeMissing',
})
source.close()
// source = null
return true
}
}
return false
}
function 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;
}
/**
* @param {HTMLElement} elt
* @param {string} content
*/
function swap(elt, content) {
api.withExtensions(elt, function(extension) {
content = extension.transformResponse(content, null, elt)
})
/**
* registerSSE looks for attributes that can contain sse events, right
* now hx-trigger and sse-swap and adds listeners based on these attributes too
* the closest event source
*
* @param {HTMLElement} elt
*/
function registerSSE(elt) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(elt, hasEventSource);
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null; // no eventsource in parentage, orphaned element
}
var swapSpec = api.getSwapSpecification(elt)
var target = api.getTarget(elt)
api.swap(target, content, swapSpec)
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement);
var source = internalData.sseEventSource;
// Add message handlers for every `sse-swap` attribute
queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) {
var sseSwapAttr = api.getAttributeValue(child, "sse-swap");
if (sseSwapAttr) {
var sseEventNames = sseSwapAttr.split(",");
} else {
var sseEventNames = getLegacySSESwaps(child);
}
for (var i = 0; i < sseEventNames.length; i++) {
var sseEventName = sseEventNames[i].trim();
var listener = function(event) {
// If the source is missing then close SSE
if (maybeCloseSSESource(sourceElement)) {
return;
}
// If the body no longer contains the element, remove the listener
if (!api.bodyContains(child)) {
source.removeEventListener(sseEventName, listener);
}
// swap the response into the DOM and trigger a notification
swap(child, event.data);
api.triggerEvent(elt, "htmx:sseMessage", event);
};
// Register the new listener
api.getInternalData(child).sseEventListener = listener;
source.addEventListener(sseEventName, listener);
}
});
// Add message handlers for every `hx-trigger="sse:*"` attribute
queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) {
var sseEventName = api.getAttributeValue(child, "hx-trigger");
if (sseEventName == null) {
return;
}
// Only process hx-triggers for events with the "sse:" prefix
if (sseEventName.slice(0, 4) != "sse:") {
return;
}
// remove the sse: prefix from here on out
sseEventName = sseEventName.substr(4);
var listener = function() {
if (maybeCloseSSESource(sourceElement)) {
return
}
if (!api.bodyContains(child)) {
source.removeEventListener(sseEventName, listener);
}
}
});
}
/**
* ensureEventSourceOnElement creates a new EventSource connection on the provided element.
* If a usable EventSource already exists, then it is returned. If not, then a new EventSource
* is created and stored in the element's internalData.
* @param {HTMLElement} elt
* @param {number} retryCount
* @returns {EventSource | null}
*/
function ensureEventSourceOnElement(elt, retryCount) {
if (elt == null) {
return null;
}
// handle extension source creation attribute
queryAttributeOnThisOrChildren(elt, "sse-connect").forEach(function(child) {
var sseURL = api.getAttributeValue(child, "sse-connect");
if (sseURL == null) {
return;
}
ensureEventSource(child, sseURL, retryCount);
});
// handle legacy sse, remove for HTMX2
queryAttributeOnThisOrChildren(elt, "hx-sse").forEach(function(child) {
var sseURL = getLegacySSEURL(child);
if (sseURL == null) {
return;
}
ensureEventSource(child, sseURL, retryCount);
});
}
function ensureEventSource(elt, url, retryCount) {
var source = htmx.createEventSource(url);
source.onerror = function(err) {
// Log an error event
api.triggerErrorEvent(elt, "htmx:sseError", { error: err, source: source });
// If parent no longer exists in the document, then clean up this EventSource
if (maybeCloseSSESource(elt)) {
return;
}
// Otherwise, try to reconnect the EventSource
if (source.readyState === EventSource.CLOSED) {
retryCount = retryCount || 0;
var timeout = Math.random() * (2 ^ retryCount) * 500;
window.setTimeout(function() {
ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1));
}, timeout);
}
};
source.onopen = function(evt) {
api.triggerEvent(elt, "htmx:sseOpen", { source: source });
}
api.getInternalData(elt).sseEventSource = source;
}
/**
* maybeCloseSSESource confirms that the parent element still exists.
* If not, then any associated SSE source is closed and the function returns true.
*
* @param {HTMLElement} elt
* @returns boolean
*/
function maybeCloseSSESource(elt) {
if (!api.bodyContains(elt)) {
var source = api.getInternalData(elt).sseEventSource;
if (source != undefined) {
source.close();
// source = null
return true;
}
}
return false;
}
/**
* queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
*
* @param {HTMLElement} elt
* @param {string} attributeName
*/
function queryAttributeOnThisOrChildren(elt, attributeName) {
var result = [];
// If the parent element also contains the requested attribute, then add it to the results too.
if (api.hasAttribute(elt, attributeName)) {
result.push(elt);
}
// Search all child nodes that match the requested attribute
elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "]").forEach(function(node) {
result.push(node);
});
return result;
}
/**
* @param {HTMLElement} elt
* @param {string} content
*/
function swap(elt, content) {
api.withExtensions(elt, function(extension) {
content = extension.transformResponse(content, null, elt);
});
var swapSpec = api.getSwapSpecification(elt);
var target = api.getTarget(elt);
var settleInfo = api.makeSettleInfo(elt);
api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo);
settleInfo.elts.forEach(function(elt) {
if (elt.classList) {
elt.classList.add(htmx.config.settlingClass);
}
api.triggerEvent(elt, 'htmx:beforeSettle');
});
// Handle settle tasks (with delay if requested)
if (swapSpec.settleDelay > 0) {
setTimeout(doSettle(settleInfo), swapSpec.settleDelay);
} else {
doSettle(settleInfo)();
}
}
/**
* doSettle mirrors much of the functionality in htmx that
* settles elements after their content has been swapped.
* TODO: this should be published by htmx, and not duplicated here
* @param {import("../htmx").HtmxSettleInfo} settleInfo
* @returns () => void
*/
function doSettle(settleInfo) {
return function() {
settleInfo.tasks.forEach(function(task) {
task.call();
});
settleInfo.elts.forEach(function(elt) {
if (elt.classList) {
elt.classList.remove(htmx.config.settlingClass);
}
api.triggerEvent(elt, 'htmx:afterSettle');
});
}
}
function hasEventSource(node) {
return api.getInternalData(node).sseEventSource != null;
}
})();
function hasEventSource(node) {
return api.getInternalData(node).sseEventSource != null
}
})()

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

7543
public/js/tabler.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

32956
public/libs/apexcharts/dist/apexcharts.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4494
public/libs/bootstrap/dist/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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
View 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 };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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
View 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 */

View 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"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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 */

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

2111
public/libs/dropzone/dist/dropzone.mjs vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View 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.

View 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 &lt;body&gt; 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 |

File diff suppressed because one or more lines are too long

View 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"
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

52
public/libs/litepicker/dist/bundle.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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
*
*/

View 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);
}
}

View 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);
}

View 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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){}]);

File diff suppressed because one or more lines are too long

View 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"}}});

View 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"}}});

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