diff --git a/.gitignore b/.gitignore index fbb8167e..5bf37c0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .idea +.qodo ._* .vscode certbot-help.txt diff --git a/.version b/.version index e4643748..fb2c0766 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.12.6 +2.13.0 diff --git a/Jenkinsfile b/Jenkinsfile index af913c2e..fc249ab4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -119,13 +119,13 @@ pipeline { always { // Dumps to analyze later sh 'mkdir -p debug/sqlite' - sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/sqlite/docker_fullstack.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q stepca) > debug/sqlite/docker_stepca.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q pdns) > debug/sqlite/docker_pdns.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/sqlite/docker_pdns-db.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/sqlite/docker_dnsrouter.log 2>&1' + sh 'docker logs $(docker compose ps --all -q fullstack) > debug/sqlite/docker_fullstack.log 2>&1' + sh 'docker logs $(docker compose ps --all -q stepca) > debug/sqlite/docker_stepca.log 2>&1' + sh 'docker logs $(docker compose ps --all -q pdns) > debug/sqlite/docker_pdns.log 2>&1' + sh 'docker logs $(docker compose ps --all -q pdns-db) > debug/sqlite/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker compose ps --all -q dnsrouter) > debug/sqlite/docker_dnsrouter.log 2>&1' junit 'test/results/junit/*' - sh 'docker-compose down --remove-orphans --volumes -t 30 || true' + sh 'docker compose down --remove-orphans --volumes -t 30 || true' } unstable { dir(path: 'test/results') { @@ -152,13 +152,13 @@ pipeline { always { // Dumps to analyze later sh 'mkdir -p debug/mysql' - sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/mysql/docker_fullstack.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q stepca) > debug/mysql/docker_stepca.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q pdns) > debug/mysql/docker_pdns.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/mysql/docker_pdns-db.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/mysql/docker_dnsrouter.log 2>&1' + sh 'docker logs $(docker compose ps --all -q fullstack) > debug/mysql/docker_fullstack.log 2>&1' + sh 'docker logs $(docker compose ps --all -q stepca) > debug/mysql/docker_stepca.log 2>&1' + sh 'docker logs $(docker compose ps --all -q pdns) > debug/mysql/docker_pdns.log 2>&1' + sh 'docker logs $(docker compose ps --all -q pdns-db) > debug/mysql/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker compose ps --all -q dnsrouter) > debug/mysql/docker_dnsrouter.log 2>&1' junit 'test/results/junit/*' - sh 'docker-compose down --remove-orphans --volumes -t 30 || true' + sh 'docker compose down --remove-orphans --volumes -t 30 || true' } unstable { dir(path: 'test/results') { @@ -185,18 +185,18 @@ pipeline { always { // Dumps to analyze later sh 'mkdir -p debug/postgres' - sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1' - sh 'docker logs $(docker-compose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1' + sh 'docker logs $(docker compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1' + sh 'docker logs $(docker compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1' + sh 'docker logs $(docker compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1' + sh 'docker logs $(docker compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1' + sh 'docker logs $(docker compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1' + sh 'docker logs $(docker compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1' + sh 'docker logs $(docker compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1' + sh 'docker logs $(docke rcompose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1' junit 'test/results/junit/*' - sh 'docker-compose down --remove-orphans --volumes -t 30 || true' + sh 'docker compose down --remove-orphans --volumes -t 30 || true' } unstable { dir(path: 'test/results') { diff --git a/README.md b/README.md index 2116a55a..f48478ed 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@



- + @@ -74,11 +74,7 @@ This is the bare minimum configuration required. See the [documentation](https:/ 3. Bring up your stack by running ```bash -docker-compose up -d - -# If using docker-compose-plugin docker compose up -d - ``` 4. Log in to the Admin UI @@ -88,14 +84,6 @@ Sometimes this can take a little bit because of the entropy of keys. [http://127.0.0.1:81](http://127.0.0.1:81) -Default Admin User: -``` -Email: admin@example.com -Password: changeme -``` - -Immediately after logging in with this default user you will be asked to modify your details and change your password. - ## Contributing diff --git a/backend/.eslintrc.json b/backend/.eslintrc.json deleted file mode 100644 index 6d6172a4..00000000 --- a/backend/.eslintrc.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "env": { - "node": true, - "es6": true - }, - "extends": [ - "eslint:recommended" - ], - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module" - }, - "plugins": [ - "align-assignments" - ], - "rules": { - "arrow-parens": [ - "error", - "always" - ], - "indent": [ - "error", - "tab" - ], - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "error", - "single" - ], - "semi": [ - "error", - "always" - ], - "key-spacing": [ - "error", - { - "align": "value" - } - ], - "comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "func-call-spacing": [ - "error", - "never" - ], - "keyword-spacing": [ - "error", - { - "before": true - } - ], - "no-irregular-whitespace": "error", - "no-unused-expressions": 0, - "align-assignments/align-assignments": [ - 2, - { - "requiresOnly": false - } - ] - } -} \ No newline at end of file diff --git a/backend/.prettierrc b/backend/.prettierrc deleted file mode 100644 index fefbcfa6..00000000 --- a/backend/.prettierrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "printWidth": 320, - "tabWidth": 4, - "useTabs": true, - "semi": true, - "singleQuote": true, - "bracketSpacing": true, - "jsxBracketSameLine": true, - "trailingComma": "all", - "proseWrap": "always" -} diff --git a/backend/app.js b/backend/app.js index 59f7def2..3039bbb8 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1,9 +1,12 @@ -const express = require('express'); -const bodyParser = require('body-parser'); -const fileUpload = require('express-fileupload'); -const compression = require('compression'); -const config = require('./lib/config'); -const log = require('./logger').express; +import bodyParser from "body-parser"; +import compression from "compression"; +import express from "express"; +import fileUpload from "express-fileupload"; +import { isDebugMode } from "./lib/config.js"; +import cors from "./lib/express/cors.js"; +import jwt from "./lib/express/jwt.js"; +import { debug, express as logger } from "./logger.js"; +import mainRoutes from "./routes/main.js"; /** * App @@ -11,7 +14,7 @@ const log = require('./logger').express; const app = express(); app.use(fileUpload()); app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({extended: true})); +app.use(bodyParser.urlencoded({ extended: true })); // Gzip app.use(compression()); @@ -20,71 +23,70 @@ app.use(compression()); * General Logging, BEFORE routes */ -app.disable('x-powered-by'); -app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); -app.enable('strict routing'); +app.disable("x-powered-by"); +app.enable("trust proxy", ["loopback", "linklocal", "uniquelocal"]); +app.enable("strict routing"); // pretty print JSON when not live -if (config.debug()) { - app.set('json spaces', 2); +if (isDebugMode()) { + app.set("json spaces", 2); } // CORS for everything -app.use(require('./lib/express/cors')); +app.use(cors); // General security/cache related headers + server header -app.use(function (req, res, next) { - let x_frame_options = 'DENY'; +app.use((_, res, next) => { + let x_frame_options = "DENY"; - if (typeof process.env.X_FRAME_OPTIONS !== 'undefined' && process.env.X_FRAME_OPTIONS) { + if (typeof process.env.X_FRAME_OPTIONS !== "undefined" && process.env.X_FRAME_OPTIONS) { x_frame_options = process.env.X_FRAME_OPTIONS; } res.set({ - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Frame-Options': x_frame_options, - 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', - Pragma: 'no-cache', - Expires: 0 + "X-XSS-Protection": "1; mode=block", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": x_frame_options, + "Cache-Control": "no-cache, no-store, max-age=0, must-revalidate", + Pragma: "no-cache", + Expires: 0, }); next(); }); -app.use(require('./lib/express/jwt')()); -app.use('/', require('./routes/main')); +app.use(jwt()); +app.use("/", mainRoutes); // production error handler // no stacktraces leaked to user -// eslint-disable-next-line -app.use(function (err, req, res, next) { - - let payload = { +app.use((err, req, res, _) => { + const payload = { error: { - code: err.status, - message: err.public ? err.message : 'Internal Error' - } + code: err.status, + message: err.public ? err.message : "Internal Error", + }, }; - if (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) { + if (typeof err.message_i18n !== "undefined") { + payload.error.message_i18n = err.message_i18n; + } + + if (isDebugMode() || (req.baseUrl + req.path).includes("nginx/certificates")) { payload.debug = { - stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null, - previous: err.previous + stack: typeof err.stack !== "undefined" && err.stack ? err.stack.split("\n") : null, + previous: err.previous, }; } // Not every error is worth logging - but this is good for now until it gets annoying. - if (typeof err.stack !== 'undefined' && err.stack) { - if (config.debug()) { - log.debug(err.stack); - } else if (typeof err.public == 'undefined' || !err.public) { - log.warn(err.message); + if (typeof err.stack !== "undefined" && err.stack) { + debug(logger, err.stack); + if (typeof err.public === "undefined" || !err.public) { + logger.warn(err.message); } } - res - .status(err.status || 500) - .send(payload); + res.status(err.status || 500).send(payload); }); -module.exports = app; +export default app; diff --git a/backend/biome.json b/backend/biome.json new file mode 100644 index 00000000..3d985b0f --- /dev/null +++ b/backend/biome.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false, + "includes": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "!**/dist/**/*" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "indentWidth": 4, + "lineWidth": 120, + "formatWithErrors": true + }, + "assist": { + "actions": { + "source": { + "organizeImports": { + "level": "on", + "options": { + "groups": [ + ":BUN:", + ":NODE:", + [ + "npm:*", + "npm:*/**" + ], + ":PACKAGE_WITH_PROTOCOL:", + ":URL:", + ":PACKAGE:", + [ + "/src/*", + "/src/**" + ], + [ + "/**" + ], + [ + "#*", + "#*/**" + ], + ":PATH:" + ] + } + } + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "useUniqueElementIds": "off" + }, + "suspicious": { + "noExplicitAny": "off" + }, + "performance": { + "noDelete": "off" + }, + "nursery": "off", + "a11y": { + "useSemanticElements": "off", + "useValidAnchor": "off" + }, + "style": { + "noParameterAssign": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSelfClosingElements": "error", + "useSingleVarDeclarator": "error", + "noUnusedTemplateLiteral": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error", + "noUselessElse": "error" + } + } + } +} diff --git a/global/README.md b/backend/certbot/README.md similarity index 97% rename from global/README.md rename to backend/certbot/README.md index 83e6c8c5..3c456462 100644 --- a/global/README.md +++ b/backend/certbot/README.md @@ -1,4 +1,4 @@ -# certbot-dns-plugins +# Certbot dns-plugins This file contains info about available Certbot DNS plugins. This only works for plugins which use the standard argument structure, so: diff --git a/global/certbot-dns-plugins.json b/backend/certbot/dns-plugins.json similarity index 98% rename from global/certbot-dns-plugins.json rename to backend/certbot/dns-plugins.json index 9f878809..6238f466 100644 --- a/global/certbot-dns-plugins.json +++ b/backend/certbot/dns-plugins.json @@ -295,6 +295,14 @@ "credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef", "full_plugin_name": "dns-hetzner" }, + "hetzner-cloud": { + "name": "Hetzner Cloud", + "package_name": "certbot-dns-hetzner-cloud", + "version": "~=1.0.4", + "dependencies": "", + "credentials": "dns_hetzner_cloud_api_token = your_api_token_here", + "full_plugin_name": "dns-hetzner-cloud" + }, "hostingnl": { "name": "Hosting.nl", "package_name": "certbot-dns-hostingnl", diff --git a/backend/db.js b/backend/db.js index 1a8b1634..bd74e518 100644 --- a/backend/db.js +++ b/backend/db.js @@ -1,27 +1,33 @@ -const config = require('./lib/config'); +import knex from "knex"; +import {configGet, configHas} from "./lib/config.js"; -if (!config.has('database')) { - throw new Error('Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/'); -} +const generateDbConfig = () => { + if (!configHas("database")) { + throw new Error( + "Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/", + ); + } -function generateDbConfig() { - const cfg = config.get('database'); - if (cfg.engine === 'knex-native') { + const cfg = configGet("database"); + + if (cfg.engine === "knex-native") { return cfg.knex; } + return { - client: cfg.engine, + client: cfg.engine, connection: { - host: cfg.host, - user: cfg.user, + host: cfg.host, + user: cfg.user, password: cfg.password, database: cfg.name, - port: cfg.port + port: cfg.port, + ...(cfg.ssl ? { ssl: cfg.ssl } : {}) }, migrations: { - tableName: 'migrations' - } + tableName: "migrations", + }, }; -} +}; -module.exports = require('knex')(generateDbConfig()); +export default knex(generateDbConfig()); diff --git a/backend/index.js b/backend/index.js index d334a7c2..00285667 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,48 +1,47 @@ #!/usr/bin/env node -const schema = require('./schema'); -const logger = require('./logger').global; +import app from "./app.js"; +import internalCertificate from "./internal/certificate.js"; +import internalIpRanges from "./internal/ip_ranges.js"; +import { global as logger } from "./logger.js"; +import { migrateUp } from "./migrate.js"; +import { getCompiledSchema } from "./schema/index.js"; +import setup from "./setup.js"; -const IP_RANGES_FETCH_ENABLED = process.env.IP_RANGES_FETCH_ENABLED !== 'false'; +const IP_RANGES_FETCH_ENABLED = process.env.IP_RANGES_FETCH_ENABLED !== "false"; -async function appStart () { - const migrate = require('./migrate'); - const setup = require('./setup'); - const app = require('./app'); - const internalCertificate = require('./internal/certificate'); - const internalIpRanges = require('./internal/ip_ranges'); - - return migrate.latest() +async function appStart() { + return migrateUp() .then(setup) - .then(schema.getCompiledSchema) + .then(getCompiledSchema) .then(() => { - if (IP_RANGES_FETCH_ENABLED) { - logger.info('IP Ranges fetch is enabled'); - return internalIpRanges.fetch().catch((err) => { - logger.error('IP Ranges fetch failed, continuing anyway:', err.message); - }); - } else { - logger.info('IP Ranges fetch is disabled by environment variable'); + if (!IP_RANGES_FETCH_ENABLED) { + logger.info("IP Ranges fetch is disabled by environment variable"); + return; } + logger.info("IP Ranges fetch is enabled"); + return internalIpRanges.fetch().catch((err) => { + logger.error("IP Ranges fetch failed, continuing anyway:", err.message); + }); }) .then(() => { internalCertificate.initTimer(); internalIpRanges.initTimer(); const server = app.listen(3000, () => { - logger.info('Backend PID ' + process.pid + ' listening on port 3000 ...'); + logger.info(`Backend PID ${process.pid} listening on port 3000 ...`); - process.on('SIGTERM', () => { - logger.info('PID ' + process.pid + ' received SIGTERM'); + process.on("SIGTERM", () => { + logger.info(`PID ${process.pid} received SIGTERM`); server.close(() => { - logger.info('Stopping.'); + logger.info("Stopping."); process.exit(0); }); }); }); }) .catch((err) => { - logger.error(err.message, err); + logger.error(`Startup Error: ${err.message}`, err); setTimeout(appStart, 1000); }); } @@ -50,7 +49,6 @@ async function appStart () { try { appStart(); } catch (err) { - logger.error(err.message, err); + logger.fatal(err); process.exit(1); } - diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index f6043e18..60a7105d 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -1,103 +1,94 @@ -const _ = require('lodash'); -const fs = require('fs'); -const batchflow = require('batchflow'); -const logger = require('../logger').access; -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const accessListModel = require('../models/access_list'); -const accessListAuthModel = require('../models/access_list_auth'); -const accessListClientModel = require('../models/access_list_client'); -const proxyHostModel = require('../models/proxy_host'); -const internalAuditLog = require('./audit-log'); -const internalNginx = require('./nginx'); +import fs from "node:fs"; +import batchflow from "batchflow"; +import _ from "lodash"; +import errs from "../lib/error.js"; +import utils from "../lib/utils.js"; +import { access as logger } from "../logger.js"; +import accessListModel from "../models/access_list.js"; +import accessListAuthModel from "../models/access_list_auth.js"; +import accessListClientModel from "../models/access_list_client.js"; +import proxyHostModel from "../models/proxy_host.js"; +import internalAuditLog from "./audit-log.js"; +import internalNginx from "./nginx.js"; -function omissions () { - return ['is_deleted']; -} +const omissions = () => { + return ["is_deleted"]; +}; const internalAccessList = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ - create: (access, data) => { - return access.can('access_lists:create', data) - .then((/*access_data*/) => { - return accessListModel - .query() - .insertAndFetch({ - name: data.name, - satisfy_any: data.satisfy_any, - pass_auth: data.pass_auth, - owner_user_id: access.token.getUserId(1) - }) - .then(utils.omitRow(omissions())); + create: async (access, data) => { + await access.can("access_lists:create", data); + const row = await accessListModel + .query() + .insertAndFetch({ + name: data.name, + satisfy_any: data.satisfy_any, + pass_auth: data.pass_auth, + owner_user_id: access.token.getUserId(1), }) - .then((row) => { - data.id = row.id; + .then(utils.omitRow(omissions())); - let promises = []; + data.id = row.id; - // Now add the items - data.items.map((item) => { - promises.push(accessListAuthModel - .query() - .insert({ - access_list_id: row.id, - username: item.username, - password: item.password - }) - ); - }); + const promises = []; + // Items + data.items.map((item) => { + promises.push( + accessListAuthModel.query().insert({ + access_list_id: row.id, + username: item.username, + password: item.password, + }), + ); + return true; + }); - // Now add the clients - if (typeof data.clients !== 'undefined' && data.clients) { - data.clients.map((client) => { - promises.push(accessListClientModel - .query() - .insert({ - access_list_id: row.id, - address: client.address, - directive: client.directive - }) - ); - }); - } + // Clients + data.clients?.map((client) => { + promises.push( + accessListClientModel.query().insert({ + access_list_id: row.id, + address: client.address, + directive: client.directive, + }), + ); + return true; + }); - return Promise.all(promises); - }) - .then(() => { - // re-fetch with expansions - return internalAccessList.get(access, { - id: data.id, - expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]'] - }, true /* <- skip masking */); - }) - .then((row) => { - // Audit log - data.meta = _.assign({}, data.meta || {}, row.meta); + await Promise.all(promises); - return internalAccessList.build(row) - .then(() => { - if (parseInt(row.proxy_host_count, 10)) { - return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); - } - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'access-list', - object_id: row.id, - meta: internalAccessList.maskItems(data) - }); - }) - .then(() => { - return internalAccessList.maskItems(row); - }); - }); + // re-fetch with expansions + const freshRow = await internalAccessList.get( + access, + { + id: data.id, + expand: ["owner", "items", "clients", "proxy_hosts.access_list.[clients,items]"], + }, + true // skip masking + ); + + // Audit log + data.meta = _.assign({}, data.meta || {}, freshRow.meta); + await internalAccessList.build(freshRow); + + if (Number.parseInt(freshRow.proxy_host_count, 10)) { + await internalNginx.bulkGenerateConfigs("proxy_host", freshRow.proxy_hosts); + } + + // Add to audit log + await internalAuditLog.add(access, { + action: "created", + object_type: "access-list", + object_id: freshRow.id, + meta: internalAccessList.maskItems(data), + }); + + return internalAccessList.maskItems(freshRow); }, /** @@ -108,129 +99,107 @@ const internalAccessList = { * @param {String} [data.items] * @return {Promise} */ - update: (access, data) => { - return access.can('access_lists:update', data.id) - .then((/*access_data*/) => { - return internalAccessList.get(access, {id: data.id}); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); - } - }) - .then(() => { - // patch name if specified - if (typeof data.name !== 'undefined' && data.name) { - return accessListModel - .query() - .where({id: data.id}) - .patch({ - name: data.name, - satisfy_any: data.satisfy_any, - pass_auth: data.pass_auth, - }); - } - }) - .then(() => { - // Check for items and add/update/remove them - if (typeof data.items !== 'undefined' && data.items) { - let promises = []; - let items_to_keep = []; + update: async (access, data) => { + await access.can("access_lists:update", data.id); + const row = await internalAccessList.get(access, { id: data.id }); + if (row.id !== data.id) { + // Sanity check that something crazy hasn't happened + throw new errs.InternalValidationError( + `Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); + } - data.items.map(function (item) { - if (item.password) { - promises.push(accessListAuthModel - .query() - .insert({ - access_list_id: data.id, - username: item.username, - password: item.password - }) - ); - } else { - // This was supplied with an empty password, which means keep it but don't change the password - items_to_keep.push(item.username); - } - }); - - let query = accessListAuthModel - .query() - .delete() - .where('access_list_id', data.id); - - if (items_to_keep.length) { - query.andWhere('username', 'NOT IN', items_to_keep); - } - - return query - .then(() => { - // Add new items - if (promises.length) { - return Promise.all(promises); - } - }); - } - }) - .then(() => { - // Check for clients and add/update/remove them - if (typeof data.clients !== 'undefined' && data.clients) { - let promises = []; - - data.clients.map(function (client) { - if (client.address) { - promises.push(accessListClientModel - .query() - .insert({ - access_list_id: data.id, - address: client.address, - directive: client.directive - }) - ); - } - }); - - let query = accessListClientModel - .query() - .delete() - .where('access_list_id', data.id); - - return query - .then(() => { - // Add new items - if (promises.length) { - return Promise.all(promises); - } - }); - } - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'access-list', - object_id: data.id, - meta: internalAccessList.maskItems(data) - }); - }) - .then(() => { - // re-fetch with expansions - return internalAccessList.get(access, { - id: data.id, - expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]'] - }, true /* <- skip masking */); - }) - .then((row) => { - return internalAccessList.build(row) - .then(() => { - if (parseInt(row.proxy_host_count, 10)) { - return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); - } - }).then(internalNginx.reload) - .then(() => { - return internalAccessList.maskItems(row); - }); + // patch name if specified + if (typeof data.name !== "undefined" && data.name) { + await accessListModel.query().where({ id: data.id }).patch({ + name: data.name, + satisfy_any: data.satisfy_any, + pass_auth: data.pass_auth, }); + } + + // Check for items and add/update/remove them + if (typeof data.items !== "undefined" && data.items) { + const promises = []; + const itemsToKeep = []; + + data.items.map((item) => { + if (item.password) { + promises.push( + accessListAuthModel.query().insert({ + access_list_id: data.id, + username: item.username, + password: item.password, + }), + ); + } else { + // This was supplied with an empty password, which means keep it but don't change the password + itemsToKeep.push(item.username); + } + return true; + }); + + const query = accessListAuthModel.query().delete().where("access_list_id", data.id); + + if (itemsToKeep.length) { + query.andWhere("username", "NOT IN", itemsToKeep); + } + + await query; + // Add new items + if (promises.length) { + await Promise.all(promises); + } + } + + // Check for clients and add/update/remove them + if (typeof data.clients !== "undefined" && data.clients) { + const clientPromises = []; + data.clients.map((client) => { + if (client.address) { + clientPromises.push( + accessListClientModel.query().insert({ + access_list_id: data.id, + address: client.address, + directive: client.directive, + }), + ); + } + return true; + }); + + const query = accessListClientModel.query().delete().where("access_list_id", data.id); + await query; + // Add new clitens + if (clientPromises.length) { + await Promise.all(clientPromises); + } + } + + // Add to audit log + await internalAuditLog.add(access, { + action: "updated", + object_type: "access-list", + object_id: data.id, + meta: internalAccessList.maskItems(data), + }); + + // re-fetch with expansions + const freshRow = await internalAccessList.get( + access, + { + id: data.id, + expand: ["owner", "items", "clients", "proxy_hosts.[certificate,access_list.[clients,items]]"], + }, + true // skip masking + ); + + await internalAccessList.build(freshRow) + if (Number.parseInt(freshRow.proxy_host_count, 10)) { + await internalNginx.bulkGenerateConfigs("proxy_host", freshRow.proxy_hosts); + } + await internalNginx.reload(); + return internalAccessList.maskItems(freshRow); }, /** @@ -239,52 +208,50 @@ const internalAccessList = { * @param {Integer} data.id * @param {Array} [data.expand] * @param {Array} [data.omit] - * @param {Boolean} [skip_masking] + * @param {Boolean} [skipMasking] * @return {Promise} */ - get: (access, data, skip_masking) => { - if (typeof data === 'undefined') { - data = {}; + get: async (access, data, skipMasking) => { + const thisData = data || {}; + const accessData = await access.can("access_lists:get", thisData.id) + + const query = accessListModel + .query() + .select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) + .leftJoin("proxy_host", function () { + this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( + "proxy_host.is_deleted", + "=", + 0, + ); + }) + .where("access_list.is_deleted", 0) + .andWhere("access_list.id", thisData.id) + .groupBy("access_list.id") + .allowGraph("[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]") + .first(); + + if (accessData.permission_visibility !== "all") { + query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); } - return access.can('access_lists:get', data.id) - .then((access_data) => { - let query = accessListModel - .query() - .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) - .leftJoin('proxy_host', function() { - this.on('proxy_host.access_list_id', '=', 'access_list.id') - .andOn('proxy_host.is_deleted', '=', 0); - }) - .where('access_list.is_deleted', 0) - .andWhere('access_list.id', data.id) - .groupBy('access_list.id') - .allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]') - .first(); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); + } - if (access_data.permission_visibility !== 'all') { - query.andWhere('access_list.owner_user_id', access.token.getUserId(1)); - } + let row = await query.then(utils.omitRow(omissions())); - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } - if (!skip_masking && typeof row.items !== 'undefined' && row.items) { - row = internalAccessList.maskItems(row); - } - // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); - } - return row; - }); + if (!row || !row.id) { + throw new errs.ItemNotFoundError(thisData.id); + } + if (!skipMasking && typeof row.items !== "undefined" && row.items) { + row = internalAccessList.maskItems(row); + } + // Custom omissions + if (typeof data.omit !== "undefined" && data.omit !== null) { + row = _.omit(row, data.omit); + } + return row; }, /** @@ -294,73 +261,64 @@ const internalAccessList = { * @param {String} [data.reason] * @returns {Promise} */ - delete: (access, data) => { - return access.can('access_lists:delete', data.id) - .then(() => { - return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']}); - }) - .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } + delete: async (access, data) => { + await access.can("access_lists:delete", data.id); + const row = await internalAccessList.get(access, { + id: data.id, + expand: ["proxy_hosts", "items", "clients"], + }); - // 1. update row to be deleted - // 2. update any proxy hosts that were using it (ignoring permissions) - // 3. reconfigure those hosts - // 4. audit log + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } - // 1. update row to be deleted - return accessListModel - .query() - .where('id', row.id) - .patch({ - is_deleted: 1 - }) - .then(() => { - // 2. update any proxy hosts that were using it (ignoring permissions) - if (row.proxy_hosts) { - return proxyHostModel - .query() - .where('access_list_id', '=', row.id) - .patch({access_list_id: 0}) - .then(() => { - // 3. reconfigure those hosts, then reload nginx + // 1. update row to be deleted + // 2. update any proxy hosts that were using it (ignoring permissions) + // 3. reconfigure those hosts + // 4. audit log - // set the access_list_id to zero for these items - row.proxy_hosts.map(function (val, idx) { - row.proxy_hosts[idx].access_list_id = 0; - }); + // 1. update row to be deleted + await accessListModel + .query() + .where("id", row.id) + .patch({ + is_deleted: 1, + }); - return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); - }) - .then(() => { - return internalNginx.reload(); - }); - } - }) - .then(() => { - // delete the htpasswd file - let htpasswd_file = internalAccessList.getFilename(row); + // 2. update any proxy hosts that were using it (ignoring permissions) + if (row.proxy_hosts) { + await proxyHostModel + .query() + .where("access_list_id", "=", row.id) + .patch({ access_list_id: 0 }); - try { - fs.unlinkSync(htpasswd_file); - } catch (err) { - // do nothing - } - }) - .then(() => { - // 4. audit log - return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'access-list', - object_id: row.id, - meta: _.omit(internalAccessList.maskItems(row), ['is_deleted', 'proxy_hosts']) - }); - }); - }) - .then(() => { + // 3. reconfigure those hosts, then reload nginx + // set the access_list_id to zero for these items + row.proxy_hosts.map((_val, idx) => { + row.proxy_hosts[idx].access_list_id = 0; return true; }); + + await internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); + } + + await internalNginx.reload(); + + // delete the htpasswd file + try { + fs.unlinkSync(internalAccessList.getFilename(row)); + } catch (_err) { + // do nothing + } + + // 4. audit log + await internalAuditLog.add(access, { + action: "deleted", + object_type: "access-list", + object_id: row.id, + meta: _.omit(internalAccessList.maskItems(row), ["is_deleted", "proxy_hosts"]), + }); + return true; }, /** @@ -368,75 +326,73 @@ const internalAccessList = { * * @param {Access} access * @param {Array} [expand] - * @param {String} [search_query] + * @param {String} [searchQuery] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access.can('access_lists:list') - .then((access_data) => { - let query = accessListModel - .query() - .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) - .leftJoin('proxy_host', function() { - this.on('proxy_host.access_list_id', '=', 'access_list.id') - .andOn('proxy_host.is_deleted', '=', 0); - }) - .where('access_list.is_deleted', 0) - .groupBy('access_list.id') - .allowGraph('[owner,items,clients]') - .orderBy('access_list.name', 'ASC'); + getAll: async (access, expand, searchQuery) => { + const accessData = await access.can("access_lists:list"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('access_list.owner_user_id', access.token.getUserId(1)); - } - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('name', 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); + const query = accessListModel + .query() + .select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) + .leftJoin("proxy_host", function () { + this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( + "proxy_host.is_deleted", + "=", + 0, + ); }) - .then((rows) => { - if (rows) { - rows.map(function (row, idx) { - if (typeof row.items !== 'undefined' && row.items) { - rows[idx] = internalAccessList.maskItems(row); - } - }); - } + .where("access_list.is_deleted", 0) + .groupBy("access_list.id") + .allowGraph("[owner,items,clients]") + .orderBy("access_list.name", "ASC"); - return rows; + if (accessData.permission_visibility !== "all") { + query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); + } + + // Query is used for searching + if (typeof searchQuery === "string") { + query.where(function () { + this.where("name", "like", `%${searchQuery}%`); }); + } + + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + const rows = await query.then(utils.omitRows(omissions())); + if (rows) { + rows.map((row, idx) => { + if (typeof row.items !== "undefined" && row.items) { + rows[idx] = internalAccessList.maskItems(row); + } + return true; + }); + } + return rows; }, /** - * Report use + * Count is used in reports * - * @param {Integer} user_id + * @param {Integer} userId * @param {String} visibility * @returns {Promise} */ - getCount: (user_id, visibility) => { - let query = accessListModel + getCount: async (userId, visibility) => { + const query = accessListModel .query() - .count('id as count') - .where('is_deleted', 0); + .count("id as count") + .where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", userId); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); + const row = await query.first(); + return Number.parseInt(row.count, 10); }, /** @@ -444,21 +400,21 @@ const internalAccessList = { * @returns {Object} */ maskItems: (list) => { - if (list && typeof list.items !== 'undefined') { - list.items.map(function (val, idx) { - let repeat_for = 8; - let first_char = '*'; + if (list && typeof list.items !== "undefined") { + list.items.map((val, idx) => { + let repeatFor = 8; + let firstChar = "*"; - if (typeof val.password !== 'undefined' && val.password) { - repeat_for = val.password.length - 1; - first_char = val.password.charAt(0); + if (typeof val.password !== "undefined" && val.password) { + repeatFor = val.password.length - 1; + firstChar = val.password.charAt(0); } - list.items[idx].hint = first_char + ('*').repeat(repeat_for); - list.items[idx].password = ''; + list.items[idx].hint = firstChar + "*".repeat(repeatFor); + list.items[idx].password = ""; + return true; }); } - return list; }, @@ -468,7 +424,7 @@ const internalAccessList = { * @returns {String} */ getFilename: (list) => { - return '/data/access/' + list.id; + return `/data/access/${list.id}`; }, /** @@ -478,63 +434,55 @@ const internalAccessList = { * @param {Array} list.items * @returns {Promise} */ - build: (list) => { - logger.info('Building Access file #' + list.id + ' for: ' + list.name); + build: async (list) => { + logger.info(`Building Access file #${list.id} for: ${list.name}`); - return new Promise((resolve, reject) => { - let htpasswd_file = internalAccessList.getFilename(list); + const htpasswdFile = internalAccessList.getFilename(list); - // 1. remove any existing access file - try { - fs.unlinkSync(htpasswd_file); - } catch (err) { - // do nothing - } + // 1. remove any existing access file + try { + fs.unlinkSync(htpasswdFile); + } catch (_err) { + // do nothing + } - // 2. create empty access file - try { - fs.writeFileSync(htpasswd_file, '', {encoding: 'utf8'}); - resolve(htpasswd_file); - } catch (err) { - reject(err); - } - }) - .then((htpasswd_file) => { - // 3. generate password for each user - if (list.items.length) { - return new Promise((resolve, reject) => { - batchflow(list.items).sequential() - .each((i, item, next) => { - if (typeof item.password !== 'undefined' && item.password.length) { - logger.info('Adding: ' + item.username); + // 2. create empty access file + fs.writeFileSync(htpasswdFile, '', {encoding: 'utf8'}); - utils.execFile('openssl', ['passwd', '-apr1', item.password]) - .then((res) => { - try { - fs.appendFileSync(htpasswd_file, item.username + ':' + res + '\n', {encoding: 'utf8'}); - } catch (err) { - reject(err); - } - next(); - }) - .catch((err) => { - logger.error(err); - next(err); - }); - } - }) - .error((err) => { - logger.error(err); - reject(err); - }) - .end((results) => { - logger.success('Built Access file #' + list.id + ' for: ' + list.name); - resolve(results); - }); + // 3. generate password for each user + if (list.items.length) { + await new Promise((resolve, reject) => { + batchflow(list.items).sequential() + .each((_i, item, next) => { + if (item.password?.length) { + logger.info(`Adding: ${item.username}`); + + utils.execFile('openssl', ['passwd', '-apr1', item.password]) + .then((res) => { + try { + fs.appendFileSync(htpasswdFile, `${item.username}:${res}\n`, {encoding: 'utf8'}); + } catch (err) { + reject(err); + } + next(); + }) + .catch((err) => { + logger.error(err); + next(err); + }); + } + }) + .error((err) => { + logger.error(err); + reject(err); + }) + .end((results) => { + logger.success(`Built Access file #${list.id} for: ${list.name}`); + resolve(results); }); - } }); + } } -}; +} -module.exports = internalAccessList; +export default internalAccessList; diff --git a/backend/internal/audit-log.js b/backend/internal/audit-log.js index 60bdd2ef..02700dc5 100644 --- a/backend/internal/audit-log.js +++ b/backend/internal/audit-log.js @@ -1,6 +1,6 @@ -const error = require('../lib/error'); -const auditLogModel = require('../models/audit-log'); -const {castJsonIfNeed} = require('../lib/helpers'); +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import auditLogModel from "../models/audit-log.js"; const internalAuditLog = { @@ -9,32 +9,60 @@ const internalAuditLog = { * * @param {Access} access * @param {Array} [expand] - * @param {String} [search_query] + * @param {String} [searchQuery] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access.can('auditlog:list') - .then(() => { - let query = auditLogModel - .query() - .orderBy('created_on', 'DESC') - .orderBy('id', 'DESC') - .limit(100) - .allowGraph('[user]'); + getAll: async (access, expand, searchQuery) => { + await access.can("auditlog:list"); - // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { - query.where(function () { - this.where(castJsonIfNeed('meta'), 'like', '%' + search_query + '%'); - }); - } + const query = auditLogModel + .query() + .orderBy("created_on", "DESC") + .orderBy("id", "DESC") + .limit(100) + .allowGraph("[user]"); - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query; + // Query is used for searching + if (typeof searchQuery === "string" && searchQuery.length > 0) { + query.where(function () { + this.where(castJsonIfNeed("meta"), "like", `%${searchQuery}`); }); + } + + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + return await query; + }, + + /** + * @param {Access} access + * @param {Object} [data] + * @param {Integer} [data.id] Defaults to the token user + * @param {Array} [data.expand] + * @return {Promise} + */ + get: async (access, data) => { + await access.can("auditlog:list"); + + const query = auditLogModel + .query() + .andWhere("id", data.id) + .allowGraph("[user]") + .first(); + + if (typeof data.expand !== "undefined" && data.expand !== null) { + query.withGraphFetched(`[${data.expand.join(", ")}]`); + } + + const row = await query; + + if (!row?.id) { + throw new errs.ItemNotFoundError(data.id); + } + + return row; }, /** @@ -51,29 +79,24 @@ const internalAuditLog = { * @param {Object} [data.meta] * @returns {Promise} */ - add: (access, data) => { - return new Promise((resolve, reject) => { - // Default the user id - if (typeof data.user_id === 'undefined' || !data.user_id) { - data.user_id = access.token.getUserId(1); - } + add: async (access, data) => { + if (typeof data.user_id === "undefined" || !data.user_id) { + data.user_id = access.token.getUserId(1); + } - if (typeof data.action === 'undefined' || !data.action) { - reject(new error.InternalValidationError('Audit log entry must contain an Action')); - } else { - // Make sure at least 1 of the IDs are set and action - resolve(auditLogModel - .query() - .insert({ - user_id: data.user_id, - action: data.action, - object_type: data.object_type || '', - object_id: data.object_id || 0, - meta: data.meta || {} - })); - } + if (typeof data.action === "undefined" || !data.action) { + throw new errs.InternalValidationError("Audit log entry must contain an Action"); + } + + // Make sure at least 1 of the IDs are set and action + return await auditLogModel.query().insert({ + user_id: data.user_id, + action: data.action, + object_type: data.object_type || "", + object_id: data.object_id || 0, + meta: data.meta || {}, }); - } + }, }; -module.exports = internalAuditLog; +export default internalAuditLog; diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index f2e845a2..1bb49ad8 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -1,44 +1,45 @@ -const _ = require('lodash'); -const fs = require('fs'); -const https = require('https'); -const tempWrite = require('temp-write'); -const moment = require('moment'); -const archiver = require('archiver'); -const path = require('path'); -const { isArray } = require('lodash'); -const logger = require('../logger').ssl; -const config = require('../lib/config'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const certbot = require('../lib/certbot'); -const certificateModel = require('../models/certificate'); -const tokenModel = require('../models/token'); -const dnsPlugins = require('../global/certbot-dns-plugins.json'); -const internalAuditLog = require('./audit-log'); -const internalNginx = require('./nginx'); -const internalHost = require('./host'); +import fs from "node:fs"; +import https from "node:https"; +import path from "path"; +import archiver from "archiver"; +import _ from "lodash"; +import moment from "moment"; +import tempWrite from "temp-write"; +import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" }; +import { installPlugin } from "../lib/certbot.js"; +import { useLetsencryptServer, useLetsencryptStaging } from "../lib/config.js"; +import error from "../lib/error.js"; +import utils from "../lib/utils.js"; +import { debug, ssl as logger } from "../logger.js"; +import certificateModel from "../models/certificate.js"; +import tokenModel from "../models/token.js"; +import userModel from "../models/user.js"; +import internalAuditLog from "./audit-log.js"; +import internalHost from "./host.js"; +import internalNginx from "./nginx.js"; +const letsencryptConfig = "/etc/letsencrypt.ini"; +const certbotCommand = "certbot"; +const certbotLogsDir = "/data/logs"; +const certbotWorkDir = "/tmp/letsencrypt-lib"; -const letsencryptStaging = config.useLetsencryptStaging(); -const letsencryptServer = config.useLetsencryptServer(); -const letsencryptConfig = '/etc/letsencrypt.ini'; -const certbotCommand = 'certbot'; - -function omissions() { - return ['is_deleted', 'owner.is_deleted']; -} +const omissions = () => { + return ["is_deleted", "owner.is_deleted", "meta.dns_provider_credentials"]; +}; const internalCertificate = { - - allowedSslFiles: ['certificate', 'certificate_key', 'intermediate_certificate'], - intervalTimeout: 1000 * 60 * 60, // 1 hour - interval: null, - intervalProcessing: false, - renewBeforeExpirationBy: [30, 'days'], + allowedSslFiles: ["certificate", "certificate_key", "intermediate_certificate"], + intervalTimeout: 1000 * 60 * 60, // 1 hour + interval: null, + intervalProcessing: false, + renewBeforeExpirationBy: [30, "days"], initTimer: () => { - logger.info('Let\'s Encrypt Renewal Timer initialized'); - internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.intervalTimeout); + logger.info("Let's Encrypt Renewal Timer initialized"); + internalCertificate.interval = setInterval( + internalCertificate.processExpiringHosts, + internalCertificate.intervalTimeout, + ); // And do this now as well internalCertificate.processExpiringHosts(); }, @@ -49,16 +50,20 @@ const internalCertificate = { processExpiringHosts: () => { if (!internalCertificate.intervalProcessing) { internalCertificate.intervalProcessing = true; - logger.info('Renewing SSL certs expiring within ' + internalCertificate.renewBeforeExpirationBy[0] + ' ' + internalCertificate.renewBeforeExpirationBy[1] + ' ...'); + logger.info( + `Renewing SSL certs expiring within ${internalCertificate.renewBeforeExpirationBy[0]} ${internalCertificate.renewBeforeExpirationBy[1]} ...`, + ); - const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss'); + const expirationThreshold = moment() + .add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]) + .format("YYYY-MM-DD HH:mm:ss"); // Fetch all the letsencrypt certs from the db that will expire within the configured threshold certificateModel .query() - .where('is_deleted', 0) - .andWhere('provider', 'letsencrypt') - .andWhere('expires_on', '<', expirationThreshold) + .where("is_deleted", 0) + .andWhere("provider", "letsencrypt") + .andWhere("expires_on", "<", expirationThreshold) .then((certificates) => { if (!certificates || !certificates.length) { return null; @@ -70,16 +75,16 @@ const internalCertificate = { */ let sequence = Promise.resolve(); - certificates.forEach(function (certificate) { + certificates.forEach((certificate) => { sequence = sequence.then(() => internalCertificate .renew( { can: () => Promise.resolve({ - permission_visibility: 'all', + permission_visibility: "all", }), - token: new tokenModel(), + token: tokenModel(), }, { id: certificate.id }, ) @@ -93,7 +98,7 @@ const internalCertificate = { return sequence; }) .then(() => { - logger.info('Completed SSL cert renew process'); + logger.info("Completed SSL cert renew process"); internalCertificate.intervalProcessing = false; }) .catch((err) => { @@ -108,143 +113,126 @@ const internalCertificate = { * @param {Object} data * @returns {Promise} */ - create: (access, data) => { - return access.can('certificates:create', data) - .then(() => { - data.owner_user_id = access.token.getUserId(1); + create: async (access, data) => { + await access.can("certificates:create", data); + data.owner_user_id = access.token.getUserId(1); - if (data.provider === 'letsencrypt') { - data.nice_name = data.domain_names.join(', '); + if (data.provider === "letsencrypt") { + data.nice_name = data.domain_names.join(", "); + } + + // this command really should clean up and delete the cert if it can't fully succeed + const certificate = await certificateModel.query().insertAndFetch(data); + + try { + if (certificate.provider === "letsencrypt") { + // Request a new Cert from LE. Let the fun begin. + + // 1. Find out any hosts that are using any of the hostnames in this cert + // 2. Disable them in nginx temporarily + // 3. Generate the LE config + // 4. Request cert + // 5. Remove LE config + // 6. Re-instate previously disabled hosts + + // 1. Find out any hosts that are using any of the hostnames in this cert + const inUseResult = await internalHost.getHostsWithDomains(certificate.domain_names); + + // 2. Disable them in nginx temporarily + await internalCertificate.disableInUseHosts(inUseResult); + + const user = await userModel.query().where("is_deleted", 0).andWhere("id", data.owner_user_id).first(); + if (!user || !user.email) { + throw new error.ValidationError( + "A valid email address must be set on your user account to use Let's Encrypt", + ); } - return certificateModel - .query() - .insertAndFetch(data) - .then(utils.omitRow(omissions())); - }) - .then((certificate) => { - if (certificate.provider === 'letsencrypt') { - // Request a new Cert from LE. Let the fun begin. - - // 1. Find out any hosts that are using any of the hostnames in this cert - // 2. Disable them in nginx temporarily - // 3. Generate the LE config - // 4. Request cert - // 5. Remove LE config - // 6. Re-instate previously disabled hosts - - // 1. Find out any hosts that are using any of the hostnames in this cert - return internalHost.getHostsWithDomains(certificate.domain_names) - .then((in_use_result) => { - // 2. Disable them in nginx temporarily - return internalCertificate.disableInUseHosts(in_use_result) - .then(() => { - return in_use_result; - }); - }) - .then((in_use_result) => { - // With DNS challenge no config is needed, so skip 3 and 5. - if (certificate.meta.dns_challenge) { - return internalNginx.reload().then(() => { - // 4. Request cert - return internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate); - }) - .then(internalNginx.reload) - .then(() => { - // 6. Re-instate previously disabled hosts - return internalCertificate.enableInUseHosts(in_use_result); - }) - .then(() => { - return certificate; - }) - .catch((err) => { - // In the event of failure, revert things and throw err back - return internalCertificate.enableInUseHosts(in_use_result) - .then(internalNginx.reload) - .then(() => { - throw err; - }); - }); - } else { - // 3. Generate the LE config - return internalNginx.generateLetsEncryptRequestConfig(certificate) - .then(internalNginx.reload) - .then(async() => await new Promise((r) => setTimeout(r, 5000))) - .then(() => { - // 4. Request cert - return internalCertificate.requestLetsEncryptSsl(certificate); - }) - .then(() => { - // 5. Remove LE config - return internalNginx.deleteLetsEncryptRequestConfig(certificate); - }) - .then(internalNginx.reload) - .then(() => { - // 6. Re-instate previously disabled hosts - return internalCertificate.enableInUseHosts(in_use_result); - }) - .then(() => { - return certificate; - }) - .catch((err) => { - // In the event of failure, revert things and throw err back - return internalNginx.deleteLetsEncryptRequestConfig(certificate) - .then(() => { - return internalCertificate.enableInUseHosts(in_use_result); - }) - .then(internalNginx.reload) - .then(() => { - throw err; - }); - }); - } - }) - .then(() => { - // At this point, the letsencrypt cert should exist on disk. - // Lets get the expiry date from the file and update the row silently - return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem') - .then((cert_info) => { - return certificateModel - .query() - .patchAndFetchById(certificate.id, { - expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss') - }) - .then(utils.omitRow(omissions())) - .then((saved_row) => { - // Add cert data for audit log - saved_row.meta = _.assign({}, saved_row.meta, { - letsencrypt_certificate: cert_info - }); - - return saved_row; - }); - }); - }).catch(async (error) => { - // Delete the certificate from the database if it was not created successfully - await certificateModel - .query() - .deleteById(certificate.id); - - throw error; - }); + // With DNS challenge no config is needed, so skip 3 and 5. + if (certificate.meta?.dns_challenge) { + try { + await internalNginx.reload(); + // 4. Request cert + await internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate, user.email); + await internalNginx.reload(); + // 6. Re-instate previously disabled hosts + await internalCertificate.enableInUseHosts(inUseResult); + } catch (err) { + // In the event of failure, revert things and throw err back + await internalCertificate.enableInUseHosts(inUseResult); + await internalNginx.reload(); + throw err; + } } else { - return certificate; + // 3. Generate the LE config + try { + await internalNginx.generateLetsEncryptRequestConfig(certificate); + await internalNginx.reload(); + setTimeout(() => {}, 5000); + // 4. Request cert + await internalCertificate.requestLetsEncryptSsl(certificate, user.email); + // 5. Remove LE config + await internalNginx.deleteLetsEncryptRequestConfig(certificate); + await internalNginx.reload(); + // 6. Re-instate previously disabled hosts + await internalCertificate.enableInUseHosts(inUseResult); + } catch (err) { + // In the event of failure, revert things and throw err back + await internalNginx.deleteLetsEncryptRequestConfig(certificate); + await internalCertificate.enableInUseHosts(inUseResult); + await internalNginx.reload(); + throw err; + } } - }).then((certificate) => { - data.meta = _.assign({}, data.meta || {}, certificate.meta); + // At this point, the letsencrypt cert should exist on disk. + // Lets get the expiry date from the file and update the row silently + try { + const certInfo = await internalCertificate.getCertificateInfoFromFile( + `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, + ); + const savedRow = await certificateModel + .query() + .patchAndFetchById(certificate.id, { + expires_on: moment(certInfo.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), + }) + .then(utils.omitRow(omissions())); - // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'certificate', - object_id: certificate.id, - meta: data - }) - .then(() => { - return certificate; + // Add cert data for audit log + savedRow.meta = _.assign({}, savedRow.meta, { + letsencrypt_certificate: certInfo, }); - }); + + await internalCertificate.addCreatedAuditLog(access, certificate.id, savedRow); + + return savedRow; + } catch (err) { + // Delete the certificate from the database if it was not created successfully + await certificateModel.query().deleteById(certificate.id); + throw err; + } + } + } catch (err) { + // Delete the certificate here. This is a hard delete, since it never existed properly + await certificateModel.query().deleteById(certificate.id); + throw err; + } + + data.meta = _.assign({}, data.meta || {}, certificate.meta); + + // Add to audit log + await internalCertificate.addCreatedAuditLog(access, certificate.id, utils.omitRow(omissions())(data)); + + return utils.omitRow(omissions())(certificate); + }, + + addCreatedAuditLog: async (access, certificate_id, meta) => { + await internalAuditLog.add(access, { + action: "created", + object_type: "certificate", + object_id: certificate_id, + meta: meta, + }); }, /** @@ -255,42 +243,39 @@ const internalCertificate = { * @param {String} [data.name] * @return {Promise} */ - update: (access, data) => { - return access.can('certificates:update', data.id) - .then((/*access_data*/) => { - return internalCertificate.get(access, {id: data.id}); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Certificate could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); - } + update: async (access, data) => { + await access.can("certificates:update", data.id); + const row = await internalCertificate.get(access, { id: data.id }); - return certificateModel - .query() - .patchAndFetchById(row.id, data) - .then(utils.omitRow(omissions())) - .then((saved_row) => { - saved_row.meta = internalCertificate.cleanMeta(saved_row.meta); - data.meta = internalCertificate.cleanMeta(data.meta); + if (row.id !== data.id) { + // Sanity check that something crazy hasn't happened + throw new error.InternalValidationError( + `Certificate could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); + } - // Add row.nice_name for custom certs - if (saved_row.provider === 'other') { - data.nice_name = saved_row.nice_name; - } + const savedRow = await certificateModel + .query() + .patchAndFetchById(row.id, data) + .then(utils.omitRow(omissions())); - // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'certificate', - object_id: row.id, - meta: _.omit(data, ['expires_on']) // this prevents json circular reference because expires_on might be raw - }) - .then(() => { - return saved_row; - }); - }); - }); + savedRow.meta = internalCertificate.cleanMeta(savedRow.meta); + data.meta = internalCertificate.cleanMeta(data.meta); + + // Add row.nice_name for custom certs + if (savedRow.provider === "other") { + data.nice_name = savedRow.nice_name; + } + + // Add to audit log + await internalAuditLog.add(access, { + action: "updated", + object_type: "certificate", + object_id: row.id, + meta: _.omit(data, ["expires_on"]), // this prevents json circular reference because expires_on might be raw + }); + + return savedRow; }, /** @@ -301,43 +286,49 @@ const internalCertificate = { * @param {Array} [data.omit] * @return {Promise} */ - get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; + get: async (access, data) => { + const accessData = await access.can("certificates:get", data.id); + const query = certificateModel + .query() + .where("is_deleted", 0) + .andWhere("id", data.id) + .allowGraph("[owner,proxy_hosts,redirection_hosts,dead_hosts,streams]") + .first(); + + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } - return access.can('certificates:get', data.id) - .then((access_data) => { - let query = certificateModel - .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[owner]') - .allowGraph('[proxy_hosts]') - .allowGraph('[redirection_hosts]') - .allowGraph('[dead_hosts]') - .first(); + if (typeof data.expand !== "undefined" && data.expand !== null) { + query.withGraphFetched(`[${data.expand.join(", ")}]`); + } - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } + const row = await query.then(utils.omitRow(omissions())); + if (!row || !row.id) { + throw new error.ItemNotFoundError(data.id); + } + // Custom omissions + if (typeof data.omit !== "undefined" && data.omit !== null) { + return _.omit(row, [...data.omit]); + } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } + return internalCertificate.cleanExpansions(row); + }, - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } - // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); - } - return row; - }); + cleanExpansions: (row) => { + if (typeof row.proxy_hosts !== "undefined") { + row.proxy_hosts = utils.omitRows(["is_deleted"])(row.proxy_hosts); + } + if (typeof row.redirection_hosts !== "undefined") { + row.redirection_hosts = utils.omitRows(["is_deleted"])(row.redirection_hosts); + } + if (typeof row.dead_hosts !== "undefined") { + row.dead_hosts = utils.omitRows(["is_deleted"])(row.dead_hosts); + } + if (typeof row.streams !== "undefined") { + row.streams = utils.omitRows(["is_deleted"])(row.streams); + } + return row; }, /** @@ -346,61 +337,50 @@ const internalCertificate = { * @param {Number} data.id * @returns {Promise} */ - download: (access, data) => { - return new Promise((resolve, reject) => { - access.can('certificates:get', data) - .then(() => { - return internalCertificate.get(access, data); - }) - .then((certificate) => { - if (certificate.provider === 'letsencrypt') { - const zipDirectory = '/etc/letsencrypt/live/npm-' + data.id; + download: async (access, data) => { + await access.can("certificates:get", data); + const certificate = await internalCertificate.get(access, data); + if (certificate.provider === "letsencrypt") { + const zipDirectory = internalCertificate.getLiveCertPath(data.id); + if (!fs.existsSync(zipDirectory)) { + throw new error.ItemNotFoundError(`Certificate ${certificate.nice_name} does not exists`); + } - if (!fs.existsSync(zipDirectory)) { - throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists'); - } + const certFiles = fs + .readdirSync(zipDirectory) + .filter((fn) => fn.endsWith(".pem")) + .map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); - let certFiles = fs.readdirSync(zipDirectory) - .filter((fn) => fn.endsWith('.pem')) - .map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); - const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`; - const opName = '/tmp/' + downloadName; - internalCertificate.zipFiles(certFiles, opName) - .then(() => { - logger.debug('zip completed : ', opName); - const resp = { - fileName: opName - }; - resolve(resp); - }).catch((err) => reject(err)); - } else { - throw new error.ValidationError('Only Let\'sEncrypt certificates can be downloaded'); - } - }).catch((err) => reject(err)); - }); + const downloadName = `npm-${data.id}-${Date.now()}.zip`; + const opName = `/tmp/${downloadName}`; + + await internalCertificate.zipFiles(certFiles, opName); + debug(logger, "zip completed : ", opName); + return { + fileName: opName, + }; + } + throw new error.ValidationError("Only Let'sEncrypt certificates can be downloaded"); }, /** - * @param {String} source - * @param {String} out - * @returns {Promise} - */ - zipFiles(source, out) { - const archive = archiver('zip', { zlib: { level: 9 } }); - const stream = fs.createWriteStream(out); + * @param {String} source + * @param {String} out + * @returns {Promise} + */ + zipFiles: async (source, out) => { + const archive = archiver("zip", { zlib: { level: 9 } }); + const stream = fs.createWriteStream(out); return new Promise((resolve, reject) => { - source - .map((fl) => { - let fileName = path.basename(fl); - logger.debug(fl, 'added to certificate zip'); - archive.file(fl, { name: fileName }); - }); - archive - .on('error', (err) => reject(err)) - .pipe(stream); - - stream.on('close', () => resolve()); + source.map((fl) => { + const fileName = path.basename(fl); + debug(logger, fl, "added to certificate zip"); + archive.file(fl, { name: fileName }); + return true; + }); + archive.on("error", (err) => reject(err)).pipe(stream); + stream.on("close", () => resolve()); archive.finalize(); }); }, @@ -412,43 +392,33 @@ const internalCertificate = { * @param {String} [data.reason] * @returns {Promise} */ - delete: (access, data) => { - return access.can('certificates:delete', data.id) - .then(() => { - return internalCertificate.get(access, {id: data.id}); - }) - .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } + delete: async (access, data) => { + await access.can("certificates:delete", data.id); + const row = await internalCertificate.get(access, { id: data.id }); - return certificateModel - .query() - .where('id', row.id) - .patch({ - is_deleted: 1 - }) - .then(() => { - // Add to audit log - row.meta = internalCertificate.cleanMeta(row.meta); + if (!row || !row.id) { + throw new error.ItemNotFoundError(data.id); + } - return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'certificate', - object_id: row.id, - meta: _.omit(row, omissions()) - }); - }) - .then(() => { - if (row.provider === 'letsencrypt') { - // Revoke the cert - return internalCertificate.revokeLetsEncryptSsl(row); - } - }); - }) - .then(() => { - return true; - }); + await certificateModel.query().where("id", row.id).patch({ + is_deleted: 1, + }); + + // Add to audit log + row.meta = internalCertificate.cleanMeta(row.meta); + + await internalAuditLog.add(access, { + action: "deleted", + object_type: "certificate", + object_id: row.id, + meta: _.omit(row, omissions()), + }); + + if (row.provider === "letsencrypt") { + // Revoke the cert + await internalCertificate.revokeLetsEncryptSsl(row); + } + return true; }, /** @@ -456,82 +426,77 @@ const internalCertificate = { * * @param {Access} access * @param {Array} [expand] - * @param {String} [search_query] + * @param {String} [searchQuery] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access.can('certificates:list') - .then((access_data) => { - let query = certificateModel - .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[owner]') - .allowGraph('[proxy_hosts]') - .allowGraph('[redirection_hosts]') - .allowGraph('[dead_hosts]') - .orderBy('nice_name', 'ASC'); + getAll: async (access, expand, searchQuery) => { + const accessData = await access.can("certificates:list"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } + const query = certificateModel + .query() + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,proxy_hosts,redirection_hosts,dead_hosts,streams]") + .orderBy("nice_name", "ASC"); - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('nice_name', 'like', '%' + search_query + '%'); - }); - } + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); + // Query is used for searching + if (typeof searchQuery === "string") { + query.where(function () { + this.where("nice_name", "like", `%${searchQuery}%`); }); + } + + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + const r = await query.then(utils.omitRows(omissions())); + for (let i = 0; i < r.length; i++) { + r[i] = internalCertificate.cleanExpansions(r[i]); + } + return r; }, /** * Report use * - * @param {Number} user_id + * @param {Number} userId * @param {String} visibility * @returns {Promise} */ - getCount: (user_id, visibility) => { - let query = certificateModel - .query() - .count('id as count') - .where('is_deleted', 0); + getCount: async (userId, visibility) => { + const query = certificateModel.query().count("id as count").where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", userId); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); + const row = await query.first(); + return Number.parseInt(row.count, 10); }, /** * @param {Object} certificate * @returns {Promise} */ - writeCustomCert: (certificate) => { - logger.info('Writing Custom Certificate:', certificate); + writeCustomCert: async (certificate) => { + logger.info("Writing Custom Certificate:", certificate); - const dir = '/data/custom_ssl/npm-' + certificate.id; + const dir = `/data/custom_ssl/npm-${certificate.id}`; return new Promise((resolve, reject) => { - if (certificate.provider === 'letsencrypt') { - reject(new Error('Refusing to write letsencrypt certs here')); + if (certificate.provider === "letsencrypt") { + reject(new Error("Refusing to write letsencrypt certs here")); return; } let certData = certificate.meta.certificate; - if (typeof certificate.meta.intermediate_certificate !== 'undefined') { - certData = certData + '\n' + certificate.meta.intermediate_certificate; + if (typeof certificate.meta.intermediate_certificate !== "undefined") { + certData = `${certData}\n${certificate.meta.intermediate_certificate}`; } try { @@ -543,40 +508,37 @@ const internalCertificate = { return; } - fs.writeFile(dir + '/fullchain.pem', certData, function (err) { + fs.writeFile(`${dir}/fullchain.pem`, certData, (err) => { if (err) { reject(err); } else { resolve(); } }); - }) - .then(() => { - return new Promise((resolve, reject) => { - fs.writeFile(dir + '/privkey.pem', certificate.meta.certificate_key, function (err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); + }).then(() => { + return new Promise((resolve, reject) => { + fs.writeFile(`${dir}/privkey.pem`, certificate.meta.certificate_key, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } }); }); + }); }, /** * @param {Access} access * @param {Object} data * @param {Array} data.domain_names - * @param {String} data.meta.letsencrypt_email - * @param {Boolean} data.meta.letsencrypt_agree * @returns {Promise} */ - createQuickCertificate: (access, data) => { - return internalCertificate.create(access, { - provider: 'letsencrypt', + createQuickCertificate: async (access, data) => { + return await internalCertificate.create(access, { + provider: "letsencrypt", domain_names: data.domain_names, - meta: data.meta + meta: data.meta, }); }, @@ -589,45 +551,39 @@ const internalCertificate = { * @returns {Promise} */ validate: (data) => { - return new Promise((resolve) => { - // Put file contents into an object - let files = {}; - _.map(data.files, (file, name) => { - if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { - files[name] = file.data.toString(); - } - }); - - resolve(files); - }) - .then((files) => { - // For each file, create a temp file and write the contents to it - // Then test it depending on the file type - let promises = []; - _.map(files, (content, type) => { - promises.push(new Promise((resolve) => { - if (type === 'certificate_key') { - resolve(internalCertificate.checkPrivateKey(content)); - } else { - // this should handle `certificate` and intermediate certificate - resolve(internalCertificate.getCertificateInfo(content, true)); - } - }).then((res) => { - return {[type]: res}; - })); - }); - - return Promise.all(promises) - .then((files) => { - let data = {}; - - _.each(files, (file) => { - data = _.assign({}, data, file); - }); - - return data; - }); + // Put file contents into an object + const files = {}; + _.map(data.files, (file, name) => { + if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { + files[name] = file.data.toString(); + } + }); + + // For each file, create a temp file and write the contents to it + // Then test it depending on the file type + const promises = []; + _.map(files, (content, type) => { + promises.push( + new Promise((resolve) => { + if (type === "certificate_key") { + resolve(internalCertificate.checkPrivateKey(content)); + } else { + // this should handle `certificate` and intermediate certificate + resolve(internalCertificate.getCertificateInfo(content, true)); + } + }).then((res) => { + return { [type]: res }; + }), + ); + }); + + return Promise.all(promises).then((files) => { + let data = {}; + _.each(files, (file) => { + data = _.assign({}, data, file); }); + return data; + }); }, /** @@ -637,73 +593,62 @@ const internalCertificate = { * @param {Object} data.files * @returns {Promise} */ - upload: (access, data) => { - return internalCertificate.get(access, {id: data.id}) - .then((row) => { - if (row.provider !== 'other') { - throw new error.ValidationError('Cannot upload certificates for this type of provider'); - } + upload: async (access, data) => { + const row = await internalCertificate.get(access, { id: data.id }); + if (row.provider !== "other") { + throw new error.ValidationError("Cannot upload certificates for this type of provider"); + } - return internalCertificate.validate(data) - .then((validations) => { - if (typeof validations.certificate === 'undefined') { - throw new error.ValidationError('Certificate file was not provided'); - } + const validations = await internalCertificate.validate(data); + if (typeof validations.certificate === "undefined") { + throw new error.ValidationError("Certificate file was not provided"); + } - _.map(data.files, (file, name) => { - if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { - row.meta[name] = file.data.toString(); - } - }); + _.map(data.files, (file, name) => { + if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { + row.meta[name] = file.data.toString(); + } + }); - // TODO: This uses a mysql only raw function that won't translate to postgres - return internalCertificate.update(access, { - id: data.id, - expires_on: moment(validations.certificate.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'), - domain_names: [validations.certificate.cn], - meta: _.clone(row.meta) // Prevent the update method from changing this value that we'll use later - }) - .then((certificate) => { - certificate.meta = row.meta; - return internalCertificate.writeCustomCert(certificate); - }); - }) - .then(() => { - return _.pick(row.meta, internalCertificate.allowedSslFiles); - }); - }); + const certificate = await internalCertificate.update(access, { + id: data.id, + expires_on: moment(validations.certificate.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), + domain_names: [validations.certificate.cn], + meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later + }); + + certificate.meta = row.meta; + await internalCertificate.writeCustomCert(certificate); + return _.pick(row.meta, internalCertificate.allowedSslFiles); }, /** * Uses the openssl command to validate the private key. * It will save the file to disk first, then run commands on it, then delete the file. * - * @param {String} private_key This is the entire key contents as a string + * @param {String} privateKey This is the entire key contents as a string */ - checkPrivateKey: (private_key) => { - return tempWrite(private_key, '/tmp') - .then((filepath) => { - return new Promise((resolve, reject) => { - const failTimeout = setTimeout(() => { - reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.')); - }, 10000); - utils - .exec('openssl pkey -in ' + filepath + ' -check -noout 2>&1 ') - .then((result) => { - clearTimeout(failTimeout); - if (!result.toLowerCase().includes('key is valid')) { - reject(new error.ValidationError('Result Validation Error: ' + result)); - } - fs.unlinkSync(filepath); - resolve(true); - }) - .catch((err) => { - clearTimeout(failTimeout); - fs.unlinkSync(filepath); - reject(new error.ValidationError('Certificate Key is not valid (' + err.message + ')', err)); - }); - }); - }); + checkPrivateKey: async (privateKey) => { + const filepath = await tempWrite(privateKey, "/tmp"); + const failTimeout = setTimeout(() => { + throw new error.ValidationError( + "Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.", + ); + }, 10000); + + try { + const result = await utils.exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `); + clearTimeout(failTimeout); + if (!result.toLowerCase().includes("key is valid")) { + throw new error.ValidationError(`Result Validation Error: ${result}`); + } + fs.unlinkSync(filepath); + return true; + } catch (err) { + clearTimeout(failTimeout); + fs.unlinkSync(filepath); + throw new error.ValidationError(`Certificate Key is not valid (${err.message})`, err); + } }, /** @@ -711,100 +656,92 @@ const internalCertificate = { * It will save the file to disk first, then run commands on it, then delete the file. * * @param {String} certificate This is the entire cert contents as a string - * @param {Boolean} [throw_expired] Throw when the certificate is out of date + * @param {Boolean} [throwExpired] Throw when the certificate is out of date */ - getCertificateInfo: (certificate, throw_expired) => { - return tempWrite(certificate, '/tmp') - .then((filepath) => { - return internalCertificate.getCertificateInfoFromFile(filepath, throw_expired) - .then((certData) => { - fs.unlinkSync(filepath); - return certData; - }).catch((err) => { - fs.unlinkSync(filepath); - throw err; - }); - }); + getCertificateInfo: async (certificate, throwExpired) => { + try { + const filepath = await tempWrite(certificate, "/tmp"); + const certData = await internalCertificate.getCertificateInfoFromFile(filepath, throwExpired); + fs.unlinkSync(filepath); + return certData; + } catch (err) { + fs.unlinkSync(filepath); + throw err; + } }, /** * Uses the openssl command to both validate and get info out of the certificate. * It will save the file to disk first, then run commands on it, then delete the file. * - * @param {String} certificate_file The file location on disk + * @param {String} certificateFile The file location on disk * @param {Boolean} [throw_expired] Throw when the certificate is out of date */ - getCertificateInfoFromFile: (certificate_file, throw_expired) => { - let certData = {}; + getCertificateInfoFromFile: async (certificateFile, throw_expired) => { + const certData = {}; - return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout') - .then((result) => { - // Examples: - // subject=CN = *.jc21.com - // subject=CN = something.example.com - const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; - const match = regex.exec(result); - if (match && typeof match[1] !== 'undefined') { - certData['cn'] = match[1]; - } - }) - .then(() => { - return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout'); - }) + try { + const result = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-subject", "-noout"]); + // Examples: + // subject=CN = *.jc21.com + // subject=CN = something.example.com + const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; + const match = regex.exec(result); + if (match && typeof match[1] !== "undefined") { + certData.cn = match[1]; + } - .then((result) => { - // Examples: - // issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 - // issuer=C = US, O = Let's Encrypt, CN = E5 - // issuer=O = NginxProxyManager, CN = NginxProxyManager Intermediate CA","O = NginxProxyManager, CN = NginxProxyManager Intermediate CA - const regex = /^(?:issuer=)?(.*)$/gim; - const match = regex.exec(result); - if (match && typeof match[1] !== 'undefined') { - certData['issuer'] = match[1]; - } - }) - .then(() => { - return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout'); - }) - .then((result) => { - // notBefore=Jul 14 04:04:29 2018 GMT - // notAfter=Oct 12 04:04:29 2018 GMT - let validFrom = null; - let validTo = null; + const result2 = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-issuer", "-noout"]); + // Examples: + // issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 + // issuer=C = US, O = Let's Encrypt, CN = E5 + // issuer=O = NginxProxyManager, CN = NginxProxyManager Intermediate CA","O = NginxProxyManager, CN = NginxProxyManager Intermediate CA + const regex2 = /^(?:issuer=)?(.*)$/gim; + const match2 = regex2.exec(result2); + if (match2 && typeof match2[1] !== "undefined") { + certData.issuer = match2[1]; + } - const lines = result.split('\n'); - lines.map(function (str) { - const regex = /^(\S+)=(.*)$/gim; - const match = regex.exec(str.trim()); + const result3 = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-dates", "-noout"]); + // notBefore=Jul 14 04:04:29 2018 GMT + // notAfter=Oct 12 04:04:29 2018 GMT + let validFrom = null; + let validTo = null; - if (match && typeof match[2] !== 'undefined') { - const date = parseInt(moment(match[2], 'MMM DD HH:mm:ss YYYY z').format('X'), 10); + const lines = result3.split("\n"); + lines.map((str) => { + const regex = /^(\S+)=(.*)$/gim; + const match = regex.exec(str.trim()); - if (match[1].toLowerCase() === 'notbefore') { - validFrom = date; - } else if (match[1].toLowerCase() === 'notafter') { - validTo = date; - } + if (match && typeof match[2] !== "undefined") { + const date = Number.parseInt(moment(match[2], "MMM DD HH:mm:ss YYYY z").format("X"), 10); + + if (match[1].toLowerCase() === "notbefore") { + validFrom = date; + } else if (match[1].toLowerCase() === "notafter") { + validTo = date; } - }); - - if (!validFrom || !validTo) { - throw new error.ValidationError('Could not determine dates from certificate: ' + result); } - - if (throw_expired && validTo < parseInt(moment().format('X'), 10)) { - throw new error.ValidationError('Certificate has expired'); - } - - certData['dates'] = { - from: validFrom, - to: validTo - }; - - return certData; - }).catch((err) => { - throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err); + return true; }); + + if (!validFrom || !validTo) { + throw new error.ValidationError(`Could not determine dates from certificate: ${result}`); + } + + if (throw_expired && validTo < Number.parseInt(moment().format("X"), 10)) { + throw new error.ValidationError("Certificate has expired"); + } + + certData.dates = { + from: validFrom, + to: validTo, + }; + + return certData; + } catch (err) { + throw new error.ValidationError(`Certificate is not valid (${err.message})`, err); + } }, /** @@ -814,104 +751,119 @@ const internalCertificate = { * @param {Boolean} [remove] * @returns {Object} */ - cleanMeta: function (meta, remove) { + cleanMeta: (meta, remove) => { internalCertificate.allowedSslFiles.map((key) => { - if (typeof meta[key] !== 'undefined' && meta[key]) { + if (typeof meta[key] !== "undefined" && meta[key]) { if (remove) { delete meta[key]; } else { meta[key] = true; } } + return true; }); - return meta; }, /** * Request a certificate using the http challenge * @param {Object} certificate the certificate row + * @param {String} email the email address to use for registration * @returns {Promise} */ - requestLetsEncryptSsl: (certificate) => { - logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); + requestLetsEncryptSsl: async (certificate, email) => { + logger.info( + `Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); - const cmd = `${certbotCommand} certonly ` + - `--config '${letsencryptConfig}' ` + - '--work-dir "/tmp/letsencrypt-lib" ' + - '--logs-dir "/tmp/letsencrypt-log" ' + - `--cert-name "npm-${certificate.id}" ` + - '--agree-tos ' + - '--authenticator webroot ' + - `--email '${certificate.meta.letsencrypt_email}' ` + - '--preferred-challenges "dns,http" ' + - `--domains "${certificate.domain_names.join(',')}" ` + - (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + - (letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); + const args = [ + "certonly", + "--config", + letsencryptConfig, + "--work-dir", + certbotWorkDir, + "--logs-dir", + certbotLogsDir, + "--cert-name", + `npm-${certificate.id}`, + "--agree-tos", + "--authenticator", + "webroot", + "-m", + email, + "--preferred-challenges", + "http", + "--domains", + certificate.domain_names.join(","), + ]; - logger.info('Command:', cmd); + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); + args.push(...adds.args); - return utils.exec(cmd) - .then((result) => { - logger.success(result); - return result; - }); + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); + + const result = await utils.execFile(certbotCommand, args, adds.opts); + logger.success(result); + return result; }, /** - * @param {Object} certificate the certificate row - * @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.json`) - * @param {String | null} credentials the content of this providers credentials file - * @param {String} propagation_seconds + * @param {Object} certificate the certificate row + * @param {String} email the email address to use for registration * @returns {Promise} */ - requestLetsEncryptSslWithDnsChallenge: async (certificate) => { - await certbot.installPlugin(certificate.meta.dns_provider); + requestLetsEncryptSslWithDnsChallenge: async (certificate, email) => { + await installPlugin(certificate.meta.dns_provider); const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; - logger.info(`Requesting Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); + logger.info( + `Requesting LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); - const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id; - fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true }); - fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600}); + const credentialsLocation = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; + fs.mkdirSync("/etc/letsencrypt/credentials", { recursive: true }); + fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, { mode: 0o600 }); // Whether the plugin has a ---credentials argument - const hasConfigArg = certificate.meta.dns_provider !== 'route53'; + const hasConfigArg = certificate.meta.dns_provider !== "route53"; - let mainCmd = certbotCommand + ' certonly ' + - `--config '${letsencryptConfig}' ` + - '--work-dir "/tmp/letsencrypt-lib" ' + - '--logs-dir "/tmp/letsencrypt-log" ' + - `--cert-name 'npm-${certificate.id}' ` + - '--agree-tos ' + - `--email '${certificate.meta.letsencrypt_email}' ` + - `--domains '${certificate.domain_names.join(',')}' ` + - `--authenticator '${dnsPlugin.full_plugin_name}' ` + - ( - hasConfigArg - ? `--${dnsPlugin.full_plugin_name}-credentials '${credentialsLocation}' ` - : '' - ) + - ( - certificate.meta.propagation_seconds !== undefined - ? `--${dnsPlugin.full_plugin_name}-propagation-seconds '${certificate.meta.propagation_seconds}' ` - : '' - ) + - (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + - (letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); + const args = [ + "certonly", + "--config", + letsencryptConfig, + "--work-dir", + certbotWorkDir, + "--logs-dir", + certbotLogsDir, + "--cert-name", + `npm-${certificate.id}`, + "--agree-tos", + "-m", + email, + "--preferred-challenges", + "dns", + "--domains", + certificate.domain_names.join(","), + "--authenticator", + dnsPlugin.full_plugin_name, + ]; - // Prepend the path to the credentials file as an environment variable - if (certificate.meta.dns_provider === 'route53') { - mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd; + if (hasConfigArg) { + args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation); + } + if (certificate.meta.propagation_seconds !== undefined) { + args.push( + `--${dnsPlugin.full_plugin_name}-propagation-seconds`, + certificate.meta.propagation_seconds.toString(), + ); } - if (certificate.meta.dns_provider === 'duckdns') { - mainCmd = mainCmd + ' --dns-duckdns-no-txt-restore'; - } + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); + args.push(...adds.args); - logger.info('Command:', mainCmd); + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); try { - const result = await utils.exec(mainCmd); + const result = await utils.execFile(certbotCommand, args, adds.opts); logger.info(result); return result; } catch (err) { @@ -921,151 +873,160 @@ const internalCertificate = { } }, - /** * @param {Access} access * @param {Object} data * @param {Number} data.id * @returns {Promise} */ - renew: (access, data) => { - return access.can('certificates:update', data) - .then(() => { - return internalCertificate.get(access, data); - }) - .then((certificate) => { - if (certificate.provider === 'letsencrypt') { - const renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl; + renew: async (access, data) => { + await access.can("certificates:update", data); + const certificate = await internalCertificate.get(access, data); - return renewMethod(certificate) - .then(() => { - return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem'); - }) - .then((cert_info) => { - return certificateModel - .query() - .patchAndFetchById(certificate.id, { - expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss') - }); - }) - .then((updated_certificate) => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'renewed', - object_type: 'certificate', - object_id: updated_certificate.id, - meta: updated_certificate - }) - .then(() => { - return updated_certificate; - }); - }); - } else { - throw new error.ValidationError('Only Let\'sEncrypt certificates can be renewed'); - } + if (certificate.provider === "letsencrypt") { + const renewMethod = certificate.meta.dns_challenge + ? internalCertificate.renewLetsEncryptSslWithDnsChallenge + : internalCertificate.renewLetsEncryptSsl; + + await renewMethod(certificate); + const certInfo = await internalCertificate.getCertificateInfoFromFile( + `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, + ); + + const updatedCertificate = await certificateModel.query().patchAndFetchById(certificate.id, { + expires_on: moment(certInfo.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), }); + + // Add to audit log + await internalAuditLog.add(access, { + action: "renewed", + object_type: "certificate", + object_id: updatedCertificate.id, + meta: updatedCertificate, + }); + + return updatedCertificate; + } + + throw new error.ValidationError("Only Let'sEncrypt certificates can be renewed"); }, /** * @param {Object} certificate the certificate row * @returns {Promise} */ - renewLetsEncryptSsl: (certificate) => { - logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); + renewLetsEncryptSsl: async (certificate) => { + logger.info( + `Renewing LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); - const cmd = certbotCommand + ' renew --force-renewal ' + - `--config '${letsencryptConfig}' ` + - '--work-dir "/tmp/letsencrypt-lib" ' + - '--logs-dir "/tmp/letsencrypt-log" ' + - `--cert-name 'npm-${certificate.id}' ` + - '--preferred-challenges "dns,http" ' + - '--no-random-sleep-on-renew ' + - '--disable-hook-validation ' + - (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + - (letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); + const args = [ + "renew", + "--force-renewal", + "--config", + letsencryptConfig, + "--work-dir", + certbotWorkDir, + "--logs-dir", + certbotLogsDir, + "--cert-name", + `npm-${certificate.id}`, + "--preferred-challenges", + "http", + "--no-random-sleep-on-renew", + "--disable-hook-validation", + ]; - logger.info('Command:', cmd); + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); + args.push(...adds.args); - return utils.exec(cmd) - .then((result) => { - logger.info(result); - return result; - }); + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); + + const result = await utils.execFile(certbotCommand, args, adds.opts); + logger.info(result); + return result; }, /** * @param {Object} certificate the certificate row * @returns {Promise} */ - renewLetsEncryptSslWithDnsChallenge: (certificate) => { + renewLetsEncryptSslWithDnsChallenge: async (certificate) => { const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; - if (!dnsPlugin) { throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); } - logger.info(`Renewing Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); + logger.info( + `Renewing LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); - let mainCmd = certbotCommand + ' renew --force-renewal ' + - `--config "${letsencryptConfig}" ` + - '--work-dir "/tmp/letsencrypt-lib" ' + - '--logs-dir "/tmp/letsencrypt-log" ' + - `--cert-name 'npm-${certificate.id}' ` + - '--disable-hook-validation ' + - '--no-random-sleep-on-renew ' + - (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + - (letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); + const args = [ + "renew", + "--force-renewal", + "--config", + letsencryptConfig, + "--work-dir", + certbotWorkDir, + "--logs-dir", + certbotLogsDir, + "--cert-name", + `npm-${certificate.id}`, + "--preferred-challenges", + "dns", + "--disable-hook-validation", + "--no-random-sleep-on-renew", + ]; - // Prepend the path to the credentials file as an environment variable - if (certificate.meta.dns_provider === 'route53') { - const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id; - mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd; - } + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); + args.push(...adds.args); - logger.info('Command:', mainCmd); + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - return utils.exec(mainCmd) - .then(async (result) => { - logger.info(result); - return result; - }); + const result = await utils.execFile(certbotCommand, args, adds.opts); + logger.info(result); + return result; }, /** * @param {Object} certificate the certificate row - * @param {Boolean} [throw_errors] + * @param {Boolean} [throwErrors] * @returns {Promise} */ - revokeLetsEncryptSsl: (certificate, throw_errors) => { - logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); + revokeLetsEncryptSsl: async (certificate, throwErrors) => { + logger.info( + `Revoking LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); - const mainCmd = certbotCommand + ' revoke ' + - `--config '${letsencryptConfig}' ` + - '--work-dir "/tmp/letsencrypt-lib" ' + - '--logs-dir "/tmp/letsencrypt-log" ' + - `--cert-path '/etc/letsencrypt/live/npm-${certificate.id}/fullchain.pem' ` + - '--delete-after-revoke ' + - (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + - (letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); + const args = [ + "revoke", + "--config", + letsencryptConfig, + "--work-dir", + certbotWorkDir, + "--logs-dir", + certbotLogsDir, + "--cert-path", + `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, + "--delete-after-revoke", + ]; - // Don't fail command if file does not exist - const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`; + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); + args.push(...adds.args); - logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd); + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - return utils.exec(mainCmd) - .then(async (result) => { - await utils.exec(delete_credentialsCmd); - logger.info(result); - return result; - }) - .catch((err) => { - logger.error(err.message); - - if (throw_errors) { - throw err; - } - }); + try { + const result = await utils.execFile(certbotCommand, args, adds.opts); + await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); + logger.info(result); + return result; + } catch (err) { + logger.error(err.message); + if (throwErrors) { + throw err; + } + } }, /** @@ -1073,172 +1034,207 @@ const internalCertificate = { * @returns {Boolean} */ hasLetsEncryptSslCerts: (certificate) => { - const letsencryptPath = '/etc/letsencrypt/live/npm-' + certificate.id; - - return fs.existsSync(letsencryptPath + '/fullchain.pem') && fs.existsSync(letsencryptPath + '/privkey.pem'); + const letsencryptPath = internalCertificate.getLiveCertPath(certificate.id); + return fs.existsSync(`${letsencryptPath}/fullchain.pem`) && fs.existsSync(`${letsencryptPath}/privkey.pem`); }, /** - * @param {Object} in_use_result - * @param {Number} in_use_result.total_count - * @param {Array} in_use_result.proxy_hosts - * @param {Array} in_use_result.redirection_hosts - * @param {Array} in_use_result.dead_hosts + * @param {Object} inUseResult + * @param {Number} inUseResult.total_count + * @param {Array} inUseResult.proxy_hosts + * @param {Array} inUseResult.redirection_hosts + * @param {Array} inUseResult.dead_hosts + * @returns {Promise} */ - disableInUseHosts: (in_use_result) => { - if (in_use_result.total_count) { - let promises = []; - - if (in_use_result.proxy_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts)); + disableInUseHosts: async (inUseResult) => { + if (inUseResult?.total_count) { + if (inUseResult?.proxy_hosts.length) { + await internalNginx.bulkDeleteConfigs("proxy_host", inUseResult.proxy_hosts); } - if (in_use_result.redirection_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs('redirection_host', in_use_result.redirection_hosts)); + if (inUseResult?.redirection_hosts.length) { + await internalNginx.bulkDeleteConfigs("redirection_host", inUseResult.redirection_hosts); } - if (in_use_result.dead_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs('dead_host', in_use_result.dead_hosts)); + if (inUseResult?.dead_hosts.length) { + await internalNginx.bulkDeleteConfigs("dead_host", inUseResult.dead_hosts); } - - return Promise.all(promises); - - } else { - return Promise.resolve(); } }, /** - * @param {Object} in_use_result - * @param {Number} in_use_result.total_count - * @param {Array} in_use_result.proxy_hosts - * @param {Array} in_use_result.redirection_hosts - * @param {Array} in_use_result.dead_hosts + * @param {Object} inUseResult + * @param {Number} inUseResult.total_count + * @param {Array} inUseResult.proxy_hosts + * @param {Array} inUseResult.redirection_hosts + * @param {Array} inUseResult.dead_hosts + * @returns {Promise} */ - enableInUseHosts: (in_use_result) => { - if (in_use_result.total_count) { - let promises = []; - - if (in_use_result.proxy_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts)); + enableInUseHosts: async (inUseResult) => { + if (inUseResult.total_count) { + if (inUseResult.proxy_hosts.length) { + await internalNginx.bulkGenerateConfigs("proxy_host", inUseResult.proxy_hosts); } - if (in_use_result.redirection_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs('redirection_host', in_use_result.redirection_hosts)); + if (inUseResult.redirection_hosts.length) { + await internalNginx.bulkGenerateConfigs("redirection_host", inUseResult.redirection_hosts); } - if (in_use_result.dead_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs('dead_host', in_use_result.dead_hosts)); + if (inUseResult.dead_hosts.length) { + await internalNginx.bulkGenerateConfigs("dead_host", inUseResult.dead_hosts); } - - return Promise.all(promises); - - } else { - return Promise.resolve(); } }, - testHttpsChallenge: async (access, domains) => { - await access.can('certificates:list'); - - if (!isArray(domains)) { - throw new error.InternalValidationError('Domains must be an array of strings'); - } - if (domains.length === 0) { - throw new error.InternalValidationError('No domains provided'); - } + /** + * + * @param {Object} payload + * @param {string[]} payload.domains + * @returns + */ + testHttpsChallenge: async (access, payload) => { + await access.can("certificates:list"); // Create a test challenge file - const testChallengeDir = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge'; - const testChallengeFile = testChallengeDir + '/test-challenge'; - fs.mkdirSync(testChallengeDir, {recursive: true}); - fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'}); - - async function performTestForDomain (domain) { - logger.info('Testing http challenge for ' + domain); - const url = `http://${domain}/.well-known/acme-challenge/test-challenge`; - const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`; - const options = { - method: 'POST', - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': Buffer.byteLength(formBody) - } - }; - - const result = await new Promise((resolve) => { - - const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) { - let responseBody = ''; - - res.on('data', (chunk) => responseBody = responseBody + chunk); - res.on('end', function () { - try { - const parsedBody = JSON.parse(responseBody + ''); - if (res.statusCode !== 200) { - logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`); - resolve(undefined); - } else { - resolve(parsedBody); - } - } catch (err) { - if (res.statusCode !== 200) { - logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned`); - } else { - logger.warn(`Failed to test HTTP challenge for domain ${domain} because response failed to be parsed: ${err.message}`); - } - resolve(undefined); - } - }); - }); - - // Make sure to write the request body. - req.write(formBody); - req.end(); - req.on('error', function (e) { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); - resolve(undefined); }); - }); - - if (!result) { - // Some error occurred while trying to get the data - return 'failed'; - } else if (result.error) { - logger.info(`HTTP challenge test failed for domain ${domain} because error was returned: ${result.error.msg}`); - return `other:${result.error.msg}`; - } else if (`${result.responsecode}` === '200' && result.htmlresponse === 'Success') { - // Server exists and has responded with the correct data - return 'ok'; - } else if (`${result.responsecode}` === '200') { - // Server exists but has responded with wrong data - logger.info(`HTTP challenge test failed for domain ${domain} because of invalid returned data:`, result.htmlresponse); - return 'wrong-data'; - } else if (`${result.responsecode}` === '404') { - // Server exists but responded with a 404 - logger.info(`HTTP challenge test failed for domain ${domain} because code 404 was returned`); - return '404'; - } else if (`${result.responsecode}` === '0' || (typeof result.reason === 'string' && result.reason.toLowerCase() === 'host unavailable')) { - // Server does not exist at domain - logger.info(`HTTP challenge test failed for domain ${domain} the host was not found`); - return 'no-host'; - } else { - // Other errors - logger.info(`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`); - return `other:${result.responsecode}`; - } - } + const testChallengeDir = "/data/letsencrypt-acme-challenge/.well-known/acme-challenge"; + const testChallengeFile = `${testChallengeDir}/test-challenge`; + fs.mkdirSync(testChallengeDir, { recursive: true }); + fs.writeFileSync(testChallengeFile, "Success", { encoding: "utf8" }); const results = {}; - - for (const domain of domains){ - results[domain] = await performTestForDomain(domain); + for (const domain of payload.domains) { + results[domain] = await internalCertificate.performTestForDomain(domain); } // Remove the test challenge file fs.unlinkSync(testChallengeFile); return results; - } + }, + + performTestForDomain: async (domain) => { + logger.info(`Testing http challenge for ${domain}`); + const url = `http://${domain}/.well-known/acme-challenge/test-challenge`; + const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`; + const options = { + method: "POST", + headers: { + "User-Agent": "Mozilla/5.0", + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": Buffer.byteLength(formBody), + }, + }; + + const result = await new Promise((resolve) => { + const req = https.request("https://www.site24x7.com/tools/restapi-tester", options, (res) => { + let responseBody = ""; + + res.on("data", (chunk) => { + responseBody = responseBody + chunk; + }); + + res.on("end", () => { + try { + const parsedBody = JSON.parse(`${responseBody}`); + if (res.statusCode !== 200) { + logger.warn( + `Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`, + ); + resolve(undefined); + } else { + resolve(parsedBody); + } + } catch (err) { + if (res.statusCode !== 200) { + logger.warn( + `Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned`, + ); + } else { + logger.warn( + `Failed to test HTTP challenge for domain ${domain} because response failed to be parsed: ${err.message}`, + ); + } + resolve(undefined); + } + }); + }); + + // Make sure to write the request body. + req.write(formBody); + req.end(); + req.on("error", (e) => { + logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); + resolve(undefined); + }); + }); + + if (!result) { + // Some error occurred while trying to get the data + return "failed"; + } + if (result.error) { + logger.info( + `HTTP challenge test failed for domain ${domain} because error was returned: ${result.error.msg}`, + ); + return `other:${result.error.msg}`; + } + if (`${result.responsecode}` === "200" && result.htmlresponse === "Success") { + // Server exists and has responded with the correct data + return "ok"; + } + if (`${result.responsecode}` === "200") { + // Server exists but has responded with wrong data + logger.info( + `HTTP challenge test failed for domain ${domain} because of invalid returned data:`, + result.htmlresponse, + ); + return "wrong-data"; + } + if (`${result.responsecode}` === "404") { + // Server exists but responded with a 404 + logger.info(`HTTP challenge test failed for domain ${domain} because code 404 was returned`); + return "404"; + } + if ( + `${result.responsecode}` === "0" || + (typeof result.reason === "string" && result.reason.toLowerCase() === "host unavailable") + ) { + // Server does not exist at domain + logger.info(`HTTP challenge test failed for domain ${domain} the host was not found`); + return "no-host"; + } + // Other errors + logger.info(`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`); + return `other:${result.responsecode}`; + }, + + getAdditionalCertbotArgs: (certificate_id, dns_provider) => { + const args = []; + if (useLetsencryptServer() !== null) { + args.push("--server", useLetsencryptServer()); + } + if (useLetsencryptStaging() && useLetsencryptServer() === null) { + args.push("--staging"); + } + + // For route53, add the credentials file as an environment variable, + // inheriting the process env + const opts = {}; + if (certificate_id && dns_provider === "route53") { + opts.env = process.env; + opts.env.AWS_CONFIG_FILE = `/etc/letsencrypt/credentials/credentials-${certificate_id}`; + } + + if (dns_provider === "duckdns") { + args.push("--dns-duckdns-no-txt-restore"); + } + + return { args: args, opts: opts }; + }, + + getLiveCertPath: (certificateId) => { + return `/etc/letsencrypt/live/npm-${certificateId}`; + }, }; -module.exports = internalCertificate; +export default internalCertificate; diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js index 6bbdf61b..21b12012 100644 --- a/backend/internal/dead-host.js +++ b/backend/internal/dead-host.js @@ -1,110 +1,96 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const deadHostModel = require('../models/dead_host'); -const internalHost = require('./host'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); -const {castJsonIfNeed} = require('../lib/helpers'); +import _ from "lodash"; +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import utils from "../lib/utils.js"; +import deadHostModel from "../models/dead_host.js"; +import internalAuditLog from "./audit-log.js"; +import internalCertificate from "./certificate.js"; +import internalHost from "./host.js"; +import internalNginx from "./nginx.js"; -function omissions () { - return ['is_deleted']; -} +const omissions = () => { + return ["is_deleted"]; +}; const internalDeadHost = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ - create: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + create: async (access, data) => { + const createCertificate = data.certificate_id === "new"; - if (create_certificate) { + if (createCertificate) { delete data.certificate_id; } - return access.can('dead_hosts:create', data) - .then((/*access_data*/) => { - // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + await access.can("dead_hosts:create", data); - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); - }); + // Get a list of the domain names and check each of them against existing records + const domainNameCheckPromises = []; - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); - }); - }) - .then(() => { - // At this point the domains should have been checked - data.owner_user_id = access.token.getUserId(1); - data = internalHost.cleanSslHstsData(data); + data.domain_names.map((domain_name) => { + domainNameCheckPromises.push(internalHost.isHostnameTaken(domain_name)); + return true; + }); - // Fix for db field not having a default value - // for this optional field. - if (typeof data.advanced_config === 'undefined') { - data.advanced_config = ''; + await Promise.all(domainNameCheckPromises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); } - - return deadHostModel - .query() - .insertAndFetch(data) - .then(utils.omitRow(omissions())); - }) - .then((row) => { - if (create_certificate) { - return internalCertificate.createQuickCertificate(access, data) - .then((cert) => { - // update host with cert id - return internalDeadHost.update(access, { - id: row.id, - certificate_id: cert.id - }); - }) - .then(() => { - return row; - }); - } else { - return row; - } - }) - .then((row) => { - // re-fetch with cert - return internalDeadHost.get(access, { - id: row.id, - expand: ['certificate', 'owner'] - }); - }) - .then((row) => { - // Configure nginx - return internalNginx.configure(deadHostModel, 'dead_host', row) - .then(() => { - return row; - }); - }) - .then((row) => { - data.meta = _.assign({}, data.meta || {}, row.meta); - - // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'dead-host', - object_id: row.id, - meta: data - }) - .then(() => { - return row; - }); + return true; }); + }); + + // At this point the domains should have been checked + data.owner_user_id = access.token.getUserId(1); + const thisData = internalHost.cleanSslHstsData(data); + + // Fix for db field not having a default value + // for this optional field. + if (typeof data.advanced_config === "undefined") { + thisData.advanced_config = ""; + } + + const row = await deadHostModel.query() + .insertAndFetch(thisData) + .then(utils.omitRow(omissions())); + + // Add to audit log + await internalAuditLog.add(access, { + action: "created", + object_type: "dead-host", + object_id: row.id, + meta: thisData, + }); + + if (createCertificate) { + const cert = await internalCertificate.createQuickCertificate(access, data); + + // update host with cert id + await internalDeadHost.update(access, { + id: row.id, + certificate_id: cert.id, + }); + } + + // re-fetch with cert + const freshRow = await internalDeadHost.get(access, { + id: row.id, + expand: ["certificate", "owner"], + }); + + // Sanity check + if (createCertificate && !freshRow.certificate_id) { + throw new errs.InternalValidationError("The host was created but the Certificate creation failed."); + } + + // Configure nginx + await internalNginx.configure(deadHostModel, "dead_host", freshRow); + + return freshRow; }, /** @@ -113,98 +99,85 @@ const internalDeadHost = { * @param {Number} data.id * @return {Promise} */ - update: (access, data) => { - let create_certificate = data.certificate_id === 'new'; - - if (create_certificate) { + update: async (access, data) => { + const createCertificate = data.certificate_id === "new"; + if (createCertificate) { delete data.certificate_id; } - return access.can('dead_hosts:update', data.id) - .then((/*access_data*/) => { - // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + await access.can("dead_hosts:update", data.id); - if (typeof data.domain_names !== 'undefined') { - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'dead', data.id)); - }); - - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); - }); - } - }) - .then(() => { - return internalDeadHost.get(access, {id: data.id}); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('404 Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); - } - - if (create_certificate) { - return internalCertificate.createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta) - }) - .then((cert) => { - // update host with cert id - data.certificate_id = cert.id; - }) - .then(() => { - return row; - }); - } else { - return row; - } - }) - .then((row) => { - // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign({}, { - domain_names: row.domain_names - }, data); - - data = internalHost.cleanSslHstsData(data, row); - - return deadHostModel - .query() - .where({id: data.id}) - .patch(data) - .then((saved_row) => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'dead-host', - object_id: row.id, - meta: data - }) - .then(() => { - return _.omit(saved_row, omissions()); - }); - }); - }) - .then(() => { - return internalDeadHost.get(access, { - id: data.id, - expand: ['owner', 'certificate'] - }) - .then((row) => { - // Configure nginx - return internalNginx.configure(deadHostModel, 'dead_host', row) - .then((new_meta) => { - row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); - }); - }); + // Get a list of the domain names and check each of them against existing records + const domainNameCheckPromises = []; + if (typeof data.domain_names !== "undefined") { + data.domain_names.map((domainName) => { + domainNameCheckPromises.push(internalHost.isHostnameTaken(domainName, "dead", data.id)); + return true; }); + + const checkResults = await Promise.all(domainNameCheckPromises); + checkResults.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; + }); + } + const row = await internalDeadHost.get(access, { id: data.id }); + + if (row.id !== data.id) { + // Sanity check that something crazy hasn't happened + throw new errs.InternalValidationError( + `404 Host could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); + } + + if (createCertificate) { + const cert = await internalCertificate.createQuickCertificate(access, { + domain_names: data.domain_names || row.domain_names, + meta: _.assign({}, row.meta, data.meta), + }); + + // update host with cert id + data.certificate_id = cert.id; + } + + // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. + let thisData = _.assign( + {}, + { + domain_names: row.domain_names, + }, + data, + ); + + thisData = internalHost.cleanSslHstsData(thisData, row); + + + // do the row update + await deadHostModel + .query() + .where({id: data.id}) + .patch(data); + + // Add to audit log + await internalAuditLog.add(access, { + action: "updated", + object_type: "dead-host", + object_id: row.id, + meta: thisData, + }); + + const thisRow = await internalDeadHost + .get(access, { + id: thisData.id, + expand: ["owner", "certificate"], + }); + + // Configure nginx + const newMeta = await internalNginx.configure(deadHostModel, "dead_host", row); + row.meta = newMeta; + return _.omit(internalHost.cleanRowCertificateMeta(thisRow), omissions()); }, /** @@ -215,40 +188,32 @@ const internalDeadHost = { * @param {Array} [data.omit] * @return {Promise} */ - get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; + get: async (access, data) => { + const accessData = await access.can("dead_hosts:get", data.id); + const query = deadHostModel + .query() + .where("is_deleted", 0) + .andWhere("id", data.id) + .allowGraph("[owner,certificate]") + .first(); + + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } - return access.can('dead_hosts:get', data.id) - .then((access_data) => { - let query = deadHostModel - .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[owner,certificate]') - .first(); + if (typeof data.expand !== "undefined" && data.expand !== null) { + query.withGraphFetched(`[${data.expand.join(", ")}]`); + } - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } - // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); - } - return row; - }); + const row = await query.then(utils.omitRow(omissions())); + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } + // Custom omissions + if (typeof data.omit !== "undefined" && data.omit !== null) { + return _.omit(row, data.omit); + } + return row; }, /** @@ -258,42 +223,32 @@ const internalDeadHost = { * @param {String} [data.reason] * @returns {Promise} */ - delete: (access, data) => { - return access.can('dead_hosts:delete', data.id) - .then(() => { - return internalDeadHost.get(access, {id: data.id}); - }) - .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } + delete: async (access, data) => { + await access.can("dead_hosts:delete", data.id) + const row = await internalDeadHost.get(access, { id: data.id }); + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } - return deadHostModel - .query() - .where('id', row.id) - .patch({ - is_deleted: 1 - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig('dead_host', row) - .then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'dead-host', - object_id: row.id, - meta: _.omit(row, omissions()) - }); - }); - }) - .then(() => { - return true; + await deadHostModel + .query() + .where("id", row.id) + .patch({ + is_deleted: 1, }); + + // Delete Nginx Config + await internalNginx.deleteConfig("dead_host", row); + await internalNginx.reload(); + + // Add to audit log + await internalAuditLog.add(access, { + action: "deleted", + object_type: "dead-host", + object_id: row.id, + meta: _.omit(row, omissions()), + }); + return true; }, /** @@ -303,46 +258,39 @@ const internalDeadHost = { * @param {String} [data.reason] * @returns {Promise} */ - enable: (access, data) => { - return access.can('dead_hosts:update', data.id) - .then(() => { - return internalDeadHost.get(access, { - id: data.id, - expand: ['certificate', 'owner'] - }); - }) - .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Host is already enabled'); - } + enable: async (access, data) => { + await access.can("dead_hosts:update", data.id) + const row = await internalDeadHost.get(access, { + id: data.id, + expand: ["certificate", "owner"], + }); + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Host is already enabled"); + } - row.enabled = 1; + row.enabled = 1; - return deadHostModel - .query() - .where('id', row.id) - .patch({ - enabled: 1 - }) - .then(() => { - // Configure nginx - return internalNginx.configure(deadHostModel, 'dead_host', row); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'dead-host', - object_id: row.id, - meta: _.omit(row, omissions()) - }); - }); - }) - .then(() => { - return true; + await deadHostModel + .query() + .where("id", row.id) + .patch({ + enabled: 1, }); + + // Configure nginx + await internalNginx.configure(deadHostModel, "dead_host", row); + + // Add to audit log + await internalAuditLog.add(access, { + action: "enabled", + object_type: "dead-host", + object_id: row.id, + meta: _.omit(row, omissions()), + }); + return true; }, /** @@ -352,46 +300,37 @@ const internalDeadHost = { * @param {String} [data.reason] * @returns {Promise} */ - disable: (access, data) => { - return access.can('dead_hosts:update', data.id) - .then(() => { - return internalDeadHost.get(access, {id: data.id}); - }) - .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Host is already disabled'); - } + disable: async (access, data) => { + await access.can("dead_hosts:update", data.id) + const row = await internalDeadHost.get(access, { id: data.id }); + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } + if (!row.enabled) { + throw new errs.ValidationError("Host is already disabled"); + } - row.enabled = 0; + row.enabled = 0; - return deadHostModel - .query() - .where('id', row.id) - .patch({ - enabled: 0 - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig('dead_host', row) - .then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'dead-host', - object_id: row.id, - meta: _.omit(row, omissions()) - }); - }); - }) - .then(() => { - return true; + await deadHostModel + .query() + .where("id", row.id) + .patch({ + enabled: 0, }); + + // Delete Nginx Config + await internalNginx.deleteConfig("dead_host", row); + await internalNginx.reload(); + + // Add to audit log + await internalAuditLog.add(access, { + action: "disabled", + object_type: "dead-host", + object_id: row.id, + meta: _.omit(row, omissions()), + }); + return true; }, /** @@ -399,43 +338,38 @@ const internalDeadHost = { * * @param {Access} access * @param {Array} [expand] - * @param {String} [search_query] + * @param {String} [searchQuery] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access.can('dead_hosts:list') - .then((access_data) => { - let query = deadHostModel - .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[owner,certificate]') - .orderBy(castJsonIfNeed('domain_names'), 'ASC'); + getAll: async (access, expand, searchQuery) => { + const accessData = await access.can("dead_hosts:list") + const query = deadHostModel + .query() + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,certificate]") + .orderBy(castJsonIfNeed("domain_names"), "ASC"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { - query.where(function () { - this.where(castJsonIfNeed('domain_names'), 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); - }) - .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { - return internalHost.cleanAllRowsCertificateMeta(rows); - } - - return rows; + // Query is used for searching + if (typeof searchQuery === "string" && searchQuery.length > 0) { + query.where(function () { + this.where(castJsonIfNeed("domain_names"), "like", `%${searchQuery}%`); }); + } + + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + const rows = await query.then(utils.omitRows(omissions())); + if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { + internalHost.cleanAllRowsCertificateMeta(rows); + } + return rows; }, /** @@ -445,21 +379,16 @@ const internalDeadHost = { * @param {String} visibility * @returns {Promise} */ - getCount: (user_id, visibility) => { - let query = deadHostModel - .query() - .count('id as count') - .where('is_deleted', 0); + getCount: async (user_id, visibility) => { + const query = deadHostModel.query().count("id as count").where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", user_id); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); - } + const row = await query.first(); + return Number.parseInt(row.count, 10); + }, }; -module.exports = internalDeadHost; +export default internalDeadHost; diff --git a/backend/internal/host.js b/backend/internal/host.js index 52c6d2bd..74871626 100644 --- a/backend/internal/host.js +++ b/backend/internal/host.js @@ -1,11 +1,10 @@ -const _ = require('lodash'); -const proxyHostModel = require('../models/proxy_host'); -const redirectionHostModel = require('../models/redirection_host'); -const deadHostModel = require('../models/dead_host'); -const {castJsonIfNeed} = require('../lib/helpers'); +import _ from "lodash"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import deadHostModel from "../models/dead_host.js"; +import proxyHostModel from "../models/proxy_host.js"; +import redirectionHostModel from "../models/redirection_host.js"; const internalHost = { - /** * Makes sure that the ssl_* and hsts_* fields play nicely together. * ie: if there is no cert, then force_ssl is off. @@ -15,25 +14,23 @@ const internalHost = { * @param {object} [existing_data] * @returns {object} */ - cleanSslHstsData: function (data, existing_data) { - existing_data = existing_data === undefined ? {} : existing_data; + cleanSslHstsData: (data, existingData) => { + const combinedData = _.assign({}, existingData || {}, data); - const combined_data = _.assign({}, existing_data, data); - - if (!combined_data.certificate_id) { - combined_data.ssl_forced = false; - combined_data.http2_support = false; + if (!combinedData.certificate_id) { + combinedData.ssl_forced = false; + combinedData.http2_support = false; } - if (!combined_data.ssl_forced) { - combined_data.hsts_enabled = false; + if (!combinedData.ssl_forced) { + combinedData.hsts_enabled = false; } - if (!combined_data.hsts_enabled) { - combined_data.hsts_subdomains = false; + if (!combinedData.hsts_enabled) { + combinedData.hsts_subdomains = false; } - return combined_data; + return combinedData; }, /** @@ -42,11 +39,12 @@ const internalHost = { * @param {Array} rows * @returns {Array} */ - cleanAllRowsCertificateMeta: function (rows) { - rows.map(function (row, idx) { - if (typeof rows[idx].certificate !== 'undefined' && rows[idx].certificate) { + cleanAllRowsCertificateMeta: (rows) => { + rows.map((_, idx) => { + if (typeof rows[idx].certificate !== "undefined" && rows[idx].certificate) { rows[idx].certificate.meta = {}; } + return true; }); return rows; @@ -58,8 +56,8 @@ const internalHost = { * @param {Object} row * @returns {Object} */ - cleanRowCertificateMeta: function (row) { - if (typeof row.certificate !== 'undefined' && row.certificate) { + cleanRowCertificateMeta: (row) => { + if (typeof row.certificate !== "undefined" && row.certificate) { row.certificate.meta = {}; } @@ -67,54 +65,33 @@ const internalHost = { }, /** - * This returns all the host types with any domain listed in the provided domain_names array. + * This returns all the host types with any domain listed in the provided domainNames array. * This is used by the certificates to temporarily disable any host that is using the domain * - * @param {Array} domain_names + * @param {Array} domainNames * @returns {Promise} */ - getHostsWithDomains: function (domain_names) { - const promises = [ - proxyHostModel - .query() - .where('is_deleted', 0), - redirectionHostModel - .query() - .where('is_deleted', 0), - deadHostModel - .query() - .where('is_deleted', 0) - ]; + getHostsWithDomains: async (domainNames) => { + const responseObject = { + total_count: 0, + dead_hosts: [], + proxy_hosts: [], + redirection_hosts: [], + }; - return Promise.all(promises) - .then((promises_results) => { - let response_object = { - total_count: 0, - dead_hosts: [], - proxy_hosts: [], - redirection_hosts: [] - }; + const proxyRes = await proxyHostModel.query().where("is_deleted", 0); + responseObject.proxy_hosts = internalHost._getHostsWithDomains(proxyRes, domainNames); + responseObject.total_count += responseObject.proxy_hosts.length; - if (promises_results[0]) { - // Proxy Hosts - response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names); - response_object.total_count += response_object.proxy_hosts.length; - } + const redirRes = await redirectionHostModel.query().where("is_deleted", 0); + responseObject.redirection_hosts = internalHost._getHostsWithDomains(redirRes, domainNames); + responseObject.total_count += responseObject.redirection_hosts.length; - if (promises_results[1]) { - // Redirection Hosts - response_object.redirection_hosts = internalHost._getHostsWithDomains(promises_results[1], domain_names); - response_object.total_count += response_object.redirection_hosts.length; - } + const deadRes = await deadHostModel.query().where("is_deleted", 0); + responseObject.dead_hosts = internalHost._getHostsWithDomains(deadRes, domainNames); + responseObject.total_count += responseObject.dead_hosts.length; - if (promises_results[2]) { - // Dead Hosts - response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names); - response_object.total_count += response_object.dead_hosts.length; - } - - return response_object; - }); + return responseObject; }, /** @@ -125,112 +102,133 @@ const internalHost = { * @param {Integer} [ignore_id] Must be supplied if type was also supplied * @returns {Promise} */ - isHostnameTaken: function (hostname, ignore_type, ignore_id) { + isHostnameTaken: (hostname, ignore_type, ignore_id) => { const promises = [ proxyHostModel .query() - .where('is_deleted', 0) - .andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'), + .where("is_deleted", 0) + .andWhere(castJsonIfNeed("domain_names"), "like", `%${hostname}%`), redirectionHostModel .query() - .where('is_deleted', 0) - .andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'), + .where("is_deleted", 0) + .andWhere(castJsonIfNeed("domain_names"), "like", `%${hostname}%`), deadHostModel .query() - .where('is_deleted', 0) - .andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%') + .where("is_deleted", 0) + .andWhere(castJsonIfNeed("domain_names"), "like", `%${hostname}%`), ]; - return Promise.all(promises) - .then((promises_results) => { - let is_taken = false; + return Promise.all(promises).then((promises_results) => { + let is_taken = false; - if (promises_results[0]) { - // Proxy Hosts - if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[0], ignore_type === 'proxy' && ignore_id ? ignore_id : 0)) { - is_taken = true; - } + if (promises_results[0]) { + // Proxy Hosts + if ( + internalHost._checkHostnameRecordsTaken( + hostname, + promises_results[0], + ignore_type === "proxy" && ignore_id ? ignore_id : 0, + ) + ) { + is_taken = true; } + } - if (promises_results[1]) { - // Redirection Hosts - if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[1], ignore_type === 'redirection' && ignore_id ? ignore_id : 0)) { - is_taken = true; - } + if (promises_results[1]) { + // Redirection Hosts + if ( + internalHost._checkHostnameRecordsTaken( + hostname, + promises_results[1], + ignore_type === "redirection" && ignore_id ? ignore_id : 0, + ) + ) { + is_taken = true; } + } - if (promises_results[2]) { - // Dead Hosts - if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[2], ignore_type === 'dead' && ignore_id ? ignore_id : 0)) { - is_taken = true; - } + if (promises_results[2]) { + // Dead Hosts + if ( + internalHost._checkHostnameRecordsTaken( + hostname, + promises_results[2], + ignore_type === "dead" && ignore_id ? ignore_id : 0, + ) + ) { + is_taken = true; } + } - return { - hostname: hostname, - is_taken: is_taken - }; - }); + return { + hostname: hostname, + is_taken: is_taken, + }; + }); }, /** * Private call only * * @param {String} hostname - * @param {Array} existing_rows - * @param {Integer} [ignore_id] + * @param {Array} existingRows + * @param {Integer} [ignoreId] * @returns {Boolean} */ - _checkHostnameRecordsTaken: function (hostname, existing_rows, ignore_id) { - let is_taken = false; + _checkHostnameRecordsTaken: (hostname, existingRows, ignoreId) => { + let isTaken = false; - if (existing_rows && existing_rows.length) { - existing_rows.map(function (existing_row) { - existing_row.domain_names.map(function (existing_hostname) { + if (existingRows?.length) { + existingRows.map((existingRow) => { + existingRow.domain_names.map((existingHostname) => { // Does this domain match? - if (existing_hostname.toLowerCase() === hostname.toLowerCase()) { - if (!ignore_id || ignore_id !== existing_row.id) { - is_taken = true; + if (existingHostname.toLowerCase() === hostname.toLowerCase()) { + if (!ignoreId || ignoreId !== existingRow.id) { + isTaken = true; } } + return true; }); + return true; }); } - return is_taken; + return isTaken; }, /** * Private call only * * @param {Array} hosts - * @param {Array} domain_names + * @param {Array} domainNames * @returns {Array} */ - _getHostsWithDomains: function (hosts, domain_names) { - let response = []; + _getHostsWithDomains: (hosts, domainNames) => { + const response = []; - if (hosts && hosts.length) { - hosts.map(function (host) { - let host_matches = false; + if (hosts?.length) { + hosts.map((host) => { + let hostMatches = false; - domain_names.map(function (domain_name) { - host.domain_names.map(function (host_domain_name) { - if (domain_name.toLowerCase() === host_domain_name.toLowerCase()) { - host_matches = true; + domainNames.map((domainName) => { + host.domain_names.map((hostDomainName) => { + if (domainName.toLowerCase() === hostDomainName.toLowerCase()) { + hostMatches = true; } + return true; }); + return true; }); - if (host_matches) { + if (hostMatches) { response.push(host); } + return true; }); } return response; - } - + }, }; -module.exports = internalHost; +export default internalHost; diff --git a/backend/internal/ip_ranges.js b/backend/internal/ip_ranges.js index d34ee5a1..662da8ed 100644 --- a/backend/internal/ip_ranges.js +++ b/backend/internal/ip_ranges.js @@ -1,45 +1,51 @@ -const https = require('https'); -const fs = require('fs'); -const logger = require('../logger').ip_ranges; -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const internalNginx = require('./nginx'); +import fs from "node:fs"; +import https from "node:https"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import errs from "../lib/error.js"; +import utils from "../lib/utils.js"; +import { ipRanges as logger } from "../logger.js"; +import internalNginx from "./nginx.js"; -const CLOUDFRONT_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json'; -const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4'; -const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const CLOUDFRONT_URL = "https://ip-ranges.amazonaws.com/ip-ranges.json"; +const CLOUDFARE_V4_URL = "https://www.cloudflare.com/ips-v4"; +const CLOUDFARE_V6_URL = "https://www.cloudflare.com/ips-v6"; const regIpV4 = /^(\d+\.?){4}\/\d+/; const regIpV6 = /^(([\da-fA-F]+)?:)+\/\d+/; const internalIpRanges = { - - interval_timeout: 1000 * 60 * 60 * 6, // 6 hours - interval: null, + interval_timeout: 1000 * 60 * 60 * 6, // 6 hours + interval: null, interval_processing: false, - iteration_count: 0, + iteration_count: 0, initTimer: () => { - logger.info('IP Ranges Renewal Timer initialized'); + logger.info("IP Ranges Renewal Timer initialized"); internalIpRanges.interval = setInterval(internalIpRanges.fetch, internalIpRanges.interval_timeout); }, fetchUrl: (url) => { return new Promise((resolve, reject) => { - logger.info('Fetching ' + url); - return https.get(url, (res) => { - res.setEncoding('utf8'); - let raw_data = ''; - res.on('data', (chunk) => { - raw_data += chunk; - }); + logger.info(`Fetching ${url}`); + return https + .get(url, (res) => { + res.setEncoding("utf8"); + let raw_data = ""; + res.on("data", (chunk) => { + raw_data += chunk; + }); - res.on('end', () => { - resolve(raw_data); + res.on("end", () => { + resolve(raw_data); + }); + }) + .on("error", (err) => { + reject(err); }); - }).on('error', (err) => { - reject(err); - }); }); }, @@ -49,27 +55,30 @@ const internalIpRanges = { fetch: () => { if (!internalIpRanges.interval_processing) { internalIpRanges.interval_processing = true; - logger.info('Fetching IP Ranges from online services...'); + logger.info("Fetching IP Ranges from online services..."); let ip_ranges = []; - return internalIpRanges.fetchUrl(CLOUDFRONT_URL) + return internalIpRanges + .fetchUrl(CLOUDFRONT_URL) .then((cloudfront_data) => { - let data = JSON.parse(cloudfront_data); + const data = JSON.parse(cloudfront_data); - if (data && typeof data.prefixes !== 'undefined') { + if (data && typeof data.prefixes !== "undefined") { data.prefixes.map((item) => { - if (item.service === 'CLOUDFRONT') { + if (item.service === "CLOUDFRONT") { ip_ranges.push(item.ip_prefix); } + return true; }); } - if (data && typeof data.ipv6_prefixes !== 'undefined') { + if (data && typeof data.ipv6_prefixes !== "undefined") { data.ipv6_prefixes.map((item) => { - if (item.service === 'CLOUDFRONT') { + if (item.service === "CLOUDFRONT") { ip_ranges.push(item.ipv6_prefix); } + return true; }); } }) @@ -77,38 +86,38 @@ const internalIpRanges = { return internalIpRanges.fetchUrl(CLOUDFARE_V4_URL); }) .then((cloudfare_data) => { - let items = cloudfare_data.split('\n').filter((line) => regIpV4.test(line)); - ip_ranges = [... ip_ranges, ... items]; + const items = cloudfare_data.split("\n").filter((line) => regIpV4.test(line)); + ip_ranges = [...ip_ranges, ...items]; }) .then(() => { return internalIpRanges.fetchUrl(CLOUDFARE_V6_URL); }) .then((cloudfare_data) => { - let items = cloudfare_data.split('\n').filter((line) => regIpV6.test(line)); - ip_ranges = [... ip_ranges, ... items]; + const items = cloudfare_data.split("\n").filter((line) => regIpV6.test(line)); + ip_ranges = [...ip_ranges, ...items]; }) .then(() => { - let clean_ip_ranges = []; + const clean_ip_ranges = []; ip_ranges.map((range) => { if (range) { clean_ip_ranges.push(range); } + return true; }); - return internalIpRanges.generateConfig(clean_ip_ranges) - .then(() => { - if (internalIpRanges.iteration_count) { - // Reload nginx - return internalNginx.reload(); - } - }); + return internalIpRanges.generateConfig(clean_ip_ranges).then(() => { + if (internalIpRanges.iteration_count) { + // Reload nginx + return internalNginx.reload(); + } + }); }) .then(() => { internalIpRanges.interval_processing = false; internalIpRanges.iteration_count++; }) .catch((err) => { - logger.error(err.message); + logger.fatal(err.message); internalIpRanges.interval_processing = false; }); } @@ -122,26 +131,26 @@ const internalIpRanges = { const renderEngine = utils.getRenderEngine(); return new Promise((resolve, reject) => { let template = null; - let filename = '/etc/nginx/conf.d/include/ip_ranges.conf'; + const filename = "/etc/nginx/conf.d/include/ip_ranges.conf"; try { - template = fs.readFileSync(__dirname + '/../templates/ip_ranges.conf', {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/ip_ranges.conf`, { encoding: "utf8" }); } catch (err) { - reject(new error.ConfigurationError(err.message)); + reject(new errs.ConfigurationError(err.message)); return; } renderEngine - .parseAndRender(template, {ip_ranges: ip_ranges}) + .parseAndRender(template, { ip_ranges: ip_ranges }) .then((config_text) => { - fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); + fs.writeFileSync(filename, config_text, { encoding: "utf8" }); resolve(true); }) .catch((err) => { - logger.warn('Could not write ' + filename + ':', err.message); - reject(new error.ConfigurationError(err.message)); + logger.warn(`Could not write ${filename}: ${err.message}`); + reject(new errs.ConfigurationError(err.message)); }); }); - } + }, }; -module.exports = internalIpRanges; +export default internalIpRanges; diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 5f802c00..83d2c3f0 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -1,12 +1,15 @@ -const _ = require('lodash'); -const fs = require('fs'); -const logger = require('../logger').nginx; -const config = require('../lib/config'); -const utils = require('../lib/utils'); -const error = require('../lib/error'); +import fs from "node:fs"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import _ from "lodash"; +import errs from "../lib/error.js"; +import utils from "../lib/utils.js"; +import { debug, nginx as logger } from "../logger.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); const internalNginx = { - /** * This will: * - test the nginx config first to make sure it's OK @@ -24,7 +27,8 @@ const internalNginx = { configure: (model, host_type, host) => { let combined_meta = {}; - return internalNginx.test() + return internalNginx + .test() .then(() => { // Nginx is OK // We're deleting this config regardless. @@ -37,49 +41,46 @@ const internalNginx = { }) .then(() => { // Test nginx again and update meta with result - return internalNginx.test() + return internalNginx + .test() .then(() => { // nginx is ok combined_meta = _.assign({}, host.meta, { nginx_online: true, - nginx_err: null + nginx_err: null, }); - return model - .query() - .where('id', host.id) - .patch({ - meta: combined_meta - }); + return model.query().where("id", host.id).patch({ + meta: combined_meta, + }); }) .catch((err) => { // Remove the error_log line because it's a docker-ism false positive that doesn't need to be reported. // It will always look like this: // nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address) - let valid_lines = []; - let err_lines = err.message.split('\n'); - err_lines.map(function (line) { - if (line.indexOf('/var/log/nginx/error.log') === -1) { + const valid_lines = []; + const err_lines = err.message.split("\n"); + err_lines.map((line) => { + if (line.indexOf("/var/log/nginx/error.log") === -1) { valid_lines.push(line); } + return true; }); - if (config.debug()) { - logger.error('Nginx test failed:', valid_lines.join('\n')); - } + debug(logger, "Nginx test failed:", valid_lines.join("\n")); // config is bad, update meta and delete config combined_meta = _.assign({}, host.meta, { nginx_online: false, - nginx_err: valid_lines.join('\n') + nginx_err: valid_lines.join("\n"), }); return model .query() - .where('id', host.id) + .where("id", host.id) .patch({ - meta: combined_meta + meta: combined_meta, }) .then(() => { internalNginx.renameConfigAsError(host_type, host); @@ -101,22 +102,18 @@ const internalNginx = { * @returns {Promise} */ test: () => { - if (config.debug()) { - logger.info('Testing Nginx configuration'); - } - - return utils.exec('/usr/sbin/nginx -t -g "error_log off;"'); + debug(logger, "Testing Nginx configuration"); + return utils.execFile("/usr/sbin/nginx", ["-t", "-g", "error_log off;"]); }, /** * @returns {Promise} */ reload: () => { - return internalNginx.test() - .then(() => { - logger.info('Reloading Nginx'); - return utils.exec('/usr/sbin/nginx -s reload'); - }); + return internalNginx.test().then(() => { + logger.info("Reloading Nginx"); + return utils.execFile("/usr/sbin/nginx", ["-s", "reload"]); + }); }, /** @@ -125,10 +122,10 @@ const internalNginx = { * @returns {String} */ getConfigName: (host_type, host_id) => { - if (host_type === 'default') { - return '/data/nginx/default_host/site.conf'; + if (host_type === "default") { + return "/data/nginx/default_host/site.conf"; } - return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf'; + return `/data/nginx/${internalNginx.getFileFriendlyHostType(host_type)}/${host_id}.conf`; }, /** @@ -141,38 +138,45 @@ const internalNginx = { let template; try { - template = fs.readFileSync(__dirname + '/../templates/_location.conf', {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/_location.conf`, { encoding: "utf8" }); } catch (err) { - reject(new error.ConfigurationError(err.message)); + reject(new errs.ConfigurationError(err.message)); return; } - const renderEngine = utils.getRenderEngine(); - let renderedLocations = ''; + const renderEngine = utils.getRenderEngine(); + let renderedLocations = ""; const locationRendering = async () => { for (let i = 0; i < host.locations.length; i++) { - let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id}, - {ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits}, - {allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support}, - {hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list}, - {certificate: host.certificate}, host.locations[i]); + const locationCopy = Object.assign( + {}, + { access_list_id: host.access_list_id }, + { certificate_id: host.certificate_id }, + { ssl_forced: host.ssl_forced }, + { caching_enabled: host.caching_enabled }, + { block_exploits: host.block_exploits }, + { allow_websocket_upgrade: host.allow_websocket_upgrade }, + { http2_support: host.http2_support }, + { hsts_enabled: host.hsts_enabled }, + { hsts_subdomains: host.hsts_subdomains }, + { access_list: host.access_list }, + { certificate: host.certificate }, + host.locations[i], + ); - if (locationCopy.forward_host.indexOf('/') > -1) { - const splitted = locationCopy.forward_host.split('/'); + if (locationCopy.forward_host.indexOf("/") > -1) { + const splitted = locationCopy.forward_host.split("/"); locationCopy.forward_host = splitted.shift(); - locationCopy.forward_path = `/${splitted.join('/')}`; + locationCopy.forward_path = `/${splitted.join("/")}`; } - // eslint-disable-next-line renderedLocations += await renderEngine.parseAndRender(template, locationCopy); } - }; locationRendering().then(() => resolve(renderedLocations)); - }); }, @@ -183,23 +187,21 @@ const internalNginx = { */ generateConfig: (host_type, host_row) => { // Prevent modifying the original object: - let host = JSON.parse(JSON.stringify(host_row)); + const host = JSON.parse(JSON.stringify(host_row)); const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); - if (config.debug()) { - logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2)); - } + debug(logger, `Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2)); const renderEngine = utils.getRenderEngine(); return new Promise((resolve, reject) => { let template = null; - let filename = internalNginx.getConfigName(nice_host_type, host.id); + const filename = internalNginx.getConfigName(nice_host_type, host.id); try { - template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/${nice_host_type}.conf`, { encoding: "utf8" }); } catch (err) { - reject(new error.ConfigurationError(err.message)); + reject(new errs.ConfigurationError(err.message)); return; } @@ -207,27 +209,26 @@ const internalNginx = { let origLocations; // Manipulate the data a bit before sending it to the template - if (nice_host_type !== 'default') { + if (nice_host_type !== "default") { host.use_default_location = true; - if (typeof host.advanced_config !== 'undefined' && host.advanced_config) { + if (typeof host.advanced_config !== "undefined" && host.advanced_config) { host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config); } } if (host.locations) { //logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2)); - origLocations = [].concat(host.locations); + origLocations = [].concat(host.locations); locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => { host.locations = renderedLocations; }); // Allow someone who is using / custom location path to use it, and skip the default / location _.map(host.locations, (location) => { - if (location.path === '/') { + if (location.path === "/") { host.use_default_location = false; } }); - } else { locationsPromise = Promise.resolve(); } @@ -239,11 +240,8 @@ const internalNginx = { renderEngine .parseAndRender(template, host) .then((config_text) => { - fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); - - if (config.debug()) { - logger.success('Wrote config:', filename, config_text); - } + fs.writeFileSync(filename, config_text, { encoding: "utf8" }); + debug(logger, "Wrote config:", filename, config_text); // Restore locations array host.locations = origLocations; @@ -251,11 +249,8 @@ const internalNginx = { resolve(true); }) .catch((err) => { - if (config.debug()) { - logger.warn('Could not write ' + filename + ':', err.message); - } - - reject(new error.ConfigurationError(err.message)); + debug(logger, `Could not write ${filename}:`, err.message); + reject(new errs.ConfigurationError(err.message)); }); }); }); @@ -270,20 +265,17 @@ const internalNginx = { * @returns {Promise} */ generateLetsEncryptRequestConfig: (certificate) => { - if (config.debug()) { - logger.info('Generating LetsEncrypt Request Config:', certificate); - } - + debug(logger, "Generating LetsEncrypt Request Config:", certificate); const renderEngine = utils.getRenderEngine(); return new Promise((resolve, reject) => { let template = null; - let filename = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf'; + const filename = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; try { - template = fs.readFileSync(__dirname + '/../templates/letsencrypt-request.conf', {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/letsencrypt-request.conf`, { encoding: "utf8" }); } catch (err) { - reject(new error.ConfigurationError(err.message)); + reject(new errs.ConfigurationError(err.message)); return; } @@ -292,20 +284,13 @@ const internalNginx = { renderEngine .parseAndRender(template, certificate) .then((config_text) => { - fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); - - if (config.debug()) { - logger.success('Wrote config:', filename, config_text); - } - + fs.writeFileSync(filename, config_text, { encoding: "utf8" }); + debug(logger, "Wrote config:", filename, config_text); resolve(true); }) .catch((err) => { - if (config.debug()) { - logger.warn('Could not write ' + filename + ':', err.message); - } - - reject(new error.ConfigurationError(err.message)); + debug(logger, `Could not write ${filename}:`, err.message); + reject(new errs.ConfigurationError(err.message)); }); }); }, @@ -316,11 +301,14 @@ const internalNginx = { * @param {String} filename */ deleteFile: (filename) => { - logger.debug('Deleting file: ' + filename); + if (!fs.existsSync(filename)) { + return; + } try { + debug(logger, `Deleting file: ${filename}`); fs.unlinkSync(filename); } catch (err) { - logger.debug('Could not delete file:', JSON.stringify(err, null, 2)); + debug(logger, "Could not delete file:", JSON.stringify(err, null, 2)); } }, @@ -330,7 +318,7 @@ const internalNginx = { * @returns String */ getFileFriendlyHostType: (host_type) => { - return host_type.replace(new RegExp('-', 'g'), '_'); + return host_type.replace(/-/g, "_"); }, /** @@ -340,8 +328,8 @@ const internalNginx = { * @returns {Promise} */ deleteLetsEncryptRequestConfig: (certificate) => { - const config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf'; - return new Promise((resolve/*, reject*/) => { + const config_file = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; + return new Promise((resolve /*, reject*/) => { internalNginx.deleteFile(config_file); resolve(); }); @@ -354,10 +342,13 @@ const internalNginx = { * @returns {Promise} */ deleteConfig: (host_type, host, delete_err_file) => { - const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); - const config_file_err = config_file + '.err'; + const config_file = internalNginx.getConfigName( + internalNginx.getFileFriendlyHostType(host_type), + typeof host === "undefined" ? 0 : host.id, + ); + const config_file_err = `${config_file}.err`; - return new Promise((resolve/*, reject*/) => { + return new Promise((resolve /*, reject*/) => { internalNginx.deleteFile(config_file); if (delete_err_file) { internalNginx.deleteFile(config_file_err); @@ -372,10 +363,13 @@ const internalNginx = { * @returns {Promise} */ renameConfigAsError: (host_type, host) => { - const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); - const config_file_err = config_file + '.err'; + const config_file = internalNginx.getConfigName( + internalNginx.getFileFriendlyHostType(host_type), + typeof host === "undefined" ? 0 : host.id, + ); + const config_file_err = `${config_file}.err`; - return new Promise((resolve/*, reject*/) => { + return new Promise((resolve /*, reject*/) => { fs.unlink(config_file, () => { // ignore result, continue fs.rename(config_file, config_file_err, () => { @@ -387,14 +381,15 @@ const internalNginx = { }, /** - * @param {String} host_type + * @param {String} hostType * @param {Array} hosts * @returns {Promise} */ - bulkGenerateConfigs: (host_type, hosts) => { - let promises = []; - hosts.map(function (host) { - promises.push(internalNginx.generateConfig(host_type, host)); + bulkGenerateConfigs: (hostType, hosts) => { + const promises = []; + hosts.map((host) => { + promises.push(internalNginx.generateConfig(hostType, host)); + return true; }); return Promise.all(promises); @@ -406,9 +401,10 @@ const internalNginx = { * @returns {Promise} */ bulkDeleteConfigs: (host_type, hosts) => { - let promises = []; - hosts.map(function (host) { + const promises = []; + hosts.map((host) => { promises.push(internalNginx.deleteConfig(host_type, host, true)); + return true; }); return Promise.all(promises); @@ -418,21 +414,19 @@ const internalNginx = { * @param {string} config * @returns {boolean} */ - advancedConfigHasDefaultLocation: function (cfg) { - return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im); - }, + advancedConfigHasDefaultLocation: (cfg) => !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im), /** * @returns {boolean} */ - ipv6Enabled: function () { - if (typeof process.env.DISABLE_IPV6 !== 'undefined') { + ipv6Enabled: () => { + if (typeof process.env.DISABLE_IPV6 !== "undefined") { const disabled = process.env.DISABLE_IPV6.toLowerCase(); - return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes'); + return !(disabled === "on" || disabled === "true" || disabled === "1" || disabled === "yes"); } return true; - } + }, }; -module.exports = internalNginx; +export default internalNginx; diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 32f2bc0d..3299012a 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -1,107 +1,106 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const proxyHostModel = require('../models/proxy_host'); -const internalHost = require('./host'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); -const {castJsonIfNeed} = require('../lib/helpers'); +import _ from "lodash"; +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import utils from "../lib/utils.js"; +import proxyHostModel from "../models/proxy_host.js"; +import internalAuditLog from "./audit-log.js"; +import internalCertificate from "./certificate.js"; +import internalHost from "./host.js"; +import internalNginx from "./nginx.js"; -function omissions () { - return ['is_deleted', 'owner.is_deleted']; -} +const omissions = () => { + return ["is_deleted", "owner.is_deleted"]; +}; const internalProxyHost = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ create: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + let thisData = data; + const createCertificate = thisData.certificate_id === "new"; - if (create_certificate) { - delete data.certificate_id; + if (createCertificate) { + delete thisData.certificate_id; } - return access.can('proxy_hosts:create', data) + return access + .can("proxy_hosts:create", thisData) .then(() => { // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + const domain_name_check_promises = []; - data.domain_names.map(function (domain_name) { + thisData.domain_names.map((domain_name) => { domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); + return true; }); - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + return Promise.all(domain_name_check_promises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; }); + }); }) .then(() => { // At this point the domains should have been checked - data.owner_user_id = access.token.getUserId(1); - data = internalHost.cleanSslHstsData(data); + thisData.owner_user_id = access.token.getUserId(1); + thisData = internalHost.cleanSslHstsData(thisData); // Fix for db field not having a default value // for this optional field. - if (typeof data.advanced_config === 'undefined') { - data.advanced_config = ''; + if (typeof thisData.advanced_config === "undefined") { + thisData.advanced_config = ""; } - return proxyHostModel - .query() - .insertAndFetch(data) - .then(utils.omitRow(omissions())); + return proxyHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); }) .then((row) => { - if (create_certificate) { - return internalCertificate.createQuickCertificate(access, data) + if (createCertificate) { + return internalCertificate + .createQuickCertificate(access, thisData) .then((cert) => { // update host with cert id return internalProxyHost.update(access, { - id: row.id, - certificate_id: cert.id + id: row.id, + certificate_id: cert.id, }); }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // re-fetch with cert return internalProxyHost.get(access, { - id: row.id, - expand: ['certificate', 'owner', 'access_list.[clients,items]'] + id: row.id, + expand: ["certificate", "owner", "access_list.[clients,items]"], }); }) .then((row) => { // Configure nginx - return internalNginx.configure(proxyHostModel, 'proxy_host', row) - .then(() => { - return row; - }); + return internalNginx.configure(proxyHostModel, "proxy_host", row).then(() => { + return row; + }); }) .then((row) => { // Audit log - data.meta = _.assign({}, data.meta || {}, row.meta); + thisData.meta = _.assign({}, thisData.meta || {}, row.meta); // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'proxy-host', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "created", + object_type: "proxy-host", + object_id: row.id, + meta: thisData, + }) .then(() => { return row; }); @@ -115,100 +114,110 @@ const internalProxyHost = { * @return {Promise} */ update: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + let thisData = data; + const create_certificate = thisData.certificate_id === "new"; if (create_certificate) { - delete data.certificate_id; + delete thisData.certificate_id; } - return access.can('proxy_hosts:update', data.id) + return access + .can("proxy_hosts:update", thisData.id) .then((/*access_data*/) => { // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + const domain_name_check_promises = []; - if (typeof data.domain_names !== 'undefined') { - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'proxy', data.id)); + if (typeof thisData.domain_names !== "undefined") { + thisData.domain_names.map((domain_name) => { + return domain_name_check_promises.push( + internalHost.isHostnameTaken(domain_name, "proxy", thisData.id), + ); }); - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + return Promise.all(domain_name_check_promises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; }); + }); } }) .then(() => { - return internalProxyHost.get(access, {id: data.id}); + return internalProxyHost.get(access, { id: thisData.id }); }) .then((row) => { - if (row.id !== data.id) { + if (row.id !== thisData.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Proxy Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `Proxy Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`, + ); } if (create_certificate) { - return internalCertificate.createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta) - }) + return internalCertificate + .createQuickCertificate(access, { + domain_names: thisData.domain_names || row.domain_names, + meta: _.assign({}, row.meta, thisData.meta), + }) .then((cert) => { // update host with cert id - data.certificate_id = cert.id; + thisData.certificate_id = cert.id; }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign({}, { - domain_names: row.domain_names - }, data); + thisData = _.assign( + {}, + { + domain_names: row.domain_names, + }, + data, + ); - data = internalHost.cleanSslHstsData(data, row); + thisData = internalHost.cleanSslHstsData(thisData, row); return proxyHostModel .query() - .where({id: data.id}) - .patch(data) + .where({ id: thisData.id }) + .patch(thisData) .then(utils.omitRow(omissions())) .then((saved_row) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'proxy-host', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "updated", + object_type: "proxy-host", + object_id: row.id, + meta: thisData, + }) .then(() => { return saved_row; }); }); }) .then(() => { - return internalProxyHost.get(access, { - id: data.id, - expand: ['owner', 'certificate', 'access_list.[clients,items]'] - }) + return internalProxyHost + .get(access, { + id: thisData.id, + expand: ["owner", "certificate", "access_list.[clients,items]"], + }) .then((row) => { if (!row.enabled) { // No need to add nginx config if host is disabled return row; } // Configure nginx - return internalNginx.configure(proxyHostModel, 'proxy_host', row) - .then((new_meta) => { - row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); - }); + return internalNginx.configure(proxyHostModel, "proxy_host", row).then((new_meta) => { + row.meta = new_meta; + return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); + }); }); }); }, @@ -222,39 +231,38 @@ const internalProxyHost = { * @return {Promise} */ get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } + const thisData = data || {}; - return access.can('proxy_hosts:get', data.id) + return access + .can("proxy_hosts:get", thisData.id) .then((access_data) => { - let query = proxyHostModel + const query = proxyHostModel .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[owner,access_list.[clients,items],certificate]') + .where("is_deleted", 0) + .andWhere("id", thisData.id) + .allowGraph("[owner,access_list.[clients,items],certificate]") .first(); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(thisData.id); } - row = internalHost.cleanRowCertificateMeta(row); + const thisRow = internalHost.cleanRowCertificateMeta(row); // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { + return _.omit(row, thisData.omit); } - return row; + return thisRow; }); }, @@ -266,35 +274,35 @@ const internalProxyHost = { * @returns {Promise} */ delete: (access, data) => { - return access.can('proxy_hosts:delete', data.id) + return access + .can("proxy_hosts:delete", data.id) .then(() => { - return internalProxyHost.get(access, {id: data.id}); + return internalProxyHost.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(data.id); } return proxyHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('proxy_host', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("proxy_host", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'proxy-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "deleted", + object_type: "proxy-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -311,39 +319,41 @@ const internalProxyHost = { * @returns {Promise} */ enable: (access, data) => { - return access.can('proxy_hosts:update', data.id) + return access + .can("proxy_hosts:update", data.id) .then(() => { return internalProxyHost.get(access, { - id: data.id, - expand: ['certificate', 'owner', 'access_list'] + id: data.id, + expand: ["certificate", "owner", "access_list"], }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Host is already enabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Host is already enabled"); } row.enabled = 1; return proxyHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 1 + enabled: 1, }) .then(() => { // Configure nginx - return internalNginx.configure(proxyHostModel, 'proxy_host', row); + return internalNginx.configure(proxyHostModel, "proxy_host", row); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'proxy-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "enabled", + object_type: "proxy-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -360,39 +370,40 @@ const internalProxyHost = { * @returns {Promise} */ disable: (access, data) => { - return access.can('proxy_hosts:update', data.id) + return access + .can("proxy_hosts:update", data.id) .then(() => { - return internalProxyHost.get(access, {id: data.id}); + return internalProxyHost.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Host is already disabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (!row.enabled) { + throw new errs.ValidationError("Host is already disabled"); } row.enabled = 0; return proxyHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 0 + enabled: 0, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('proxy_host', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("proxy_host", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'proxy-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "disabled", + object_type: "proxy-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -409,40 +420,35 @@ const internalProxyHost = { * @param {String} [search_query] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access.can('proxy_hosts:list') - .then((access_data) => { - let query = proxyHostModel - .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[owner,access_list,certificate]') - .orderBy(castJsonIfNeed('domain_names'), 'ASC'); + getAll: async (access, expand, searchQuery) => { + const accessData = await access.can("proxy_hosts:list"); + const query = proxyHostModel + .query() + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,access_list,certificate]") + .orderBy(castJsonIfNeed("domain_names"), "ASC"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { - query.where(function () { - this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); - }) - .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { - return internalHost.cleanAllRowsCertificateMeta(rows); - } - - return rows; + // Query is used for searching + if (typeof searchQuery === "string" && searchQuery.length > 0) { + query.where(function () { + this.where(castJsonIfNeed("domain_names"), "like", `%${searchQuery}%`); }); + } + + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + const rows = await query.then(utils.omitRows(omissions())); + if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { + return internalHost.cleanAllRowsCertificateMeta(rows); + } + return rows; }, /** @@ -453,20 +459,16 @@ const internalProxyHost = { * @returns {Promise} */ getCount: (user_id, visibility) => { - let query = proxyHostModel - .query() - .count('id as count') - .where('is_deleted', 0); + const query = proxyHostModel.query().count("id as count").where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", user_id); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); - } + return query.first().then((row) => { + return Number.parseInt(row.count, 10); + }); + }, }; -module.exports = internalProxyHost; +export default internalProxyHost; diff --git a/backend/internal/redirection-host.js b/backend/internal/redirection-host.js index 6a81b866..159ffd8b 100644 --- a/backend/internal/redirection-host.js +++ b/backend/internal/redirection-host.js @@ -1,73 +1,73 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const redirectionHostModel = require('../models/redirection_host'); -const internalHost = require('./host'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); -const {castJsonIfNeed} = require('../lib/helpers'); +import _ from "lodash"; +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import utils from "../lib/utils.js"; +import redirectionHostModel from "../models/redirection_host.js"; +import internalAuditLog from "./audit-log.js"; +import internalCertificate from "./certificate.js"; +import internalHost from "./host.js"; +import internalNginx from "./nginx.js"; -function omissions () { - return ['is_deleted']; -} +const omissions = () => { + return ["is_deleted"]; +}; const internalRedirectionHost = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ create: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + let thisData = data || {}; + const createCertificate = thisData.certificate_id === "new"; - if (create_certificate) { - delete data.certificate_id; + if (createCertificate) { + delete thisData.certificate_id; } - return access.can('redirection_hosts:create', data) + return access + .can("redirection_hosts:create", thisData) .then((/*access_data*/) => { // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + const domain_name_check_promises = []; - data.domain_names.map(function (domain_name) { + thisData.domain_names.map((domain_name) => { domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); + return true; }); - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + return Promise.all(domain_name_check_promises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; }); + }); }) .then(() => { // At this point the domains should have been checked - data.owner_user_id = access.token.getUserId(1); - data = internalHost.cleanSslHstsData(data); + thisData.owner_user_id = access.token.getUserId(1); + thisData = internalHost.cleanSslHstsData(thisData); // Fix for db field not having a default value // for this optional field. - if (typeof data.advanced_config === 'undefined') { - data.advanced_config = ''; + if (typeof data.advanced_config === "undefined") { + data.advanced_config = ""; } - return redirectionHostModel - .query() - .insertAndFetch(data) - .then(utils.omitRow(omissions())); + return redirectionHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); }) .then((row) => { - if (create_certificate) { - return internalCertificate.createQuickCertificate(access, data) + if (createCertificate) { + return internalCertificate + .createQuickCertificate(access, thisData) .then((cert) => { // update host with cert id return internalRedirectionHost.update(access, { - id: row.id, - certificate_id: cert.id + id: row.id, + certificate_id: cert.id, }); }) .then(() => { @@ -79,27 +79,27 @@ const internalRedirectionHost = { .then((row) => { // re-fetch with cert return internalRedirectionHost.get(access, { - id: row.id, - expand: ['certificate', 'owner'] + id: row.id, + expand: ["certificate", "owner"], }); }) .then((row) => { // Configure nginx - return internalNginx.configure(redirectionHostModel, 'redirection_host', row) - .then(() => { - return row; - }); + return internalNginx.configure(redirectionHostModel, "redirection_host", row).then(() => { + return row; + }); }) .then((row) => { - data.meta = _.assign({}, data.meta || {}, row.meta); + thisData.meta = _.assign({}, thisData.meta || {}, row.meta); // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'redirection-host', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "created", + object_type: "redirection-host", + object_id: row.id, + meta: thisData, + }) .then(() => { return row; }); @@ -113,94 +113,107 @@ const internalRedirectionHost = { * @return {Promise} */ update: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + let thisData = data || {}; + const createCertificate = thisData.certificate_id === "new"; - if (create_certificate) { - delete data.certificate_id; + if (createCertificate) { + delete thisData.certificate_id; } - return access.can('redirection_hosts:update', data.id) + return access + .can("redirection_hosts:update", thisData.id) .then((/*access_data*/) => { // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + const domain_name_check_promises = []; - if (typeof data.domain_names !== 'undefined') { - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'redirection', data.id)); + if (typeof thisData.domain_names !== "undefined") { + thisData.domain_names.map((domain_name) => { + domain_name_check_promises.push( + internalHost.isHostnameTaken(domain_name, "redirection", thisData.id), + ); + return true; }); - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + return Promise.all(domain_name_check_promises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; }); + }); } }) .then(() => { - return internalRedirectionHost.get(access, {id: data.id}); + return internalRedirectionHost.get(access, { id: thisData.id }); }) .then((row) => { - if (row.id !== data.id) { + if (row.id !== thisData.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Redirection Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `Redirection Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`, + ); } - if (create_certificate) { - return internalCertificate.createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta) - }) + if (createCertificate) { + return internalCertificate + .createQuickCertificate(access, { + domain_names: thisData.domain_names || row.domain_names, + meta: _.assign({}, row.meta, thisData.meta), + }) .then((cert) => { // update host with cert id - data.certificate_id = cert.id; + thisData.certificate_id = cert.id; }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign({}, { - domain_names: row.domain_names - }, data); + thisData = _.assign( + {}, + { + domain_names: row.domain_names, + }, + thisData, + ); - data = internalHost.cleanSslHstsData(data, row); + thisData = internalHost.cleanSslHstsData(thisData, row); return redirectionHostModel .query() - .where({id: data.id}) - .patch(data) + .where({ id: thisData.id }) + .patch(thisData) .then((saved_row) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'redirection-host', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "updated", + object_type: "redirection-host", + object_id: row.id, + meta: thisData, + }) .then(() => { return _.omit(saved_row, omissions()); }); }); }) .then(() => { - return internalRedirectionHost.get(access, { - id: data.id, - expand: ['owner', 'certificate'] - }) + return internalRedirectionHost + .get(access, { + id: thisData.id, + expand: ["owner", "certificate"], + }) .then((row) => { // Configure nginx - return internalNginx.configure(redirectionHostModel, 'redirection_host', row) + return internalNginx + .configure(redirectionHostModel, "redirection_host", row) .then((new_meta) => { row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); + return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); }); }); }); @@ -215,39 +228,39 @@ const internalRedirectionHost = { * @return {Promise} */ get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } + const thisData = data || {}; - return access.can('redirection_hosts:get', data.id) + return access + .can("redirection_hosts:get", thisData.id) .then((access_data) => { - let query = redirectionHostModel + const query = redirectionHostModel .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[owner,certificate]') + .where("is_deleted", 0) + .andWhere("id", thisData.id) + .allowGraph("[owner,certificate]") .first(); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + let thisRow = row; + if (!thisRow || !thisRow.id) { + throw new errs.ItemNotFoundError(thisData.id); } - row = internalHost.cleanRowCertificateMeta(row); + thisRow = internalHost.cleanRowCertificateMeta(thisRow); // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { + return _.omit(thisRow, thisData.omit); } - return row; + return thisRow; }); }, @@ -259,35 +272,35 @@ const internalRedirectionHost = { * @returns {Promise} */ delete: (access, data) => { - return access.can('redirection_hosts:delete', data.id) + return access + .can("redirection_hosts:delete", data.id) .then(() => { - return internalRedirectionHost.get(access, {id: data.id}); + return internalRedirectionHost.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(data.id); } return redirectionHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('redirection_host', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("redirection_host", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'redirection-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "deleted", + object_type: "redirection-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -304,39 +317,41 @@ const internalRedirectionHost = { * @returns {Promise} */ enable: (access, data) => { - return access.can('redirection_hosts:update', data.id) + return access + .can("redirection_hosts:update", data.id) .then(() => { return internalRedirectionHost.get(access, { - id: data.id, - expand: ['certificate', 'owner'] + id: data.id, + expand: ["certificate", "owner"], }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Host is already enabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Host is already enabled"); } row.enabled = 1; return redirectionHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 1 + enabled: 1, }) .then(() => { // Configure nginx - return internalNginx.configure(redirectionHostModel, 'redirection_host', row); + return internalNginx.configure(redirectionHostModel, "redirection_host", row); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'redirection-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "enabled", + object_type: "redirection-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -353,39 +368,40 @@ const internalRedirectionHost = { * @returns {Promise} */ disable: (access, data) => { - return access.can('redirection_hosts:update', data.id) + return access + .can("redirection_hosts:update", data.id) .then(() => { - return internalRedirectionHost.get(access, {id: data.id}); + return internalRedirectionHost.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Host is already disabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (!row.enabled) { + throw new errs.ValidationError("Host is already disabled"); } row.enabled = 0; return redirectionHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 0 + enabled: 0, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('redirection_host', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("redirection_host", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'redirection-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "disabled", + object_type: "redirection-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -403,34 +419,35 @@ const internalRedirectionHost = { * @returns {Promise} */ getAll: (access, expand, search_query) => { - return access.can('redirection_hosts:list') + return access + .can("redirection_hosts:list") .then((access_data) => { - let query = redirectionHostModel + const query = redirectionHostModel .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[owner,certificate]') - .orderBy(castJsonIfNeed('domain_names'), 'ASC'); + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,certificate]") + .orderBy(castJsonIfNeed("domain_names"), "ASC"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { + if (typeof search_query === "string" && search_query.length > 0) { query.where(function () { - this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`); + this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`); }); } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); } return query.then(utils.omitRows(omissions())); }) .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { + if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { return internalHost.cleanAllRowsCertificateMeta(rows); } @@ -446,20 +463,16 @@ const internalRedirectionHost = { * @returns {Promise} */ getCount: (user_id, visibility) => { - let query = redirectionHostModel - .query() - .count('id as count') - .where('is_deleted', 0); + const query = redirectionHostModel.query().count("id as count").where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", user_id); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); - } + return query.first().then((row) => { + return Number.parseInt(row.count, 10); + }); + }, }; -module.exports = internalRedirectionHost; +export default internalRedirectionHost; diff --git a/backend/internal/report.js b/backend/internal/report.js index 4dde659b..8bad7ec2 100644 --- a/backend/internal/report.js +++ b/backend/internal/report.js @@ -1,38 +1,37 @@ -const internalProxyHost = require('./proxy-host'); -const internalRedirectionHost = require('./redirection-host'); -const internalDeadHost = require('./dead-host'); -const internalStream = require('./stream'); +import internalDeadHost from "./dead-host.js"; +import internalProxyHost from "./proxy-host.js"; +import internalRedirectionHost from "./redirection-host.js"; +import internalStream from "./stream.js"; const internalReport = { - /** * @param {Access} access * @return {Promise} */ getHostsReport: (access) => { - return access.can('reports:hosts', 1) + return access + .can("reports:hosts", 1) .then((access_data) => { - let user_id = access.token.getUserId(1); + const userId = access.token.getUserId(1); - let promises = [ - internalProxyHost.getCount(user_id, access_data.visibility), - internalRedirectionHost.getCount(user_id, access_data.visibility), - internalStream.getCount(user_id, access_data.visibility), - internalDeadHost.getCount(user_id, access_data.visibility) + const promises = [ + internalProxyHost.getCount(userId, access_data.visibility), + internalRedirectionHost.getCount(userId, access_data.visibility), + internalStream.getCount(userId, access_data.visibility), + internalDeadHost.getCount(userId, access_data.visibility), ]; return Promise.all(promises); }) .then((counts) => { return { - proxy: counts.shift(), + proxy: counts.shift(), redirection: counts.shift(), - stream: counts.shift(), - dead: counts.shift() + stream: counts.shift(), + dead: counts.shift(), }; }); - - } + }, }; -module.exports = internalReport; +export default internalReport; diff --git a/backend/internal/setting.js b/backend/internal/setting.js index d4ac67d8..f8fc7114 100644 --- a/backend/internal/setting.js +++ b/backend/internal/setting.js @@ -1,10 +1,9 @@ -const fs = require('fs'); -const error = require('../lib/error'); -const settingModel = require('../models/setting'); -const internalNginx = require('./nginx'); +import fs from "node:fs"; +import errs from "../lib/error.js"; +import settingModel from "../models/setting.js"; +import internalNginx from "./nginx.js"; const internalSetting = { - /** * @param {Access} access * @param {Object} data @@ -12,37 +11,38 @@ const internalSetting = { * @return {Promise} */ update: (access, data) => { - return access.can('settings:update', data.id) + return access + .can("settings:update", data.id) .then((/*access_data*/) => { - return internalSetting.get(access, {id: data.id}); + return internalSetting.get(access, { id: data.id }); }) .then((row) => { if (row.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Setting could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `Setting could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); } - return settingModel - .query() - .where({id: data.id}) - .patch(data); + return settingModel.query().where({ id: data.id }).patch(data); }) .then(() => { return internalSetting.get(access, { - id: data.id + id: data.id, }); }) .then((row) => { - if (row.id === 'default-site') { + if (row.id === "default-site") { // write the html if we need to - if (row.value === 'html') { - fs.writeFileSync('/data/nginx/default_www/index.html', row.meta.html, {encoding: 'utf8'}); + if (row.value === "html") { + fs.writeFileSync("/data/nginx/default_www/index.html", row.meta.html, { encoding: "utf8" }); } // Configure nginx - return internalNginx.deleteConfig('default') + return internalNginx + .deleteConfig("default") .then(() => { - return internalNginx.generateConfig('default', row); + return internalNginx.generateConfig("default", row); }) .then(() => { return internalNginx.test(); @@ -54,7 +54,8 @@ const internalSetting = { return row; }) .catch((/*err*/) => { - internalNginx.deleteConfig('default') + internalNginx + .deleteConfig("default") .then(() => { return internalNginx.test(); }) @@ -63,12 +64,11 @@ const internalSetting = { }) .then(() => { // I'm being slack here I know.. - throw new error.ValidationError('Could not reconfigure Nginx. Please check logs.'); + throw new errs.ValidationError("Could not reconfigure Nginx. Please check logs."); }); }); - } else { - return row; } + return row; }); }, @@ -79,19 +79,16 @@ const internalSetting = { * @return {Promise} */ get: (access, data) => { - return access.can('settings:get', data.id) + return access + .can("settings:get", data.id) .then(() => { - return settingModel - .query() - .where('id', data.id) - .first(); + return settingModel.query().where("id", data.id).first(); }) .then((row) => { if (row) { return row; - } else { - throw new error.ItemNotFoundError(data.id); } + throw new errs.ItemNotFoundError(data.id); }); }, @@ -102,15 +99,13 @@ const internalSetting = { * @returns {*} */ getCount: (access) => { - return access.can('settings:list') + return access + .can("settings:list") .then(() => { - return settingModel - .query() - .count('id as count') - .first(); + return settingModel.query().count("id as count").first(); }) .then((row) => { - return parseInt(row.count, 10); + return Number.parseInt(row.count, 10); }); }, @@ -121,13 +116,10 @@ const internalSetting = { * @returns {Promise} */ getAll: (access) => { - return access.can('settings:list') - .then(() => { - return settingModel - .query() - .orderBy('description', 'ASC'); - }); - } + return access.can("settings:list").then(() => { + return settingModel.query().orderBy("description", "ASC"); + }); + }, }; -module.exports = internalSetting; +export default internalSetting; diff --git a/backend/internal/stream.js b/backend/internal/stream.js index 50ce0832..805b6652 100644 --- a/backend/internal/stream.js +++ b/backend/internal/stream.js @@ -1,88 +1,85 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const streamModel = require('../models/stream'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); -const internalHost = require('./host'); -const {castJsonIfNeed} = require('../lib/helpers'); +import _ from "lodash"; +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import utils from "../lib/utils.js"; +import streamModel from "../models/stream.js"; +import internalAuditLog from "./audit-log.js"; +import internalCertificate from "./certificate.js"; +import internalHost from "./host.js"; +import internalNginx from "./nginx.js"; -function omissions () { - return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted']; -} +const omissions = () => { + return ["is_deleted", "owner.is_deleted", "certificate.is_deleted"]; +}; const internalStream = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ create: (access, data) => { - const create_certificate = data.certificate_id === 'new'; + const create_certificate = data.certificate_id === "new"; if (create_certificate) { delete data.certificate_id; } - return access.can('streams:create', data) + return access + .can("streams:create", data) .then((/*access_data*/) => { // TODO: At this point the existing ports should have been checked data.owner_user_id = access.token.getUserId(1); - if (typeof data.meta === 'undefined') { + if (typeof data.meta === "undefined") { data.meta = {}; } // streams aren't routed by domain name so don't store domain names in the DB - let data_no_domains = structuredClone(data); + const data_no_domains = structuredClone(data); delete data_no_domains.domain_names; - return streamModel - .query() - .insertAndFetch(data_no_domains) - .then(utils.omitRow(omissions())); + return streamModel.query().insertAndFetch(data_no_domains).then(utils.omitRow(omissions())); }) .then((row) => { if (create_certificate) { - return internalCertificate.createQuickCertificate(access, data) + return internalCertificate + .createQuickCertificate(access, data) .then((cert) => { // update host with cert id return internalStream.update(access, { - id: row.id, - certificate_id: cert.id + id: row.id, + certificate_id: cert.id, }); }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // re-fetch with cert return internalStream.get(access, { - id: row.id, - expand: ['certificate', 'owner'] + id: row.id, + expand: ["certificate", "owner"], }); }) .then((row) => { // Configure nginx - return internalNginx.configure(streamModel, 'stream', row) - .then(() => { - return row; - }); + return internalNginx.configure(streamModel, "stream", row).then(() => { + return row; + }); }) .then((row) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'stream', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "created", + object_type: "stream", + object_id: row.id, + meta: data, + }) .then(() => { return row; }); @@ -96,72 +93,78 @@ const internalStream = { * @return {Promise} */ update: (access, data) => { - const create_certificate = data.certificate_id === 'new'; + let thisData = data; + const create_certificate = thisData.certificate_id === "new"; if (create_certificate) { - delete data.certificate_id; + delete thisData.certificate_id; } - return access.can('streams:update', data.id) + return access + .can("streams:update", thisData.id) .then((/*access_data*/) => { // TODO: at this point the existing streams should have been checked - return internalStream.get(access, {id: data.id}); + return internalStream.get(access, { id: thisData.id }); }) .then((row) => { - if (row.id !== data.id) { + if (row.id !== thisData.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `Stream could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`, + ); } if (create_certificate) { - return internalCertificate.createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta) - }) + return internalCertificate + .createQuickCertificate(access, { + domain_names: thisData.domain_names || row.domain_names, + meta: _.assign({}, row.meta, thisData.meta), + }) .then((cert) => { // update host with cert id - data.certificate_id = cert.id; + thisData.certificate_id = cert.id; }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign({}, { - domain_names: row.domain_names - }, data); + thisData = _.assign( + {}, + { + domain_names: row.domain_names, + }, + thisData, + ); return streamModel .query() - .patchAndFetchById(row.id, data) + .patchAndFetchById(row.id, thisData) .then(utils.omitRow(omissions())) .then((saved_row) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'stream', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "updated", + object_type: "stream", + object_id: row.id, + meta: thisData, + }) .then(() => { return saved_row; }); }); }) .then(() => { - return internalStream.get(access, {id: data.id, expand: ['owner', 'certificate']}) - .then((row) => { - return internalNginx.configure(streamModel, 'stream', row) - .then((new_meta) => { - row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); - }); + return internalStream.get(access, { id: thisData.id, expand: ["owner", "certificate"] }).then((row) => { + return internalNginx.configure(streamModel, "stream", row).then((new_meta) => { + row.meta = new_meta; + return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); }); + }); }); }, @@ -174,39 +177,39 @@ const internalStream = { * @return {Promise} */ get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } + const thisData = data || {}; - return access.can('streams:get', data.id) + return access + .can("streams:get", thisData.id) .then((access_data) => { - let query = streamModel + const query = streamModel .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[owner,certificate]') + .where("is_deleted", 0) + .andWhere("id", thisData.id) + .allowGraph("[owner,certificate]") .first(); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + let thisRow = row; + if (!thisRow || !thisRow.id) { + throw new errs.ItemNotFoundError(thisData.id); } - row = internalHost.cleanRowCertificateMeta(row); + thisRow = internalHost.cleanRowCertificateMeta(thisRow); // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { + return _.omit(thisRow, thisData.omit); } - return row; + return thisRow; }); }, @@ -218,35 +221,35 @@ const internalStream = { * @returns {Promise} */ delete: (access, data) => { - return access.can('streams:delete', data.id) + return access + .can("streams:delete", data.id) .then(() => { - return internalStream.get(access, {id: data.id}); + return internalStream.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(data.id); } return streamModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('stream', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("stream", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'stream', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "deleted", + object_type: "stream", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -263,39 +266,41 @@ const internalStream = { * @returns {Promise} */ enable: (access, data) => { - return access.can('streams:update', data.id) + return access + .can("streams:update", data.id) .then(() => { return internalStream.get(access, { - id: data.id, - expand: ['certificate', 'owner'] + id: data.id, + expand: ["certificate", "owner"], }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Stream is already enabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Stream is already enabled"); } row.enabled = 1; return streamModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 1 + enabled: 1, }) .then(() => { // Configure nginx - return internalNginx.configure(streamModel, 'stream', row); + return internalNginx.configure(streamModel, "stream", row); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'stream', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "enabled", + object_type: "stream", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -312,39 +317,40 @@ const internalStream = { * @returns {Promise} */ disable: (access, data) => { - return access.can('streams:update', data.id) + return access + .can("streams:update", data.id) .then(() => { - return internalStream.get(access, {id: data.id}); + return internalStream.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Stream is already disabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (!row.enabled) { + throw new errs.ValidationError("Stream is already disabled"); } row.enabled = 0; return streamModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 0 + enabled: 0, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('stream', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("stream", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'stream-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "disabled", + object_type: "stream", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -362,34 +368,35 @@ const internalStream = { * @returns {Promise} */ getAll: (access, expand, search_query) => { - return access.can('streams:list') + return access + .can("streams:list") .then((access_data) => { const query = streamModel .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[owner,certificate]') - .orderBy('incoming_port', 'ASC'); + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,certificate]") + .orderBy("incoming_port", "ASC"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { + if (typeof search_query === "string" && search_query.length > 0) { query.where(function () { - this.where(castJsonIfNeed('incoming_port'), 'like', `%${search_query}%`); + this.where(castJsonIfNeed("incoming_port"), "like", `%${search_query}%`); }); } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); } return query.then(utils.omitRows(omissions())); }) .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { + if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { return internalHost.cleanAllRowsCertificateMeta(rows); } @@ -405,20 +412,16 @@ const internalStream = { * @returns {Promise} */ getCount: (user_id, visibility) => { - const query = streamModel - .query() - .count('id AS count') - .where('is_deleted', 0); + const query = streamModel.query().count("id AS count").where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", user_id); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); - } + return query.first().then((row) => { + return Number.parseInt(row.count, 10); + }); + }, }; -module.exports = internalStream; +export default internalStream; diff --git a/backend/internal/token.js b/backend/internal/token.js index 0e6dec5e..1935b16d 100644 --- a/backend/internal/token.js +++ b/backend/internal/token.js @@ -1,14 +1,14 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const userModel = require('../models/user'); -const authModel = require('../models/auth'); -const helpers = require('../lib/helpers'); -const TokenModel = require('../models/token'); +import _ from "lodash"; +import errs from "../lib/error.js"; +import { parseDatePeriod } from "../lib/helpers.js"; +import authModel from "../models/auth.js"; +import TokenModel from "../models/token.js"; +import userModel from "../models/user.js"; -const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password'; - -module.exports = { +const ERROR_MESSAGE_INVALID_AUTH = "Invalid email or password"; +const ERROR_MESSAGE_INVALID_AUTH_I18N = "error.invalid-auth"; +export default { /** * @param {Object} data * @param {String} data.identity @@ -18,70 +18,66 @@ module.exports = { * @param {String} [issuer] * @returns {Promise} */ - getTokenFromEmail: (data, issuer) => { - let Token = new TokenModel(); + getTokenFromEmail: async (data, issuer) => { + const Token = TokenModel(); - data.scope = data.scope || 'user'; - data.expiry = data.expiry || '1d'; + data.scope = data.scope || "user"; + data.expiry = data.expiry || "1d"; - return userModel + const user = await userModel .query() - .where('email', data.identity.toLowerCase().trim()) - .andWhere('is_deleted', 0) - .andWhere('is_disabled', 0) - .first() - .then((user) => { - if (user) { - // Get auth - return authModel - .query() - .where('user_id', '=', user.id) - .where('type', '=', 'password') - .first() - .then((auth) => { - if (auth) { - return auth.verifyPassword(data.secret) - .then((valid) => { - if (valid) { + .where("email", data.identity.toLowerCase().trim()) + .andWhere("is_deleted", 0) + .andWhere("is_disabled", 0) + .first(); - if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) { - // The scope requested doesn't exist as a role against the user, - // you shall not pass. - throw new error.AuthError('Invalid scope: ' + data.scope); - } + if (!user) { + throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH); + } - // Create a moment of the expiry expression - let expiry = helpers.parseDatePeriod(data.expiry); - if (expiry === null) { - throw new error.AuthError('Invalid expiry time: ' + data.expiry); - } + const auth = await authModel + .query() + .where("user_id", "=", user.id) + .where("type", "=", "password") + .first(); - return Token.create({ - iss: issuer || 'api', - attrs: { - id: user.id - }, - scope: [data.scope], - expiresIn: data.expiry - }) - .then((signed) => { - return { - token: signed.token, - expires: expiry.toISOString() - }; - }); - } else { - throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); - } - }); - } else { - throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); - } - }); - } else { - throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); - } - }); + if (!auth) { + throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH); + } + + const valid = await auth.verifyPassword(data.secret); + if (!valid) { + throw new errs.AuthError( + ERROR_MESSAGE_INVALID_AUTH, + ERROR_MESSAGE_INVALID_AUTH_I18N, + ); + } + + if (data.scope !== "user" && _.indexOf(user.roles, data.scope) === -1) { + // The scope requested doesn't exist as a role against the user, + // you shall not pass. + throw new errs.AuthError(`Invalid scope: ${data.scope}`); + } + + // Create a moment of the expiry expression + const expiry = parseDatePeriod(data.expiry); + if (expiry === null) { + throw new errs.AuthError(`Invalid expiry time: ${data.expiry}`); + } + + const signed = await Token.create({ + iss: issuer || "api", + attrs: { + id: user.id, + }, + scope: [data.scope], + expiresIn: data.expiry, + }); + + return { + token: signed.token, + expires: expiry.toISOString(), + }; }, /** @@ -91,74 +87,70 @@ module.exports = { * @param {String} [data.scope] Only considered if existing token scope is admin * @returns {Promise} */ - getFreshToken: (access, data) => { - let Token = new TokenModel(); + getFreshToken: async (access, data) => { + const Token = TokenModel(); + const thisData = data || {}; - data = data || {}; - data.expiry = data.expiry || '1d'; - - if (access && access.token.getUserId(0)) { + thisData.expiry = thisData.expiry || "1d"; + if (access?.token.getUserId(0)) { // Create a moment of the expiry expression - let expiry = helpers.parseDatePeriod(data.expiry); + const expiry = parseDatePeriod(thisData.expiry); if (expiry === null) { - throw new error.AuthError('Invalid expiry time: ' + data.expiry); + throw new errs.AuthError(`Invalid expiry time: ${thisData.expiry}`); } - let token_attrs = { - id: access.token.getUserId(0) + const token_attrs = { + id: access.token.getUserId(0), }; // Only admins can request otherwise scoped tokens - let scope = access.token.get('scope'); - if (data.scope && access.token.hasScope('admin')) { - scope = [data.scope]; + let scope = access.token.get("scope"); + if (thisData.scope && access.token.hasScope("admin")) { + scope = [thisData.scope]; - if (data.scope === 'job-board' || data.scope === 'worker') { + if (thisData.scope === "job-board" || thisData.scope === "worker") { token_attrs.id = 0; } } - return Token.create({ - iss: 'api', - scope: scope, - attrs: token_attrs, - expiresIn: data.expiry - }) - .then((signed) => { - return { - token: signed.token, - expires: expiry.toISOString() - }; - }); - } else { - throw new error.AssertionFailedError('Existing token contained invalid user data'); + const signed = await Token.create({ + iss: "api", + scope: scope, + attrs: token_attrs, + expiresIn: thisData.expiry, + }); + + return { + token: signed.token, + expires: expiry.toISOString(), + }; } + throw new error.AssertionFailedError("Existing token contained invalid user data"); }, /** * @param {Object} user * @returns {Promise} */ - getTokenFromUser: (user) => { - const expire = '1d'; - const Token = new TokenModel(); - const expiry = helpers.parseDatePeriod(expire); + getTokenFromUser: async (user) => { + const expire = "1d"; + const Token = TokenModel(); + const expiry = parseDatePeriod(expire); - return Token.create({ - iss: 'api', + const signed = await Token.create({ + iss: "api", attrs: { - id: user.id + id: user.id, }, - scope: ['user'], - expiresIn: expire - }) - .then((signed) => { - return { - token: signed.token, - expires: expiry.toISOString(), - user: user - }; - }); - } + scope: ["user"], + expiresIn: expire, + }); + + return { + token: signed.token, + expires: expiry.toISOString(), + user: user, + }; + }, }; diff --git a/backend/internal/user.js b/backend/internal/user.js index 742ab65d..d13931d5 100644 --- a/backend/internal/user.js +++ b/backend/internal/user.js @@ -1,93 +1,76 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const userModel = require('../models/user'); -const userPermissionModel = require('../models/user_permission'); -const authModel = require('../models/auth'); -const gravatar = require('gravatar'); -const internalToken = require('./token'); -const internalAuditLog = require('./audit-log'); +import gravatar from "gravatar"; +import _ from "lodash"; +import errs from "../lib/error.js"; +import utils from "../lib/utils.js"; +import authModel from "../models/auth.js"; +import userModel from "../models/user.js"; +import userPermissionModel from "../models/user_permission.js"; +import internalAuditLog from "./audit-log.js"; +import internalToken from "./token.js"; -function omissions () { - return ['is_deleted']; -} +const omissions = () => { + return ["is_deleted", "permissions.id", "permissions.user_id", "permissions.created_on", "permissions.modified_on"]; +}; + +const DEFAULT_AVATAR = gravatar.url("admin@example.com", { default: "mm" }); const internalUser = { - /** + * Create a user can happen unauthenticated only once and only when no active users exist. + * Otherwise, a valid auth method is required. + * * @param {Access} access * @param {Object} data * @returns {Promise} */ - create: (access, data) => { - let auth = data.auth || null; + create: async (access, data) => { + const auth = data.auth || null; delete data.auth; - data.avatar = data.avatar || ''; - data.roles = data.roles || []; + data.avatar = data.avatar || ""; + data.roles = data.roles || []; - if (typeof data.is_disabled !== 'undefined') { + if (typeof data.is_disabled !== "undefined") { data.is_disabled = data.is_disabled ? 1 : 0; } - return access.can('users:create', data) - .then(() => { - data.avatar = gravatar.url(data.email, {default: 'mm'}); + await access.can("users:create", data); + data.avatar = gravatar.url(data.email, { default: "mm" }); - return userModel - .query() - .insertAndFetch(data) - .then(utils.omitRow(omissions())); - }) - .then((user) => { - if (auth) { - return authModel - .query() - .insert({ - user_id: user.id, - type: auth.type, - secret: auth.secret, - meta: {} - }) - .then(() => { - return user; - }); - } else { - return user; - } - }) - .then((user) => { - // Create permissions row as well - let is_admin = data.roles.indexOf('admin') !== -1; - - return userPermissionModel - .query() - .insert({ - user_id: user.id, - visibility: is_admin ? 'all' : 'user', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - dead_hosts: 'manage', - streams: 'manage', - access_lists: 'manage', - certificates: 'manage' - }) - .then(() => { - return internalUser.get(access, {id: user.id, expand: ['permissions']}); - }); - }) - .then((user) => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'user', - object_id: user.id, - meta: user - }) - .then(() => { - return user; - }); + let user = await userModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); + if (auth) { + user = await authModel.query().insert({ + user_id: user.id, + type: auth.type, + secret: auth.secret, + meta: {}, }); + } + + // Create permissions row as well + const isAdmin = data.roles.indexOf("admin") !== -1; + + await userPermissionModel.query().insert({ + user_id: user.id, + visibility: isAdmin ? "all" : "user", + proxy_hosts: "manage", + redirection_hosts: "manage", + dead_hosts: "manage", + streams: "manage", + access_lists: "manage", + certificates: "manage", + }); + + user = await internalUser.get(access, { id: user.id, expand: ["permissions"] }); + + await internalAuditLog.add(access, { + action: "created", + object_type: "user", + object_id: user.id, + meta: user, + }); + + return user; }, /** @@ -99,62 +82,57 @@ const internalUser = { * @return {Promise} */ update: (access, data) => { - if (typeof data.is_disabled !== 'undefined') { + if (typeof data.is_disabled !== "undefined") { data.is_disabled = data.is_disabled ? 1 : 0; } - return access.can('users:update', data.id) + return access + .can("users:update", data.id) .then(() => { - // Make sure that the user being updated doesn't change their email to another user that is already using it // 1. get user we want to update - return internalUser.get(access, {id: data.id}) - .then((user) => { + return internalUser.get(access, { id: data.id }).then((user) => { + // 2. if email is to be changed, find other users with that email + if (typeof data.email !== "undefined") { + data.email = data.email.toLowerCase().trim(); - // 2. if email is to be changed, find other users with that email - if (typeof data.email !== 'undefined') { - data.email = data.email.toLowerCase().trim(); - - if (user.email !== data.email) { - return internalUser.isEmailAvailable(data.email, data.id) - .then((available) => { - if (!available) { - throw new error.ValidationError('Email address already in use - ' + data.email); - } - - return user; - }); - } + if (user.email !== data.email) { + return internalUser.isEmailAvailable(data.email, data.id).then((available) => { + if (!available) { + throw new errs.ValidationError(`Email address already in use - ${data.email}`); + } + return user; + }); } + } - // No change to email: - return user; - }); + // No change to email: + return user; + }); }) .then((user) => { if (user.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `User could not be updated, IDs do not match: ${user.id} !== ${data.id}`, + ); } - data.avatar = gravatar.url(data.email || user.email, {default: 'mm'}); - - return userModel - .query() - .patchAndFetchById(user.id, data) - .then(utils.omitRow(omissions())); + data.avatar = gravatar.url(data.email || user.email, { default: "mm" }); + return userModel.query().patchAndFetchById(user.id, data).then(utils.omitRow(omissions())); }) .then(() => { - return internalUser.get(access, {id: data.id}); + return internalUser.get(access, { id: data.id }); }) .then((user) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'user', - object_id: user.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "updated", + object_type: "user", + object_id: user.id, + meta: { ...data, id: user.id, name: user.name }, + }) .then(() => { return user; }); @@ -170,37 +148,41 @@ const internalUser = { * @return {Promise} */ get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; + const thisData = data || {}; + + if (typeof thisData.id === "undefined" || !thisData.id) { + thisData.id = access.token.getUserId(0); } - if (typeof data.id === 'undefined' || !data.id) { - data.id = access.token.getUserId(0); - } - - return access.can('users:get', data.id) + return access + .can("users:get", thisData.id) .then(() => { - let query = userModel + const query = userModel .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[permissions]') + .where("is_deleted", 0) + .andWhere("id", thisData.id) + .allowGraph("[permissions]") .first(); - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(thisData.id); } // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { + return _.omit(row, thisData.omit); } + + if (row.avatar === "") { + row.avatar = DEFAULT_AVATAR; + } + return row; }); }, @@ -213,20 +195,15 @@ const internalUser = { * @param user_id */ isEmailAvailable: (email, user_id) => { - let query = userModel - .query() - .where('email', '=', email.toLowerCase().trim()) - .where('is_deleted', 0) - .first(); + const query = userModel.query().where("email", "=", email.toLowerCase().trim()).where("is_deleted", 0).first(); - if (typeof user_id !== 'undefined') { - query.where('id', '!=', user_id); + if (typeof user_id !== "undefined") { + query.where("id", "!=", user_id); } - return query - .then((user) => { - return !user; - }); + return query.then((user) => { + return !user; + }); }, /** @@ -237,33 +214,34 @@ const internalUser = { * @returns {Promise} */ delete: (access, data) => { - return access.can('users:delete', data.id) + return access + .can("users:delete", data.id) .then(() => { - return internalUser.get(access, {id: data.id}); + return internalUser.get(access, { id: data.id }); }) .then((user) => { if (!user) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(data.id); } // Make sure user can't delete themselves if (user.id === access.token.getUserId(0)) { - throw new error.PermissionError('You cannot delete yourself.'); + throw new errs.PermissionError("You cannot delete yourself."); } return userModel .query() - .where('id', user.id) + .where("id", user.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'user', - object_id: user.id, - meta: _.omit(user, omissions()) + action: "deleted", + object_type: "user", + object_id: user.id, + meta: _.omit(user, omissions()), }); }); }) @@ -272,6 +250,14 @@ const internalUser = { }); }, + deleteAll: async () => { + await userModel + .query() + .patch({ + is_deleted: 1, + }); + }, + /** * This will only count the users * @@ -280,26 +266,26 @@ const internalUser = { * @returns {*} */ getCount: (access, search_query) => { - return access.can('users:list') + return access + .can("users:list") .then(() => { - let query = userModel - .query() - .count('id as count') - .where('is_deleted', 0) - .first(); + const query = userModel.query().count("id as count").where("is_deleted", 0).first(); // Query is used for searching - if (typeof search_query === 'string') { + if (typeof search_query === "string") { query.where(function () { - this.where('user.name', 'like', '%' + search_query + '%') - .orWhere('user.email', 'like', '%' + search_query + '%'); + this.where("user.name", "like", `%${search_query}%`).orWhere( + "user.email", + "like", + `%${search_query}%`, + ); }); } return query; }) .then((row) => { - return parseInt(row.count, 10); + return Number.parseInt(row.count, 10); }); }, @@ -311,30 +297,28 @@ const internalUser = { * @param {String} [search_query] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access.can('users:list') - .then(() => { - let query = userModel - .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[permissions]') - .orderBy('name', 'ASC'); + getAll: async (access, expand, search_query) => { + await access.can("users:list"); + const query = userModel + .query() + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[permissions]") + .orderBy("name", "ASC"); - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('name', 'like', '%' + search_query + '%') - .orWhere('email', 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); + // Query is used for searching + if (typeof search_query === "string") { + query.where(function () { + this.where("name", "like", `%${search_query}%`).orWhere("email", "like", `%${search_query}%`); }); + } + + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + const res = await query; + return utils.omitRows(omissions())(res); }, /** @@ -342,11 +326,11 @@ const internalUser = { * @param {Integer} [id_requested] * @returns {[String]} */ - getUserOmisionsByAccess: (access, id_requested) => { + getUserOmisionsByAccess: (access, idRequested) => { let response = []; // Admin response - if (!access.token.hasScope('admin') && access.token.getUserId(0) !== id_requested) { - response = ['roles', 'is_deleted']; // Restricted response + if (!access.token.hasScope("admin") && access.token.getUserId(0) !== idRequested) { + response = ["is_deleted"]; // Restricted response } return response; @@ -361,26 +345,30 @@ const internalUser = { * @return {Promise} */ setPassword: (access, data) => { - return access.can('users:password', data.id) + return access + .can("users:password", data.id) .then(() => { - return internalUser.get(access, {id: data.id}); + return internalUser.get(access, { id: data.id }); }) .then((user) => { if (user.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `User could not be updated, IDs do not match: ${user.id} !== ${data.id}`, + ); } if (user.id === access.token.getUserId(0)) { // they're setting their own password. Make sure their current password is correct - if (typeof data.current === 'undefined' || !data.current) { - throw new error.ValidationError('Current password was not supplied'); + if (typeof data.current === "undefined" || !data.current) { + throw new errs.ValidationError("Current password was not supplied"); } - return internalToken.getTokenFromEmail({ - identity: user.email, - secret: data.current - }) + return internalToken + .getTokenFromEmail({ + identity: user.email, + secret: data.current, + }) .then(() => { return user; }); @@ -392,43 +380,36 @@ const internalUser = { // Get auth, patch if it exists return authModel .query() - .where('user_id', user.id) - .andWhere('type', data.type) + .where("user_id", user.id) + .andWhere("type", data.type) .first() .then((existing_auth) => { if (existing_auth) { // patch - return authModel - .query() - .where('user_id', user.id) - .andWhere('type', data.type) - .patch({ - type: data.type, // This is required for the model to encrypt on save - secret: data.secret - }); - } else { - // insert - return authModel - .query() - .insert({ - user_id: user.id, - type: data.type, - secret: data.secret, - meta: {} - }); + return authModel.query().where("user_id", user.id).andWhere("type", data.type).patch({ + type: data.type, // This is required for the model to encrypt on save + secret: data.secret, + }); } + // insert + return authModel.query().insert({ + user_id: user.id, + type: data.type, + secret: data.secret, + meta: {}, + }); }) .then(() => { // Add to Audit Log return internalAuditLog.add(access, { - action: 'updated', - object_type: 'user', - object_id: user.id, - meta: { - name: user.name, + action: "updated", + object_type: "user", + object_id: user.id, + meta: { + name: user.name, password_changed: true, - auth_type: data.type - } + auth_type: data.type, + }, }); }); }) @@ -443,14 +424,17 @@ const internalUser = { * @return {Promise} */ setPermissions: (access, data) => { - return access.can('users:permissions', data.id) + return access + .can("users:permissions", data.id) .then(() => { - return internalUser.get(access, {id: data.id}); + return internalUser.get(access, { id: data.id }); }) .then((user) => { if (user.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `User could not be updated, IDs do not match: ${user.id} !== ${data.id}`, + ); } return user; @@ -459,34 +443,30 @@ const internalUser = { // Get perms row, patch if it exists return userPermissionModel .query() - .where('user_id', user.id) + .where("user_id", user.id) .first() .then((existing_auth) => { if (existing_auth) { // patch return userPermissionModel .query() - .where('user_id', user.id) - .patchAndFetchById(existing_auth.id, _.assign({user_id: user.id}, data)); - } else { - // insert - return userPermissionModel - .query() - .insertAndFetch(_.assign({user_id: user.id}, data)); + .where("user_id", user.id) + .patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, data)); } + // insert + return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data)); }) .then((permissions) => { // Add to Audit Log return internalAuditLog.add(access, { - action: 'updated', - object_type: 'user', - object_id: user.id, - meta: { - name: user.name, - permissions: permissions - } + action: "updated", + object_type: "user", + object_id: user.id, + meta: { + name: user.name, + permissions: permissions, + }, }); - }); }) .then(() => { @@ -500,14 +480,15 @@ const internalUser = { * @param {Integer} data.id */ loginAs: (access, data) => { - return access.can('users:loginas', data.id) + return access + .can("users:loginas", data.id) .then(() => { return internalUser.get(access, data); }) .then((user) => { return internalToken.getTokenFromUser(user); }); - } + }, }; -module.exports = internalUser; +export default internalUser; diff --git a/backend/lib/access.js b/backend/lib/access.js index 0e658a65..a4dec5c4 100644 --- a/backend/lib/access.js +++ b/backend/lib/access.js @@ -4,91 +4,90 @@ * "scope" in this file means "where did this token come from and what is using it", so 99% of the time * the "scope" is going to be "user" because it would be a user token. This is not to be confused with * the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else. - * - * */ -const _ = require('lodash'); -const logger = require('../logger').access; -const Ajv = require('ajv/dist/2020'); -const error = require('./error'); -const userModel = require('../models/user'); -const proxyHostModel = require('../models/proxy_host'); -const TokenModel = require('../models/token'); -const roleSchema = require('./access/roles.json'); -const permsSchema = require('./access/permissions.json'); +import fs from "node:fs"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import Ajv from "ajv/dist/2020.js"; +import _ from "lodash"; +import { access as logger } from "../logger.js"; +import proxyHostModel from "../models/proxy_host.js"; +import TokenModel from "../models/token.js"; +import userModel from "../models/user.js"; +import permsSchema from "./access/permissions.json" with { type: "json" }; +import roleSchema from "./access/roles.json" with { type: "json" }; +import errs from "./error.js"; -module.exports = function (token_string) { - let Token = new TokenModel(); - let token_data = null; - let initialised = false; - let object_cache = {}; - let allow_internal_access = false; - let user_roles = []; - let permissions = {}; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export default function (tokenString) { + const Token = TokenModel(); + let tokenData = null; + let initialised = false; + const objectCache = {}; + let allowInternalAccess = false; + let userRoles = []; + let permissions = {}; /** * Loads the Token object from the token string * * @returns {Promise} */ - this.init = () => { - return new Promise((resolve, reject) => { - if (initialised) { - resolve(); - } else if (!token_string) { - reject(new error.PermissionError('Permission Denied')); + this.init = async () => { + if (initialised) { + return; + } + + if (!tokenString) { + throw new errs.PermissionError("Permission Denied"); + } + + tokenData = await Token.load(tokenString); + + // At this point we need to load the user from the DB and make sure they: + // - exist (and not soft deleted) + // - still have the appropriate scopes for this token + // This is only required when the User ID is supplied or if the token scope has `user` + if ( + tokenData.attrs.id || + (typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, "user") !== -1) + ) { + // Has token user id or token user scope + const user = await userModel + .query() + .where("id", tokenData.attrs.id) + .andWhere("is_deleted", 0) + .andWhere("is_disabled", 0) + .allowGraph("[permissions]") + .withGraphFetched("[permissions]") + .first(); + + if (user) { + // make sure user has all scopes of the token + // The `user` role is not added against the user row, so we have to just add it here to get past this check. + user.roles.push("user"); + + let ok = true; + _.forEach(tokenData.scope, (scope_item) => { + if (_.indexOf(user.roles, scope_item) === -1) { + ok = false; + } + }); + + if (!ok) { + throw new errs.AuthError("Invalid token scope for User"); + } + initialised = true; + userRoles = user.roles; + permissions = user.permissions; } else { - resolve(Token.load(token_string) - .then((data) => { - token_data = data; - - // At this point we need to load the user from the DB and make sure they: - // - exist (and not soft deleted) - // - still have the appropriate scopes for this token - // This is only required when the User ID is supplied or if the token scope has `user` - - if (token_data.attrs.id || (typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'user') !== -1)) { - // Has token user id or token user scope - return userModel - .query() - .where('id', token_data.attrs.id) - .andWhere('is_deleted', 0) - .andWhere('is_disabled', 0) - .allowGraph('[permissions]') - .withGraphFetched('[permissions]') - .first() - .then((user) => { - if (user) { - // make sure user has all scopes of the token - // The `user` role is not added against the user row, so we have to just add it here to get past this check. - user.roles.push('user'); - - let is_ok = true; - _.forEach(token_data.scope, (scope_item) => { - if (_.indexOf(user.roles, scope_item) === -1) { - is_ok = false; - } - }); - - if (!is_ok) { - throw new error.AuthError('Invalid token scope for User'); - } else { - initialised = true; - user_roles = user.roles; - permissions = user.permissions; - } - - } else { - throw new error.AuthError('User cannot be loaded for Token'); - } - }); - } else { - initialised = true; - } - })); + throw new errs.AuthError("User cannot be loaded for Token"); } - }); + } + initialised = true; }; /** @@ -96,140 +95,121 @@ module.exports = function (token_string) { * This only applies to USER token scopes, as all other tokens are not really bound * by object scopes * - * @param {String} object_type + * @param {String} objectType * @returns {Promise} */ - this.loadObjects = (object_type) => { - return new Promise((resolve, reject) => { - if (Token.hasScope('user')) { - if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) { - reject(new error.AuthError('User Token supplied without a User ID')); - } else { - let token_user_id = token_data.attrs.id ? token_data.attrs.id : 0; - let query; + this.loadObjects = async (objectType) => { + let objects = null; - if (typeof object_cache[object_type] === 'undefined') { - switch (object_type) { + if (Token.hasScope("user")) { + if (typeof tokenData.attrs.id === "undefined" || !tokenData.attrs.id) { + throw new errs.AuthError("User Token supplied without a User ID"); + } - // USERS - should only return yourself - case 'users': - resolve(token_user_id ? [token_user_id] : []); - break; + const tokenUserId = tokenData.attrs.id ? tokenData.attrs.id : 0; - // Proxy Hosts - case 'proxy_hosts': - query = proxyHostModel - .query() - .select('id') - .andWhere('is_deleted', 0); + if (typeof objectCache[objectType] !== "undefined") { + objects = objectCache[objectType]; + } else { + switch (objectType) { + // USERS - should only return yourself + case "users": + objects = tokenUserId ? [tokenUserId] : []; + break; - if (permissions.visibility === 'user') { - query.andWhere('owner_user_id', token_user_id); - } + // Proxy Hosts + case "proxy_hosts": { + const query = proxyHostModel + .query() + .select("id") + .andWhere("is_deleted", 0); - resolve(query - .then((rows) => { - let result = []; - _.forEach(rows, (rule_row) => { - result.push(rule_row.id); - }); - - // enum should not have less than 1 item - if (!result.length) { - result.push(0); - } - - return result; - }) - ); - break; - - // DEFAULT: null - default: - resolve(null); - break; + if (permissions.visibility === "user") { + query.andWhere("owner_user_id", tokenUserId); } - } else { - resolve(object_cache[object_type]); + + const rows = await query; + objects = []; + _.forEach(rows, (ruleRow) => { + objects.push(ruleRow.id); + }); + + // enum should not have less than 1 item + if (!objects.length) { + objects.push(0); + } + break; } } - } else { - resolve(null); + objectCache[objectType] = objects; } - }) - .then((objects) => { - object_cache[object_type] = objects; - return objects; - }); + } + return objects; }; /** * Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema * - * @param {String} permission_label + * @param {String} permissionLabel * @returns {Object} */ - this.getObjectSchema = (permission_label) => { - let base_object_type = permission_label.split(':').shift(); + this.getObjectSchema = async (permissionLabel) => { + const baseObjectType = permissionLabel.split(":").shift(); - let schema = { - $id: 'objects', - description: 'Actor Properties', - type: 'object', + const schema = { + $id: "objects", + description: "Actor Properties", + type: "object", additionalProperties: false, - properties: { + properties: { user_id: { anyOf: [ { - type: 'number', - enum: [Token.get('attrs').id] - } - ] + type: "number", + enum: [Token.get("attrs").id], + }, + ], }, scope: { - type: 'string', - pattern: '^' + Token.get('scope') + '$' - } - } + type: "string", + pattern: `^${Token.get("scope")}$`, + }, + }, }; - return this.loadObjects(base_object_type) - .then((object_result) => { - if (typeof object_result === 'object' && object_result !== null) { - schema.properties[base_object_type] = { - type: 'number', - enum: object_result, - minimum: 1 - }; - } else { - schema.properties[base_object_type] = { - type: 'number', - minimum: 1 - }; - } + const result = await this.loadObjects(baseObjectType); + if (typeof result === "object" && result !== null) { + schema.properties[baseObjectType] = { + type: "number", + enum: result, + minimum: 1, + }; + } else { + schema.properties[baseObjectType] = { + type: "number", + minimum: 1, + }; + } - return schema; - }); + return schema; }; - return { + // here: + return { token: Token, /** * - * @param {Boolean} [allow_internal] + * @param {Boolean} [allowInternal] * @returns {Promise} */ - load: (allow_internal) => { - return new Promise(function (resolve/*, reject*/) { - if (token_string) { - resolve(Token.load(token_string)); - } else { - allow_internal_access = allow_internal; - resolve(allow_internal_access || null); - } - }); + load: async (allowInternal) => { + if (tokenString) { + return await Token.load(tokenString); + } + allowInternalAccess = allowInternal; + return allowInternal || null; }, reloadObjects: this.loadObjects, @@ -240,68 +220,59 @@ module.exports = function (token_string) { * @param {*} [data] * @returns {Promise} */ - can: (permission, data) => { - if (allow_internal_access === true) { - return Promise.resolve(true); - //return true; - } else { - return this.init() - .then(() => { - // Initialised, token decoded ok - return this.getObjectSchema(permission) - .then((objectSchema) => { - const data_schema = { - [permission]: { - data: data, - scope: Token.get('scope'), - roles: user_roles, - permission_visibility: permissions.visibility, - permission_proxy_hosts: permissions.proxy_hosts, - permission_redirection_hosts: permissions.redirection_hosts, - permission_dead_hosts: permissions.dead_hosts, - permission_streams: permissions.streams, - permission_access_lists: permissions.access_lists, - permission_certificates: permissions.certificates - } - }; - - let permissionSchema = { - $async: true, - $id: 'permissions', - type: 'object', - additionalProperties: false, - properties: {} - }; - - permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json'); - - const ajv = new Ajv({ - verbose: true, - allErrors: true, - breakOnError: true, - coerceTypes: true, - schemas: [ - roleSchema, - permsSchema, - objectSchema, - permissionSchema - ] - }); - - return ajv.validate('permissions', data_schema) - .then(() => { - return data_schema[permission]; - }); - }); - }) - .catch((err) => { - err.permission = permission; - err.permission_data = data; - logger.error(permission, data, err.message); - - throw new error.PermissionError('Permission Denied', err); - }); + can: async (permission, data) => { + if (allowInternalAccess === true) { + return true; } - } + + try { + await this.init(); + const objectSchema = await this.getObjectSchema(permission); + + const dataSchema = { + [permission]: { + data: data, + scope: Token.get("scope"), + roles: userRoles, + permission_visibility: permissions.visibility, + permission_proxy_hosts: permissions.proxy_hosts, + permission_redirection_hosts: permissions.redirection_hosts, + permission_dead_hosts: permissions.dead_hosts, + permission_streams: permissions.streams, + permission_access_lists: permissions.access_lists, + permission_certificates: permissions.certificates, + }, + }; + + const permissionSchema = { + $async: true, + $id: "permissions", + type: "object", + additionalProperties: false, + properties: {}, + }; + + const rawData = fs.readFileSync(`${__dirname}/access/${permission.replace(/:/gim, "-")}.json`, { + encoding: "utf8", + }); + permissionSchema.properties[permission] = JSON.parse(rawData); + + const ajv = new Ajv({ + verbose: true, + allErrors: true, + breakOnError: true, + coerceTypes: true, + schemas: [roleSchema, permsSchema, objectSchema, permissionSchema], + }); + + const valid = await ajv.validate("permissions", dataSchema); + return valid && dataSchema[permission]; + } catch (err) { + err.permission = permission; + err.permission_data = data; + logger.error(permission, data, err.message); + throw errs.PermissionError("Permission Denied", err); + } + }, }; -}; +} diff --git a/backend/lib/certbot.js b/backend/lib/certbot.js index 96d94710..3a2dd072 100644 --- a/backend/lib/certbot.js +++ b/backend/lib/certbot.js @@ -1,85 +1,86 @@ -const dnsPlugins = require('../global/certbot-dns-plugins.json'); -const utils = require('./utils'); -const error = require('./error'); -const logger = require('../logger').certbot; -const batchflow = require('batchflow'); +import batchflow from "batchflow"; +import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" }; +import { certbot as logger } from "../logger.js"; +import errs from "./error.js"; +import utils from "./utils.js"; -const CERTBOT_VERSION_REPLACEMENT = '$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')'; +const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0-9]+)+')"; -const certbot = { +/** + * Installs a cerbot plugin given the key for the object from + * ../certbot/dns-plugins.json + * + * @param {string} pluginKey + * @returns {Object} + */ +const installPlugin = async (pluginKey) => { + if (typeof dnsPlugins[pluginKey] === "undefined") { + // throw Error(`Certbot plugin ${pluginKey} not found`); + throw new errs.ItemNotFoundError(pluginKey); + } - /** - * @param {array} pluginKeys - */ - installPlugins: async (pluginKeys) => { - let hasErrors = false; + const plugin = dnsPlugins[pluginKey]; + logger.start(`Installing ${pluginKey}...`); - return new Promise((resolve, reject) => { - if (pluginKeys.length === 0) { - resolve(); - return; - } + plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); + plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); - batchflow(pluginKeys).sequential() - .each((_i, pluginKey, next) => { - certbot.installPlugin(pluginKey) - .then(() => { - next(); - }) - .catch((err) => { - hasErrors = true; - next(err); - }); - }) - .error((err) => { - logger.error(err.message); - }) - .end(() => { - if (hasErrors) { - reject(new error.CommandError('Some plugins failed to install. Please check the logs above', 1)); - } else { - resolve(); - } - }); + // SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly + // in new versions of Python + let env = Object.assign({}, process.env, { SETUPTOOLS_USE_DISTUTILS: "stdlib" }); + if (typeof plugin.env === "object") { + env = Object.assign(env, plugin.env); + } + + const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version} && deactivate`; + return utils + .exec(cmd, { env }) + .then((result) => { + logger.complete(`Installed ${pluginKey}`); + return result; + }) + .catch((err) => { + throw err; }); - }, - - /** - * Installs a cerbot plugin given the key for the object from - * ../global/certbot-dns-plugins.json - * - * @param {string} pluginKey - * @returns {Object} - */ - installPlugin: async (pluginKey) => { - if (typeof dnsPlugins[pluginKey] === 'undefined') { - // throw Error(`Certbot plugin ${pluginKey} not found`); - throw new error.ItemNotFoundError(pluginKey); - } - - const plugin = dnsPlugins[pluginKey]; - logger.start(`Installing ${pluginKey}...`); - - plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); - plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); - - // SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly - // in new versions of Python - let env = Object.assign({}, process.env, {SETUPTOOLS_USE_DISTUTILS: 'stdlib'}); - if (typeof plugin.env === 'object') { - env = Object.assign(env, plugin.env); - } - - const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version} && deactivate`; - return utils.exec(cmd, {env}) - .then((result) => { - logger.complete(`Installed ${pluginKey}`); - return result; - }) - .catch((err) => { - throw err; - }); - }, }; -module.exports = certbot; +/** + * @param {array} pluginKeys + */ +const installPlugins = async (pluginKeys) => { + let hasErrors = false; + + return new Promise((resolve, reject) => { + if (pluginKeys.length === 0) { + resolve(); + return; + } + + batchflow(pluginKeys) + .sequential() + .each((_i, pluginKey, next) => { + installPlugin(pluginKey) + .then(() => { + next(); + }) + .catch((err) => { + hasErrors = true; + next(err); + }); + }) + .error((err) => { + logger.error(err.message); + }) + .end(() => { + if (hasErrors) { + reject( + new errs.CommandError("Some plugins failed to install. Please check the logs above", 1), + ); + } else { + resolve(); + } + }); + }); +}; + +export { installPlugins, installPlugin }; diff --git a/backend/lib/config.js b/backend/lib/config.js index 23184f3e..0b80c748 100644 --- a/backend/lib/config.js +++ b/backend/lib/config.js @@ -1,6 +1,6 @@ -const fs = require('fs'); -const NodeRSA = require('node-rsa'); -const logger = require('../logger').global; +import fs from "node:fs"; +import NodeRSA from "node-rsa"; +import { global as logger } from "../logger.js"; const keysFile = '/data/keys.json'; const mysqlEngine = 'mysql2'; @@ -12,37 +12,45 @@ let instance = null; // 1. Load from config file first (not recommended anymore) // 2. Use config env variables next const configure = () => { - const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json'; + const filename = `${process.env.NODE_CONFIG_DIR || "./config"}/${process.env.NODE_ENV || "default"}.json`; if (fs.existsSync(filename)) { let configData; try { - configData = require(filename); + // Load this json synchronously + const rawData = fs.readFileSync(filename); + configData = JSON.parse(rawData); } catch (_) { // do nothing } - if (configData && configData.database) { + if (configData?.database) { logger.info(`Using configuration from file: ${filename}`); - instance = configData; + instance = configData; instance.keys = getKeys(); return; } } - const envMysqlHost = process.env.DB_MYSQL_HOST || null; - const envMysqlUser = process.env.DB_MYSQL_USER || null; - const envMysqlName = process.env.DB_MYSQL_NAME || null; + const toBool = (v) => /^(1|true|yes|on)$/i.test((v || '').trim()); + + const envMysqlHost = process.env.DB_MYSQL_HOST || null; + const envMysqlUser = process.env.DB_MYSQL_USER || null; + const envMysqlName = process.env.DB_MYSQL_NAME || null; + const envMysqlSSL = toBool(process.env.DB_MYSQL_SSL); + const envMysqlSSLRejectUnauthorized = process.env.DB_MYSQL_SSL_REJECT_UNAUTHORIZED === undefined ? true : toBool(process.env.DB_MYSQL_SSL_REJECT_UNAUTHORIZED); + const envMysqlSSLVerifyIdentity = process.env.DB_MYSQL_SSL_VERIFY_IDENTITY === undefined ? true : toBool(process.env.DB_MYSQL_SSL_VERIFY_IDENTITY); if (envMysqlHost && envMysqlUser && envMysqlName) { // we have enough mysql creds to go with mysql - logger.info('Using MySQL configuration'); + logger.info("Using MySQL configuration"); instance = { database: { - engine: mysqlEngine, - host: envMysqlHost, - port: process.env.DB_MYSQL_PORT || 3306, - user: envMysqlUser, + engine: mysqlEngine, + host: envMysqlHost, + port: process.env.DB_MYSQL_PORT || 3306, + user: envMysqlUser, password: process.env.DB_MYSQL_PASSWORD, name: envMysqlName, + ssl: envMysqlSSL ? { rejectUnauthorized: envMysqlSSLRejectUnauthorized, verifyIdentity: envMysqlSSLVerifyIdentity } : false, }, keys: getKeys(), }; @@ -54,33 +62,33 @@ const configure = () => { const envPostgresName = process.env.DB_POSTGRES_NAME || null; if (envPostgresHost && envPostgresUser && envPostgresName) { // we have enough postgres creds to go with postgres - logger.info('Using Postgres configuration'); + logger.info("Using Postgres configuration"); instance = { database: { - engine: postgresEngine, - host: envPostgresHost, - port: process.env.DB_POSTGRES_PORT || 5432, - user: envPostgresUser, + engine: postgresEngine, + host: envPostgresHost, + port: process.env.DB_POSTGRES_PORT || 5432, + user: envPostgresUser, password: process.env.DB_POSTGRES_PASSWORD, - name: envPostgresName, + name: envPostgresName, }, keys: getKeys(), }; return; } - const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite'; + const envSqliteFile = process.env.DB_SQLITE_FILE || "/data/database.sqlite"; logger.info(`Using Sqlite: ${envSqliteFile}`); instance = { database: { - engine: 'knex-native', - knex: { - client: sqliteClientName, + engine: "knex-native", + knex: { + client: sqliteClientName, connection: { - filename: envSqliteFile + filename: envSqliteFile, }, - useNullAsDefault: true - } + useNullAsDefault: true, + }, }, keys: getKeys(), }; @@ -88,150 +96,157 @@ const configure = () => { const getKeys = () => { // Get keys from file + if (isDebugMode()) { + logger.debug("Checking for keys file:", keysFile); + } if (!fs.existsSync(keysFile)) { generateKeys(); } else if (process.env.DEBUG) { - logger.info('Keys file exists OK'); + logger.info("Keys file exists OK"); } try { - return require(keysFile); + // Load this json keysFile synchronously and return the json object + const rawData = fs.readFileSync(keysFile); + return JSON.parse(rawData); } catch (err) { - logger.error('Could not read JWT key pair from config file: ' + keysFile, err); + logger.error(`Could not read JWT key pair from config file: ${keysFile}`, err); process.exit(1); } }; const generateKeys = () => { - logger.info('Creating a new JWT key pair...'); + logger.info("Creating a new JWT key pair..."); // Now create the keys and save them in the config. const key = new NodeRSA({ b: 2048 }); key.generateKeyPair(); const keys = { - key: key.exportKey('private').toString(), - pub: key.exportKey('public').toString(), + key: key.exportKey("private").toString(), + pub: key.exportKey("public").toString(), }; // Write keys config try { fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2)); } catch (err) { - logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' + err.message); + logger.error(`Could not write JWT key pair to config file: ${keysFile}: ${err.message}`); process.exit(1); } - logger.info('Wrote JWT key pair to config file: ' + keysFile); + logger.info(`Wrote JWT key pair to config file: ${keysFile}`); }; -module.exports = { - - /** - * - * @param {string} key ie: 'database' or 'database.engine' - * @returns {boolean} - */ - has: function(key) { - instance === null && configure(); - const keys = key.split('.'); - let level = instance; - let has = true; - keys.forEach((keyItem) =>{ - if (typeof level[keyItem] === 'undefined') { - has = false; - } else { - level = level[keyItem]; - } - }); - - return has; - }, - - /** - * Gets a specific key from the top level - * - * @param {string} key - * @returns {*} - */ - get: function (key) { - instance === null && configure(); - if (key && typeof instance[key] !== 'undefined') { - return instance[key]; +/** + * + * @param {string} key ie: 'database' or 'database.engine' + * @returns {boolean} + */ +const configHas = (key) => { + instance === null && configure(); + const keys = key.split("."); + let level = instance; + let has = true; + keys.forEach((keyItem) => { + if (typeof level[keyItem] === "undefined") { + has = false; + } else { + level = level[keyItem]; } - return instance; - }, + }); - /** - * Is this a sqlite configuration? - * - * @returns {boolean} - */ - isSqlite: function () { - instance === null && configure(); - return instance.database.knex && instance.database.knex.client === sqliteClientName; - }, + return has; +}; - /** - * Is this a mysql configuration? - * - * @returns {boolean} - */ - isMysql: function () { - instance === null && configure(); - return instance.database.engine === mysqlEngine; - }, - - /** - * Is this a postgres configuration? - * - * @returns {boolean} - */ - isPostgres: function () { - instance === null && configure(); - return instance.database.engine === postgresEngine; - }, - - /** - * Are we running in debug mdoe? - * - * @returns {boolean} - */ - debug: function () { - return !!process.env.DEBUG; - }, - - /** - * Returns a public key - * - * @returns {string} - */ - getPublicKey: function () { - instance === null && configure(); - return instance.keys.pub; - }, - - /** - * Returns a private key - * - * @returns {string} - */ - getPrivateKey: function () { - instance === null && configure(); - return instance.keys.key; - }, - - /** - * @returns {boolean} - */ - useLetsencryptStaging: function () { - return !!process.env.LE_STAGING; - }, - - /** - * @returns {string|null} - */ - useLetsencryptServer: function () { - if (process.env.LE_SERVER) { - return process.env.LE_SERVER; - } - return null; +/** + * Gets a specific key from the top level + * + * @param {string} key + * @returns {*} + */ +const configGet = (key) => { + instance === null && configure(); + if (key && typeof instance[key] !== "undefined") { + return instance[key]; } + return instance; }; + +/** + * Is this a sqlite configuration? + * + * @returns {boolean} + */ +const isSqlite = () => { + instance === null && configure(); + return instance.database.knex && instance.database.knex.client === sqliteClientName; +}; + +/** + * Is this a mysql configuration? + * + * @returns {boolean} + */ +const isMysql = () => { + instance === null && configure(); + return instance.database.engine === mysqlEngine; +}; + +/** + * Is this a postgres configuration? + * + * @returns {boolean} + */ +const isPostgres = () => { + instance === null && configure(); + return instance.database.engine === postgresEngine; +}; + +/** + * Are we running in debug mdoe? + * + * @returns {boolean} + */ +const isDebugMode = () => !!process.env.DEBUG; + +/** + * Are we running in CI? + * + * @returns {boolean} + */ +const isCI = () => process.env.CI === 'true' && process.env.DEBUG === 'true'; + +/** + * Returns a public key + * + * @returns {string} + */ +const getPublicKey = () => { + instance === null && configure(); + return instance.keys.pub; +}; + +/** + * Returns a private key + * + * @returns {string} + */ +const getPrivateKey = () => { + instance === null && configure(); + return instance.keys.key; +}; + +/** + * @returns {boolean} + */ +const useLetsencryptStaging = () => !!process.env.LE_STAGING; + +/** + * @returns {string|null} + */ +const useLetsencryptServer = () => { + if (process.env.LE_SERVER) { + return process.env.LE_SERVER; + } + return null; +}; + +export { isCI, configHas, configGet, isSqlite, isMysql, isPostgres, isDebugMode, getPrivateKey, getPublicKey, useLetsencryptStaging, useLetsencryptServer }; diff --git a/backend/lib/error.js b/backend/lib/error.js index 413d6a7d..d7dbf0c9 100644 --- a/backend/lib/error.js +++ b/backend/lib/error.js @@ -1,99 +1,103 @@ -const _ = require('lodash'); -const util = require('util'); +import _ from "lodash"; -module.exports = { - - PermissionError: function (message, previous) { +const errs = { + PermissionError: function (_, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = 'Permission Denied'; - this.public = true; - this.status = 403; + this.message = "Permission Denied"; + this.public = true; + this.status = 403; }, ItemNotFoundError: function (id, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = 'Item Not Found - ' + id; - this.public = true; - this.status = 404; + this.message = "Not Found"; + if (id) { + this.message = `Not Found - ${id}`; + } + this.public = true; + this.status = 404; }, - AuthError: function (message, previous) { + AuthError: function (message, messageI18n, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.public = true; - this.status = 401; + this.message = message; + this.message_i18n = messageI18n; + this.public = true; + this.status = 400; }, InternalError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.status = 500; - this.public = false; + this.message = message; + this.status = 500; + this.public = false; }, InternalValidationError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.status = 400; - this.public = false; + this.message = message; + this.status = 400; + this.public = false; }, ConfigurationError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.status = 400; - this.public = true; + this.message = message; + this.status = 400; + this.public = true; }, CacheError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.message = message; + this.name = this.constructor.name; + this.message = message; this.previous = previous; - this.status = 500; - this.public = false; + this.status = 500; + this.public = false; }, ValidationError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.public = true; - this.status = 400; + this.message = message; + this.public = true; + this.status = 400; }, AssertionFailedError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.public = false; - this.status = 400; + this.message = message; + this.public = false; + this.status = 400; }, CommandError: function (stdErr, code, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = stdErr; - this.code = code; - this.public = false; + this.message = stdErr; + this.code = code; + this.public = false; }, }; -_.forEach(module.exports, function (error) { - util.inherits(error, Error); +_.forEach(errs, (err) => { + err.prototype = Object.create(Error.prototype); }); + +export default errs; diff --git a/backend/lib/express/cors.js b/backend/lib/express/cors.js index 6d5b8b5f..6fbf3baf 100644 --- a/backend/lib/express/cors.js +++ b/backend/lib/express/cors.js @@ -1,12 +1,13 @@ -module.exports = function (req, res, next) { +export default (req, res, next) => { if (req.headers.origin) { res.set({ - 'Access-Control-Allow-Origin': req.headers.origin, - 'Access-Control-Allow-Credentials': true, - 'Access-Control-Allow-Methods': 'OPTIONS, GET, POST', - 'Access-Control-Allow-Headers': 'Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit', - 'Access-Control-Max-Age': 5 * 60, - 'Access-Control-Expose-Headers': 'X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit' + "Access-Control-Allow-Origin": req.headers.origin, + "Access-Control-Allow-Credentials": true, + "Access-Control-Allow-Methods": "OPTIONS, GET, POST", + "Access-Control-Allow-Headers": + "Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit", + "Access-Control-Max-Age": 5 * 60, + "Access-Control-Expose-Headers": "X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit", }); next(); } else { diff --git a/backend/lib/express/jwt-decode.js b/backend/lib/express/jwt-decode.js index 17edccec..90fe241e 100644 --- a/backend/lib/express/jwt-decode.js +++ b/backend/lib/express/jwt-decode.js @@ -1,15 +1,15 @@ -const Access = require('../access'); +import Access from "../access.js"; -module.exports = () => { - return function (req, res, next) { - res.locals.access = null; - let access = new Access(res.locals.token || null); - access.load() - .then(() => { - res.locals.access = access; - next(); - }) - .catch(next); +export default () => { + return async (_, res, next) => { + try { + res.locals.access = null; + const access = new Access(res.locals.token || null); + await access.load(); + res.locals.access = access; + next(); + } catch (err) { + next(err); + } }; }; - diff --git a/backend/lib/express/jwt.js b/backend/lib/express/jwt.js index 44aa3693..ce907b6d 100644 --- a/backend/lib/express/jwt.js +++ b/backend/lib/express/jwt.js @@ -1,13 +1,13 @@ -module.exports = function () { - return function (req, res, next) { +export default function () { + return (req, res, next) => { if (req.headers.authorization) { - let parts = req.headers.authorization.split(' '); + const parts = req.headers.authorization.split(" "); - if (parts && parts[0] === 'Bearer' && parts[1]) { + if (parts && parts[0] === "Bearer" && parts[1]) { res.locals.token = parts[1]; } } next(); }; -}; +} diff --git a/backend/lib/express/pagination.js b/backend/lib/express/pagination.js index 24ffa58d..188df277 100644 --- a/backend/lib/express/pagination.js +++ b/backend/lib/express/pagination.js @@ -1,7 +1,6 @@ -let _ = require('lodash'); - -module.exports = function (default_sort, default_offset, default_limit, max_limit) { +import _ from "lodash"; +export default (default_sort, default_offset, default_limit, max_limit) => { /** * This will setup the req query params with filtered data and defaults * @@ -11,34 +10,35 @@ module.exports = function (default_sort, default_offset, default_limit, max_limi * */ - return function (req, res, next) { - - req.query.offset = typeof req.query.limit === 'undefined' ? default_offset || 0 : parseInt(req.query.offset, 10); - req.query.limit = typeof req.query.limit === 'undefined' ? default_limit || 50 : parseInt(req.query.limit, 10); + return (req, _res, next) => { + req.query.offset = + typeof req.query.limit === "undefined" ? default_offset || 0 : Number.parseInt(req.query.offset, 10); + req.query.limit = + typeof req.query.limit === "undefined" ? default_limit || 50 : Number.parseInt(req.query.limit, 10); if (max_limit && req.query.limit > max_limit) { req.query.limit = max_limit; } // Sorting - let sort = typeof req.query.sort === 'undefined' ? default_sort : req.query.sort; - let myRegexp = /.*\.(asc|desc)$/ig; - let sort_array = []; + let sort = typeof req.query.sort === "undefined" ? default_sort : req.query.sort; + const myRegexp = /.*\.(asc|desc)$/gi; + const sort_array = []; - sort = sort.split(','); - _.map(sort, function (val) { - let matches = myRegexp.exec(val); + sort = sort.split(","); + _.map(sort, (val) => { + const matches = myRegexp.exec(val); if (matches !== null) { - let dir = matches[1]; + const dir = matches[1]; sort_array.push({ field: val.substr(0, val.length - (dir.length + 1)), - dir: dir.toLowerCase() + dir: dir.toLowerCase(), }); } else { sort_array.push({ field: val, - dir: 'asc' + dir: "asc", }); } }); diff --git a/backend/lib/express/user-id-from-me.js b/backend/lib/express/user-id-from-me.js index 4a37a406..9c29ba27 100644 --- a/backend/lib/express/user-id-from-me.js +++ b/backend/lib/express/user-id-from-me.js @@ -1,9 +1,8 @@ -module.exports = (req, res, next) => { +export default (req, res, next) => { if (req.params.user_id === 'me' && res.locals.access) { req.params.user_id = res.locals.access.token.get('attrs').id; } else { - req.params.user_id = parseInt(req.params.user_id, 10); + req.params.user_id = Number.parseInt(req.params.user_id, 10); } - next(); }; diff --git a/backend/lib/helpers.js b/backend/lib/helpers.js index ad3df3c2..853a7a55 100644 --- a/backend/lib/helpers.js +++ b/backend/lib/helpers.js @@ -1,62 +1,58 @@ -const moment = require('moment'); -const {isPostgres} = require('./config'); -const {ref} = require('objection'); +import moment from "moment"; +import { ref } from "objection"; +import { isPostgres } from "./config.js"; -module.exports = { - - /** - * Takes an expression such as 30d and returns a moment object of that date in future - * - * Key Shorthand - * ================== - * years y - * quarters Q - * months M - * weeks w - * days d - * hours h - * minutes m - * seconds s - * milliseconds ms - * - * @param {String} expression - * @returns {Object} - */ - parseDatePeriod: function (expression) { - let matches = expression.match(/^([0-9]+)(y|Q|M|w|d|h|m|s|ms)$/m); - if (matches) { - return moment().add(matches[1], matches[2]); - } - - return null; - }, - - convertIntFieldsToBool: function (obj, fields) { - fields.forEach(function (field) { - if (typeof obj[field] !== 'undefined') { - obj[field] = obj[field] === 1; - } - }); - return obj; - }, - - convertBoolFieldsToInt: function (obj, fields) { - fields.forEach(function (field) { - if (typeof obj[field] !== 'undefined') { - obj[field] = obj[field] ? 1 : 0; - } - }); - return obj; - }, - - /** - * Casts a column to json if using postgres - * - * @param {string} colName - * @returns {string|Objection.ReferenceBuilder} - */ - castJsonIfNeed: function (colName) { - return isPostgres() ? ref(colName).castText() : colName; +/** + * Takes an expression such as 30d and returns a moment object of that date in future + * + * Key Shorthand + * ================== + * years y + * quarters Q + * months M + * weeks w + * days d + * hours h + * minutes m + * seconds s + * milliseconds ms + * + * @param {String} expression + * @returns {Object} + */ +const parseDatePeriod = (expression) => { + const matches = expression.match(/^([0-9]+)(y|Q|M|w|d|h|m|s|ms)$/m); + if (matches) { + return moment().add(matches[1], matches[2]); } + return null; }; + +const convertIntFieldsToBool = (obj, fields) => { + fields.forEach((field) => { + if (typeof obj[field] !== "undefined") { + obj[field] = obj[field] === 1; + } + }); + return obj; +}; + +const convertBoolFieldsToInt = (obj, fields) => { + fields.forEach((field) => { + if (typeof obj[field] !== "undefined") { + obj[field] = obj[field] ? 1 : 0; + } + }); + return obj; +}; + +/** + * Casts a column to json if using postgres + * + * @param {string} colName + * @returns {string|Objection.ReferenceBuilder} + */ +const castJsonIfNeed = (colName) => (isPostgres() ? ref(colName).castText() : colName); + +export { parseDatePeriod, convertIntFieldsToBool, convertBoolFieldsToInt, castJsonIfNeed }; diff --git a/backend/lib/migrate_template.js b/backend/lib/migrate_template.js index f75f77ef..0b8e2840 100644 --- a/backend/lib/migrate_template.js +++ b/backend/lib/migrate_template.js @@ -1,33 +1,34 @@ -const migrate_name = 'identifier_for_migrate'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "identifier_for_migrate"; /** * Migrate * * @see http://knexjs.org/#Schema * - * @param {Object} knex - * @param {Promise} Promise + * @param {Object} knex * @returns {Promise} */ -exports.up = function (knex, Promise) { - - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (_knex) => { + logger.info(`[${migrateName}] Migrating Up...`); // Create Table example: - /*return knex.schema.createTable('notification', (table) => { + /* + return knex.schema.createTable('notification', (table) => { table.increments().primary(); table.string('name').notNull(); table.string('type').notNull(); table.integer('created_on').notNull(); table.integer('modified_on').notNull(); }) - .then(function () { - logger.info('[' + migrate_name + '] Notification Table created'); - });*/ + .then(function () { + logger.info('[' + migrateName + '] Notification Table created'); + }); + */ - logger.info('[' + migrate_name + '] Migrating Up Complete'); + logger.info(`[${migrateName}] Migrating Up Complete`); return Promise.resolve(true); }; @@ -35,21 +36,24 @@ exports.up = function (knex, Promise) { /** * Undo Migrate * - * @param {Object} knex - * @param {Promise} Promise + * @param {Object} knex * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (_knex) => { + logger.info(`[${migrateName}] Migrating Down...`); // Drop table example: - /*return knex.schema.dropTable('notification') - .then(() => { - logger.info('[' + migrate_name + '] Notification Table dropped'); - });*/ + /* + return knex.schema.dropTable('notification') + .then(() => { + logger.info(`[${migrateName}] Notification Table dropped`); + }); + */ - logger.info('[' + migrate_name + '] Migrating Down Complete'); + logger.info(`[${migrateName}] Migrating Down Complete`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/lib/utils.js b/backend/lib/utils.js index 66f2dfd9..af7ad3c9 100644 --- a/backend/lib/utils.js +++ b/backend/lib/utils.js @@ -1,106 +1,110 @@ -const _ = require('lodash'); -const exec = require('node:child_process').exec; -const execFile = require('node:child_process').execFile; -const { Liquid } = require('liquidjs'); -const logger = require('../logger').global; -const error = require('./error'); +import { exec as nodeExec, execFile as nodeExecFile } from "node:child_process"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { Liquid } from "liquidjs"; +import _ from "lodash"; +import { debug, global as logger } from "../logger.js"; +import errs from "./error.js"; -module.exports = { +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); - exec: async (cmd, options = {}) => { - logger.debug('CMD:', cmd); - - const { stdout, stderr } = await new Promise((resolve, reject) => { - const child = exec(cmd, options, (isError, stdout, stderr) => { - if (isError) { - reject(new error.CommandError(stderr, isError)); - } else { - resolve({ stdout, stderr }); - } - }); - - child.on('error', (e) => { - reject(new error.CommandError(stderr, 1, e)); - }); - }); - return stdout; - }, - - /** - * @param {String} cmd - * @param {Array} args - * @returns {Promise} - */ - execFile: (cmd, args) => { - // logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : '')); - - return new Promise((resolve, reject) => { - execFile(cmd, args, (err, stdout, /*stderr*/) => { - if (err && typeof err === 'object') { - reject(err); - } else { - resolve(stdout.trim()); - } - }); - }); - }, - - /** - * Used in objection query builder - * - * @param {Array} omissions - * @returns {Function} - */ - omitRow: (omissions) => { - /** - * @param {Object} row - * @returns {Object} - */ - return (row) => { - return _.omit(row, omissions); - }; - }, - - /** - * Used in objection query builder - * - * @param {Array} omissions - * @returns {Function} - */ - omitRows: (omissions) => { - /** - * @param {Array} rows - * @returns {Object} - */ - return (rows) => { - rows.forEach((row, idx) => { - rows[idx] = _.omit(row, omissions); - }); - return rows; - }; - }, - - /** - * @returns {Object} Liquid render engine - */ - getRenderEngine: () => { - const renderEngine = new Liquid({ - root: `${__dirname}/../templates/` - }); - - /** - * nginxAccessRule expects the object given to have 2 properties: - * - * directive string - * address string - */ - renderEngine.registerFilter('nginxAccessRule', (v) => { - if (typeof v.directive !== 'undefined' && typeof v.address !== 'undefined' && v.directive && v.address) { - return `${v.directive} ${v.address};`; +const exec = async (cmd, options = {}) => { + debug(logger, "CMD:", cmd); + const { stdout, stderr } = await new Promise((resolve, reject) => { + const child = nodeExec(cmd, options, (isError, stdout, stderr) => { + if (isError) { + reject(new errs.CommandError(stderr, isError)); + } else { + resolve({ stdout, stderr }); } - return ''; }); - return renderEngine; - } + child.on("error", (e) => { + reject(new errs.CommandError(stderr, 1, e)); + }); + }); + return stdout; }; + +/** + * @param {String} cmd + * @param {Array} args + * @param {Object|undefined} options + * @returns {Promise} + */ +const execFile = (cmd, args, options) => { + debug(logger, `CMD: ${cmd} ${args ? args.join(" ") : ""}`); + const opts = options || {}; + + return new Promise((resolve, reject) => { + nodeExecFile(cmd, args, opts, (err, stdout, stderr) => { + if (err && typeof err === "object") { + reject(new errs.CommandError(stderr, 1, err)); + } else { + resolve(stdout.trim()); + } + }); + }); +}; + +/** + * Used in objection query builder + * + * @param {Array} omissions + * @returns {Function} + */ +const omitRow = (omissions) => { + /** + * @param {Object} row + * @returns {Object} + */ + return (row) => { + return _.omit(row, omissions); + }; +}; + +/** + * Used in objection query builder + * + * @param {Array} omissions + * @returns {Function} + */ +const omitRows = (omissions) => { + /** + * @param {Array} rows + * @returns {Object} + */ + return (rows) => { + rows.forEach((row, idx) => { + rows[idx] = _.omit(row, omissions); + }); + return rows; + }; +}; + +/** + * @returns {Object} Liquid render engine + */ +const getRenderEngine = () => { + const renderEngine = new Liquid({ + root: `${__dirname}/../templates/`, + }); + + /** + * nginxAccessRule expects the object given to have 2 properties: + * + * directive string + * address string + */ + renderEngine.registerFilter("nginxAccessRule", (v) => { + if (typeof v.directive !== "undefined" && typeof v.address !== "undefined" && v.directive && v.address) { + return `${v.directive} ${v.address};`; + } + return ""; + }); + + return renderEngine; +}; + +export default { exec, execFile, omitRow, omitRows, getRenderEngine }; diff --git a/backend/lib/validator/api.js b/backend/lib/validator/api.js index fb31e64c..6c738d50 100644 --- a/backend/lib/validator/api.js +++ b/backend/lib/validator/api.js @@ -1,12 +1,12 @@ -const Ajv = require('ajv/dist/2020'); -const error = require('../error'); +import Ajv from "ajv/dist/2020.js"; +import errs from "../error.js"; const ajv = new Ajv({ - verbose: true, - allErrors: true, + verbose: true, + allErrors: true, allowUnionTypes: true, - strict: false, - coerceTypes: true, + strict: false, + coerceTypes: true, }); /** @@ -14,30 +14,32 @@ const ajv = new Ajv({ * @param {Object} payload * @returns {Promise} */ -function apiValidator (schema, payload/*, description*/) { - return new Promise(function Promise_apiValidator (resolve, reject) { - if (schema === null) { - reject(new error.ValidationError('Schema is undefined')); - return; - } +const apiValidator = async (schema, payload /*, description*/) => { + if (!schema) { + throw new errs.ValidationError("Schema is undefined"); + } - if (typeof payload === 'undefined') { - reject(new error.ValidationError('Payload is undefined')); - return; - } + // Can't use falsy check here as valid payload could be `0` or `false` + if (typeof payload === "undefined") { + throw new errs.ValidationError("Payload is undefined"); + } - const validate = ajv.compile(schema); - const valid = validate(payload); - if (valid && !validate.errors) { - resolve(payload); - } else { - let message = ajv.errorsText(validate.errors); - let err = new error.ValidationError(message); - err.debug = [validate.errors, payload]; - reject(err); - } - }); -} + const validate = ajv.compile(schema); -module.exports = apiValidator; + const valid = validate(payload); + + + if (valid && !validate.errors) { + return payload; + } + + + + const message = ajv.errorsText(validate.errors); + const err = new errs.ValidationError(message); + err.debug = {validationErrors: validate.errors, payload}; + throw err; +}; + +export default apiValidator; diff --git a/backend/lib/validator/index.js b/backend/lib/validator/index.js index c6d24096..5f2586fd 100644 --- a/backend/lib/validator/index.js +++ b/backend/lib/validator/index.js @@ -1,17 +1,17 @@ -const _ = require('lodash'); -const Ajv = require('ajv/dist/2020'); -const error = require('../error'); -const commonDefinitions = require('../../schema/common.json'); +import Ajv from 'ajv/dist/2020.js'; +import _ from "lodash"; +import commonDefinitions from "../../schema/common.json" with { type: "json" }; +import errs from "../error.js"; RegExp.prototype.toJSON = RegExp.prototype.toString; const ajv = new Ajv({ - verbose: true, - allErrors: true, + verbose: true, + allErrors: true, allowUnionTypes: true, - coerceTypes: true, - strict: false, - schemas: [commonDefinitions] + coerceTypes: true, + strict: false, + schemas: [commonDefinitions], }); /** @@ -20,26 +20,26 @@ const ajv = new Ajv({ * @param {Object} payload * @returns {Promise} */ -function validator (schema, payload) { - return new Promise(function (resolve, reject) { +const validator = (schema, payload) => { + return new Promise((resolve, reject) => { if (!payload) { - reject(new error.InternalValidationError('Payload is falsy')); + reject(new errs.InternalValidationError("Payload is falsy")); } else { try { - let validate = ajv.compile(schema); - let valid = validate(payload); + const validate = ajv.compile(schema); + const valid = validate(payload); if (valid && !validate.errors) { resolve(_.cloneDeep(payload)); } else { - let message = ajv.errorsText(validate.errors); - reject(new error.InternalValidationError(message)); + const message = ajv.errorsText(validate.errors); + reject(new errs.InternalValidationError(message)); } } catch (err) { reject(err); } } }); -} +}; -module.exports = validator; +export default validator; diff --git a/backend/logger.js b/backend/logger.js index 0ebb07c5..7bf4ee05 100644 --- a/backend/logger.js +++ b/backend/logger.js @@ -1,14 +1,25 @@ -const {Signale} = require('signale'); +import signale from "signale"; +import { isDebugMode } from "./lib/config.js"; -module.exports = { - global: new Signale({scope: 'Global '}), - migrate: new Signale({scope: 'Migrate '}), - express: new Signale({scope: 'Express '}), - access: new Signale({scope: 'Access '}), - nginx: new Signale({scope: 'Nginx '}), - ssl: new Signale({scope: 'SSL '}), - certbot: new Signale({scope: 'Certbot '}), - import: new Signale({scope: 'Importer '}), - setup: new Signale({scope: 'Setup '}), - ip_ranges: new Signale({scope: 'IP Ranges'}) +const opts = { + logLevel: "info", }; + +const global = new signale.Signale({ scope: "Global ", ...opts }); +const migrate = new signale.Signale({ scope: "Migrate ", ...opts }); +const express = new signale.Signale({ scope: "Express ", ...opts }); +const access = new signale.Signale({ scope: "Access ", ...opts }); +const nginx = new signale.Signale({ scope: "Nginx ", ...opts }); +const ssl = new signale.Signale({ scope: "SSL ", ...opts }); +const certbot = new signale.Signale({ scope: "Certbot ", ...opts }); +const importer = new signale.Signale({ scope: "Importer ", ...opts }); +const setup = new signale.Signale({ scope: "Setup ", ...opts }); +const ipRanges = new signale.Signale({ scope: "IP Ranges", ...opts }); + +const debug = (logger, ...args) => { + if (isDebugMode()) { + logger.debug(...args); + } +}; + +export { debug, global, migrate, express, access, nginx, ssl, certbot, importer, setup, ipRanges }; diff --git a/backend/migrate.js b/backend/migrate.js index 263c8702..dd3f1b61 100644 --- a/backend/migrate.js +++ b/backend/migrate.js @@ -1,15 +1,13 @@ -const db = require('./db'); -const logger = require('./logger').migrate; +import db from "./db.js"; +import { migrate as logger } from "./logger.js"; -module.exports = { - latest: function () { - return db.migrate.currentVersion() - .then((version) => { - logger.info('Current database version:', version); - return db.migrate.latest({ - tableName: 'migrations', - directory: 'migrations' - }); - }); - } +const migrateUp = async () => { + const version = await db.migrate.currentVersion(); + logger.info("Current database version:", version); + return await db.migrate.latest({ + tableName: "migrations", + directory: "migrations", + }); }; + +export { migrateUp }; diff --git a/backend/migrations/20180618015850_initial.js b/backend/migrations/20180618015850_initial.js index a112e826..d3c55d9c 100644 --- a/backend/migrations/20180618015850_initial.js +++ b/backend/migrations/20180618015850_initial.js @@ -1,5 +1,6 @@ -const migrate_name = 'initial-schema'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "initial-schema"; /** * Migrate @@ -7,199 +8,199 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.createTable('auth', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('user_id').notNull().unsigned(); - table.string('type', 30).notNull(); - table.string('secret').notNull(); - table.json('meta').notNull(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - }) + return knex.schema + .createTable("auth", (table) => { + table.increments().primary(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("user_id").notNull().unsigned(); + table.string("type", 30).notNull(); + table.string("secret").notNull(); + table.json("meta").notNull(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + }) .then(() => { - logger.info('[' + migrate_name + '] auth Table created'); + logger.info(`[${migrateName}] auth Table created`); - return knex.schema.createTable('user', (table) => { + return knex.schema.createTable("user", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.integer('is_disabled').notNull().unsigned().defaultTo(0); - table.string('email').notNull(); - table.string('name').notNull(); - table.string('nickname').notNull(); - table.string('avatar').notNull(); - table.json('roles').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.integer("is_disabled").notNull().unsigned().defaultTo(0); + table.string("email").notNull(); + table.string("name").notNull(); + table.string("nickname").notNull(); + table.string("avatar").notNull(); + table.json("roles").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] user Table created'); + logger.info(`[${migrateName}] user Table created`); - return knex.schema.createTable('user_permission', (table) => { + return knex.schema.createTable("user_permission", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('user_id').notNull().unsigned(); - table.string('visibility').notNull(); - table.string('proxy_hosts').notNull(); - table.string('redirection_hosts').notNull(); - table.string('dead_hosts').notNull(); - table.string('streams').notNull(); - table.string('access_lists').notNull(); - table.string('certificates').notNull(); - table.unique('user_id'); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("user_id").notNull().unsigned(); + table.string("visibility").notNull(); + table.string("proxy_hosts").notNull(); + table.string("redirection_hosts").notNull(); + table.string("dead_hosts").notNull(); + table.string("streams").notNull(); + table.string("access_lists").notNull(); + table.string("certificates").notNull(); + table.unique("user_id"); }); }) .then(() => { - logger.info('[' + migrate_name + '] user_permission Table created'); + logger.info(`[${migrateName}] user_permission Table created`); - return knex.schema.createTable('proxy_host', (table) => { + return knex.schema.createTable("proxy_host", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.json('domain_names').notNull(); - table.string('forward_ip').notNull(); - table.integer('forward_port').notNull().unsigned(); - table.integer('access_list_id').notNull().unsigned().defaultTo(0); - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - table.integer('ssl_forced').notNull().unsigned().defaultTo(0); - table.integer('caching_enabled').notNull().unsigned().defaultTo(0); - table.integer('block_exploits').notNull().unsigned().defaultTo(0); - table.text('advanced_config').notNull().defaultTo(''); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.json("domain_names").notNull(); + table.string("forward_ip").notNull(); + table.integer("forward_port").notNull().unsigned(); + table.integer("access_list_id").notNull().unsigned().defaultTo(0); + table.integer("certificate_id").notNull().unsigned().defaultTo(0); + table.integer("ssl_forced").notNull().unsigned().defaultTo(0); + table.integer("caching_enabled").notNull().unsigned().defaultTo(0); + table.integer("block_exploits").notNull().unsigned().defaultTo(0); + table.text("advanced_config").notNull().defaultTo(""); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table created'); + logger.info(`[${migrateName}] proxy_host Table created`); - return knex.schema.createTable('redirection_host', (table) => { + return knex.schema.createTable("redirection_host", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.json('domain_names').notNull(); - table.string('forward_domain_name').notNull(); - table.integer('preserve_path').notNull().unsigned().defaultTo(0); - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - table.integer('ssl_forced').notNull().unsigned().defaultTo(0); - table.integer('block_exploits').notNull().unsigned().defaultTo(0); - table.text('advanced_config').notNull().defaultTo(''); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.json("domain_names").notNull(); + table.string("forward_domain_name").notNull(); + table.integer("preserve_path").notNull().unsigned().defaultTo(0); + table.integer("certificate_id").notNull().unsigned().defaultTo(0); + table.integer("ssl_forced").notNull().unsigned().defaultTo(0); + table.integer("block_exploits").notNull().unsigned().defaultTo(0); + table.text("advanced_config").notNull().defaultTo(""); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table created'); + logger.info(`[${migrateName}] redirection_host Table created`); - return knex.schema.createTable('dead_host', (table) => { + return knex.schema.createTable("dead_host", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.json('domain_names').notNull(); - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - table.integer('ssl_forced').notNull().unsigned().defaultTo(0); - table.text('advanced_config').notNull().defaultTo(''); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.json("domain_names").notNull(); + table.integer("certificate_id").notNull().unsigned().defaultTo(0); + table.integer("ssl_forced").notNull().unsigned().defaultTo(0); + table.text("advanced_config").notNull().defaultTo(""); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] dead_host Table created'); + logger.info(`[${migrateName}] dead_host Table created`); - return knex.schema.createTable('stream', (table) => { + return knex.schema.createTable("stream", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.integer('incoming_port').notNull().unsigned(); - table.string('forward_ip').notNull(); - table.integer('forwarding_port').notNull().unsigned(); - table.integer('tcp_forwarding').notNull().unsigned().defaultTo(0); - table.integer('udp_forwarding').notNull().unsigned().defaultTo(0); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.integer("incoming_port").notNull().unsigned(); + table.string("forward_ip").notNull(); + table.integer("forwarding_port").notNull().unsigned(); + table.integer("tcp_forwarding").notNull().unsigned().defaultTo(0); + table.integer("udp_forwarding").notNull().unsigned().defaultTo(0); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] stream Table created'); + logger.info(`[${migrateName}] stream Table created`); - return knex.schema.createTable('access_list', (table) => { + return knex.schema.createTable("access_list", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.string('name').notNull(); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.string("name").notNull(); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] access_list Table created'); + logger.info(`[${migrateName}] access_list Table created`); - return knex.schema.createTable('certificate', (table) => { + return knex.schema.createTable("certificate", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.string('provider').notNull(); - table.string('nice_name').notNull().defaultTo(''); - table.json('domain_names').notNull(); - table.dateTime('expires_on').notNull(); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.string("provider").notNull(); + table.string("nice_name").notNull().defaultTo(""); + table.json("domain_names").notNull(); + table.dateTime("expires_on").notNull(); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] certificate Table created'); + logger.info(`[${migrateName}] certificate Table created`); - return knex.schema.createTable('access_list_auth', (table) => { + return knex.schema.createTable("access_list_auth", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('access_list_id').notNull().unsigned(); - table.string('username').notNull(); - table.string('password').notNull(); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("access_list_id").notNull().unsigned(); + table.string("username").notNull(); + table.string("password").notNull(); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] access_list_auth Table created'); + logger.info(`[${migrateName}] access_list_auth Table created`); - return knex.schema.createTable('audit_log', (table) => { + return knex.schema.createTable("audit_log", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('user_id').notNull().unsigned(); - table.string('object_type').notNull().defaultTo(''); - table.integer('object_id').notNull().unsigned().defaultTo(0); - table.string('action').notNull(); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("user_id").notNull().unsigned(); + table.string("object_type").notNull().defaultTo(""); + table.integer("object_id").notNull().unsigned().defaultTo(0); + table.string("action").notNull(); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] audit_log Table created'); + logger.info(`[${migrateName}] audit_log Table created`); }); - }; /** * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down the initial data.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down the initial data.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20180929054513_websockets.js b/backend/migrations/20180929054513_websockets.js index 06054850..cce80d37 100644 --- a/backend/migrations/20180929054513_websockets.js +++ b/backend/migrations/20180929054513_websockets.js @@ -1,5 +1,6 @@ -const migrate_name = 'websockets'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "websockets"; /** * Migrate @@ -7,29 +8,29 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.integer("allow_websocket_upgrade").notNull().unsigned().defaultTo(0); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); }); - }; /** * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); -}; \ No newline at end of file +}; + +export { up, down }; diff --git a/backend/migrations/20181019052346_forward_host.js b/backend/migrations/20181019052346_forward_host.js index 05c27739..fe11edc5 100644 --- a/backend/migrations/20181019052346_forward_host.js +++ b/backend/migrations/20181019052346_forward_host.js @@ -1,5 +1,6 @@ -const migrate_name = 'forward_host'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "forward_host"; /** * Migrate @@ -7,17 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.renameColumn('forward_ip', 'forward_host'); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.renameColumn("forward_ip", "forward_host"); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); }); }; @@ -25,10 +26,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); -}; \ No newline at end of file +}; + +export { up, down }; diff --git a/backend/migrations/20181113041458_http2_support.js b/backend/migrations/20181113041458_http2_support.js index 9f6b4336..cfa94a99 100644 --- a/backend/migrations/20181113041458_http2_support.js +++ b/backend/migrations/20181113041458_http2_support.js @@ -1,5 +1,6 @@ -const migrate_name = 'http2_support'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "http2_support"; /** * Migrate @@ -7,31 +8,31 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.integer('http2_support').notNull().unsigned().defaultTo(0); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.integer("http2_support").notNull().unsigned().defaultTo(0); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); - return knex.schema.table('redirection_host', function (redirection_host) { - redirection_host.integer('http2_support').notNull().unsigned().defaultTo(0); + return knex.schema.table("redirection_host", (redirection_host) => { + redirection_host.integer("http2_support").notNull().unsigned().defaultTo(0); }); }) .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + logger.info(`[${migrateName}] redirection_host Table altered`); - return knex.schema.table('dead_host', function (dead_host) { - dead_host.integer('http2_support').notNull().unsigned().defaultTo(0); + return knex.schema.table("dead_host", (dead_host) => { + dead_host.integer("http2_support").notNull().unsigned().defaultTo(0); }); }) .then(() => { - logger.info('[' + migrate_name + '] dead_host Table altered'); + logger.info(`[${migrateName}] dead_host Table altered`); }); }; @@ -39,11 +40,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; +export { up, down }; diff --git a/backend/migrations/20181213013211_forward_scheme.js b/backend/migrations/20181213013211_forward_scheme.js index 22ae619e..ba3bc562 100644 --- a/backend/migrations/20181213013211_forward_scheme.js +++ b/backend/migrations/20181213013211_forward_scheme.js @@ -1,5 +1,6 @@ -const migrate_name = 'forward_scheme'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "forward_scheme"; /** * Migrate @@ -7,17 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.string('forward_scheme').notNull().defaultTo('http'); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.string("forward_scheme").notNull().defaultTo("http"); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); }); }; @@ -25,10 +26,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20190104035154_disabled.js b/backend/migrations/20190104035154_disabled.js index 2780c4df..28fcc7b5 100644 --- a/backend/migrations/20190104035154_disabled.js +++ b/backend/migrations/20190104035154_disabled.js @@ -1,5 +1,6 @@ -const migrate_name = 'disabled'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "disabled"; /** * Migrate @@ -7,38 +8,38 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.integer('enabled').notNull().unsigned().defaultTo(1); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.integer("enabled").notNull().unsigned().defaultTo(1); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); - return knex.schema.table('redirection_host', function (redirection_host) { - redirection_host.integer('enabled').notNull().unsigned().defaultTo(1); + return knex.schema.table("redirection_host", (redirection_host) => { + redirection_host.integer("enabled").notNull().unsigned().defaultTo(1); }); }) .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + logger.info(`[${migrateName}] redirection_host Table altered`); - return knex.schema.table('dead_host', function (dead_host) { - dead_host.integer('enabled').notNull().unsigned().defaultTo(1); + return knex.schema.table("dead_host", (dead_host) => { + dead_host.integer("enabled").notNull().unsigned().defaultTo(1); }); }) .then(() => { - logger.info('[' + migrate_name + '] dead_host Table altered'); + logger.info(`[${migrateName}] dead_host Table altered`); - return knex.schema.table('stream', function (stream) { - stream.integer('enabled').notNull().unsigned().defaultTo(1); + return knex.schema.table("stream", (stream) => { + stream.integer("enabled").notNull().unsigned().defaultTo(1); }); }) .then(() => { - logger.info('[' + migrate_name + '] stream Table altered'); + logger.info(`[${migrateName}] stream Table altered`); }); }; @@ -46,10 +47,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20190215115310_customlocations.js b/backend/migrations/20190215115310_customlocations.js index 4bcfd51a..c4f77977 100644 --- a/backend/migrations/20190215115310_customlocations.js +++ b/backend/migrations/20190215115310_customlocations.js @@ -1,5 +1,6 @@ -const migrate_name = 'custom_locations'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "custom_locations"; /** * Migrate @@ -8,17 +9,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.json('locations'); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.json("locations"); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); }); }; @@ -26,10 +27,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20190218060101_hsts.js b/backend/migrations/20190218060101_hsts.js index 648b162a..1253130e 100644 --- a/backend/migrations/20190218060101_hsts.js +++ b/backend/migrations/20190218060101_hsts.js @@ -1,5 +1,6 @@ -const migrate_name = 'hsts'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "hsts"; /** * Migrate @@ -7,34 +8,34 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0); - proxy_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.integer("hsts_enabled").notNull().unsigned().defaultTo(0); + proxy_host.integer("hsts_subdomains").notNull().unsigned().defaultTo(0); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); - return knex.schema.table('redirection_host', function (redirection_host) { - redirection_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0); - redirection_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0); + return knex.schema.table("redirection_host", (redirection_host) => { + redirection_host.integer("hsts_enabled").notNull().unsigned().defaultTo(0); + redirection_host.integer("hsts_subdomains").notNull().unsigned().defaultTo(0); }); }) .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + logger.info(`[${migrateName}] redirection_host Table altered`); - return knex.schema.table('dead_host', function (dead_host) { - dead_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0); - dead_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0); + return knex.schema.table("dead_host", (dead_host) => { + dead_host.integer("hsts_enabled").notNull().unsigned().defaultTo(0); + dead_host.integer("hsts_subdomains").notNull().unsigned().defaultTo(0); }); }) .then(() => { - logger.info('[' + migrate_name + '] dead_host Table altered'); + logger.info(`[${migrateName}] dead_host Table altered`); }); }; @@ -42,10 +43,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20190227065017_settings.js b/backend/migrations/20190227065017_settings.js index 7dc9c192..a6cbe2c9 100644 --- a/backend/migrations/20190227065017_settings.js +++ b/backend/migrations/20190227065017_settings.js @@ -1,5 +1,6 @@ -const migrate_name = 'settings'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "settings"; /** * Migrate @@ -7,11 +8,10 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); return knex.schema.createTable('setting', (table) => { table.string('id').notNull().primary(); @@ -21,7 +21,7 @@ exports.up = function (knex/*, Promise*/) { table.json('meta').notNull(); }) .then(() => { - logger.info('[' + migrate_name + '] setting Table created'); + logger.info(`[${migrateName}] setting Table created`); }); }; @@ -29,10 +29,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down the initial data.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down the initial data.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20200410143839_access_list_client.js b/backend/migrations/20200410143839_access_list_client.js index 3511e35b..e6825048 100644 --- a/backend/migrations/20200410143839_access_list_client.js +++ b/backend/migrations/20200410143839_access_list_client.js @@ -1,5 +1,6 @@ -const migrate_name = 'access_list_client'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "access_list_client"; /** * Migrate @@ -7,32 +8,30 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - logger.info('[' + migrate_name + '] Migrating Up...'); + return knex.schema + .createTable("access_list_client", (table) => { + table.increments().primary(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("access_list_id").notNull().unsigned(); + table.string("address").notNull(); + table.string("directive").notNull(); + table.json("meta").notNull(); + }) + .then(() => { + logger.info(`[${migrateName}] access_list_client Table created`); - return knex.schema.createTable('access_list_client', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('access_list_id').notNull().unsigned(); - table.string('address').notNull(); - table.string('directive').notNull(); - table.json('meta').notNull(); - - }) - .then(function () { - logger.info('[' + migrate_name + '] access_list_client Table created'); - - return knex.schema.table('access_list', function (access_list) { - access_list.integer('satify_any').notNull().defaultTo(0); + return knex.schema.table("access_list", (access_list) => { + access_list.integer("satify_any").notNull().defaultTo(0); }); }) .then(() => { - logger.info('[' + migrate_name + '] access_list Table altered'); + logger.info(`[${migrateName}] access_list Table altered`); }); }; @@ -40,14 +39,14 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.dropTable('access_list_client') - .then(() => { - logger.info('[' + migrate_name + '] access_list_client Table dropped'); - }); + return knex.schema.dropTable("access_list_client").then(() => { + logger.info(`[${migrateName}] access_list_client Table dropped`); + }); }; + +export { up, down }; diff --git a/backend/migrations/20200410143840_access_list_client_fix.js b/backend/migrations/20200410143840_access_list_client_fix.js index ee0f0906..6bdaedb6 100644 --- a/backend/migrations/20200410143840_access_list_client_fix.js +++ b/backend/migrations/20200410143840_access_list_client_fix.js @@ -1,5 +1,6 @@ -const migrate_name = 'access_list_client_fix'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "access_list_client_fix"; /** * Migrate @@ -7,17 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('access_list', function (access_list) { - access_list.renameColumn('satify_any', 'satisfy_any'); - }) + return knex.schema + .table("access_list", (access_list) => { + access_list.renameColumn("satify_any", "satisfy_any"); + }) .then(() => { - logger.info('[' + migrate_name + '] access_list Table altered'); + logger.info(`[${migrateName}] access_list Table altered`); }); }; @@ -25,10 +26,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20201014143841_pass_auth.js b/backend/migrations/20201014143841_pass_auth.js index a7767eb1..dc57e2a7 100644 --- a/backend/migrations/20201014143841_pass_auth.js +++ b/backend/migrations/20201014143841_pass_auth.js @@ -1,5 +1,6 @@ -const migrate_name = 'pass_auth'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "pass_auth"; /** * Migrate @@ -7,18 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema.table('access_list', function (access_list) { - access_list.integer('pass_auth').notNull().defaultTo(1); - }) + return knex.schema + .table("access_list", (access_list) => { + access_list.integer("pass_auth").notNull().defaultTo(1); + }) .then(() => { - logger.info('[' + migrate_name + '] access_list Table altered'); + logger.info(`[${migrateName}] access_list Table altered`); }); }; @@ -26,16 +26,18 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.table('access_list', function (access_list) { - access_list.dropColumn('pass_auth'); - }) + return knex.schema + .table("access_list", (access_list) => { + access_list.dropColumn("pass_auth"); + }) .then(() => { - logger.info('[' + migrate_name + '] access_list pass_auth Column dropped'); + logger.info(`[${migrateName}] access_list pass_auth Column dropped`); }); }; + +export { up, down }; diff --git a/backend/migrations/20210210154702_redirection_scheme.js b/backend/migrations/20210210154702_redirection_scheme.js index 0dad4876..b3f18aeb 100644 --- a/backend/migrations/20210210154702_redirection_scheme.js +++ b/backend/migrations/20210210154702_redirection_scheme.js @@ -1,5 +1,6 @@ -const migrate_name = 'redirection_scheme'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "redirection_scheme"; /** * Migrate @@ -7,18 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema.table('redirection_host', (table) => { - table.string('forward_scheme').notNull().defaultTo('$scheme'); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + return knex.schema + .table("redirection_host", (table) => { + table.string("forward_scheme").notNull().defaultTo("$scheme"); + }) + .then(() => { + logger.info(`[${migrateName}] redirection_host Table altered`); }); }; @@ -26,16 +26,18 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.table('redirection_host', (table) => { - table.dropColumn('forward_scheme'); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + return knex.schema + .table("redirection_host", (table) => { + table.dropColumn("forward_scheme"); + }) + .then(() => { + logger.info(`[${migrateName}] redirection_host Table altered`); }); }; + +export { up, down }; diff --git a/backend/migrations/20210210154703_redirection_status_code.js b/backend/migrations/20210210154703_redirection_status_code.js index b9bea0b9..cf84298d 100644 --- a/backend/migrations/20210210154703_redirection_status_code.js +++ b/backend/migrations/20210210154703_redirection_status_code.js @@ -1,5 +1,6 @@ -const migrate_name = 'redirection_status_code'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "redirection_status_code"; /** * Migrate @@ -7,18 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema.table('redirection_host', (table) => { - table.integer('forward_http_code').notNull().unsigned().defaultTo(302); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + return knex.schema + .table("redirection_host", (table) => { + table.integer("forward_http_code").notNull().unsigned().defaultTo(302); + }) + .then(() => { + logger.info(`[${migrateName}] redirection_host Table altered`); }); }; @@ -26,16 +26,18 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.table('redirection_host', (table) => { - table.dropColumn('forward_http_code'); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + return knex.schema + .table("redirection_host", (table) => { + table.dropColumn("forward_http_code"); + }) + .then(() => { + logger.info(`[${migrateName}] redirection_host Table altered`); }); }; + +export { up, down }; diff --git a/backend/migrations/20210423103500_stream_domain.js b/backend/migrations/20210423103500_stream_domain.js index a894ca5e..b4afabd4 100644 --- a/backend/migrations/20210423103500_stream_domain.js +++ b/backend/migrations/20210423103500_stream_domain.js @@ -1,40 +1,43 @@ -const migrate_name = 'stream_domain'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "stream_domain"; /** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @returns {Promise} + */ +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('stream', (table) => { - table.renameColumn('forward_ip', 'forwarding_host'); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); + return knex.schema + .table("stream", (table) => { + table.renameColumn("forward_ip", "forwarding_host"); + }) + .then(() => { + logger.info(`[${migrateName}] stream Table altered`); }); }; /** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Down...'); + * Undo Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.table('stream', (table) => { - table.renameColumn('forwarding_host', 'forward_ip'); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); + return knex.schema + .table("stream", (table) => { + table.renameColumn("forwarding_host", "forward_ip"); + }) + .then(() => { + logger.info(`[${migrateName}] stream Table altered`); }); }; + +export { up, down }; diff --git a/backend/migrations/20211108145214_regenerate_default_host.js b/backend/migrations/20211108145214_regenerate_default_host.js index 4c50941f..c2805152 100644 --- a/backend/migrations/20211108145214_regenerate_default_host.js +++ b/backend/migrations/20211108145214_regenerate_default_host.js @@ -1,17 +1,19 @@ -const migrate_name = 'stream_domain'; -const logger = require('../logger').migrate; -const internalNginx = require('../internal/nginx'); +import internalNginx from "../internal/nginx.js"; +import { migrate as logger } from "../logger.js"; + +const migrateName = "stream_domain"; async function regenerateDefaultHost(knex) { - const row = await knex('setting').select('*').where('id', 'default-site').first(); + const row = await knex("setting").select("*").where("id", "default-site").first(); if (!row) { return Promise.resolve(); } - return internalNginx.deleteConfig('default') + return internalNginx + .deleteConfig("default") .then(() => { - return internalNginx.generateConfig('default', row); + return internalNginx.generateConfig("default", row); }) .then(() => { return internalNginx.test(); @@ -22,29 +24,29 @@ async function regenerateDefaultHost(knex) { } /** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex) { - logger.info('[' + migrate_name + '] Migrating Up...'); + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @returns {Promise} + */ +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); return regenerateDefaultHost(knex); }; /** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex) { - logger.info('[' + migrate_name + '] Migrating Down...'); + * Undo Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); return regenerateDefaultHost(knex); -}; \ No newline at end of file +}; + +export { up, down }; diff --git a/backend/migrations/20240427161436_stream_ssl.js b/backend/migrations/20240427161436_stream_ssl.js index 5f47b18e..0fbba110 100644 --- a/backend/migrations/20240427161436_stream_ssl.js +++ b/backend/migrations/20240427161436_stream_ssl.js @@ -1,5 +1,6 @@ -const migrate_name = 'stream_ssl'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "stream_ssl"; /** * Migrate @@ -9,14 +10,15 @@ const logger = require('../logger').migrate; * @param {Object} knex * @returns {Promise} */ -exports.up = function (knex) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('stream', (table) => { - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); + return knex.schema + .table("stream", (table) => { + table.integer("certificate_id").notNull().unsigned().defaultTo(0); + }) + .then(() => { + logger.info(`[${migrateName}] stream Table altered`); }); }; @@ -26,13 +28,16 @@ exports.up = function (knex) { * @param {Object} knex * @returns {Promise} */ -exports.down = function (knex) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.table('stream', (table) => { - table.dropColumn('certificate_id'); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); + return knex.schema + .table("stream", (table) => { + table.dropColumn("certificate_id"); + }) + .then(() => { + logger.info(`[${migrateName}] stream Table altered`); }); }; + +export { up, down }; diff --git a/backend/models/access_list.js b/backend/models/access_list.js index 959df05f..98016a17 100644 --- a/backend/models/access_list.js +++ b/backend/models/access_list.js @@ -1,103 +1,98 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const User = require('./user'); -const AccessListAuth = require('./access_list_auth'); -const AccessListClient = require('./access_list_client'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import AccessListAuth from "./access_list_auth.js"; +import AccessListClient from "./access_list_client.js"; +import now from "./now_helper.js"; +import ProxyHostModel from "./proxy_host.js"; +import User from "./user.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', - 'satisfy_any', - 'pass_auth', -]; +const boolFields = ["is_deleted", "satisfy_any", "pass_auth"]; class AccessList extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'AccessList'; + static get name() { + return "AccessList"; } - static get tableName () { - return 'access_list'; + static get tableName() { + return "access_list"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { - const ProxyHost = require('./proxy_host'); - + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'access_list.owner_user_id', - to: 'user.id' + join: { + from: "access_list.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, items: { - relation: Model.HasManyRelation, + relation: Model.HasManyRelation, modelClass: AccessListAuth, - join: { - from: 'access_list.id', - to: 'access_list_auth.access_list_id' - } + join: { + from: "access_list.id", + to: "access_list_auth.access_list_id", + }, }, clients: { - relation: Model.HasManyRelation, + relation: Model.HasManyRelation, modelClass: AccessListClient, - join: { - from: 'access_list.id', - to: 'access_list_client.access_list_id' - } + join: { + from: "access_list.id", + to: "access_list_client.access_list_id", + }, }, proxy_hosts: { - relation: Model.HasManyRelation, - modelClass: ProxyHost, - join: { - from: 'access_list.id', - to: 'proxy_host.access_list_id' + relation: Model.HasManyRelation, + modelClass: ProxyHostModel, + join: { + from: "access_list.id", + to: "proxy_host.access_list_id", }, - modify: function (qb) { - qb.where('proxy_host.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("proxy_host.is_deleted", 0); + }, + }, }; } } -module.exports = AccessList; +export default AccessList; diff --git a/backend/models/access_list_auth.js b/backend/models/access_list_auth.js index 3895539c..a4fd85a5 100644 --- a/backend/models/access_list_auth.js +++ b/backend/models/access_list_auth.js @@ -1,54 +1,55 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import accessListModel from "./access_list.js"; +import now from "./now_helper.js"; Model.knex(db); class AccessListAuth extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } - static get name () { - return 'AccessListAuth'; + static get name() { + return "AccessListAuth"; } - static get tableName () { - return 'access_list_auth'; + static get tableName() { + return "access_list_auth"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { + static get relationMappings() { return { access_list: { - relation: Model.HasOneRelation, - modelClass: require('./access_list'), - join: { - from: 'access_list_auth.access_list_id', - to: 'access_list.id' + relation: Model.HasOneRelation, + modelClass: accessListModel, + join: { + from: "access_list_auth.access_list_id", + to: "access_list.id", }, - modify: function (qb) { - qb.where('access_list.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("access_list.is_deleted", 0); + }, + }, }; } } -module.exports = AccessListAuth; +export default AccessListAuth; diff --git a/backend/models/access_list_client.js b/backend/models/access_list_client.js index bffc0023..4b63aec9 100644 --- a/backend/models/access_list_client.js +++ b/backend/models/access_list_client.js @@ -1,54 +1,55 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import accessListModel from "./access_list.js"; +import now from "./now_helper.js"; Model.knex(db); class AccessListClient extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } - static get name () { - return 'AccessListClient'; + static get name() { + return "AccessListClient"; } - static get tableName () { - return 'access_list_client'; + static get tableName() { + return "access_list_client"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { + static get relationMappings() { return { access_list: { - relation: Model.HasOneRelation, - modelClass: require('./access_list'), - join: { - from: 'access_list_client.access_list_id', - to: 'access_list.id' + relation: Model.HasOneRelation, + modelClass: accessListModel, + join: { + from: "access_list_client.access_list_id", + to: "access_list.id", }, - modify: function (qb) { - qb.where('access_list.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("access_list.is_deleted", 0); + }, + }, }; } } -module.exports = AccessListClient; +export default AccessListClient; diff --git a/backend/models/audit-log.js b/backend/models/audit-log.js index 45a4b460..a9b2d563 100644 --- a/backend/models/audit-log.js +++ b/backend/models/audit-log.js @@ -1,52 +1,52 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); class AuditLog extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } - static get name () { - return 'AuditLog'; + static get name() { + return "AuditLog"; } - static get tableName () { - return 'audit_log'; + static get tableName() { + return "audit_log"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { + static get relationMappings() { return { user: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'audit_log.user_id', - to: 'user.id' - } - } + join: { + from: "audit_log.user_id", + to: "user.id", + }, + }, }; } } -module.exports = AuditLog; +export default AuditLog; diff --git a/backend/models/auth.js b/backend/models/auth.js index 469e96bf..4ba50b41 100644 --- a/backend/models/auth.js +++ b/backend/models/auth.js @@ -1,59 +1,53 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const bcrypt = require('bcrypt'); -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const User = require('./user'); -const now = require('./now_helper'); +import bcrypt from "bcrypt"; +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', -]; +const boolFields = ["is_deleted"]; -function encryptPassword () { - /* jshint -W040 */ - let _this = this; - - if (_this.type === 'password' && _this.secret) { - return bcrypt.hash(_this.secret, 13) - .then(function (hash) { - _this.secret = hash; - }); +function encryptPassword() { + if (this.type === "password" && this.secret) { + return bcrypt.hash(this.secret, 13).then((hash) => { + this.secret = hash; + }); } return null; } class Auth extends Model { - $beforeInsert (queryContext) { - this.created_on = now(); + $beforeInsert(queryContext) { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } return encryptPassword.apply(this, queryContext); } - $beforeUpdate (queryContext) { + $beforeUpdate(queryContext) { this.modified_on = now(); return encryptPassword.apply(this, queryContext); } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } /** @@ -62,37 +56,37 @@ class Auth extends Model { * @param {String} password * @returns {Promise} */ - verifyPassword (password) { + verifyPassword(password) { return bcrypt.compare(password, this.secret); } - static get name () { - return 'Auth'; + static get name() { + return "Auth"; } - static get tableName () { - return 'auth'; + static get tableName() { + return "auth"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { + static get relationMappings() { return { user: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'auth.user_id', - to: 'user.id' + join: { + from: "auth.user_id", + to: "user.id", }, filter: { - is_deleted: 0 - } - } + is_deleted: 0, + }, + }, }; } } -module.exports = Auth; +export default Auth; diff --git a/backend/models/certificate.js b/backend/models/certificate.js index d4ea21ad..9ad03c89 100644 --- a/backend/models/certificate.js +++ b/backend/models/certificate.js @@ -1,124 +1,133 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import deadHostModel from "./dead_host.js"; +import now from "./now_helper.js"; +import proxyHostModel from "./proxy_host.js"; +import redirectionHostModel from "./redirection_host.js"; +import streamModel from "./stream.js"; +import userModel from "./user.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', -]; +const boolFields = ["is_deleted"]; class Certificate extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for expires_on - if (typeof this.expires_on === 'undefined') { + if (typeof this.expires_on === "undefined") { this.expires_on = now(); } // Default for domain_names - if (typeof this.domain_names === 'undefined') { + if (typeof this.domain_names === "undefined") { this.domain_names = []; } // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } this.domain_names.sort(); } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); // Sort domain_names - if (typeof this.domain_names !== 'undefined') { + if (typeof this.domain_names !== "undefined") { this.domain_names.sort(); } } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'Certificate'; + static get name() { + return "Certificate"; } - static get tableName () { - return 'certificate'; + static get tableName() { + return "certificate"; } - static get jsonAttributes () { - return ['domain_names', 'meta']; + static get jsonAttributes() { + return ["domain_names", "meta"]; } - static get relationMappings () { - const ProxyHost = require('./proxy_host'); - const DeadHost = require('./dead_host'); - const User = require('./user'); - const RedirectionHost = require('./redirection_host'); - + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, - modelClass: User, - join: { - from: 'certificate.owner_user_id', - to: 'user.id' + relation: Model.HasOneRelation, + modelClass: userModel, + join: { + from: "certificate.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, proxy_hosts: { - relation: Model.HasManyRelation, - modelClass: ProxyHost, - join: { - from: 'certificate.id', - to: 'proxy_host.certificate_id' + relation: Model.HasManyRelation, + modelClass: proxyHostModel, + join: { + from: "certificate.id", + to: "proxy_host.certificate_id", + }, + modify: (qb) => { + qb.where("proxy_host.is_deleted", 0); }, - modify: function (qb) { - qb.where('proxy_host.is_deleted', 0); - } }, dead_hosts: { - relation: Model.HasManyRelation, - modelClass: DeadHost, - join: { - from: 'certificate.id', - to: 'dead_host.certificate_id' + relation: Model.HasManyRelation, + modelClass: deadHostModel, + join: { + from: "certificate.id", + to: "dead_host.certificate_id", + }, + modify: (qb) => { + qb.where("dead_host.is_deleted", 0); }, - modify: function (qb) { - qb.where('dead_host.is_deleted', 0); - } }, redirection_hosts: { - relation: Model.HasManyRelation, - modelClass: RedirectionHost, - join: { - from: 'certificate.id', - to: 'redirection_host.certificate_id' + relation: Model.HasManyRelation, + modelClass: redirectionHostModel, + join: { + from: "certificate.id", + to: "redirection_host.certificate_id", }, - modify: function (qb) { - qb.where('redirection_host.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("redirection_host.is_deleted", 0); + }, + }, + streams: { + relation: Model.HasManyRelation, + modelClass: streamModel, + join: { + from: "certificate.id", + to: "stream.certificate_id", + }, + modify: (qb) => { + qb.where("stream.is_deleted", 0); + }, + }, }; } } -module.exports = Certificate; +export default Certificate; diff --git a/backend/models/dead_host.js b/backend/models/dead_host.js index 3386caab..56807012 100644 --- a/backend/models/dead_host.js +++ b/backend/models/dead_host.js @@ -1,99 +1,92 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const User = require('./user'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import Certificate from "./certificate.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', - 'ssl_forced', - 'http2_support', - 'enabled', - 'hsts_enabled', - 'hsts_subdomains', -]; +const boolFields = ["is_deleted", "ssl_forced", "http2_support", "enabled", "hsts_enabled", "hsts_subdomains"]; class DeadHost extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for domain_names - if (typeof this.domain_names === 'undefined') { + if (typeof this.domain_names === "undefined") { this.domain_names = []; } // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } this.domain_names.sort(); } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); // Sort domain_names - if (typeof this.domain_names !== 'undefined') { + if (typeof this.domain_names !== "undefined") { this.domain_names.sort(); } } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'DeadHost'; + static get name() { + return "DeadHost"; } - static get tableName () { - return 'dead_host'; + static get tableName() { + return "dead_host"; } - static get jsonAttributes () { - return ['domain_names', 'meta']; + static get jsonAttributes() { + return ["domain_names", "meta"]; } - static get relationMappings () { + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'dead_host.owner_user_id', - to: 'user.id' + join: { + from: "dead_host.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, certificate: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: Certificate, - join: { - from: 'dead_host.certificate_id', - to: 'certificate.id' + join: { + from: "dead_host.certificate_id", + to: "certificate.id", }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("certificate.is_deleted", 0); + }, + }, }; } } -module.exports = DeadHost; +export default DeadHost; diff --git a/backend/models/now_helper.js b/backend/models/now_helper.js index dec70c3d..4dc71cea 100644 --- a/backend/models/now_helper.js +++ b/backend/models/now_helper.js @@ -1,13 +1,12 @@ -const db = require('../db'); -const config = require('../lib/config'); -const Model = require('objection').Model; +import { Model } from "objection"; +import db from "../db.js"; +import { isSqlite } from "../lib/config.js"; Model.knex(db); -module.exports = function () { - if (config.isSqlite()) { - // eslint-disable-next-line +export default () => { + if (isSqlite()) { return Model.raw("datetime('now','localtime')"); } - return Model.raw('NOW()'); + return Model.raw("NOW()"); }; diff --git a/backend/models/proxy_host.js b/backend/models/proxy_host.js index 07aa5dd3..119fe2b7 100644 --- a/backend/models/proxy_host.js +++ b/backend/models/proxy_host.js @@ -1,114 +1,114 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const User = require('./user'); -const AccessList = require('./access_list'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import AccessList from "./access_list.js"; +import Certificate from "./certificate.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); const boolFields = [ - 'is_deleted', - 'ssl_forced', - 'caching_enabled', - 'block_exploits', - 'allow_websocket_upgrade', - 'http2_support', - 'enabled', - 'hsts_enabled', - 'hsts_subdomains', + "is_deleted", + "ssl_forced", + "caching_enabled", + "block_exploits", + "allow_websocket_upgrade", + "http2_support", + "enabled", + "hsts_enabled", + "hsts_subdomains", ]; class ProxyHost extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for domain_names - if (typeof this.domain_names === 'undefined') { + if (typeof this.domain_names === "undefined") { this.domain_names = []; } // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } this.domain_names.sort(); } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); // Sort domain_names - if (typeof this.domain_names !== 'undefined') { + if (typeof this.domain_names !== "undefined") { this.domain_names.sort(); } } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'ProxyHost'; + static get name() { + return "ProxyHost"; } - static get tableName () { - return 'proxy_host'; + static get tableName() { + return "proxy_host"; } - static get jsonAttributes () { - return ['domain_names', 'meta', 'locations']; + static get jsonAttributes() { + return ["domain_names", "meta", "locations"]; } - static get relationMappings () { + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'proxy_host.owner_user_id', - to: 'user.id' + join: { + from: "proxy_host.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, access_list: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: AccessList, - join: { - from: 'proxy_host.access_list_id', - to: 'access_list.id' + join: { + from: "proxy_host.access_list_id", + to: "access_list.id", + }, + modify: (qb) => { + qb.where("access_list.is_deleted", 0); }, - modify: function (qb) { - qb.where('access_list.is_deleted', 0); - } }, certificate: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: Certificate, - join: { - from: 'proxy_host.certificate_id', - to: 'certificate.id' + join: { + from: "proxy_host.certificate_id", + to: "certificate.id", }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("certificate.is_deleted", 0); + }, + }, }; } } -module.exports = ProxyHost; +export default ProxyHost; diff --git a/backend/models/redirection_host.js b/backend/models/redirection_host.js index 80162791..bb397baa 100644 --- a/backend/models/redirection_host.js +++ b/backend/models/redirection_host.js @@ -1,102 +1,101 @@ - // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const User = require('./user'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import Certificate from "./certificate.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); const boolFields = [ - 'is_deleted', - 'enabled', - 'preserve_path', - 'ssl_forced', - 'block_exploits', - 'hsts_enabled', - 'hsts_subdomains', - 'http2_support', + "is_deleted", + "enabled", + "preserve_path", + "ssl_forced", + "block_exploits", + "hsts_enabled", + "hsts_subdomains", + "http2_support", ]; class RedirectionHost extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for domain_names - if (typeof this.domain_names === 'undefined') { + if (typeof this.domain_names === "undefined") { this.domain_names = []; } // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } this.domain_names.sort(); } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); // Sort domain_names - if (typeof this.domain_names !== 'undefined') { + if (typeof this.domain_names !== "undefined") { this.domain_names.sort(); } } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'RedirectionHost'; + static get name() { + return "RedirectionHost"; } - static get tableName () { - return 'redirection_host'; + static get tableName() { + return "redirection_host"; } - static get jsonAttributes () { - return ['domain_names', 'meta']; + static get jsonAttributes() { + return ["domain_names", "meta"]; } - static get relationMappings () { + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'redirection_host.owner_user_id', - to: 'user.id' + join: { + from: "redirection_host.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, certificate: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: Certificate, - join: { - from: 'redirection_host.certificate_id', - to: 'certificate.id' + join: { + from: "redirection_host.certificate_id", + to: "certificate.id", }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("certificate.is_deleted", 0); + }, + }, }; } } -module.exports = RedirectionHost; +export default RedirectionHost; diff --git a/backend/models/setting.js b/backend/models/setting.js index 75aa9007..0e0d6f4f 100644 --- a/backend/models/setting.js +++ b/backend/models/setting.js @@ -1,8 +1,8 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; +import { Model } from "objection"; +import db from "../db.js"; Model.knex(db); @@ -27,4 +27,4 @@ class Setting extends Model { } } -module.exports = Setting; +export default Setting; diff --git a/backend/models/stream.js b/backend/models/stream.js index 5d1cb6c1..92d335ff 100644 --- a/backend/models/stream.js +++ b/backend/models/stream.js @@ -1,82 +1,77 @@ -const Model = require('objection').Model; -const db = require('../db'); -const helpers = require('../lib/helpers'); -const User = require('./user'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import Certificate from "./certificate.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', - 'enabled', - 'tcp_forwarding', - 'udp_forwarding', -]; +const boolFields = ["is_deleted", "enabled", "tcp_forwarding", "udp_forwarding"]; class Stream extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'Stream'; + static get name() { + return "Stream"; } - static get tableName () { - return 'stream'; + static get tableName() { + return "stream"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'stream.owner_user_id', - to: 'user.id' + join: { + from: "stream.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, certificate: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: Certificate, - join: { - from: 'stream.certificate_id', - to: 'certificate.id' + join: { + from: "stream.certificate_id", + to: "certificate.id", }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("certificate.is_deleted", 0); + }, + }, }; } } -module.exports = Stream; +export default Stream; diff --git a/backend/models/token.js b/backend/models/token.js index 7cf11e03..4edab5bb 100644 --- a/backend/models/token.js +++ b/backend/models/token.js @@ -3,17 +3,17 @@ and then has abilities after that. */ -const _ = require('lodash'); -const jwt = require('jsonwebtoken'); -const crypto = require('crypto'); -const config = require('../lib/config'); -const error = require('../lib/error'); -const logger = require('../logger').global; -const ALGO = 'RS256'; +import crypto from "node:crypto"; +import jwt from "jsonwebtoken"; +import _ from "lodash"; +import { getPrivateKey, getPublicKey } from "../lib/config.js"; +import errs from "../lib/error.js"; +import { global as logger } from "../logger.js"; -module.exports = function () { +const ALGO = "RS256"; - let token_data = {}; +export default () => { + let tokenData = {}; const self = { /** @@ -21,28 +21,26 @@ module.exports = function () { * @returns {Promise} */ create: (payload) => { - if (!config.getPrivateKey()) { - logger.error('Private key is empty!'); + if (!getPrivateKey()) { + logger.error("Private key is empty!"); } // sign with RSA SHA256 const options = { algorithm: ALGO, - expiresIn: payload.expiresIn || '1d' + expiresIn: payload.expiresIn || "1d", }; - payload.jti = crypto.randomBytes(12) - .toString('base64') - .substring(-8); + payload.jti = crypto.randomBytes(12).toString("base64").substring(-8); return new Promise((resolve, reject) => { - jwt.sign(payload, config.getPrivateKey(), options, (err, token) => { + jwt.sign(payload, getPrivateKey(), options, (err, token) => { if (err) { reject(err); } else { - token_data = payload; + tokenData = payload; resolve({ - token: token, - payload: payload + token: token, + payload: payload, }); } }); @@ -53,42 +51,47 @@ module.exports = function () { * @param {String} token * @returns {Promise} */ - load: function (token) { - if (!config.getPublicKey()) { - logger.error('Public key is empty!'); + load: (token) => { + if (!getPublicKey()) { + logger.error("Public key is empty!"); } return new Promise((resolve, reject) => { try { - if (!token || token === null || token === 'null') { - reject(new error.AuthError('Empty token')); + if (!token || token === null || token === "null") { + reject(new errs.AuthError("Empty token")); } else { - jwt.verify(token, config.getPublicKey(), {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => { - if (err) { - - if (err.name === 'TokenExpiredError') { - reject(new error.AuthError('Token has expired', err)); + jwt.verify( + token, + getPublicKey(), + { ignoreExpiration: false, algorithms: [ALGO] }, + (err, result) => { + if (err) { + if (err.name === "TokenExpiredError") { + reject(new errs.AuthError("Token has expired", err)); + } else { + reject(err); + } } else { - reject(err); + tokenData = result; + + // Hack: some tokens out in the wild have a scope of 'all' instead of 'user'. + // For 30 days at least, we need to replace 'all' with user. + if ( + typeof tokenData.scope !== "undefined" && + _.indexOf(tokenData.scope, "all") !== -1 + ) { + tokenData.scope = ["user"]; + } + + resolve(tokenData); } - - } else { - token_data = result; - - // Hack: some tokens out in the wild have a scope of 'all' instead of 'user'. - // For 30 days at least, we need to replace 'all' with user. - if ((typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'all') !== -1)) { - token_data.scope = ['user']; - } - - resolve(token_data); - } - }); + }, + ); } } catch (err) { reject(err); } }); - }, /** @@ -97,17 +100,15 @@ module.exports = function () { * @param {String} scope * @returns {Boolean} */ - hasScope: function (scope) { - return typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, scope) !== -1; - }, + hasScope: (scope) => typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, scope) !== -1, /** * @param {String} key * @return {*} */ - get: function (key) { - if (typeof token_data[key] !== 'undefined') { - return token_data[key]; + get: (key) => { + if (typeof tokenData[key] !== "undefined") { + return tokenData[key]; } return null; @@ -117,22 +118,22 @@ module.exports = function () { * @param {String} key * @param {*} value */ - set: function (key, value) { - token_data[key] = value; + set: (key, value) => { + tokenData[key] = value; }, /** - * @param [default_value] + * @param [defaultValue] * @returns {Integer} */ - getUserId: (default_value) => { - const attrs = self.get('attrs'); - if (attrs && typeof attrs.id !== 'undefined' && attrs.id) { + getUserId: (defaultValue) => { + const attrs = self.get("attrs"); + if (attrs?.id) { return attrs.id; } - return default_value || 0; - } + return defaultValue || 0; + }, }; return self; diff --git a/backend/models/user.js b/backend/models/user.js index 78fd3dd6..64aed05d 100644 --- a/backend/models/user.js +++ b/backend/models/user.js @@ -1,69 +1,65 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const UserPermission = require('./user_permission'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import now from "./now_helper.js"; +import UserPermission from "./user_permission.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', - 'is_disabled', -]; +const boolFields = ["is_deleted", "is_disabled"]; class User extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for roles - if (typeof this.roles === 'undefined') { + if (typeof this.roles === "undefined") { this.roles = []; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'User'; + static get name() { + return "User"; } - static get tableName () { - return 'user'; + static get tableName() { + return "user"; } - static get jsonAttributes () { - return ['roles']; + static get jsonAttributes() { + return ["roles"]; } - static get relationMappings () { + static get relationMappings() { return { permissions: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: UserPermission, - join: { - from: 'user.id', - to: 'user_permission.user_id' - } - } + join: { + from: "user.id", + to: "user_permission.user_id", + }, + }, }; } - } -module.exports = User; +export default User; diff --git a/backend/models/user_permission.js b/backend/models/user_permission.js index bb87d5dc..49ea2d90 100644 --- a/backend/models/user_permission.js +++ b/backend/models/user_permission.js @@ -1,9 +1,9 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import now from "./now_helper.js"; Model.knex(db); @@ -26,4 +26,4 @@ class UserPermission extends Model { } } -module.exports = UserPermission; +export default UserPermission; diff --git a/backend/nodemon.json b/backend/nodemon.json index 3d6d1342..90223a21 100644 --- a/backend/nodemon.json +++ b/backend/nodemon.json @@ -3,5 +3,5 @@ "ignore": [ "data" ], - "ext": "js json ejs" + "ext": "js json ejs cjs" } diff --git a/backend/package.json b/backend/package.json index 30984a33..c316fd83 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,8 +1,16 @@ { "name": "nginx-proxy-manager", - "version": "0.0.0", + "version": "2.0.0", "description": "A beautiful interface for creating Nginx endpoints", + "author": "Jamie Curnow ", + "license": "MIT", "main": "index.js", + "type": "module", + "scripts": { + "lint": "biome lint", + "prettier": "biome format --write .", + "validate-schema": "node validate-schema.js" + }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.7.0", "ajv": "^8.17.1", @@ -12,37 +20,30 @@ "body-parser": "^1.20.3", "compression": "^1.7.4", "express": "^4.20.0", - "express-fileupload": "^1.1.9", - "gravatar": "^1.8.0", - "jsonwebtoken": "^9.0.0", + "express-fileupload": "^1.5.2", + "gravatar": "^1.8.2", + "jsonwebtoken": "^9.0.2", "knex": "2.4.2", "liquidjs": "10.6.1", "lodash": "^4.17.21", - "moment": "^2.29.4", - "mysql2": "^3.11.1", - "node-rsa": "^1.0.8", + "moment": "^2.30.1", + "mysql2": "^3.15.3", + "node-rsa": "^1.1.1", "objection": "3.0.1", "path": "^0.12.7", - "pg": "^8.13.1", + "pg": "^8.16.3", "signale": "1.4.0", - "sqlite3": "5.1.6", + "sqlite3": "^5.1.7", "temp-write": "^4.0.0" }, + "devDependencies": { + "@apidevtools/swagger-parser": "^10.1.0", + "@biomejs/biome": "^2.3.2", + "chalk": "4.1.2", + "nodemon": "^2.0.2" + }, "signale": { "displayDate": true, "displayTimestamp": true - }, - "author": "Jamie Curnow ", - "license": "MIT", - "devDependencies": { - "@apidevtools/swagger-parser": "^10.1.0", - "chalk": "4.1.2", - "eslint": "^8.36.0", - "eslint-plugin-align-assignments": "^1.1.2", - "nodemon": "^2.0.2", - "prettier": "^2.0.4" - }, - "scripts": { - "validate-schema": "node validate-schema.js" } } diff --git a/backend/routes/audit-log.js b/backend/routes/audit-log.js index c68c7b35..c40b1628 100644 --- a/backend/routes/audit-log.js +++ b/backend/routes/audit-log.js @@ -1,19 +1,20 @@ -const express = require('express'); -const validator = require('../lib/validator'); -const jwtdecode = require('../lib/express/jwt-decode'); -const internalAuditLog = require('../internal/audit-log'); +import express from "express"; +import internalAuditLog from "../internal/audit-log.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; +import validator from "../lib/validator/index.js"; +import { debug, express as logger } from "../logger.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/audit-log */ router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -24,29 +25,83 @@ router * * Retrieve all logs */ - .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + .get(async (req, res, next) => { + try { + const data = await validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) - .then((data) => { - return internalAuditLog.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200) - .send(rows); - }) - .catch(next); + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ); + const rows = await internalAuditLog.getAll(res.locals.access, data.expand, data.query); + res.status(200).send(rows); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); -module.exports = router; +/** + * Specific audit log entry + * + * /api/audit-log/123 + */ +router + .route("/:event_id") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * GET /api/audit-log/123 + * + * Retrieve a specific entry + */ + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["event_id"], + additionalProperties: false, + properties: { + event_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, + }, + }, + { + event_id: req.params.event_id, + expand: + typeof req.query.expand === "string" + ? req.query.expand.split(",") + : null, + }, + ); + + const item = await internalAuditLog.get(res.locals.access, { + id: data.event_id, + expand: data.expand, + }); + res.status(200).send(item); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +export default router; diff --git a/backend/routes/main.js b/backend/routes/main.js index b97096d0..7bc4323d 100644 --- a/backend/routes/main.js +++ b/backend/routes/main.js @@ -1,51 +1,66 @@ -const express = require('express'); -const pjson = require('../package.json'); -const error = require('../lib/error'); +import express from "express"; +import errs from "../lib/error.js"; +import pjson from "../package.json" with { type: "json" }; +import { isSetup } from "../setup.js"; +import auditLogRoutes from "./audit-log.js"; +import accessListsRoutes from "./nginx/access_lists.js"; +import certificatesHostsRoutes from "./nginx/certificates.js"; +import deadHostsRoutes from "./nginx/dead_hosts.js"; +import proxyHostsRoutes from "./nginx/proxy_hosts.js"; +import redirectionHostsRoutes from "./nginx/redirection_hosts.js"; +import streamsRoutes from "./nginx/streams.js"; +import reportsRoutes from "./reports.js"; +import schemaRoutes from "./schema.js"; +import settingsRoutes from "./settings.js"; +import tokensRoutes from "./tokens.js"; +import usersRoutes from "./users.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * Health Check * GET /api */ -router.get('/', (req, res/*, next*/) => { - let version = pjson.version.split('-').shift().split('.'); +router.get("/", async (_, res /*, next*/) => { + const version = pjson.version.split("-").shift().split("."); + const setup = await isSetup(); res.status(200).send({ - status: 'OK', + status: "OK", + setup, version: { - major: parseInt(version.shift(), 10), - minor: parseInt(version.shift(), 10), - revision: parseInt(version.shift(), 10) - } + major: Number.parseInt(version.shift(), 10), + minor: Number.parseInt(version.shift(), 10), + revision: Number.parseInt(version.shift(), 10), + }, }); }); -router.use('/schema', require('./schema')); -router.use('/tokens', require('./tokens')); -router.use('/users', require('./users')); -router.use('/audit-log', require('./audit-log')); -router.use('/reports', require('./reports')); -router.use('/settings', require('./settings')); -router.use('/nginx/proxy-hosts', require('./nginx/proxy_hosts')); -router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts')); -router.use('/nginx/dead-hosts', require('./nginx/dead_hosts')); -router.use('/nginx/streams', require('./nginx/streams')); -router.use('/nginx/access-lists', require('./nginx/access_lists')); -router.use('/nginx/certificates', require('./nginx/certificates')); +router.use("/schema", schemaRoutes); +router.use("/tokens", tokensRoutes); +router.use("/users", usersRoutes); +router.use("/audit-log", auditLogRoutes); +router.use("/reports", reportsRoutes); +router.use("/settings", settingsRoutes); +router.use("/nginx/proxy-hosts", proxyHostsRoutes); +router.use("/nginx/redirection-hosts", redirectionHostsRoutes); +router.use("/nginx/dead-hosts", deadHostsRoutes); +router.use("/nginx/streams", streamsRoutes); +router.use("/nginx/access-lists", accessListsRoutes); +router.use("/nginx/certificates", certificatesHostsRoutes); /** * API 404 for all other routes * * ALL /api/* */ -router.all(/(.+)/, function (req, _, next) { - req.params.page = req.params['0']; - next(new error.ItemNotFoundError(req.params.page)); +router.all(/(.+)/, (req, _, next) => { + req.params.page = req.params["0"]; + next(new errs.ItemNotFoundError(req.params.page)); }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/access_lists.js b/backend/routes/nginx/access_lists.js index 38375127..9dfcf7ec 100644 --- a/backend/routes/nginx/access_lists.js +++ b/backend/routes/nginx/access_lists.js @@ -1,22 +1,23 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalAccessList = require('../../internal/access-list'); -const schema = require('../../schema'); +import express from "express"; +import internalAccessList from "../../internal/access-list.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { debug, express as logger } from "../../logger.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/access-lists */ router - .route('/') - .options((req, res) => { + .route("/") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -26,29 +27,31 @@ router * * Retrieve all access-lists */ - .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + .get(async (req, res, next) => { + try { + const data = await validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) - .then((data) => { - return internalAccessList.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200) - .send(rows); - }) - .catch(next); + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ); + const rows = await internalAccessList.getAll(res.locals.access, data.expand, data.query); + res.status(200).send(rows); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -56,16 +59,15 @@ router * * Create a new access-list */ - .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/access-lists', 'post'), req.body) - .then((payload) => { - return internalAccessList.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/access-lists", "post"), req.body); + const result = await internalAccessList.create(res.locals.access, payload); + res.status(201).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -74,7 +76,7 @@ router * /api/nginx/access-lists/123 */ router - .route('/:list_id') + .route("/:list_id") .options((_, res) => { res.sendStatus(204); }) @@ -85,33 +87,35 @@ router * * Retrieve a specific access-list */ - .get((req, res, next) => { - validator({ - required: ['list_id'], - additionalProperties: false, - properties: { - list_id: { - $ref: 'common#/properties/id' + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["list_id"], + additionalProperties: false, + properties: { + list_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - list_id: req.params.list_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) - .then((data) => { - return internalAccessList.get(res.locals.access, { - id: parseInt(data.list_id, 10), - expand: data.expand - }); - }) - .then((row) => { - res.status(200) - .send(row); - }) - .catch(next); + { + list_id: req.params.list_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ); + const row = await internalAccessList.get(res.locals.access, { + id: Number.parseInt(data.list_id, 10), + expand: data.expand, + }); + res.status(200).send(row); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -119,17 +123,16 @@ router * * Update and existing access-list */ - .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/access-lists/{listID}', 'put'), req.body) - .then((payload) => { - payload.id = parseInt(req.params.list_id, 10); - return internalAccessList.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .put(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/access-lists/{listID}", "put"), req.body); + payload.id = Number.parseInt(req.params.list_id, 10); + const result = await internalAccessList.update(res.locals.access, payload); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -137,13 +140,16 @@ router * * Delete and existing access-list */ - .delete((req, res, next) => { - internalAccessList.delete(res.locals.access, {id: parseInt(req.params.list_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .delete(async (req, res, next) => { + try { + const result = await internalAccessList.delete(res.locals.access, { + id: Number.parseInt(req.params.list_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/certificates.js b/backend/routes/nginx/certificates.js index bf47c03f..99f429b4 100644 --- a/backend/routes/nginx/certificates.js +++ b/backend/routes/nginx/certificates.js @@ -1,22 +1,24 @@ -const express = require('express'); -const error = require('../../lib/error'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalCertificate = require('../../internal/certificate'); -const schema = require('../../schema'); +import express from "express"; +import dnsPlugins from "../../certbot/dns-plugins.json" with { type: "json" }; +import internalCertificate from "../../internal/certificate.js"; +import errs from "../../lib/error.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { debug, express as logger } from "../../logger.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/certificates */ router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -27,29 +29,38 @@ router * * Retrieve all certificates */ - .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + .get(async (req, res, next) => { + try { + const data = await validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) - .then((data) => { - return internalCertificate.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200) - .send(rows); - }) - .catch(next); + { + expand: + typeof req.query.expand === "string" + ? req.query.expand.split(",") + : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ); + const rows = await internalCertificate.getAll( + res.locals.access, + data.expand, + data.query, + ); + res.status(200).send(rows); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -57,17 +68,56 @@ router * * Create a new certificate */ - .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/certificates', 'post'), req.body) - .then((payload) => { - req.setTimeout(900000); // 15 minutes timeout - return internalCertificate.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const payload = await apiValidator( + getValidationSchema("/nginx/certificates", "post"), + req.body, + ); + req.setTimeout(900000); // 15 minutes timeout + const result = await internalCertificate.create( + res.locals.access, + payload, + ); + res.status(201).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +/** + * /api/nginx/certificates/dns-providers + */ +router + .route("/dns-providers") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * GET /api/nginx/certificates/dns-providers + * + * Get list of all supported DNS providers + */ + .get(async (req, res, next) => { + try { + if (!res.locals.access.token.getUserId()) { + throw new errs.PermissionError("Login required"); + } + const clean = Object.keys(dnsPlugins).map((key) => ({ + id: key, + name: dnsPlugins[key].name, + credentials: dnsPlugins[key].credentials, + })); + + clean.sort((a, b) => a.name.localeCompare(b.name)); + res.status(200).send(clean); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -76,180 +126,34 @@ router * /api/nginx/certificates/test-http */ router - .route('/test-http') + .route("/test-http") .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) /** - * GET /api/nginx/certificates/test-http + * POST /api/nginx/certificates/test-http * * Test HTTP challenge for domains */ - .get((req, res, next) => { - if (req.query.domains === undefined) { - next(new error.ValidationError('Domains are required as query parameters')); - return; + .post(async (req, res, next) => { + try { + const payload = await apiValidator( + getValidationSchema("/nginx/certificates/test-http", "post"), + req.body, + ); + req.setTimeout(60000); // 1 minute timeout + + const result = await internalCertificate.testHttpsChallenge( + res.locals.access, + payload, + ); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); } - - internalCertificate.testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains)) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); - }); - -/** - * Specific certificate - * - * /api/nginx/certificates/123 - */ -router - .route('/:certificate_id') - .options((_, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/certificates/123 - * - * Retrieve a specific certificate - */ - .get((req, res, next) => { - validator({ - required: ['certificate_id'], - additionalProperties: false, - properties: { - certificate_id: { - $ref: 'common#/properties/id' - }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - certificate_id: req.params.certificate_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) - .then((data) => { - return internalCertificate.get(res.locals.access, { - id: parseInt(data.certificate_id, 10), - expand: data.expand - }); - }) - .then((row) => { - res.status(200) - .send(row); - }) - .catch(next); - }) - - /** - * DELETE /api/nginx/certificates/123 - * - * Update and existing certificate - */ - .delete((req, res, next) => { - internalCertificate.delete(res.locals.access, {id: parseInt(req.params.certificate_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); - }); - -/** - * Upload Certs - * - * /api/nginx/certificates/123/upload - */ -router - .route('/:certificate_id/upload') - .options((_, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/certificates/123/upload - * - * Upload certificates - */ - .post((req, res, next) => { - if (!req.files) { - res.status(400) - .send({error: 'No files were uploaded'}); - } else { - internalCertificate.upload(res.locals.access, { - id: parseInt(req.params.certificate_id, 10), - files: req.files - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); - } - }); - -/** - * Renew LE Certs - * - * /api/nginx/certificates/123/renew - */ -router - .route('/:certificate_id/renew') - .options((_, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/certificates/123/renew - * - * Renew certificate - */ - .post((req, res, next) => { - req.setTimeout(900000); // 15 minutes timeout - internalCertificate.renew(res.locals.access, { - id: parseInt(req.params.certificate_id, 10) - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); - }); - -/** - * Download LE Certs - * - * /api/nginx/certificates/123/download - */ -router - .route('/:certificate_id/download') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/certificates/123/download - * - * Renew certificate - */ - .get((req, res, next) => { - internalCertificate.download(res.locals.access, { - id: parseInt(req.params.certificate_id, 10) - }) - .then((result) => { - res.status(200) - .download(result.fileName); - }) - .catch(next); }); /** @@ -258,7 +162,7 @@ router * /api/nginx/certificates/validate */ router - .route('/validate') + .route("/validate") .options((_, res) => { res.sendStatus(204); }) @@ -269,20 +173,183 @@ router * * Validate certificates */ - .post((req, res, next) => { + .post(async (req, res, next) => { if (!req.files) { - res.status(400) - .send({error: 'No files were uploaded'}); - } else { - internalCertificate.validate({ - files: req.files - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + res.status(400).send({ error: "No files were uploaded" }); + return; + } + + try { + const result = await internalCertificate.validate({ + files: req.files, + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); } }); -module.exports = router; +/** + * Specific certificate + * + * /api/nginx/certificates/123 + */ +router + .route("/:certificate_id") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * GET /api/nginx/certificates/123 + * + * Retrieve a specific certificate + */ + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["certificate_id"], + additionalProperties: false, + properties: { + certificate_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, + }, + }, + { + certificate_id: req.params.certificate_id, + expand: + typeof req.query.expand === "string" + ? req.query.expand.split(",") + : null, + }, + ); + const row = await internalCertificate.get(res.locals.access, { + id: Number.parseInt(data.certificate_id, 10), + expand: data.expand, + }); + res.status(200).send(row); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }) + + /** + * DELETE /api/nginx/certificates/123 + * + * Update and existing certificate + */ + .delete(async (req, res, next) => { + try { + const result = await internalCertificate.delete(res.locals.access, { + id: Number.parseInt(req.params.certificate_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +/** + * Upload Certs + * + * /api/nginx/certificates/123/upload + */ +router + .route("/:certificate_id/upload") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * POST /api/nginx/certificates/123/upload + * + * Upload certificates + */ + .post(async (req, res, next) => { + if (!req.files) { + res.status(400).send({ error: "No files were uploaded" }); + return; + } + + try { + const result = await internalCertificate.upload(res.locals.access, { + id: Number.parseInt(req.params.certificate_id, 10), + files: req.files, + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +/** + * Renew LE Certs + * + * /api/nginx/certificates/123/renew + */ +router + .route("/:certificate_id/renew") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * POST /api/nginx/certificates/123/renew + * + * Renew certificate + */ + .post(async (req, res, next) => { + req.setTimeout(900000); // 15 minutes timeout + try { + const result = await internalCertificate.renew(res.locals.access, { + id: Number.parseInt(req.params.certificate_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +/** + * Download LE Certs + * + * /api/nginx/certificates/123/download + */ +router + .route("/:certificate_id/download") + .options((_req, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * GET /api/nginx/certificates/123/download + * + * Renew certificate + */ + .get(async (req, res, next) => { + try { + const result = await internalCertificate.download(res.locals.access, { + id: Number.parseInt(req.params.certificate_id, 10), + }); + res.status(200).download(result.fileName); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +export default router; diff --git a/backend/routes/nginx/dead_hosts.js b/backend/routes/nginx/dead_hosts.js index 83b37765..31f70436 100644 --- a/backend/routes/nginx/dead_hosts.js +++ b/backend/routes/nginx/dead_hosts.js @@ -1,21 +1,22 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalDeadHost = require('../../internal/dead-host'); -const schema = require('../../schema'); +import express from "express"; +import internalDeadHost from "../../internal/dead-host.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { debug, express as logger } from "../../logger.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/dead-hosts */ router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -26,29 +27,31 @@ router * * Retrieve all dead-hosts */ - .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + .get(async (req, res, next) => { + try { + const data = await validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) - .then((data) => { - return internalDeadHost.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200) - .send(rows); - }) - .catch(next); + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ); + const rows = await internalDeadHost.getAll(res.locals.access, data.expand, data.query); + res.status(200).send(rows); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -56,16 +59,15 @@ router * * Create a new dead-host */ - .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/dead-hosts', 'post'), req.body) - .then((payload) => { - return internalDeadHost.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/dead-hosts", "post"), req.body); + const result = await internalDeadHost.create(res.locals.access, payload); + res.status(201).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -74,8 +76,8 @@ router * /api/nginx/dead-hosts/123 */ router - .route('/:host_id') - .options((req, res) => { + .route("/:host_id") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -85,65 +87,69 @@ router * * Retrieve a specific dead-host */ - .get((req, res, next) => { - validator({ - required: ['host_id'], - additionalProperties: false, - properties: { - host_id: { - $ref: 'common#/properties/id' + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["host_id"], + additionalProperties: false, + properties: { + host_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - host_id: req.params.host_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) - .then((data) => { - return internalDeadHost.get(res.locals.access, { - id: parseInt(data.host_id, 10), - expand: data.expand - }); - }) - .then((row) => { - res.status(200) - .send(row); - }) - .catch(next); + { + host_id: req.params.host_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ); + const row = await internalDeadHost.get(res.locals.access, { + id: Number.parseInt(data.host_id, 10), + expand: data.expand, + }); + res.status(200).send(row); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** * PUT /api/nginx/dead-hosts/123 * - * Update and existing dead-host + * Update an existing dead-host */ - .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/dead-hosts/{hostID}', 'put'), req.body) - .then((payload) => { - payload.id = parseInt(req.params.host_id, 10); - return internalDeadHost.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .put(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/dead-hosts/{hostID}", "put"), req.body); + payload.id = Number.parseInt(req.params.host_id, 10); + const result = await internalDeadHost.update(res.locals.access, payload); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** * DELETE /api/nginx/dead-hosts/123 * - * Update and existing dead-host + * Delete a dead-host */ - .delete((req, res, next) => { - internalDeadHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .delete(async (req, res, next) => { + try { + const result = await internalDeadHost.delete(res.locals.access, { + id: Number.parseInt(req.params.host_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -152,7 +158,7 @@ router * /api/nginx/dead-hosts/123/enable */ router - .route('/:host_id/enable') + .route("/:host_id/enable") .options((_, res) => { res.sendStatus(204); }) @@ -161,13 +167,16 @@ router /** * POST /api/nginx/dead-hosts/123/enable */ - .post((req, res, next) => { - internalDeadHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const result = await internalDeadHost.enable(res.locals.access, { + id: Number.parseInt(req.params.host_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -176,7 +185,7 @@ router * /api/nginx/dead-hosts/123/disable */ router - .route('/:host_id/disable') + .route("/:host_id/disable") .options((_, res) => { res.sendStatus(204); }) @@ -186,12 +195,13 @@ router * POST /api/nginx/dead-hosts/123/disable */ .post((req, res, next) => { - internalDeadHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + try { + const result = internalDeadHost.disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/proxy_hosts.js b/backend/routes/nginx/proxy_hosts.js index 3be4582a..7045a195 100644 --- a/backend/routes/nginx/proxy_hosts.js +++ b/backend/routes/nginx/proxy_hosts.js @@ -1,22 +1,23 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalProxyHost = require('../../internal/proxy-host'); -const schema = require('../../schema'); +import express from "express"; +import internalProxyHost from "../../internal/proxy-host.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { debug, express as logger } from "../../logger.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/proxy-hosts */ router - .route('/') - .options((req, res) => { + .route("/") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -26,29 +27,31 @@ router * * Retrieve all proxy-hosts */ - .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + .get(async (req, res, next) => { + try { + const data = await validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) - .then((data) => { - return internalProxyHost.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200) - .send(rows); - }) - .catch(next); + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ); + const rows = await internalProxyHost.getAll(res.locals.access, data.expand, data.query); + res.status(200).send(rows); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -56,16 +59,15 @@ router * * Create a new proxy-host */ - .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/proxy-hosts', 'post'), req.body) - .then((payload) => { - return internalProxyHost.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/proxy-hosts", "post"), req.body); + const result = await internalProxyHost.create(res.locals.access, payload); + res.status(201).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err} ${JSON.stringify(err.debug, null, 2)}`); + next(err); + } }); /** @@ -74,8 +76,8 @@ router * /api/nginx/proxy-hosts/123 */ router - .route('/:host_id') - .options((req, res) => { + .route("/:host_id") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -85,33 +87,35 @@ router * * Retrieve a specific proxy-host */ - .get((req, res, next) => { - validator({ - required: ['host_id'], - additionalProperties: false, - properties: { - host_id: { - $ref: 'common#/properties/id' + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["host_id"], + additionalProperties: false, + properties: { + host_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - host_id: req.params.host_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) - .then((data) => { - return internalProxyHost.get(res.locals.access, { - id: parseInt(data.host_id, 10), - expand: data.expand - }); - }) - .then((row) => { - res.status(200) - .send(row); - }) - .catch(next); + { + host_id: req.params.host_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ); + const row = await internalProxyHost.get(res.locals.access, { + id: Number.parseInt(data.host_id, 10), + expand: data.expand, + }); + res.status(200).send(row); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -119,17 +123,16 @@ router * * Update and existing proxy-host */ - .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/proxy-hosts/{hostID}', 'put'), req.body) - .then((payload) => { - payload.id = parseInt(req.params.host_id, 10); - return internalProxyHost.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .put(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/proxy-hosts/{hostID}", "put"), req.body); + payload.id = Number.parseInt(req.params.host_id, 10); + const result = await internalProxyHost.update(res.locals.access, payload); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -137,13 +140,16 @@ router * * Update and existing proxy-host */ - .delete((req, res, next) => { - internalProxyHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .delete(async (req, res, next) => { + try { + const result = await internalProxyHost.delete(res.locals.access, { + id: Number.parseInt(req.params.host_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -152,7 +158,7 @@ router * /api/nginx/proxy-hosts/123/enable */ router - .route('/:host_id/enable') + .route("/:host_id/enable") .options((_, res) => { res.sendStatus(204); }) @@ -161,13 +167,16 @@ router /** * POST /api/nginx/proxy-hosts/123/enable */ - .post((req, res, next) => { - internalProxyHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const result = await internalProxyHost.enable(res.locals.access, { + id: Number.parseInt(req.params.host_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -176,7 +185,7 @@ router * /api/nginx/proxy-hosts/123/disable */ router - .route('/:host_id/disable') + .route("/:host_id/disable") .options((_, res) => { res.sendStatus(204); }) @@ -185,13 +194,16 @@ router /** * POST /api/nginx/proxy-hosts/123/disable */ - .post((req, res, next) => { - internalProxyHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const result = await internalProxyHost.disable(res.locals.access, { + id: Number.parseInt(req.params.host_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/redirection_hosts.js b/backend/routes/nginx/redirection_hosts.js index a46feb84..9b5b5b37 100644 --- a/backend/routes/nginx/redirection_hosts.js +++ b/backend/routes/nginx/redirection_hosts.js @@ -1,22 +1,23 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalRedirectionHost = require('../../internal/redirection-host'); -const schema = require('../../schema'); +import express from "express"; +import internalRedirectionHost from "../../internal/redirection-host.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { debug, express as logger } from "../../logger.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/redirection-hosts */ router - .route('/') - .options((req, res) => { + .route("/") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -26,29 +27,31 @@ router * * Retrieve all redirection-hosts */ - .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + .get(async (req, res, next) => { + try { + const data = await validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) - .then((data) => { - return internalRedirectionHost.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200) - .send(rows); - }) - .catch(next); + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ); + const rows = await internalRedirectionHost.getAll(res.locals.access, data.expand, data.query); + res.status(200).send(rows); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -56,16 +59,15 @@ router * * Create a new redirection-host */ - .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/redirection-hosts', 'post'), req.body) - .then((payload) => { - return internalRedirectionHost.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/redirection-hosts", "post"), req.body); + const result = await internalRedirectionHost.create(res.locals.access, payload); + res.status(201).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -74,8 +76,8 @@ router * /api/nginx/redirection-hosts/123 */ router - .route('/:host_id') - .options((req, res) => { + .route("/:host_id") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -85,33 +87,35 @@ router * * Retrieve a specific redirection-host */ - .get((req, res, next) => { - validator({ - required: ['host_id'], - additionalProperties: false, - properties: { - host_id: { - $ref: 'common#/properties/id' + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["host_id"], + additionalProperties: false, + properties: { + host_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - host_id: req.params.host_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) - .then((data) => { - return internalRedirectionHost.get(res.locals.access, { - id: parseInt(data.host_id, 10), - expand: data.expand - }); - }) - .then((row) => { - res.status(200) - .send(row); - }) - .catch(next); + { + host_id: req.params.host_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ); + const row = await internalRedirectionHost.get(res.locals.access, { + id: Number.parseInt(data.host_id, 10), + expand: data.expand, + }); + res.status(200).send(row); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -119,17 +123,19 @@ router * * Update and existing redirection-host */ - .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/redirection-hosts/{hostID}', 'put'), req.body) - .then((payload) => { - payload.id = parseInt(req.params.host_id, 10); - return internalRedirectionHost.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .put(async (req, res, next) => { + try { + const payload = await apiValidator( + getValidationSchema("/nginx/redirection-hosts/{hostID}", "put"), + req.body, + ); + payload.id = Number.parseInt(req.params.host_id, 10); + const result = await internalRedirectionHost.update(res.locals.access, payload); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -137,13 +143,16 @@ router * * Update and existing redirection-host */ - .delete((req, res, next) => { - internalRedirectionHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .delete(async (req, res, next) => { + try { + const result = await internalRedirectionHost.delete(res.locals.access, { + id: Number.parseInt(req.params.host_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -152,8 +161,8 @@ router * /api/nginx/redirection-hosts/123/enable */ router - .route('/:host_id/enable') - .options((req, res) => { + .route("/:host_id/enable") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -161,13 +170,16 @@ router /** * POST /api/nginx/redirection-hosts/123/enable */ - .post((req, res, next) => { - internalRedirectionHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const result = await internalRedirectionHost.enable(res.locals.access, { + id: Number.parseInt(req.params.host_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -176,8 +188,8 @@ router * /api/nginx/redirection-hosts/123/disable */ router - .route('/:host_id/disable') - .options((req, res) => { + .route("/:host_id/disable") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -185,13 +197,16 @@ router /** * POST /api/nginx/redirection-hosts/123/disable */ - .post((req, res, next) => { - internalRedirectionHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const result = await internalRedirectionHost.disable(res.locals.access, { + id: Number.parseInt(req.params.host_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/streams.js b/backend/routes/nginx/streams.js index c033f2ef..dec2e1a1 100644 --- a/backend/routes/nginx/streams.js +++ b/backend/routes/nginx/streams.js @@ -1,22 +1,23 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalStream = require('../../internal/stream'); -const schema = require('../../schema'); +import express from "express"; +import internalStream from "../../internal/stream.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { debug, express as logger } from "../../logger.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/streams */ router - .route('/') - .options((req, res) => { + .route("/") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes @@ -26,29 +27,31 @@ router * * Retrieve all streams */ - .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + .get(async (req, res, next) => { + try { + const data = await validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) - .then((data) => { - return internalStream.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200) - .send(rows); - }) - .catch(next); + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ); + const rows = await internalStream.getAll(res.locals.access, data.expand, data.query); + res.status(200).send(rows); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -56,16 +59,15 @@ router * * Create a new stream */ - .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/streams', 'post'), req.body) - .then((payload) => { - return internalStream.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/streams", "post"), req.body); + const result = await internalStream.create(res.locals.access, payload); + res.status(201).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -74,8 +76,8 @@ router * /api/nginx/streams/123 */ router - .route('/:stream_id') - .options((req, res) => { + .route("/:stream_id") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes @@ -85,33 +87,35 @@ router * * Retrieve a specific stream */ - .get((req, res, next) => { - validator({ - required: ['stream_id'], - additionalProperties: false, - properties: { - stream_id: { - $ref: 'common#/properties/id' + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["stream_id"], + additionalProperties: false, + properties: { + stream_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - stream_id: req.params.stream_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) - .then((data) => { - return internalStream.get(res.locals.access, { - id: parseInt(data.stream_id, 10), - expand: data.expand - }); - }) - .then((row) => { - res.status(200) - .send(row); - }) - .catch(next); + { + stream_id: req.params.stream_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ); + const row = await internalStream.get(res.locals.access, { + id: Number.parseInt(data.stream_id, 10), + expand: data.expand, + }); + res.status(200).send(row); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -119,17 +123,16 @@ router * * Update and existing stream */ - .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/streams/{streamID}', 'put'), req.body) - .then((payload) => { - payload.id = parseInt(req.params.stream_id, 10); - return internalStream.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .put(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/streams/{streamID}", "put"), req.body); + payload.id = Number.parseInt(req.params.stream_id, 10); + const result = await internalStream.update(res.locals.access, payload); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -137,13 +140,16 @@ router * * Update and existing stream */ - .delete((req, res, next) => { - internalStream.delete(res.locals.access, {id: parseInt(req.params.stream_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .delete(async (req, res, next) => { + try { + const result = await internalStream.delete(res.locals.access, { + id: Number.parseInt(req.params.stream_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -152,7 +158,7 @@ router * /api/nginx/streams/123/enable */ router - .route('/:host_id/enable') + .route("/:host_id/enable") .options((_, res) => { res.sendStatus(204); }) @@ -161,13 +167,16 @@ router /** * POST /api/nginx/streams/123/enable */ - .post((req, res, next) => { - internalStream.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const result = await internalStream.enable(res.locals.access, { + id: Number.parseInt(req.params.host_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -176,7 +185,7 @@ router * /api/nginx/streams/123/disable */ router - .route('/:host_id/disable') + .route("/:host_id/disable") .options((_, res) => { res.sendStatus(204); }) @@ -185,13 +194,16 @@ router /** * POST /api/nginx/streams/123/disable */ - .post((req, res, next) => { - internalStream.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const result = await internalStream.disable(res.locals.access, { + id: Number.parseInt(req.params.host_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); -module.exports = router; +export default router; diff --git a/backend/routes/reports.js b/backend/routes/reports.js index 98c6cf86..df9962ab 100644 --- a/backend/routes/reports.js +++ b/backend/routes/reports.js @@ -1,29 +1,32 @@ -const express = require('express'); -const jwtdecode = require('../lib/express/jwt-decode'); -const internalReport = require('../internal/report'); +import express from "express"; +import internalReport from "../internal/report.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; +import { debug, express as logger } from "../logger.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); router - .route('/hosts') + .route("/hosts") .options((_, res) => { res.sendStatus(204); }) + .all(jwtdecode()) /** * GET /reports/hosts */ - .get(jwtdecode(), (_, res, next) => { - internalReport.getHostsReport(res.locals.access) - .then((data) => { - res.status(200) - .send(data); - }) - .catch(next); + .get(async (req, res, next) => { + try { + const data = await internalReport.getHostsReport(res.locals.access); + res.status(200).send(data); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); -module.exports = router; +export default router; diff --git a/backend/routes/schema.js b/backend/routes/schema.js index fc3e48b6..86bc1866 100644 --- a/backend/routes/schema.js +++ b/backend/routes/schema.js @@ -1,15 +1,16 @@ -const express = require('express'); -const schema = require('../schema'); -const PACKAGE = require('../package.json'); +import express from "express"; +import { debug, express as logger } from "../logger.js"; +import PACKAGE from "../package.json" with { type: "json" }; +import { getCompiledSchema } from "../schema/index.js"; const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -18,21 +19,26 @@ router * GET /schema */ .get(async (req, res) => { - let swaggerJSON = await schema.getCompiledSchema(); + try { + const swaggerJSON = await getCompiledSchema(); - let proto = req.protocol; - if (typeof req.headers['x-forwarded-proto'] !== 'undefined' && req.headers['x-forwarded-proto']) { - proto = req.headers['x-forwarded-proto']; + let proto = req.protocol; + if (typeof req.headers["x-forwarded-proto"] !== "undefined" && req.headers["x-forwarded-proto"]) { + proto = req.headers["x-forwarded-proto"]; + } + + let origin = `${proto}://${req.hostname}`; + if (typeof req.headers.origin !== "undefined" && req.headers.origin) { + origin = req.headers.origin; + } + + swaggerJSON.info.version = PACKAGE.version; + swaggerJSON.servers[0].url = `${origin}/api`; + res.status(200).send(swaggerJSON); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); } - - let origin = proto + '://' + req.hostname; - if (typeof req.headers.origin !== 'undefined' && req.headers.origin) { - origin = req.headers.origin; - } - - swaggerJSON.info.version = PACKAGE.version; - swaggerJSON.servers[0].url = origin + '/api'; - res.status(200).send(swaggerJSON); }); -module.exports = router; +export default router; diff --git a/backend/routes/settings.js b/backend/routes/settings.js index dac4c3d1..ca4af656 100644 --- a/backend/routes/settings.js +++ b/backend/routes/settings.js @@ -1,21 +1,22 @@ -const express = require('express'); -const validator = require('../lib/validator'); -const jwtdecode = require('../lib/express/jwt-decode'); -const apiValidator = require('../lib/validator/api'); -const internalSetting = require('../internal/setting'); -const schema = require('../schema'); +import express from "express"; +import internalSetting from "../internal/setting.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; +import apiValidator from "../lib/validator/api.js"; +import validator from "../lib/validator/index.js"; +import { debug, express as logger } from "../logger.js"; +import { getValidationSchema } from "../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/settings */ router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -26,13 +27,14 @@ router * * Retrieve all settings */ - .get((_, res, next) => { - internalSetting.getAll(res.locals.access) - .then((rows) => { - res.status(200) - .send(rows); - }) - .catch(next); + .get(async (req, res, next) => { + try { + const rows = await internalSetting.getAll(res.locals.access); + res.status(200).send(rows); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -41,7 +43,7 @@ router * /api/settings/something */ router - .route('/:setting_id') + .route("/:setting_id") .options((_, res) => { res.sendStatus(204); }) @@ -52,29 +54,31 @@ router * * Retrieve a specific setting */ - .get((req, res, next) => { - validator({ - required: ['setting_id'], - additionalProperties: false, - properties: { - setting_id: { - type: 'string', - minLength: 1 - } - } - }, { - setting_id: req.params.setting_id - }) - .then((data) => { - return internalSetting.get(res.locals.access, { - id: data.setting_id - }); - }) - .then((row) => { - res.status(200) - .send(row); - }) - .catch(next); + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["setting_id"], + additionalProperties: false, + properties: { + setting_id: { + type: "string", + minLength: 1, + }, + }, + }, + { + setting_id: req.params.setting_id, + }, + ); + const row = await internalSetting.get(res.locals.access, { + id: data.setting_id, + }); + res.status(200).send(row); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -82,17 +86,16 @@ router * * Update and existing setting */ - .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/settings/{settingID}', 'put'), req.body) - .then((payload) => { - payload.id = req.params.setting_id; - return internalSetting.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .put(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/settings/{settingID}", "put"), req.body); + payload.id = req.params.setting_id; + const result = await internalSetting.update(res.locals.access, payload); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); -module.exports = router; +export default router; diff --git a/backend/routes/tokens.js b/backend/routes/tokens.js index 72d01d41..b8599319 100644 --- a/backend/routes/tokens.js +++ b/backend/routes/tokens.js @@ -1,17 +1,18 @@ -const express = require('express'); -const jwtdecode = require('../lib/express/jwt-decode'); -const apiValidator = require('../lib/validator/api'); -const internalToken = require('../internal/token'); -const schema = require('../schema'); +import express from "express"; +import internalToken from "../internal/token.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; +import apiValidator from "../lib/validator/api.js"; +import { debug, express as logger } from "../logger.js"; +import { getValidationSchema } from "../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -23,16 +24,17 @@ router * We also piggy back on to this method, allowing admins to get tokens * for services like Job board and Worker. */ - .get(jwtdecode(), (req, res, next) => { - internalToken.getFreshToken(res.locals.access, { - expiry: (typeof req.query.expiry !== 'undefined' ? req.query.expiry : null), - scope: (typeof req.query.scope !== 'undefined' ? req.query.scope : null) - }) - .then((data) => { - res.status(200) - .send(data); - }) - .catch(next); + .get(jwtdecode(), async (req, res, next) => { + try { + const data = await internalToken.getFreshToken(res.locals.access, { + expiry: typeof req.query.expiry !== "undefined" ? req.query.expiry : null, + scope: typeof req.query.scope !== "undefined" ? req.query.scope : null, + }); + res.status(200).send(data); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -41,13 +43,14 @@ router * Create a new Token */ .post(async (req, res, next) => { - apiValidator(schema.getValidationSchema('/tokens', 'post'), req.body) - .then(internalToken.getTokenFromEmail) - .then((data) => { - res.status(200) - .send(data); - }) - .catch(next); + try { + const data = await apiValidator(getValidationSchema("/tokens", "post"), req.body); + const result = await internalToken.getTokenFromEmail(data); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); -module.exports = router; +export default router; diff --git a/backend/routes/users.js b/backend/routes/users.js index e41bf6cf..7159b8b5 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -1,22 +1,27 @@ -const express = require('express'); -const validator = require('../lib/validator'); -const jwtdecode = require('../lib/express/jwt-decode'); -const userIdFromMe = require('../lib/express/user-id-from-me'); -const internalUser = require('../internal/user'); -const apiValidator = require('../lib/validator/api'); -const schema = require('../schema'); +import express from "express"; +import internalUser from "../internal/user.js"; +import Access from "../lib/access.js"; +import { isCI } from "../lib/config.js"; +import errs from "../lib/error.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; +import userIdFromMe from "../lib/express/user-id-from-me.js"; +import apiValidator from "../lib/validator/api.js"; +import validator from "../lib/validator/index.js"; +import { debug, express as logger } from "../logger.js"; +import { getValidationSchema } from "../schema/index.js"; +import { isSetup } from "../setup.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/users */ router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -27,33 +32,38 @@ router * * Retrieve all users */ - .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + .get(async (req, res, next) => { + try { + const data = await validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) - .then((data) => { - return internalUser.getAll(res.locals.access, data.expand, data.query); - }) - .then((users) => { - res.status(200) - .send(users); - }) - .catch((err) => { - console.log(err); - next(err); - }); - //.catch(next); + { + expand: + typeof req.query.expand === "string" + ? req.query.expand.split(",") + : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ); + const users = await internalUser.getAll( + res.locals.access, + data.expand, + data.query, + ); + res.status(200).send(users); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -61,16 +71,66 @@ router * * Create a new User */ - .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/users', 'post'), req.body) - .then((payload) => { - return internalUser.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + const body = req.body; + + try { + // If we are in setup mode, we don't check access for current user + const setup = await isSetup(); + if (!setup) { + logger.info("Creating a new user in setup mode"); + const access = new Access(null); + await access.load(true); + res.locals.access = access; + + // We are in setup mode, set some defaults for this first new user, such as making + // them an admin. + body.is_disabled = false; + if (typeof body.roles !== "object" || body.roles === null) { + body.roles = []; + } + if (body.roles.indexOf("admin") === -1) { + body.roles.push("admin"); + } + } + + const payload = await apiValidator( + getValidationSchema("/users", "post"), + body, + ); + const user = await internalUser.create(res.locals.access, payload); + res.status(201).send(user); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }) + + /** + * DELETE /api/users + * + * Deletes ALL users. This is NOT GENERALLY AVAILABLE! + * (!) It is NOT an authenticated endpoint. + * (!) Only CI should be able to call this endpoint. As a result, + * + * it will only work when the env vars DEBUG=true and CI=true + * + * Do NOT set those env vars in a production environment! + */ + .delete(async (_, res, next) => { + if (isCI()) { + try { + logger.warn("Deleting all users - CI environment detected, allowing this operation"); + await internalUser.deleteAll(); + res.status(200).send(true); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + return; + } + + next(new errs.ItemNotFoundError()); }); /** @@ -79,7 +139,7 @@ router * /api/users/123 */ router - .route('/:user_id') + .route("/:user_id") .options((_, res) => { res.sendStatus(204); }) @@ -91,37 +151,43 @@ router * * Retrieve a specific user */ - .get((req, res, next) => { - validator({ - required: ['user_id'], - additionalProperties: false, - properties: { - user_id: { - $ref: 'common#/properties/id' + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["user_id"], + additionalProperties: false, + properties: { + user_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - user_id: req.params.user_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) - .then((data) => { - return internalUser.get(res.locals.access, { - id: data.user_id, - expand: data.expand, - omit: internalUser.getUserOmisionsByAccess(res.locals.access, data.user_id) - }); - }) - .then((user) => { - res.status(200) - .send(user); - }) - .catch((err) => { - console.log(err); - next(err); + { + user_id: req.params.user_id, + expand: + typeof req.query.expand === "string" + ? req.query.expand.split(",") + : null, + }, + ); + + const user = await internalUser.get(res.locals.access, { + id: data.user_id, + expand: data.expand, + omit: internalUser.getUserOmisionsByAccess( + res.locals.access, + data.user_id, + ), }); + res.status(200).send(user); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -129,17 +195,19 @@ router * * Update and existing user */ - .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/users/{userID}', 'put'), req.body) - .then((payload) => { - payload.id = req.params.user_id; - return internalUser.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .put(async (req, res, next) => { + try { + const payload = await apiValidator( + getValidationSchema("/users/{userID}", "put"), + req.body, + ); + payload.id = req.params.user_id; + const result = await internalUser.update(res.locals.access, payload); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }) /** @@ -147,13 +215,16 @@ router * * Update and existing user */ - .delete((req, res, next) => { - internalUser.delete(res.locals.access, {id: req.params.user_id}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .delete(async (req, res, next) => { + try { + const result = await internalUser.delete(res.locals.access, { + id: req.params.user_id, + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -162,8 +233,8 @@ router * /api/users/123/auth */ router - .route('/:user_id/auth') - .options((req, res) => { + .route("/:user_id/auth") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -174,17 +245,19 @@ router * * Update password for a user */ - .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/users/{userID}/auth', 'put'), req.body) - .then((payload) => { - payload.id = req.params.user_id; - return internalUser.setPassword(res.locals.access, payload); - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .put(async (req, res, next) => { + try { + const payload = await apiValidator( + getValidationSchema("/users/{userID}/auth", "put"), + req.body, + ); + payload.id = req.params.user_id; + const result = await internalUser.setPassword(res.locals.access, payload); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -193,8 +266,8 @@ router * /api/users/123/permissions */ router - .route('/:user_id/permissions') - .options((req, res) => { + .route("/:user_id/permissions") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -205,17 +278,22 @@ router * * Set some or all permissions for a user */ - .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/users/{userID}/permissions', 'put'), req.body) - .then((payload) => { - payload.id = req.params.user_id; - return internalUser.setPermissions(res.locals.access, payload); - }) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .put(async (req, res, next) => { + try { + const payload = await apiValidator( + getValidationSchema("/users/{userID}/permissions", "put"), + req.body, + ); + payload.id = req.params.user_id; + const result = await internalUser.setPermissions( + res.locals.access, + payload, + ); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); /** @@ -224,7 +302,7 @@ router * /api/users/123/login */ router - .route('/:user_id/login') + .route("/:user_id/login") .options((_, res) => { res.sendStatus(204); }) @@ -235,13 +313,16 @@ router * * Log in as a user */ - .post((req, res, next) => { - internalUser.loginAs(res.locals.access, {id: parseInt(req.params.user_id, 10)}) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + .post(async (req, res, next) => { + try { + const result = await internalUser.loginAs(res.locals.access, { + id: Number.parseInt(req.params.user_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } }); -module.exports = router; +export default router; diff --git a/backend/schema/common.json b/backend/schema/common.json index 83de0143..00b06e00 100644 --- a/backend/schema/common.json +++ b/backend/schema/common.json @@ -7,7 +7,8 @@ "description": "Unique identifier", "readOnly": true, "type": "integer", - "minimum": 1 + "minimum": 1, + "example": 11 }, "expand": { "anyOf": [ @@ -38,35 +39,42 @@ "created_on": { "description": "Date and time of creation", "readOnly": true, - "type": "string" + "type": "string", + "example": "2025-10-28T04:17:54.000Z" }, "modified_on": { "description": "Date and time of last update", "readOnly": true, - "type": "string" + "type": "string", + "example": "2025-10-28T04:17:54.000Z" }, "user_id": { "description": "User ID", "type": "integer", - "minimum": 1 + "minimum": 1, + "example": 2 }, "certificate_id": { "description": "Certificate ID", "anyOf": [ { "type": "integer", - "minimum": 0 + "minimum": 0, + "example": 5 }, { "type": "string", - "pattern": "^new$" + "pattern": "^new$", + "example": "new" } - ] + ], + "example": 5 }, "access_list_id": { "description": "Access List ID", "type": "integer", - "minimum": 0 + "minimum": 0, + "example": 3 }, "domain_names": { "description": "Domain Names separated by a comma", @@ -77,39 +85,157 @@ "items": { "type": "string", "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$" - } + }, + "example": ["example.com", "www.example.com"] }, "enabled": { "description": "Is Enabled", - "type": "boolean" + "type": "boolean", + "example": false }, "ssl_forced": { "description": "Is SSL Forced", - "type": "boolean" + "type": "boolean", + "example": true }, "hsts_enabled": { "description": "Is HSTS Enabled", - "type": "boolean" + "type": "boolean", + "example": true }, "hsts_subdomains": { "description": "Is HSTS applicable to all subdomains", - "type": "boolean" + "type": "boolean", + "example": true }, "ssl_provider": { "type": "string", - "pattern": "^(letsencrypt|other)$" + "pattern": "^(letsencrypt|other)$", + "example": "letsencrypt" }, "http2_support": { "description": "HTTP2 Protocol Support", - "type": "boolean" + "type": "boolean", + "example": true }, "block_exploits": { "description": "Should we block common exploits", - "type": "boolean" + "type": "boolean", + "example": false }, "caching_enabled": { "description": "Should we cache assets", - "type": "boolean" + "type": "boolean", + "example": true + }, + "email": { + "description": "Email address", + "type": "string", + "pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$", + "example": "me@example.com" + }, + "directive": { + "type": "string", + "enum": ["allow", "deny"], + "example": "allow" + }, + "address": { + "oneOf": [ + { + "type": "string", + "pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" + }, + { + "type": "string", + "pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$" + }, + { + "type": "string", + "pattern": "^all$" + } + ], + "example": "192.168.0.11" + }, + "access_items": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string", + "minLength": 1 + }, + "password": { + "type": "string" + } + }, + "example": { + "username": "admin", + "password": "pass" + } + }, + "example": [ + { + "username": "admin", + "password": "pass" + } + ] + }, + "access_clients": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "address": { + "$ref": "#/properties/address" + }, + "directive": { + "$ref": "#/properties/directive" + } + }, + "example": { + "directive": "allow", + "address": "192.168.0.0/24" + } + }, + "example": [ + { + "directive": "allow", + "address": "192.168.0.0/24" + } + ] + }, + "certificate_files": { + "description": "Certificate Files", + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "additionalProperties": false, + "required": ["certificate", "certificate_key"], + "properties": { + "certificate": { + "type": "string", + "example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----" + }, + "certificate_key": { + "type": "string", + "example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----" + }, + "intermediate_certificate": { + "type": "string", + "example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----" + } + } + }, + "example": { + "certificate": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----", + "certificate_key": "-----BEGIN PRIVATE\nMIID...-----END CERTIFICATE-----" + } + } + } } } } diff --git a/backend/schema/components/access-list-object.json b/backend/schema/components/access-list-object.json index cd0218d7..d80eb06d 100644 --- a/backend/schema/components/access-list-object.json +++ b/backend/schema/components/access-list-object.json @@ -1,8 +1,7 @@ { "type": "object", "description": "Access List object", - "required": ["id", "created_on", "modified_on", "owner_user_id", "name", "directive", "address", "satisfy_any", "pass_auth", "meta"], - "additionalProperties": false, + "required": ["id", "created_on", "modified_on", "owner_user_id", "name", "meta", "satisfy_any", "pass_auth", "proxy_host_count"], "properties": { "id": { "$ref": "../common.json#/properties/id" @@ -18,36 +17,25 @@ }, "name": { "type": "string", - "minLength": 1 - }, - "directive": { - "type": "string", - "enum": ["allow", "deny"] - }, - "address": { - "oneOf": [ - { - "type": "string", - "pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" - }, - { - "type": "string", - "pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$" - }, - { - "type": "string", - "pattern": "^all$" - } - ] - }, - "satisfy_any": { - "type": "boolean" - }, - "pass_auth": { - "type": "boolean" + "minLength": 1, + "example": "My Access List" }, "meta": { - "type": "object" + "type": "object", + "example": {} + }, + "satisfy_any": { + "type": "boolean", + "example": true + }, + "pass_auth": { + "type": "boolean", + "example": false + }, + "proxy_host_count": { + "type": "integer", + "minimum": 0, + "example": 3 } } } diff --git a/backend/schema/components/audit-log-list.json b/backend/schema/components/audit-log-list.json new file mode 100644 index 00000000..74368528 --- /dev/null +++ b/backend/schema/components/audit-log-list.json @@ -0,0 +1,7 @@ +{ + "type": "array", + "description": "Audit Log list", + "items": { + "$ref": "./audit-log-object.json" + } +} diff --git a/backend/schema/components/audit-log-object.json b/backend/schema/components/audit-log-object.json index 3e5e8594..307cac82 100644 --- a/backend/schema/components/audit-log-object.json +++ b/backend/schema/components/audit-log-object.json @@ -1,7 +1,16 @@ { "type": "object", "description": "Audit Log object", - "required": ["id", "created_on", "modified_on", "user_id", "object_type", "object_id", "action", "meta"], + "required": [ + "id", + "created_on", + "modified_on", + "user_id", + "object_type", + "object_id", + "action", + "meta" + ], "additionalProperties": false, "properties": { "id": { @@ -17,16 +26,22 @@ "$ref": "../common.json#/properties/user_id" }, "object_type": { - "type": "string" + "type": "string", + "example": "certificate" }, "object_id": { "$ref": "../common.json#/properties/id" }, "action": { - "type": "string" + "type": "string", + "example": "created" }, "meta": { - "type": "object" + "type": "object", + "example": {} + }, + "user": { + "$ref": "./user-object.json" } } } diff --git a/backend/schema/components/certificate-object.json b/backend/schema/components/certificate-object.json index b75dcf61..ef2d613f 100644 --- a/backend/schema/components/certificate-object.json +++ b/backend/schema/components/certificate-object.json @@ -21,7 +21,8 @@ }, "nice_name": { "type": "string", - "description": "Nice Name for the custom certificate" + "description": "Nice Name for the custom certificate", + "example": "My Custom Cert" }, "domain_names": { "description": "Domain Names separated by a comma", @@ -31,12 +32,14 @@ "items": { "type": "string", "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$" - } + }, + "example": ["example.com", "www.example.com"] }, "expires_on": { "description": "Date and time of expiration", "readOnly": true, - "type": "string" + "type": "string", + "example": "2025-10-28T04:17:54.000Z" }, "owner": { "$ref": "./user-object.json" @@ -56,25 +59,22 @@ "dns_challenge": { "type": "boolean" }, - "dns_provider": { - "type": "string" - }, "dns_provider_credentials": { "type": "string" }, - "letsencrypt_agree": { - "type": "boolean" + "dns_provider": { + "type": "string" }, "letsencrypt_certificate": { "type": "object" }, - "letsencrypt_email": { - "type": "string" - }, "propagation_seconds": { "type": "integer", "minimum": 0 } + }, + "example": { + "dns_challenge": false } } } diff --git a/backend/schema/components/dead-host-object.json b/backend/schema/components/dead-host-object.json index 792c2f81..b876ebf0 100644 --- a/backend/schema/components/dead-host-object.json +++ b/backend/schema/components/dead-host-object.json @@ -35,13 +35,30 @@ "$ref": "../common.json#/properties/http2_support" }, "advanced_config": { - "type": "string" + "type": "string", + "example": "" }, "enabled": { "$ref": "../common.json#/properties/enabled" }, "meta": { - "type": "object" + "type": "object", + "example": {} + }, + "certificate": { + "oneOf": [ + { + "type": "null", + "example": null + }, + { + "$ref": "./certificate-object.json" + } + ], + "example": null + }, + "owner": { + "$ref": "./user-object.json" } } } diff --git a/backend/schema/components/dns-providers-list.json b/backend/schema/components/dns-providers-list.json new file mode 100644 index 00000000..c240db18 --- /dev/null +++ b/backend/schema/components/dns-providers-list.json @@ -0,0 +1,23 @@ +{ + "type": "array", + "description": "DNS Providers list", + "items": { + "type": "object", + "required": ["id", "name", "credentials"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the DNS provider, matching the python package" + }, + "name": { + "type": "string", + "description": "Human-readable name of the DNS provider" + }, + "credentials": { + "type": "string", + "description": "Instructions on how to format the credentials for this DNS provider" + } + } + } +} diff --git a/backend/schema/components/error-object.json b/backend/schema/components/error-object.json index c2540cf1..6f350a08 100644 --- a/backend/schema/components/error-object.json +++ b/backend/schema/components/error-object.json @@ -5,10 +5,12 @@ "required": ["code", "message"], "properties": { "code": { - "type": "integer" + "type": "integer", + "example": 400 }, "message": { - "type": "string" + "type": "string", + "example": "Bad Request" } } } diff --git a/backend/schema/components/health-object.json b/backend/schema/components/health-object.json index 8d223417..592ead2c 100644 --- a/backend/schema/components/health-object.json +++ b/backend/schema/components/health-object.json @@ -9,6 +9,11 @@ "description": "Healthy", "example": "OK" }, + "setup": { + "type": "boolean", + "description": "Whether the initial setup has been completed", + "example": true + }, "version": { "type": "object", "description": "The version object", @@ -22,15 +27,18 @@ "properties": { "major": { "type": "integer", - "minimum": 0 + "minimum": 0, + "example": 2 }, "minor": { "type": "integer", - "minimum": 0 + "minimum": 0, + "example": 10 }, "revision": { "type": "integer", - "minimum": 0 + "minimum": 0, + "example": 1 } } } diff --git a/backend/schema/components/permission-object.json b/backend/schema/components/permission-object.json index b852a014..cae9d26c 100644 --- a/backend/schema/components/permission-object.json +++ b/backend/schema/components/permission-object.json @@ -5,37 +5,44 @@ "visibility": { "type": "string", "description": "Visibility Type", - "enum": ["all", "user"] + "enum": ["all", "user"], + "example": "all" }, "access_lists": { "type": "string", "description": "Access Lists Permissions", - "enum": ["hidden", "view", "manage"] + "enum": ["hidden", "view", "manage"], + "example": "view" }, "dead_hosts": { "type": "string", "description": "404 Hosts Permissions", - "enum": ["hidden", "view", "manage"] + "enum": ["hidden", "view", "manage"], + "example": "manage" }, "proxy_hosts": { "type": "string", "description": "Proxy Hosts Permissions", - "enum": ["hidden", "view", "manage"] + "enum": ["hidden", "view", "manage"], + "example": "hidden" }, "redirection_hosts": { "type": "string", "description": "Redirection Permissions", - "enum": ["hidden", "view", "manage"] + "enum": ["hidden", "view", "manage"], + "example": "view" }, "streams": { "type": "string", "description": "Streams Permissions", - "enum": ["hidden", "view", "manage"] + "enum": ["hidden", "view", "manage"], + "example": "manage" }, "certificates": { "type": "string", "description": "Certificates Permissions", - "enum": ["hidden", "view", "manage"] + "enum": ["hidden", "view", "manage"], + "example": "hidden" } } } diff --git a/backend/schema/components/proxy-host-object.json b/backend/schema/components/proxy-host-object.json index e9dcacb5..464b188e 100644 --- a/backend/schema/components/proxy-host-object.json +++ b/backend/schema/components/proxy-host-object.json @@ -24,7 +24,6 @@ "hsts_enabled", "hsts_subdomains" ], - "additionalProperties": false, "properties": { "id": { "$ref": "../common.json#/properties/id" @@ -44,12 +43,14 @@ "forward_host": { "type": "string", "minLength": 1, - "maxLength": 255 + "maxLength": 255, + "example": "127.0.0.1" }, "forward_port": { "type": "integer", "minimum": 1, - "maximum": 65535 + "maximum": 65535, + "example": 8080 }, "access_list_id": { "$ref": "../common.json#/properties/access_list_id" @@ -67,22 +68,28 @@ "$ref": "../common.json#/properties/block_exploits" }, "advanced_config": { - "type": "string" + "type": "string", + "example": "" }, "meta": { - "type": "object" + "type": "object", + "example": { + "nginx_online": true, + "nginx_err": null + } }, "allow_websocket_upgrade": { "description": "Allow Websocket Upgrade for all paths", - "example": true, - "type": "boolean" + "type": "boolean", + "example": true }, "http2_support": { "$ref": "../common.json#/properties/http2_support" }, "forward_scheme": { "type": "string", - "enum": ["http", "https"] + "enum": ["http", "https"], + "example": "http" }, "enabled": { "$ref": "../common.json#/properties/enabled" @@ -118,7 +125,15 @@ "type": "string" } } - } + }, + "example": [ + { + "path": "/app", + "forward_scheme": "http", + "forward_host": "example.com", + "forward_port": 80 + } + ] }, "hsts_enabled": { "$ref": "../common.json#/properties/hsts_enabled" @@ -129,12 +144,14 @@ "certificate": { "oneOf": [ { - "type": "null" + "type": "null", + "example": null }, { "$ref": "./certificate-object.json" } - ] + ], + "example": null }, "owner": { "$ref": "./user-object.json" @@ -142,12 +159,14 @@ "access_list": { "oneOf": [ { - "type": "null" + "type": "null", + "example": null }, { "$ref": "./access-list-object.json" } - ] + ], + "example": null } } } diff --git a/backend/schema/components/redirection-host-object.json b/backend/schema/components/redirection-host-object.json index e7a495fd..58169720 100644 --- a/backend/schema/components/redirection-host-object.json +++ b/backend/schema/components/redirection-host-object.json @@ -1,7 +1,26 @@ { "type": "object", "description": "Redirection Host object", - "required": ["id", "created_on", "modified_on", "owner_user_id", "domain_names", "forward_http_code", "forward_scheme", "forward_domain_name", "preserve_path", "certificate_id", "ssl_forced", "hsts_enabled", "hsts_subdomains", "http2_support", "block_exploits", "advanced_config", "enabled", "meta"], + "required": [ + "id", + "created_on", + "modified_on", + "owner_user_id", + "domain_names", + "forward_http_code", + "forward_scheme", + "forward_domain_name", + "preserve_path", + "certificate_id", + "ssl_forced", + "hsts_enabled", + "hsts_subdomains", + "http2_support", + "block_exploits", + "advanced_config", + "enabled", + "meta" + ], "additionalProperties": false, "properties": { "id": { @@ -21,25 +40,30 @@ }, "forward_http_code": { "description": "Redirect HTTP Status Code", - "example": 302, "type": "integer", "minimum": 300, - "maximum": 308 + "maximum": 308, + "example": 302 }, "forward_scheme": { "type": "string", - "enum": ["auto", "http", "https"] + "enum": [ + "auto", + "http", + "https" + ], + "example": "http" }, "forward_domain_name": { "description": "Domain Name", - "example": "jc21.com", "type": "string", - "pattern": "^(?:[^.*]+\\.?)+[^.]$" + "pattern": "^(?:[^.*]+\\.?)+[^.]$", + "example": "jc21.com" }, "preserve_path": { "description": "Should the path be preserved", - "example": true, - "type": "boolean" + "type": "boolean", + "example": true }, "certificate_id": { "$ref": "../common.json#/properties/certificate_id" @@ -60,13 +84,33 @@ "$ref": "../common.json#/properties/block_exploits" }, "advanced_config": { - "type": "string" + "type": "string", + "example": "" }, "enabled": { "$ref": "../common.json#/properties/enabled" }, "meta": { - "type": "object" + "type": "object", + "example": { + "nginx_online": true, + "nginx_err": null + } + }, + "certificate": { + "oneOf": [ + { + "type": "null", + "example": null + }, + { + "$ref": "./certificate-object.json" + } + ], + "example": null + }, + "owner": { + "$ref": "./user-object.json" } } } diff --git a/backend/schema/components/security-schemes.json b/backend/schema/components/security-schemes.json index 82407be3..4ae57bd7 100644 --- a/backend/schema/components/security-schemes.json +++ b/backend/schema/components/security-schemes.json @@ -1,6 +1,8 @@ { - "BearerAuth": { + "bearerAuth": { "type": "http", - "scheme": "bearer" + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "JWT Bearer Token authentication" } } diff --git a/backend/schema/components/stream-object.json b/backend/schema/components/stream-object.json index 848c30e6..602073ce 100644 --- a/backend/schema/components/stream-object.json +++ b/backend/schema/components/stream-object.json @@ -1,7 +1,19 @@ { "type": "object", "description": "Stream object", - "required": ["id", "created_on", "modified_on", "owner_user_id", "incoming_port", "forwarding_host", "forwarding_port", "tcp_forwarding", "udp_forwarding", "enabled", "meta"], + "required": [ + "id", + "created_on", + "modified_on", + "owner_user_id", + "incoming_port", + "forwarding_host", + "forwarding_port", + "tcp_forwarding", + "udp_forwarding", + "enabled", + "meta" + ], "additionalProperties": false, "properties": { "id": { @@ -19,36 +31,41 @@ "incoming_port": { "type": "integer", "minimum": 1, - "maximum": 65535 + "maximum": 65535, + "example": 9090 }, "forwarding_host": { "anyOf": [ { "description": "Domain Name", - "example": "jc21.com", "type": "string", - "pattern": "^(?:[^.*]+\\.?)+[^.]$" + "pattern": "^(?:[^.*]+\\.?)+[^.]$", + "example": "example.com" }, { "type": "string", - "format": "ipv4" + "format": "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$" }, { "type": "string", "format": "ipv6" } - ] + ], + "example": "example.com" }, "forwarding_port": { "type": "integer", "minimum": 1, - "maximum": 65535 + "maximum": 65535, + "example": 80 }, "tcp_forwarding": { - "type": "boolean" + "type": "boolean", + "example": true }, "udp_forwarding": { - "type": "boolean" + "type": "boolean", + "example": false }, "enabled": { "$ref": "../common.json#/properties/enabled" @@ -57,10 +74,8 @@ "$ref": "../common.json#/properties/certificate_id" }, "meta": { - "type": "object" - }, - "owner": { - "$ref": "./user-object.json" + "type": "object", + "example": {} }, "certificate": { "oneOf": [ @@ -70,7 +85,11 @@ { "$ref": "./certificate-object.json" } - ] + ], + "example": null + }, + "owner": { + "$ref": "./user-object.json" } } } diff --git a/backend/schema/components/user-object.json b/backend/schema/components/user-object.json index 180e8f19..7acd0a42 100644 --- a/backend/schema/components/user-object.json +++ b/backend/schema/components/user-object.json @@ -54,6 +54,63 @@ "items": { "type": "string" } + }, + "permissions": { + "type": "object", + "description": "Permissions if expanded in request", + "required": [ + "visibility", + "proxy_hosts", + "redirection_hosts", + "dead_hosts", + "streams", + "access_lists", + "certificates" + ], + "properties": { + "visibility": { + "type": "string", + "description": "Visibility level", + "example": "all", + "pattern": "^(all|user)$" + }, + "proxy_hosts": { + "type": "string", + "description": "Proxy Hosts access level", + "example": "manage", + "pattern": "^(manage|view|hidden)$" + }, + "redirection_hosts": { + "type": "string", + "description": "Redirection Hosts access level", + "example": "manage", + "pattern": "^(manage|view|hidden)$" + }, + "dead_hosts": { + "type": "string", + "description": "Dead Hosts access level", + "example": "manage", + "pattern": "^(manage|view|hidden)$" + }, + "streams": { + "type": "string", + "description": "Streams access level", + "example": "manage", + "pattern": "^(manage|view|hidden)$" + }, + "access_lists": { + "type": "string", + "description": "Access Lists access level", + "example": "hidden", + "pattern": "^(manage|view|hidden)$" + }, + "certificates": { + "type": "string", + "description": "Certificates access level", + "example": "view", + "pattern": "^(manage|view|hidden)$" + } + } } } } diff --git a/backend/schema/index.js b/backend/schema/index.js index 87b75f25..0478486b 100644 --- a/backend/schema/index.js +++ b/backend/schema/index.js @@ -1,41 +1,46 @@ -const refParser = require('@apidevtools/json-schema-ref-parser'); +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import $RefParser from "@apidevtools/json-schema-ref-parser"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); let compiledSchema = null; -module.exports = { - - /** - * Compiles the schema, by dereferencing it, only once - * and returns the memory cached value - */ - getCompiledSchema: async () => { - if (compiledSchema === null) { - compiledSchema = await refParser.dereference(__dirname + '/swagger.json', { - mutateInputSchema: false, - }); - } - return compiledSchema; - }, - - /** - * Scans the schema for the validation schema for the given path and method - * and returns it. - * - * @param {string} path - * @param {string} method - * @returns string|null - */ - getValidationSchema: (path, method) => { - if (compiledSchema !== null && - typeof compiledSchema.paths[path] !== 'undefined' && - typeof compiledSchema.paths[path][method] !== 'undefined' && - typeof compiledSchema.paths[path][method].requestBody !== 'undefined' && - typeof compiledSchema.paths[path][method].requestBody.content !== 'undefined' && - typeof compiledSchema.paths[path][method].requestBody.content['application/json'] !== 'undefined' && - typeof compiledSchema.paths[path][method].requestBody.content['application/json'].schema !== 'undefined' - ) { - return compiledSchema.paths[path][method].requestBody.content['application/json'].schema; - } - return null; +/** + * Compiles the schema, by dereferencing it, only once + * and returns the memory cached value + */ +const getCompiledSchema = async () => { + if (compiledSchema === null) { + compiledSchema = await $RefParser.dereference(`${__dirname}/swagger.json`, { + mutateInputSchema: false, + }); } + return compiledSchema; }; + +/** + * Scans the schema for the validation schema for the given path and method + * and returns it. + * + * @param {string} path + * @param {string} method + * @returns string|null + */ +const getValidationSchema = (path, method) => { + if ( + compiledSchema !== null && + typeof compiledSchema.paths[path] !== "undefined" && + typeof compiledSchema.paths[path][method] !== "undefined" && + typeof compiledSchema.paths[path][method].requestBody !== "undefined" && + typeof compiledSchema.paths[path][method].requestBody.content !== "undefined" && + typeof compiledSchema.paths[path][method].requestBody.content["application/json"] !== "undefined" && + typeof compiledSchema.paths[path][method].requestBody.content["application/json"].schema !== "undefined" + ) { + return compiledSchema.paths[path][method].requestBody.content["application/json"].schema; + } + return null; +}; + +export { getCompiledSchema, getValidationSchema }; diff --git a/backend/schema/paths/audit-log/get.json b/backend/schema/paths/audit-log/get.json index bc43e29d..62c09ce1 100644 --- a/backend/schema/paths/audit-log/get.json +++ b/backend/schema/paths/audit-log/get.json @@ -1,10 +1,10 @@ { - "operationId": "getAuditLog", - "summary": "Get Audit Log", - "tags": ["Audit Log"], + "operationId": "getAuditLogs", + "summary": "Get Audit Logs", + "tags": ["audit-log"], "security": [ { - "BearerAuth": ["audit-log"] + "bearerAuth": ["admin"] } ], "responses": { @@ -44,7 +44,7 @@ } }, "schema": { - "$ref": "../../components/audit-log-object.json" + "$ref": "../../components/audit-log-list.json" } } } diff --git a/backend/schema/paths/audit-log/id/get.json b/backend/schema/paths/audit-log/id/get.json new file mode 100644 index 00000000..38ff1c76 --- /dev/null +++ b/backend/schema/paths/audit-log/id/get.json @@ -0,0 +1,72 @@ +{ + "operationId": "getAuditLog", + "summary": "Get Audit Log Event", + "tags": ["audit-log"], + "security": [ + { + "bearerAuth": [ + "admin" + ] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "description": "Audit Log Event ID", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "example": 1 + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "id": 1, + "created_on": "2025-09-15T17:27:45.000Z", + "modified_on": "2025-09-15T17:27:45.000Z", + "user_id": 1, + "object_type": "user", + "object_id": 1, + "action": "created", + "meta": { + "id": 1, + "created_on": "2025-09-15T17:27:45.000Z", + "modified_on": "2025-09-15T17:27:45.000Z", + "is_disabled": false, + "email": "jc@jc21.com", + "name": "Jamie", + "nickname": "Jamie", + "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", + "roles": [ + "admin" + ], + "permissions": { + "visibility": "all", + "proxy_hosts": "manage", + "redirection_hosts": "manage", + "dead_hosts": "manage", + "streams": "manage", + "access_lists": "manage", + "certificates": "manage" + } + } + } + } + }, + "schema": { + "$ref": "../../../components/audit-log-object.json" + } + } + } + } + } +} diff --git a/backend/schema/paths/get.json b/backend/schema/paths/get.json index 8c3a4e02..9f6ba2a9 100644 --- a/backend/schema/paths/get.json +++ b/backend/schema/paths/get.json @@ -1,7 +1,7 @@ { "operationId": "health", "summary": "Returns the API health status", - "tags": ["Public"], + "tags": ["public"], "responses": { "200": { "description": "200 response", @@ -11,6 +11,7 @@ "default": { "value": { "status": "OK", + "setup": true, "version": { "major": 2, "minor": 1, diff --git a/backend/schema/paths/nginx/access-lists/get.json b/backend/schema/paths/nginx/access-lists/get.json index a8b9adc6..ada40f5b 100644 --- a/backend/schema/paths/nginx/access-lists/get.json +++ b/backend/schema/paths/nginx/access-lists/get.json @@ -1,10 +1,12 @@ { "operationId": "getAccessLists", "summary": "Get all access lists", - "tags": ["Access Lists"], + "tags": ["access-lists"], "security": [ { - "BearerAuth": ["access_lists"] + "bearerAuth": [ + "access_lists.view" + ] } ], "parameters": [ @@ -14,7 +16,12 @@ "description": "Expansions", "schema": { "type": "string", - "enum": ["owner", "items", "clients", "proxy_hosts"] + "enum": [ + "owner", + "items", + "clients", + "proxy_hosts" + ] } } ], @@ -23,22 +30,16 @@ "description": "200 response", "content": { "application/json": { - "examples": { - "default": { - "value": [ - { - "id": 1, - "created_on": "2024-10-08T22:15:40.000Z", - "modified_on": "2024-10-08T22:15:40.000Z", - "owner_user_id": 1, - "name": "test1234", - "meta": {}, - "satisfy_any": true, - "pass_auth": false, - "proxy_host_count": 0 - } - ] - } + "example": { + "id": 1, + "created_on": "2024-10-08T22:15:40.000Z", + "modified_on": "2024-10-08T22:15:40.000Z", + "owner_user_id": 1, + "name": "test1234", + "meta": {}, + "satisfy_any": true, + "pass_auth": false, + "proxy_host_count": 0 }, "schema": { "$ref": "../../../components/access-list-object.json" diff --git a/backend/schema/paths/nginx/access-lists/listID/delete.json b/backend/schema/paths/nginx/access-lists/listID/delete.json index 073585c8..182a883a 100644 --- a/backend/schema/paths/nginx/access-lists/listID/delete.json +++ b/backend/schema/paths/nginx/access-lists/listID/delete.json @@ -1,16 +1,17 @@ { "operationId": "deleteAccessList", "summary": "Delete a Access List", - "tags": ["Access Lists"], + "tags": ["access-lists"], "security": [ { - "BearerAuth": ["access_lists"] + "bearerAuth": ["access_lists.manage"] } ], "parameters": [ { "in": "path", "name": "listID", + "description": "Access List ID", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/access-lists/listID/get.json b/backend/schema/paths/nginx/access-lists/listID/get.json index e67023f8..9705826f 100644 --- a/backend/schema/paths/nginx/access-lists/listID/get.json +++ b/backend/schema/paths/nginx/access-lists/listID/get.json @@ -1,49 +1,54 @@ { - "operationId": "getAccessList", - "summary": "Get a access List", - "tags": ["Access Lists"], - "security": [ - { - "BearerAuth": ["access_lists"] - } - ], - "parameters": [ - { - "in": "path", - "name": "listID", - "schema": { - "type": "integer", - "minimum": 1 - }, - "required": true, - "example": 1 - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "id": 1, - "created_on": "2020-01-30T09:36:08.000Z", - "modified_on": "2020-01-30T09:41:04.000Z", - "is_disabled": false, - "email": "jc@jc21.com", - "name": "Jamie Curnow", - "nickname": "James", - "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": ["admin"] - } - } - }, - "schema": { - "$ref": "../../../../components/access-list-object.json" - } - } - } - } - } + "operationId": "getAccessList", + "summary": "Get a access List", + "tags": [ + "access-lists" + ], + "security": [ + { + "bearerAuth": [ + "access_lists.view" + ] + } + ], + "parameters": [ + { + "in": "path", + "name": "listID", + "description": "Access List ID", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "example": 1 + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "id": 1, + "created_on": "2025-10-28T04:06:55.000Z", + "modified_on": "2025-10-29T22:48:20.000Z", + "owner_user_id": 1, + "name": "My Access List", + "meta": {}, + "satisfy_any": false, + "pass_auth": false, + "proxy_host_count": 1 + } + } + }, + "schema": { + "$ref": "../../../../components/access-list-object.json" + } + } + } + } + } } diff --git a/backend/schema/paths/nginx/access-lists/listID/put.json b/backend/schema/paths/nginx/access-lists/listID/put.json index 7f887dad..61e80440 100644 --- a/backend/schema/paths/nginx/access-lists/listID/put.json +++ b/backend/schema/paths/nginx/access-lists/listID/put.json @@ -1,16 +1,17 @@ { "operationId": "updateAccessList", "summary": "Update a Access List", - "tags": ["Access Lists"], + "tags": ["access-lists"], "security": [ { - "BearerAuth": ["access_lists"] + "bearerAuth": ["access_lists.manage"] } ], "parameters": [ { "in": "path", "name": "listID", + "description": "Access List ID", "schema": { "type": "integer", "minimum": 1 @@ -39,50 +40,29 @@ "$ref": "../../../../components/access-list-object.json#/properties/pass_auth" }, "items": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "username": { - "type": "string", - "minLength": 1 - }, - "password": { - "type": "string" - } - } - } + "$ref": "../../../../common.json#/properties/access_items" }, "clients": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "address": { - "oneOf": [ - { - "type": "string", - "pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" - }, - { - "type": "string", - "pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$" - }, - { - "type": "string", - "pattern": "^all$" - } - ] - }, - "directive": { - "$ref": "../../../../components/access-list-object.json#/properties/directive" - } - } - } + "$ref": "../../../../common.json#/properties/access_clients" } } + }, + "example": { + "name": "My Access List", + "satisfy_any": true, + "pass_auth": false, + "items": [ + { + "username": "admin2", + "password": "pass2" + } + ], + "clients": [ + { + "directive": "allow", + "address": "192.168.0.0/24" + } + ] } } } @@ -108,7 +88,6 @@ "id": 1, "created_on": "2024-10-07T22:43:55.000Z", "modified_on": "2024-10-08T12:52:54.000Z", - "is_deleted": false, "is_disabled": false, "email": "admin@example.com", "name": "Administrator", diff --git a/backend/schema/paths/nginx/access-lists/post.json b/backend/schema/paths/nginx/access-lists/post.json index 4c5a4edd..38b7003a 100644 --- a/backend/schema/paths/nginx/access-lists/post.json +++ b/backend/schema/paths/nginx/access-lists/post.json @@ -1,10 +1,12 @@ { "operationId": "createAccessList", "summary": "Create a Access List", - "tags": ["Access Lists"], + "tags": ["access-lists"], "security": [ { - "BearerAuth": ["access_lists"] + "bearerAuth": [ + "access_lists.manage" + ] } ], "requestBody": { @@ -15,7 +17,9 @@ "schema": { "type": "object", "additionalProperties": false, - "required": ["name"], + "required": [ + "name" + ], "properties": { "name": { "$ref": "../../../components/access-list-object.json#/properties/name" @@ -27,54 +31,29 @@ "$ref": "../../../components/access-list-object.json#/properties/pass_auth" }, "items": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "username": { - "type": "string", - "minLength": 1 - }, - "password": { - "type": "string", - "minLength": 1 - } - } - } + "$ref": "../../../common.json#/properties/access_items" }, "clients": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "address": { - "oneOf": [ - { - "type": "string", - "pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" - }, - { - "type": "string", - "pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$" - }, - { - "type": "string", - "pattern": "^all$" - } - ] - }, - "directive": { - "$ref": "../../../components/access-list-object.json#/properties/directive" - } - } - } - }, - "meta": { - "$ref": "../../../components/access-list-object.json#/properties/meta" + "$ref": "../../../common.json#/properties/access_clients" } } + }, + "example": { + "name": "My Access List", + "satisfy_any": true, + "pass_auth": false, + "items": [ + { + "username": "admin", + "password": "pass" + } + ], + "clients": [ + { + "directive": "allow", + "address": "192.168.0.0/24" + } + ] } } } @@ -100,13 +79,14 @@ "id": 1, "created_on": "2024-10-07T22:43:55.000Z", "modified_on": "2024-10-08T12:52:54.000Z", - "is_deleted": false, "is_disabled": false, "email": "admin@example.com", "name": "Administrator", "nickname": "some guy", "avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm", - "roles": ["admin"] + "roles": [ + "admin" + ] }, "items": [ { diff --git a/backend/schema/paths/nginx/certificates/certID/delete.json b/backend/schema/paths/nginx/certificates/certID/delete.json index 0d40bcb8..a99f619d 100644 --- a/backend/schema/paths/nginx/certificates/certID/delete.json +++ b/backend/schema/paths/nginx/certificates/certID/delete.json @@ -1,16 +1,17 @@ { "operationId": "deleteCertificate", "summary": "Delete a Certificate", - "tags": ["Certificates"], + "tags": ["certificates"], "security": [ { - "BearerAuth": ["certificates"] + "bearerAuth": ["certificates.manage"] } ], "parameters": [ { "in": "path", "name": "certID", + "description": "Certificate ID", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/certificates/certID/download/get.json b/backend/schema/paths/nginx/certificates/certID/download/get.json index 4b858cae..7c18d019 100644 --- a/backend/schema/paths/nginx/certificates/certID/download/get.json +++ b/backend/schema/paths/nginx/certificates/certID/download/get.json @@ -1,16 +1,17 @@ { "operationId": "downloadCertificate", "summary": "Downloads a Certificate", - "tags": ["Certificates"], + "tags": ["certificates"], "security": [ { - "BearerAuth": ["certificates"] + "bearerAuth": ["certificates.manage"] } ], "parameters": [ { "in": "path", "name": "certID", + "description": "Certificate ID", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/certificates/certID/get.json b/backend/schema/paths/nginx/certificates/certID/get.json index 22317b33..46afbf87 100644 --- a/backend/schema/paths/nginx/certificates/certID/get.json +++ b/backend/schema/paths/nginx/certificates/certID/get.json @@ -1,16 +1,17 @@ { "operationId": "getCertificate", "summary": "Get a Certificate", - "tags": ["Certificates"], + "tags": ["certificates"], "security": [ { - "BearerAuth": ["certificates"] + "bearerAuth": ["certificates.view"] } ], "parameters": [ { "in": "path", "name": "certID", + "description": "Certificate ID", "schema": { "type": "integer", "minimum": 1 @@ -36,8 +37,6 @@ "domain_names": ["test.example.com"], "expires_on": "2025-01-07T04:34:18.000Z", "meta": { - "letsencrypt_email": "jc@jc21.com", - "letsencrypt_agree": true, "dns_challenge": false } } diff --git a/backend/schema/paths/nginx/certificates/certID/renew/post.json b/backend/schema/paths/nginx/certificates/certID/renew/post.json index ef4d20e5..4466d38d 100644 --- a/backend/schema/paths/nginx/certificates/certID/renew/post.json +++ b/backend/schema/paths/nginx/certificates/certID/renew/post.json @@ -1,16 +1,17 @@ { "operationId": "renewCertificate", "summary": "Renews a Certificate", - "tags": ["Certificates"], + "tags": ["certificates"], "security": [ { - "BearerAuth": ["certificates"] + "bearerAuth": ["certificates.manage"] } ], "parameters": [ { "in": "path", "name": "certID", + "description": "Certificate ID", "schema": { "type": "integer", "minimum": 1 @@ -32,13 +33,10 @@ "id": 4, "created_on": "2024-10-09T05:31:58.000Z", "owner_user_id": 1, - "is_deleted": false, "provider": "letsencrypt", "nice_name": "My Test Cert", "domain_names": ["test.jc21.supernerd.pro"], "meta": { - "letsencrypt_email": "jc@jc21.com", - "letsencrypt_agree": true, "dns_challenge": false } } diff --git a/backend/schema/paths/nginx/certificates/certID/upload/post.json b/backend/schema/paths/nginx/certificates/certID/upload/post.json index f38b8102..2b1ba3e6 100644 --- a/backend/schema/paths/nginx/certificates/certID/upload/post.json +++ b/backend/schema/paths/nginx/certificates/certID/upload/post.json @@ -1,16 +1,17 @@ { "operationId": "uploadCertificate", "summary": "Uploads a custom Certificate", - "tags": ["Certificates"], + "tags": ["certificates"], "security": [ { - "BearerAuth": ["certificates"] + "bearerAuth": ["certificates.manage"] } ], "parameters": [ { "in": "path", "name": "certID", + "description": "Certificate ID", "schema": { "type": "integer", "minimum": 1 @@ -20,28 +21,7 @@ } ], "requestBody": { - "description": "Certificate Files", - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "additionalProperties": false, - "required": ["certificate", "certificate_key"], - "properties": { - "certificate": { - "type": "string" - }, - "certificate_key": { - "type": "string" - }, - "intermediate_certificate": { - "type": "string" - } - } - } - } - } + "$ref": "../../../../../common.json#/properties/certificate_files" }, "responses": { "200": { @@ -63,15 +43,18 @@ "properties": { "certificate": { "type": "string", - "minLength": 1 + "minLength": 1, + "example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----" }, "certificate_key": { "type": "string", - "minLength": 1 + "minLength": 1, + "example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----" }, "intermediate_certificate": { "type": "string", - "minLength": 1 + "minLength": 1, + "example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----" } } } diff --git a/backend/schema/paths/nginx/certificates/dns-providers/get.json b/backend/schema/paths/nginx/certificates/dns-providers/get.json new file mode 100644 index 00000000..3efb1a06 --- /dev/null +++ b/backend/schema/paths/nginx/certificates/dns-providers/get.json @@ -0,0 +1,48 @@ +{ + "operationId": "getDNSProviders", + "summary": "Get DNS Providers for Certificates", + "tags": ["certificates"], + "security": [ + { + "bearerAuth": ["certificates.view"] + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "examples": { + "default": { + "value": [ + { + "id": "vultr", + "name": "Vultr", + "credentials": "dns_vultr_key = YOUR_VULTR_API_KEY" + }, + { + "id": "websupport", + "name": "Websupport.sk", + "credentials": "dns_websupport_identifier = \ndns_websupport_secret_key = " + }, + { + "id": "wedos", + "name": "Wedos", + "credentials": "dns_wedos_user = \ndns_wedos_auth = " + }, + { + "id": "zoneedit", + "name": "ZoneEdit", + "credentials": "dns_zoneedit_user = \ndns_zoneedit_token = " + } + ] + } + }, + "schema": { + "$ref": "../../../../components/dns-providers-list.json" + } + } + } + } + } +} diff --git a/backend/schema/paths/nginx/certificates/get.json b/backend/schema/paths/nginx/certificates/get.json index 2f4b556a..5884c972 100644 --- a/backend/schema/paths/nginx/certificates/get.json +++ b/backend/schema/paths/nginx/certificates/get.json @@ -1,10 +1,10 @@ { "operationId": "getCertificates", "summary": "Get all certificates", - "tags": ["Certificates"], + "tags": ["certificates"], "security": [ { - "BearerAuth": ["certificates"] + "bearerAuth": ["certificates.view"] } ], "parameters": [ @@ -36,8 +36,6 @@ "domain_names": ["test.example.com"], "expires_on": "2025-01-07T04:34:18.000Z", "meta": { - "letsencrypt_email": "jc@jc21.com", - "letsencrypt_agree": true, "dns_challenge": false } } diff --git a/backend/schema/paths/nginx/certificates/post.json b/backend/schema/paths/nginx/certificates/post.json index 5a3306c2..15406c8b 100644 --- a/backend/schema/paths/nginx/certificates/post.json +++ b/backend/schema/paths/nginx/certificates/post.json @@ -1,10 +1,10 @@ { "operationId": "createCertificate", "summary": "Create a Certificate", - "tags": ["Certificates"], + "tags": ["certificates"], "security": [ { - "BearerAuth": ["certificates"] + "bearerAuth": ["certificates.manage"] } ], "requestBody": { @@ -30,6 +30,13 @@ "$ref": "../../../components/certificate-object.json#/properties/meta" } } + }, + "example": { + "provider": "letsencrypt", + "domain_names": ["test.example.com"], + "meta": { + "dns_challenge": false + } } } } @@ -47,13 +54,10 @@ "id": 5, "created_on": "2024-10-09 05:28:35", "owner_user_id": 1, - "is_deleted": false, "provider": "letsencrypt", "nice_name": "test.example.com", "domain_names": ["test.example.com"], "meta": { - "letsencrypt_email": "jc@jc21.com", - "letsencrypt_agree": true, "dns_challenge": false, "letsencrypt_certificate": { "cn": "test.example.com", diff --git a/backend/schema/paths/nginx/certificates/test-http/get.json b/backend/schema/paths/nginx/certificates/test-http/post.json similarity index 51% rename from backend/schema/paths/nginx/certificates/test-http/get.json rename to backend/schema/paths/nginx/certificates/test-http/post.json index 2b9a8dd3..4d738d01 100644 --- a/backend/schema/paths/nginx/certificates/test-http/get.json +++ b/backend/schema/paths/nginx/certificates/test-http/post.json @@ -1,24 +1,30 @@ { "operationId": "testHttpReach", "summary": "Test HTTP Reachability", - "tags": ["Certificates"], + "tags": ["certificates"], "security": [ { - "BearerAuth": ["certificates"] + "bearerAuth": ["certificates.view"] } ], - "parameters": [ - { - "in": "query", - "name": "domains", - "description": "Expansions", - "required": true, - "schema": { - "type": "string", - "example": "[\"test.example.ord\",\"test.example.com\",\"nonexistent.example.com\"]" + "requestBody": { + "description": "Test Payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "required": ["domains"], + "properties": { + "domains": { + "$ref": "../../../../common.json#/properties/domain_names" + } + } + } } } - ], + }, "responses": { "200": { "description": "200 response", diff --git a/backend/schema/paths/nginx/certificates/validate/post.json b/backend/schema/paths/nginx/certificates/validate/post.json index 21eb325e..9fa2bd12 100644 --- a/backend/schema/paths/nginx/certificates/validate/post.json +++ b/backend/schema/paths/nginx/certificates/validate/post.json @@ -1,35 +1,14 @@ { "operationId": "validateCertificates", "summary": "Validates given Custom Certificates", - "tags": ["Certificates"], + "tags": ["certificates"], "security": [ { - "BearerAuth": ["certificates"] + "bearerAuth": ["certificates.manage"] } ], "requestBody": { - "description": "Certificate Files", - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "additionalProperties": false, - "required": ["certificate", "certificate_key"], - "properties": { - "certificate": { - "type": "string" - }, - "certificate_key": { - "type": "string" - }, - "intermediate_certificate": { - "type": "string" - } - } - } - } - } + "$ref": "../../../../common.json#/properties/certificate_files" }, "responses": { "200": { @@ -62,10 +41,12 @@ "required": ["cn", "issuer", "dates"], "properties": { "cn": { - "type": "string" + "type": "string", + "example": "example.com" }, "issuer": { - "type": "string" + "type": "string", + "example": "C = US, O = Let's Encrypt, CN = E5" }, "dates": { "type": "object", @@ -78,12 +59,17 @@ "to": { "type": "integer" } + }, + "example": { + "from": 1728448218, + "to": 1736224217 } } } }, "certificate_key": { - "type": "boolean" + "type": "boolean", + "example": true } } } diff --git a/backend/schema/paths/nginx/dead-hosts/get.json b/backend/schema/paths/nginx/dead-hosts/get.json index 8a11a3f6..feb04ff2 100644 --- a/backend/schema/paths/nginx/dead-hosts/get.json +++ b/backend/schema/paths/nginx/dead-hosts/get.json @@ -1,10 +1,10 @@ { "operationId": "getDeadHosts", "summary": "Get all 404 hosts", - "tags": ["404 Hosts"], + "tags": ["404-hosts"], "security": [ { - "BearerAuth": ["dead_hosts"] + "bearerAuth": ["dead_hosts.view"] } ], "parameters": [ diff --git a/backend/schema/paths/nginx/dead-hosts/hostID/delete.json b/backend/schema/paths/nginx/dead-hosts/hostID/delete.json index f3aa81a5..eed0ebc0 100644 --- a/backend/schema/paths/nginx/dead-hosts/hostID/delete.json +++ b/backend/schema/paths/nginx/dead-hosts/hostID/delete.json @@ -1,16 +1,17 @@ { "operationId": "deleteDeadHost", "summary": "Delete a 404 Host", - "tags": ["404 Hosts"], + "tags": ["404-hosts"], "security": [ { - "BearerAuth": ["dead_hosts"] + "bearerAuth": ["dead_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the 404 Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/dead-hosts/hostID/disable/post.json b/backend/schema/paths/nginx/dead-hosts/hostID/disable/post.json index 2cdcecf4..2a4d08ae 100644 --- a/backend/schema/paths/nginx/dead-hosts/hostID/disable/post.json +++ b/backend/schema/paths/nginx/dead-hosts/hostID/disable/post.json @@ -1,16 +1,17 @@ { "operationId": "disableDeadHost", "summary": "Disable a 404 Host", - "tags": ["404 Hosts"], + "tags": ["404-hosts"], "security": [ { - "BearerAuth": ["dead_hosts"] + "bearerAuth": ["dead_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the 404 Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/dead-hosts/hostID/enable/post.json b/backend/schema/paths/nginx/dead-hosts/hostID/enable/post.json index ca3ce9fa..512c2a0a 100644 --- a/backend/schema/paths/nginx/dead-hosts/hostID/enable/post.json +++ b/backend/schema/paths/nginx/dead-hosts/hostID/enable/post.json @@ -1,16 +1,17 @@ { "operationId": "enableDeadHost", "summary": "Enable a 404 Host", - "tags": ["404 Hosts"], + "tags": ["404-hosts"], "security": [ { - "BearerAuth": ["dead_hosts"] + "bearerAuth": ["dead_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the 404 Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/dead-hosts/hostID/get.json b/backend/schema/paths/nginx/dead-hosts/hostID/get.json index 47e2f8b1..a3c24edc 100644 --- a/backend/schema/paths/nginx/dead-hosts/hostID/get.json +++ b/backend/schema/paths/nginx/dead-hosts/hostID/get.json @@ -1,16 +1,17 @@ { "operationId": "getDeadHost", "summary": "Get a 404 Host", - "tags": ["404 Hosts"], + "tags": ["404-hosts"], "security": [ { - "BearerAuth": ["dead_hosts"] + "bearerAuth": ["dead_hosts.view"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the 404 Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/dead-hosts/hostID/put.json b/backend/schema/paths/nginx/dead-hosts/hostID/put.json index f9505ed4..e07217c4 100644 --- a/backend/schema/paths/nginx/dead-hosts/hostID/put.json +++ b/backend/schema/paths/nginx/dead-hosts/hostID/put.json @@ -1,16 +1,17 @@ { "operationId": "updateDeadHost", "summary": "Update a 404 Host", - "tags": ["404 Hosts"], + "tags": ["404-hosts"], "security": [ { - "BearerAuth": ["dead_hosts"] + "bearerAuth": ["dead_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the 404 Host", "schema": { "type": "integer", "minimum": 1 @@ -86,7 +87,6 @@ "id": 1, "created_on": "2024-10-09T00:59:56.000Z", "modified_on": "2024-10-09T00:59:56.000Z", - "is_deleted": false, "is_disabled": false, "email": "admin@example.com", "name": "Administrator", diff --git a/backend/schema/paths/nginx/dead-hosts/post.json b/backend/schema/paths/nginx/dead-hosts/post.json index c8bbb693..1ec38720 100644 --- a/backend/schema/paths/nginx/dead-hosts/post.json +++ b/backend/schema/paths/nginx/dead-hosts/post.json @@ -1,10 +1,12 @@ { "operationId": "create404Host", "summary": "Create a 404 Host", - "tags": ["404 Hosts"], + "tags": ["404-hosts"], "security": [ { - "BearerAuth": ["dead_hosts"] + "bearerAuth": [ + "dead_hosts.manage" + ] } ], "requestBody": { @@ -15,7 +17,9 @@ "schema": { "type": "object", "additionalProperties": false, - "required": ["domain_names"], + "required": [ + "domain_names" + ], "properties": { "domain_names": { "$ref": "../../../components/dead-host-object.json#/properties/domain_names" @@ -42,6 +46,18 @@ "$ref": "../../../components/dead-host-object.json#/properties/meta" } } + }, + "example": { + "domain_names": [ + "test.example.com" + ], + "certificate_id": 0, + "ssl_forced": false, + "advanced_config": "", + "http2_support": false, + "hsts_enabled": false, + "hsts_subdomains": false, + "meta": {} } } } @@ -58,7 +74,9 @@ "created_on": "2024-10-09T01:38:52.000Z", "modified_on": "2024-10-09T01:38:52.000Z", "owner_user_id": 1, - "domain_names": ["test.example.com"], + "domain_names": [ + "test.example.com" + ], "certificate_id": 0, "ssl_forced": false, "advanced_config": "", @@ -72,13 +90,14 @@ "id": 1, "created_on": "2024-10-09T00:59:56.000Z", "modified_on": "2024-10-09T00:59:56.000Z", - "is_deleted": false, "is_disabled": false, "email": "admin@example.com", "name": "Administrator", "nickname": "Admin", "avatar": "", - "roles": ["admin"] + "roles": [ + "admin" + ] } } } diff --git a/backend/schema/paths/nginx/proxy-hosts/get.json b/backend/schema/paths/nginx/proxy-hosts/get.json index 1d9f6335..7f8cb148 100644 --- a/backend/schema/paths/nginx/proxy-hosts/get.json +++ b/backend/schema/paths/nginx/proxy-hosts/get.json @@ -1,10 +1,12 @@ { "operationId": "getProxyHosts", "summary": "Get all proxy hosts", - "tags": ["Proxy Hosts"], + "tags": ["proxy-hosts"], "security": [ { - "BearerAuth": ["proxy_hosts"] + "bearerAuth": [ + "proxy_hosts.view" + ] } ], "parameters": [ @@ -14,7 +16,11 @@ "description": "Expansions", "schema": { "type": "string", - "enum": ["access_list", "owner", "certificate"] + "enum": [ + "access_list", + "owner", + "certificate" + ] } } ], @@ -28,14 +34,16 @@ "value": [ { "id": 1, - "created_on": "2024-10-08T23:23:03.000Z", - "modified_on": "2024-10-08T23:23:04.000Z", + "created_on": "2025-10-28T01:10:26.000Z", + "modified_on": "2025-10-28T04:07:16.000Z", "owner_user_id": 1, - "domain_names": ["test.example.com"], + "domain_names": [ + "test.jc21com" + ], "forward_host": "127.0.0.1", - "forward_port": 8989, - "access_list_id": 0, - "certificate_id": 0, + "forward_port": 8081, + "access_list_id": 1, + "certificate_id": 1, "ssl_forced": false, "caching_enabled": false, "block_exploits": false, @@ -48,7 +56,7 @@ "http2_support": false, "forward_scheme": "http", "enabled": true, - "locations": null, + "locations": [], "hsts_enabled": false, "hsts_subdomains": false } diff --git a/backend/schema/paths/nginx/proxy-hosts/hostID/delete.json b/backend/schema/paths/nginx/proxy-hosts/hostID/delete.json index 991ef0e9..da67944d 100644 --- a/backend/schema/paths/nginx/proxy-hosts/hostID/delete.json +++ b/backend/schema/paths/nginx/proxy-hosts/hostID/delete.json @@ -1,16 +1,17 @@ { "operationId": "deleteProxyHost", "summary": "Delete a Proxy Host", - "tags": ["Proxy Hosts"], + "tags": ["proxy-hosts"], "security": [ { - "BearerAuth": ["proxy_hosts"] + "bearerAuth": ["proxy_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the Proxy Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/proxy-hosts/hostID/disable/post.json b/backend/schema/paths/nginx/proxy-hosts/hostID/disable/post.json index 54ff8a66..14c2689c 100644 --- a/backend/schema/paths/nginx/proxy-hosts/hostID/disable/post.json +++ b/backend/schema/paths/nginx/proxy-hosts/hostID/disable/post.json @@ -1,16 +1,17 @@ { "operationId": "disableProxyHost", "summary": "Disable a Proxy Host", - "tags": ["Proxy Hosts"], + "tags": ["proxy-hosts"], "security": [ { - "BearerAuth": ["proxy_hosts"] + "bearerAuth": ["proxy_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the Proxy Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/proxy-hosts/hostID/enable/post.json b/backend/schema/paths/nginx/proxy-hosts/hostID/enable/post.json index 9f052de0..fe504b78 100644 --- a/backend/schema/paths/nginx/proxy-hosts/hostID/enable/post.json +++ b/backend/schema/paths/nginx/proxy-hosts/hostID/enable/post.json @@ -1,16 +1,17 @@ { "operationId": "enableProxyHost", "summary": "Enable a Proxy Host", - "tags": ["Proxy Hosts"], + "tags": ["proxy-hosts"], "security": [ { - "BearerAuth": ["proxy_hosts"] + "bearerAuth": ["proxy_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the Proxy Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/proxy-hosts/hostID/get.json b/backend/schema/paths/nginx/proxy-hosts/hostID/get.json index 5e10a9cf..351451c4 100644 --- a/backend/schema/paths/nginx/proxy-hosts/hostID/get.json +++ b/backend/schema/paths/nginx/proxy-hosts/hostID/get.json @@ -1,16 +1,19 @@ { "operationId": "getProxyHost", "summary": "Get a Proxy Host", - "tags": ["Proxy Hosts"], + "tags": ["proxy-hosts"], "security": [ { - "BearerAuth": ["proxy_hosts"] + "bearerAuth": [ + "proxy_hosts.view" + ] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the Proxy Host", "schema": { "type": "integer", "minimum": 1 @@ -27,13 +30,15 @@ "examples": { "default": { "value": { - "id": 1, - "created_on": "2024-10-08T23:23:03.000Z", - "modified_on": "2024-10-08T23:26:38.000Z", + "id": 3, + "created_on": "2025-10-30T01:12:05.000Z", + "modified_on": "2025-10-30T01:12:05.000Z", "owner_user_id": 1, - "domain_names": ["test.example.com"], - "forward_host": "192.168.0.10", - "forward_port": 8989, + "domain_names": [ + "test.example.com" + ], + "forward_host": "127.0.0.1", + "forward_port": 8080, "access_list_id": 0, "certificate_id": 0, "ssl_forced": false, @@ -48,9 +53,22 @@ "http2_support": false, "forward_scheme": "http", "enabled": true, - "locations": null, + "locations": [], "hsts_enabled": false, - "hsts_subdomains": false + "hsts_subdomains": false, + "owner": { + "id": 1, + "created_on": "2025-10-28T00:50:24.000Z", + "modified_on": "2025-10-28T00:50:24.000Z", + "is_disabled": false, + "email": "jc@jc21.com", + "name": "jamiec", + "nickname": "jamiec", + "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", + "roles": [ + "admin" + ] + } } } }, diff --git a/backend/schema/paths/nginx/proxy-hosts/hostID/put.json b/backend/schema/paths/nginx/proxy-hosts/hostID/put.json index 5cab6e75..7ae60e1a 100644 --- a/backend/schema/paths/nginx/proxy-hosts/hostID/put.json +++ b/backend/schema/paths/nginx/proxy-hosts/hostID/put.json @@ -1,16 +1,19 @@ { "operationId": "updateProxyHost", "summary": "Update a Proxy Host", - "tags": ["Proxy Hosts"], + "tags": ["proxy-hosts"], "security": [ { - "BearerAuth": ["proxy_hosts"] + "bearerAuth": [ + "proxy_hosts.manage" + ] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the Proxy Host", "schema": { "type": "integer", "minimum": 1 @@ -93,13 +96,15 @@ "examples": { "default": { "value": { - "id": 1, - "created_on": "2024-10-08T23:23:03.000Z", - "modified_on": "2024-10-08T23:26:37.000Z", + "id": 3, + "created_on": "2025-10-30T01:12:05.000Z", + "modified_on": "2025-10-30T01:17:06.000Z", "owner_user_id": 1, - "domain_names": ["test.example.com"], - "forward_host": "192.168.0.10", - "forward_port": 8989, + "domain_names": [ + "test.example.com" + ], + "forward_host": "127.0.0.1", + "forward_port": 8080, "access_list_id": 0, "certificate_id": 0, "ssl_forced": false, @@ -114,19 +119,21 @@ "http2_support": false, "forward_scheme": "http", "enabled": true, + "locations": [], "hsts_enabled": false, "hsts_subdomains": false, "owner": { "id": 1, - "created_on": "2024-10-07T22:43:55.000Z", - "modified_on": "2024-10-08T12:52:54.000Z", - "is_deleted": false, + "created_on": "2025-10-28T00:50:24.000Z", + "modified_on": "2025-10-28T00:50:24.000Z", "is_disabled": false, - "email": "admin@example.com", - "name": "Administrator", - "nickname": "some guy", - "avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm", - "roles": ["admin"] + "email": "jc@jc21.com", + "name": "jamiec", + "nickname": "jamiec", + "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", + "roles": [ + "admin" + ] }, "certificate": null, "access_list": null diff --git a/backend/schema/paths/nginx/proxy-hosts/post.json b/backend/schema/paths/nginx/proxy-hosts/post.json index 85455fb6..77d772e9 100644 --- a/backend/schema/paths/nginx/proxy-hosts/post.json +++ b/backend/schema/paths/nginx/proxy-hosts/post.json @@ -1,10 +1,12 @@ { "operationId": "createProxyHost", "summary": "Create a Proxy Host", - "tags": ["Proxy Hosts"], + "tags": ["proxy-hosts"], "security": [ { - "BearerAuth": ["proxy_hosts"] + "bearerAuth": [ + "proxy_hosts.manage" + ] } ], "requestBody": { @@ -15,7 +17,12 @@ "schema": { "type": "object", "additionalProperties": false, - "required": ["domain_names", "forward_scheme", "forward_host", "forward_port"], + "required": [ + "domain_names", + "forward_scheme", + "forward_host", + "forward_port" + ], "properties": { "domain_names": { "$ref": "../../../components/proxy-host-object.json#/properties/domain_names" @@ -69,6 +76,14 @@ "$ref": "../../../components/proxy-host-object.json#/properties/locations" } } + }, + "example": { + "domain_names": [ + "test.example.com" + ], + "forward_scheme": "http", + "forward_host": "127.0.0.1", + "forward_port": 8080 } } } @@ -81,13 +96,15 @@ "examples": { "default": { "value": { - "id": 1, - "created_on": "2024-10-08T23:23:03.000Z", - "modified_on": "2024-10-08T23:23:03.000Z", + "id": 3, + "created_on": "2025-10-30T01:12:05.000Z", + "modified_on": "2025-10-30T01:12:05.000Z", "owner_user_id": 1, - "domain_names": ["test.example.com"], + "domain_names": [ + "test.example.com" + ], "forward_host": "127.0.0.1", - "forward_port": 8989, + "forward_port": 8080, "access_list_id": 0, "certificate_id": 0, "ssl_forced": false, @@ -99,20 +116,22 @@ "http2_support": false, "forward_scheme": "http", "enabled": true, + "locations": [], "hsts_enabled": false, "hsts_subdomains": false, "certificate": null, "owner": { "id": 1, - "created_on": "2024-10-07T22:43:55.000Z", - "modified_on": "2024-10-08T12:52:54.000Z", - "is_deleted": false, + "created_on": "2025-10-28T00:50:24.000Z", + "modified_on": "2025-10-28T00:50:24.000Z", "is_disabled": false, - "email": "admin@example.com", - "name": "Administrator", - "nickname": "some guy", - "avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm", - "roles": ["admin"] + "email": "jc@jc21.com", + "name": "jamiec", + "nickname": "jamiec", + "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", + "roles": [ + "admin" + ] }, "access_list": null } diff --git a/backend/schema/paths/nginx/redirection-hosts/get.json b/backend/schema/paths/nginx/redirection-hosts/get.json index 0b35e0fc..dfeb6049 100644 --- a/backend/schema/paths/nginx/redirection-hosts/get.json +++ b/backend/schema/paths/nginx/redirection-hosts/get.json @@ -1,10 +1,10 @@ { "operationId": "getRedirectionHosts", "summary": "Get all Redirection hosts", - "tags": ["Redirection Hosts"], + "tags": ["redirection-hosts"], "security": [ { - "BearerAuth": ["redirection_hosts"] + "bearerAuth": ["redirection_hosts.view"] } ], "parameters": [ diff --git a/backend/schema/paths/nginx/redirection-hosts/hostID/delete.json b/backend/schema/paths/nginx/redirection-hosts/hostID/delete.json index 7330f362..0b3232e8 100644 --- a/backend/schema/paths/nginx/redirection-hosts/hostID/delete.json +++ b/backend/schema/paths/nginx/redirection-hosts/hostID/delete.json @@ -1,16 +1,17 @@ { "operationId": "deleteRedirectionHost", "summary": "Delete a Redirection Host", - "tags": ["Redirection Hosts"], + "tags": ["redirection-hosts"], "security": [ { - "BearerAuth": ["redirection_hosts"] + "bearerAuth": ["redirection_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the Redirection Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/redirection-hosts/hostID/disable/post.json b/backend/schema/paths/nginx/redirection-hosts/hostID/disable/post.json index 8433220d..f44af11f 100644 --- a/backend/schema/paths/nginx/redirection-hosts/hostID/disable/post.json +++ b/backend/schema/paths/nginx/redirection-hosts/hostID/disable/post.json @@ -1,16 +1,17 @@ { "operationId": "disableRedirectionHost", "summary": "Disable a Redirection Host", - "tags": ["Redirection Hosts"], + "tags": ["redirection-hosts"], "security": [ { - "BearerAuth": ["redirection_hosts"] + "bearerAuth": ["redirection_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the Redirection Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/redirection-hosts/hostID/enable/post.json b/backend/schema/paths/nginx/redirection-hosts/hostID/enable/post.json index bef53436..07bf44dd 100644 --- a/backend/schema/paths/nginx/redirection-hosts/hostID/enable/post.json +++ b/backend/schema/paths/nginx/redirection-hosts/hostID/enable/post.json @@ -1,16 +1,17 @@ { "operationId": "enableRedirectionHost", "summary": "Enable a Redirection Host", - "tags": ["Redirection Hosts"], + "tags": ["redirection-hosts"], "security": [ { - "BearerAuth": ["redirection_hosts"] + "bearerAuth": ["redirection_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the Redirection Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/redirection-hosts/hostID/get.json b/backend/schema/paths/nginx/redirection-hosts/hostID/get.json index d780f874..577b5144 100644 --- a/backend/schema/paths/nginx/redirection-hosts/hostID/get.json +++ b/backend/schema/paths/nginx/redirection-hosts/hostID/get.json @@ -1,16 +1,17 @@ { "operationId": "getRedirectionHost", "summary": "Get a Redirection Host", - "tags": ["Redirection Hosts"], + "tags": ["redirection-hosts"], "security": [ { - "BearerAuth": ["redirection_hosts"] + "bearerAuth": ["redirection_hosts.view"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the Redirection Host", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/redirection-hosts/hostID/put.json b/backend/schema/paths/nginx/redirection-hosts/hostID/put.json index fd97cbfa..454a3880 100644 --- a/backend/schema/paths/nginx/redirection-hosts/hostID/put.json +++ b/backend/schema/paths/nginx/redirection-hosts/hostID/put.json @@ -1,16 +1,17 @@ { "operationId": "updateRedirectionHost", "summary": "Update a Redirection Host", - "tags": ["Redirection Hosts"], + "tags": ["redirection-hosts"], "security": [ { - "BearerAuth": ["redirection_hosts"] + "bearerAuth": ["redirection_hosts.manage"] } ], "parameters": [ { "in": "path", "name": "hostID", + "description": "The ID of the Redirection Host", "schema": { "type": "integer", "minimum": 1 @@ -106,7 +107,6 @@ "id": 1, "created_on": "2024-10-09T00:59:56.000Z", "modified_on": "2024-10-09T00:59:56.000Z", - "is_deleted": false, "is_disabled": false, "email": "admin@example.com", "name": "Administrator", diff --git a/backend/schema/paths/nginx/redirection-hosts/post.json b/backend/schema/paths/nginx/redirection-hosts/post.json index 5bfde2c3..6aa53301 100644 --- a/backend/schema/paths/nginx/redirection-hosts/post.json +++ b/backend/schema/paths/nginx/redirection-hosts/post.json @@ -1,10 +1,12 @@ { "operationId": "createRedirectionHost", "summary": "Create a Redirection Host", - "tags": ["Redirection Hosts"], + "tags": ["redirection-hosts"], "security": [ { - "BearerAuth": ["redirection_hosts"] + "bearerAuth": [ + "redirection_hosts.manage" + ] } ], "requestBody": { @@ -15,7 +17,12 @@ "schema": { "type": "object", "additionalProperties": false, - "required": ["domain_names", "forward_scheme", "forward_http_code", "forward_domain_name"], + "required": [ + "domain_names", + "forward_scheme", + "forward_http_code", + "forward_domain_name" + ], "properties": { "domain_names": { "$ref": "../../../components/redirection-host-object.json#/properties/domain_names" @@ -57,6 +64,23 @@ "$ref": "../../../components/redirection-host-object.json#/properties/meta" } } + }, + "example": { + "domain_names": [ + "test.example.com" + ], + "forward_domain_name": "example.com", + "forward_scheme": "auto", + "forward_http_code": 301, + "preserve_path": false, + "block_exploits": false, + "certificate_id": 0, + "ssl_forced": false, + "http2_support": false, + "hsts_enabled": false, + "hsts_subdomains": false, + "advanced_config": "", + "meta": {} } } } @@ -69,12 +93,14 @@ "examples": { "default": { "value": { - "id": 1, - "created_on": "2024-10-09T01:13:12.000Z", - "modified_on": "2024-10-09T01:13:12.000Z", + "id": 2, + "created_on": "2025-10-30T01:27:04.000Z", + "modified_on": "2025-10-30T01:27:04.000Z", "owner_user_id": 1, - "domain_names": ["test.example.com"], - "forward_domain_name": "something-else.com", + "domain_names": [ + "test.example.com" + ], + "forward_domain_name": "example.com", "preserve_path": false, "certificate_id": 0, "ssl_forced": false, @@ -85,20 +111,21 @@ "enabled": true, "hsts_enabled": false, "hsts_subdomains": false, - "forward_scheme": "http", + "forward_scheme": "auto", "forward_http_code": 301, "certificate": null, "owner": { "id": 1, - "created_on": "2024-10-09T00:59:56.000Z", - "modified_on": "2024-10-09T00:59:56.000Z", - "is_deleted": false, + "created_on": "2025-10-28T00:50:24.000Z", + "modified_on": "2025-10-28T00:50:24.000Z", "is_disabled": false, - "email": "admin@example.com", - "name": "Administrator", - "nickname": "Admin", - "avatar": "", - "roles": ["admin"] + "email": "jc@jc21.com", + "name": "jamiec", + "nickname": "jamiec", + "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", + "roles": [ + "admin" + ] } } } diff --git a/backend/schema/paths/nginx/streams/get.json b/backend/schema/paths/nginx/streams/get.json index 17969ee4..6dda8e34 100644 --- a/backend/schema/paths/nginx/streams/get.json +++ b/backend/schema/paths/nginx/streams/get.json @@ -1,10 +1,10 @@ { "operationId": "getStreams", "summary": "Get all streams", - "tags": ["Streams"], + "tags": ["streams"], "security": [ { - "BearerAuth": ["streams"] + "bearerAuth": ["streams.view"] } ], "parameters": [ diff --git a/backend/schema/paths/nginx/streams/post.json b/backend/schema/paths/nginx/streams/post.json index d26996b6..0c986de8 100644 --- a/backend/schema/paths/nginx/streams/post.json +++ b/backend/schema/paths/nginx/streams/post.json @@ -1,10 +1,12 @@ { "operationId": "createStream", "summary": "Create a Stream", - "tags": ["Streams"], + "tags": ["streams"], "security": [ { - "BearerAuth": ["streams"] + "bearerAuth": [ + "streams.manage" + ] } ], "requestBody": { @@ -15,7 +17,11 @@ "schema": { "type": "object", "additionalProperties": false, - "required": ["incoming_port", "forwarding_host", "forwarding_port"], + "required": [ + "incoming_port", + "forwarding_host", + "forwarding_port" + ], "properties": { "incoming_port": { "$ref": "../../../components/stream-object.json#/properties/incoming_port" @@ -37,8 +43,20 @@ }, "meta": { "$ref": "../../../components/stream-object.json#/properties/meta" + }, + "domain_names": { + "$ref": "../../../components/dead-host-object.json#/properties/domain_names" } } + }, + "example": { + "incoming_port": 8888, + "forwarding_host": "127.0.0.1", + "forwarding_port": 8080, + "tcp_forwarding": true, + "udp_forwarding": false, + "certificate_id": 0, + "meta": {} } } } @@ -69,13 +87,14 @@ "id": 1, "created_on": "2024-10-09T02:33:16.000Z", "modified_on": "2024-10-09T02:33:16.000Z", - "is_deleted": false, "is_disabled": false, "email": "admin@example.com", "name": "Administrator", "nickname": "Admin", "avatar": "", - "roles": ["admin"] + "roles": [ + "admin" + ] }, "certificate_id": 0 } diff --git a/backend/schema/paths/nginx/streams/streamID/delete.json b/backend/schema/paths/nginx/streams/streamID/delete.json index 3a968525..585cd82d 100644 --- a/backend/schema/paths/nginx/streams/streamID/delete.json +++ b/backend/schema/paths/nginx/streams/streamID/delete.json @@ -1,16 +1,17 @@ { "operationId": "deleteStream", "summary": "Delete a Stream", - "tags": ["Streams"], + "tags": ["streams"], "security": [ { - "BearerAuth": ["streams"] + "bearerAuth": ["streams.manage"] } ], "parameters": [ { "in": "path", "name": "streamID", + "description": "The ID of the Stream", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/streams/streamID/disable/post.json b/backend/schema/paths/nginx/streams/streamID/disable/post.json index d1c1b1c8..61de8516 100644 --- a/backend/schema/paths/nginx/streams/streamID/disable/post.json +++ b/backend/schema/paths/nginx/streams/streamID/disable/post.json @@ -1,16 +1,17 @@ { "operationId": "disableStream", "summary": "Disable a Stream", - "tags": ["Streams"], + "tags": ["streams"], "security": [ { - "BearerAuth": ["streams"] + "bearerAuth": ["streams.manage"] } ], "parameters": [ { "in": "path", "name": "streamID", + "description": "The ID of the Stream", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/streams/streamID/enable/post.json b/backend/schema/paths/nginx/streams/streamID/enable/post.json index dc914f5f..d27ce526 100644 --- a/backend/schema/paths/nginx/streams/streamID/enable/post.json +++ b/backend/schema/paths/nginx/streams/streamID/enable/post.json @@ -1,16 +1,17 @@ { "operationId": "enableStream", "summary": "Enable a Stream", - "tags": ["Streams"], + "tags": ["streams"], "security": [ { - "BearerAuth": ["streams"] + "bearerAuth": ["streams.manage"] } ], "parameters": [ { "in": "path", "name": "streamID", + "description": "The ID of the Stream", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/streams/streamID/get.json b/backend/schema/paths/nginx/streams/streamID/get.json index 801af13a..22fae887 100644 --- a/backend/schema/paths/nginx/streams/streamID/get.json +++ b/backend/schema/paths/nginx/streams/streamID/get.json @@ -1,16 +1,17 @@ { "operationId": "getStream", "summary": "Get a Stream", - "tags": ["Streams"], + "tags": ["streams"], "security": [ { - "BearerAuth": ["streams"] + "bearerAuth": ["streams.view"] } ], "parameters": [ { "in": "path", "name": "streamID", + "description": "The ID of the Stream", "schema": { "type": "integer", "minimum": 1 diff --git a/backend/schema/paths/nginx/streams/streamID/put.json b/backend/schema/paths/nginx/streams/streamID/put.json index 14adb163..21ae71ef 100644 --- a/backend/schema/paths/nginx/streams/streamID/put.json +++ b/backend/schema/paths/nginx/streams/streamID/put.json @@ -1,16 +1,17 @@ { "operationId": "updateStream", "summary": "Update a Stream", - "tags": ["Streams"], + "tags": ["streams"], "security": [ { - "BearerAuth": ["streams"] + "bearerAuth": ["streams.manage"] } ], "parameters": [ { "in": "path", "name": "streamID", + "description": "The ID of the Stream", "schema": { "type": "integer", "minimum": 1 @@ -81,7 +82,6 @@ "id": 1, "created_on": "2024-10-09T02:33:16.000Z", "modified_on": "2024-10-09T02:33:16.000Z", - "is_deleted": false, "is_disabled": false, "email": "admin@example.com", "name": "Administrator", diff --git a/backend/schema/paths/reports/hosts/get.json b/backend/schema/paths/reports/hosts/get.json index a40ddc72..682a97b8 100644 --- a/backend/schema/paths/reports/hosts/get.json +++ b/backend/schema/paths/reports/hosts/get.json @@ -1,10 +1,10 @@ { "operationId": "reportsHosts", "summary": "Report on Host Statistics", - "tags": ["Reports"], + "tags": ["reports"], "security": [ { - "BearerAuth": ["reports"] + "bearerAuth": [] } ], "responses": { @@ -27,19 +27,23 @@ "properties": { "proxy": { "type": "integer", - "description": "Proxy Hosts Count" + "description": "Proxy Hosts Count", + "example": 20 }, "redirection": { "type": "integer", - "description": "Redirection Hosts Count" + "description": "Redirection Hosts Count", + "example": 2 }, "stream": { "type": "integer", - "description": "Streams Count" + "description": "Streams Count", + "example": 0 }, "dead": { "type": "integer", - "description": "404 Hosts Count" + "description": "404 Hosts Count", + "example": 3 } } } diff --git a/backend/schema/paths/schema/get.json b/backend/schema/paths/schema/get.json index d435b004..c9e601ea 100644 --- a/backend/schema/paths/schema/get.json +++ b/backend/schema/paths/schema/get.json @@ -1,7 +1,7 @@ { "operationId": "schema", "summary": "Returns this swagger API schema", - "tags": ["Public"], + "tags": ["public"], "responses": { "200": { "description": "200 response" diff --git a/backend/schema/paths/settings/get.json b/backend/schema/paths/settings/get.json index 5d148d8a..a99f7f22 100644 --- a/backend/schema/paths/settings/get.json +++ b/backend/schema/paths/settings/get.json @@ -1,10 +1,10 @@ { "operationId": "getSettings", "summary": "Get all settings", - "tags": ["Settings"], + "tags": ["settings"], "security": [ { - "BearerAuth": ["settings"] + "bearerAuth": ["admin"] } ], "responses": { diff --git a/backend/schema/paths/settings/settingID/get.json b/backend/schema/paths/settings/settingID/get.json index 405b976d..929f3e5f 100644 --- a/backend/schema/paths/settings/settingID/get.json +++ b/backend/schema/paths/settings/settingID/get.json @@ -1,10 +1,10 @@ { "operationId": "getSetting", "summary": "Get a setting", - "tags": ["Settings"], + "tags": ["settings"], "security": [ { - "BearerAuth": ["settings"] + "bearerAuth": ["admin"] } ], "parameters": [ diff --git a/backend/schema/paths/settings/settingID/put.json b/backend/schema/paths/settings/settingID/put.json index 4ca62429..050ad441 100644 --- a/backend/schema/paths/settings/settingID/put.json +++ b/backend/schema/paths/settings/settingID/put.json @@ -1,10 +1,10 @@ { "operationId": "updateSetting", "summary": "Update a setting", - "tags": ["Settings"], + "tags": ["settings"], "security": [ { - "BearerAuth": ["settings"] + "bearerAuth": ["admin"] } ], "parameters": [ @@ -34,7 +34,8 @@ "value": { "type": "string", "minLength": 1, - "enum": ["congratulations", "404", "444", "redirect", "html"] + "enum": ["congratulations", "404", "444", "redirect", "html"], + "example": "html" }, "meta": { "type": "object", @@ -46,9 +47,16 @@ "html": { "type": "string" } + }, + "example": { + "html": "

hello world

" } } } + }, + "example": { + "value": "congratulations", + "meta": {} } } } diff --git a/backend/schema/paths/tokens/get.json b/backend/schema/paths/tokens/get.json index ef842eaf..4e6ae244 100644 --- a/backend/schema/paths/tokens/get.json +++ b/backend/schema/paths/tokens/get.json @@ -1,10 +1,10 @@ { "operationId": "refreshToken", "summary": "Refresh your access token", - "tags": ["Tokens"], + "tags": ["tokens"], "security": [ { - "BearerAuth": ["tokens"] + "bearerAuth": [] } ], "responses": { diff --git a/backend/schema/paths/tokens/post.json b/backend/schema/paths/tokens/post.json index 99703ff0..f8a639ad 100644 --- a/backend/schema/paths/tokens/post.json +++ b/backend/schema/paths/tokens/post.json @@ -1,7 +1,7 @@ { "operationId": "requestToken", "summary": "Request a new access token from credentials", - "tags": ["Tokens"], + "tags": ["tokens"], "requestBody": { "description": "Credentials Payload", "required": true, @@ -12,20 +12,27 @@ "properties": { "identity": { "minLength": 1, - "type": "string" + "type": "string", + "example": "me@example.com" }, "scope": { "minLength": 1, "type": "string", - "enum": ["user"] + "enum": ["user"], + "example": "user" }, "secret": { "minLength": 1, - "type": "string" + "type": "string", + "example": "bigredhorsebanana" } }, "required": ["identity", "secret"], "type": "object" + }, + "example": { + "identity": "me@example.com", + "secret": "bigredhorsebanana" } } } @@ -37,10 +44,8 @@ "examples": { "default": { "value": { - "result": { - "expires": "2025-02-04T20:40:46.340Z", - "token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" - } + "expires": "2025-02-04T20:40:46.340Z", + "token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" } } }, diff --git a/backend/schema/paths/users/get.json b/backend/schema/paths/users/get.json index 37415301..79c31ba8 100644 --- a/backend/schema/paths/users/get.json +++ b/backend/schema/paths/users/get.json @@ -1,10 +1,10 @@ { "operationId": "getUsers", "summary": "Get all users", - "tags": ["Users"], + "tags": ["users"], "security": [ { - "BearerAuth": ["users"] + "bearerAuth": ["admin"] } ], "parameters": [ diff --git a/backend/schema/paths/users/post.json b/backend/schema/paths/users/post.json index c0213fe0..49025e32 100644 --- a/backend/schema/paths/users/post.json +++ b/backend/schema/paths/users/post.json @@ -1,10 +1,10 @@ { "operationId": "createUser", "summary": "Create a User", - "tags": ["Users"], + "tags": ["users"], "security": [ { - "BearerAuth": ["users"] + "bearerAuth": ["admin"] } ], "requestBody": { diff --git a/backend/schema/paths/users/userID/auth/put.json b/backend/schema/paths/users/userID/auth/put.json index a72f5617..3dba45b5 100644 --- a/backend/schema/paths/users/userID/auth/put.json +++ b/backend/schema/paths/users/userID/auth/put.json @@ -1,10 +1,10 @@ { "operationId": "updateUserAuth", "summary": "Update a User's Authentication", - "tags": ["Users"], + "tags": ["users"], "security": [ { - "BearerAuth": ["users"] + "bearerAuth": ["admin"] } ], "parameters": [ diff --git a/backend/schema/paths/users/userID/delete.json b/backend/schema/paths/users/userID/delete.json index 7d4f3615..767edfe8 100644 --- a/backend/schema/paths/users/userID/delete.json +++ b/backend/schema/paths/users/userID/delete.json @@ -1,10 +1,10 @@ { "operationId": "deleteUser", "summary": "Delete a User", - "tags": ["Users"], + "tags": ["users"], "security": [ { - "BearerAuth": ["users"] + "bearerAuth": ["admin"] } ], "parameters": [ diff --git a/backend/schema/paths/users/userID/get.json b/backend/schema/paths/users/userID/get.json index cb8ac61b..2cf5587d 100644 --- a/backend/schema/paths/users/userID/get.json +++ b/backend/schema/paths/users/userID/get.json @@ -1,10 +1,10 @@ { "operationId": "getUser", "summary": "Get a user", - "tags": ["Users"], + "tags": ["users"], "security": [ { - "BearerAuth": ["users"] + "bearerAuth": ["admin"] } ], "parameters": [ diff --git a/backend/schema/paths/users/userID/login/post.json b/backend/schema/paths/users/userID/login/post.json index 6148d182..46001d2e 100644 --- a/backend/schema/paths/users/userID/login/post.json +++ b/backend/schema/paths/users/userID/login/post.json @@ -1,10 +1,10 @@ { "operationId": "loginAsUser", "summary": "Login as this user", - "tags": ["Users"], + "tags": ["users"], "security": [ { - "BearerAuth": ["users"] + "bearerAuth": ["admin"] } ], "parameters": [ @@ -35,11 +35,11 @@ "created_on": "2020-01-30T10:43:44.000Z", "modified_on": "2020-01-30T10:43:44.000Z", "is_disabled": false, - "email": "jc@jc21.com", - "name": "Jamie Curnow", - "nickname": "James", + "email": "user2@example.com", + "name": "John Doe", + "nickname": "Jonny", "avatar": "//www.gravatar.com/avatar/3c8d73f45fd8763f827b964c76e6032a?default=mm", - "roles": ["admin"] + "roles": [] } } } @@ -50,16 +50,15 @@ "required": ["expires", "token", "user"], "additionalProperties": false, "properties": { - "expires": { - "description": "Token Expiry Unix Time", - "example": 1566540249, - "minimum": 1, - "type": "number" - }, "token": { "description": "JWT Token", - "example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4", - "type": "string" + "type": "string", + "example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" + }, + "expires": { + "description": "Token Expiry Timestamp", + "type": "string", + "example": "2020-01-30T10:43:44.000Z" }, "user": { "$ref": "../../../../components/user-object.json" diff --git a/backend/schema/paths/users/userID/permissions/put.json b/backend/schema/paths/users/userID/permissions/put.json index 2dcd2aed..764e7466 100644 --- a/backend/schema/paths/users/userID/permissions/put.json +++ b/backend/schema/paths/users/userID/permissions/put.json @@ -1,10 +1,10 @@ { "operationId": "updateUserPermissions", "summary": "Update a User's Permissions", - "tags": ["Users"], + "tags": ["users"], "security": [ { - "BearerAuth": ["users"] + "bearerAuth": ["admin"] } ], "parameters": [ @@ -27,6 +27,15 @@ "application/json": { "schema": { "$ref": "../../../../components/permission-object.json" + }, + "example": { + "visibility": "all", + "access_lists": "view", + "certificates": "hidden", + "dead_hosts": "hidden", + "proxy_hosts": "manage", + "redirection_hosts": "hidden", + "streams": "hidden" } } } diff --git a/backend/schema/paths/users/userID/put.json b/backend/schema/paths/users/userID/put.json index 60a6cd13..eabaa3a0 100644 --- a/backend/schema/paths/users/userID/put.json +++ b/backend/schema/paths/users/userID/put.json @@ -1,10 +1,10 @@ { "operationId": "updateUser", "summary": "Update a User", - "tags": ["Users"], + "tags": ["users"], "security": [ { - "BearerAuth": ["users"] + "bearerAuth": ["admin"] } ], "parameters": [ diff --git a/backend/schema/swagger.json b/backend/schema/swagger.json index 4a502b4e..e7234d4e 100644 --- a/backend/schema/swagger.json +++ b/backend/schema/swagger.json @@ -2,7 +2,8 @@ "openapi": "3.1.0", "info": { "title": "Nginx Proxy Manager API", - "version": "2.x.x" + "version": "2.x.x", + "description": "This is the official API documentation for Nginx Proxy Manager.\n\nMost endpoints require authentication via Bearer Token (JWT). You can generate a token by logging in via the `POST /tokens` endpoint.\n\nFor more information, visit the [Nginx Proxy Manager Documentation](https://nginxproxymanager.com)." }, "servers": [ { @@ -11,13 +12,59 @@ ], "components": { "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } + "$ref": "./components/security-schemes.json" } }, + "tags": [ + { + "name": "public", + "description": "Endpoints that do not require authentication" + }, + { + "name": "audit-log", + "description": "Endpoints related to Audit Logs" + }, + { + "name": "access-lists", + "description": "Endpoints related to Access Lists" + }, + { + "name": "certificates", + "description": "Endpoints related to Certificates" + }, + { + "name": "404-hosts", + "description": "Endpoints related to 404 Hosts" + }, + { + "name": "proxy-hosts", + "description": "Endpoints related to Proxy Hosts" + }, + { + "name": "redirection-hosts", + "description": "Endpoints related to Redirection Hosts" + }, + { + "name": "streams", + "description": "Endpoints related to Streams" + }, + { + "name": "reports", + "description": "Endpoints for viewing reports" + }, + { + "name": "settings", + "description": "Endpoints for managing application settings" + }, + { + "name": "tokens", + "description": "Endpoints for managing authentication tokens" + }, + { + "name": "users", + "description": "Endpoints for managing users" + } + ], "paths": { "/": { "get": { @@ -29,6 +76,11 @@ "$ref": "./paths/audit-log/get.json" } }, + "/audit-log/{id}": { + "get": { + "$ref": "./paths/audit-log/id/get.json" + } + }, "/nginx/access-lists": { "get": { "$ref": "./paths/nginx/access-lists/get.json" @@ -56,14 +108,19 @@ "$ref": "./paths/nginx/certificates/post.json" } }, + "/nginx/certificates/dns-providers": { + "get": { + "$ref": "./paths/nginx/certificates/dns-providers/get.json" + } + }, "/nginx/certificates/validate": { "post": { "$ref": "./paths/nginx/certificates/validate/post.json" } }, "/nginx/certificates/test-http": { - "get": { - "$ref": "./paths/nginx/certificates/test-http/get.json" + "post": { + "$ref": "./paths/nginx/certificates/test-http/post.json" } }, "/nginx/certificates/{certID}": { diff --git a/backend/scripts/install-certbot-plugins b/backend/scripts/install-certbot-plugins index bf995410..6acb0226 100755 --- a/backend/scripts/install-certbot-plugins +++ b/backend/scripts/install-certbot-plugins @@ -1,7 +1,7 @@ #!/usr/bin/node // Usage: -// Install all plugins defined in `certbot-dns-plugins.json`: +// Install all plugins defined in `../certbot/dns-plugins.json`: // ./install-certbot-plugins // Install one or more specific plugins: // ./install-certbot-plugins route53 cloudflare @@ -10,22 +10,23 @@ // docker exec npm_core /command/s6-setuidgid 1000:1000 bash -c "/app/scripts/install-certbot-plugins" // -const dnsPlugins = require('../global/certbot-dns-plugins.json'); -const certbot = require('../lib/certbot'); -const logger = require('../logger').certbot; -const batchflow = require('batchflow'); +import batchflow from "batchflow"; +import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" }; +import { installPlugin } from "../lib/certbot.js"; +import { certbot as logger } from "../logger.js"; -let hasErrors = false; -let failingPlugins = []; +let hasErrors = false; +const failingPlugins = []; let pluginKeys = Object.keys(dnsPlugins); if (process.argv.length > 2) { pluginKeys = process.argv.slice(2); } -batchflow(pluginKeys).sequential() +batchflow(pluginKeys) + .sequential() .each((i, pluginKey, next) => { - certbot.installPlugin(pluginKey) + installPlugin(pluginKey) .then(() => { next(); }) @@ -40,10 +41,14 @@ batchflow(pluginKeys).sequential() }) .end(() => { if (hasErrors) { - logger.error('Some plugins failed to install. Please check the logs above. Failing plugins: ' + '\n - ' + failingPlugins.join('\n - ')); + logger.error( + "Some plugins failed to install. Please check the logs above. Failing plugins: " + + "\n - " + + failingPlugins.join("\n - "), + ); process.exit(1); } else { - logger.complete('Plugins installed successfully'); + logger.complete("Plugins installed successfully"); process.exit(0); } }); diff --git a/backend/setup.js b/backend/setup.js index 6b9b8e78..b2c0dcb7 100644 --- a/backend/setup.js +++ b/backend/setup.js @@ -1,72 +1,74 @@ -const config = require('./lib/config'); -const logger = require('./logger').setup; -const certificateModel = require('./models/certificate'); -const userModel = require('./models/user'); -const userPermissionModel = require('./models/user_permission'); -const utils = require('./lib/utils'); -const authModel = require('./models/auth'); -const settingModel = require('./models/setting'); -const certbot = require('./lib/certbot'); +import { installPlugins } from "./lib/certbot.js"; +import utils from "./lib/utils.js"; +import { setup as logger } from "./logger.js"; +import authModel from "./models/auth.js"; +import certificateModel from "./models/certificate.js"; +import settingModel from "./models/setting.js"; +import userModel from "./models/user.js"; +import userPermissionModel from "./models/user_permission.js"; + +export const isSetup = async () => { + const row = await userModel.query().select("id").where("is_deleted", 0).first(); + return row?.id > 0; +} + /** * Creates a default admin users if one doesn't already exist in the database * * @returns {Promise} */ -const setupDefaultUser = () => { - return userModel - .query() - .select('id', ) - .where('is_deleted', 0) - .first() - .then((row) => { - if (!row || !row.id) { - // Create a new user and set password - const email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com'; - const password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme'; +const setupDefaultUser = async () => { + const initialAdminEmail = process.env.INITIAL_ADMIN_EMAIL; + const initialAdminPassword = process.env.INITIAL_ADMIN_PASSWORD; - logger.info('Creating a new user: ' + email + ' with password: ' + password); + // This will only create a new user when there are no active users in the database + // and the INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD environment variables are set. + // Otherwise, users should be shown the setup wizard in the frontend. + // I'm keeping this legacy behavior in case some people are automating deployments. - const data = { - is_deleted: 0, - email: email, - name: 'Administrator', - nickname: 'Admin', - avatar: '', - roles: ['admin'], - }; + if (!initialAdminEmail || !initialAdminPassword) { + return Promise.resolve(); + } - return userModel - .query() - .insertAndFetch(data) - .then((user) => { - return authModel - .query() - .insert({ - user_id: user.id, - type: 'password', - secret: password, - meta: {}, - }) - .then(() => { - return userPermissionModel.query().insert({ - user_id: user.id, - visibility: 'all', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - dead_hosts: 'manage', - streams: 'manage', - access_lists: 'manage', - certificates: 'manage', - }); - }); - }) - .then(() => { - logger.info('Initial admin setup completed'); - }); - } else if (config.debug()) { - logger.info('Admin user setup not required'); - } + const userIsetup = await isSetup(); + if (!userIsetup) { + // Create a new user and set password + logger.info(`Creating a new user: ${initialAdminEmail} with password: ${initialAdminPassword}`); + + const data = { + is_deleted: 0, + email: email, + name: "Administrator", + nickname: "Admin", + avatar: "", + roles: ["admin"], + }; + + const user = await userModel + .query() + .insertAndFetch(data); + + await authModel + .query() + .insert({ + user_id: user.id, + type: "password", + secret: password, + meta: {}, + }); + + await userPermissionModel.query().insert({ + user_id: user.id, + visibility: "all", + proxy_hosts: "manage", + redirection_hosts: "manage", + dead_hosts: "manage", + streams: "manage", + access_lists: "manage", + certificates: "manage", }); + logger.info("Initial admin setup completed"); + } }; /** @@ -74,31 +76,25 @@ const setupDefaultUser = () => { * * @returns {Promise} */ -const setupDefaultSettings = () => { - return settingModel +const setupDefaultSettings = async () => { + const row = await settingModel .query() - .select('id') - .where({id: 'default-site'}) - .first() - .then((row) => { - if (!row || !row.id) { - settingModel - .query() - .insert({ - id: 'default-site', - name: 'Default Site', - description: 'What to show when Nginx is hit with an unknown Host', - value: 'congratulations', - meta: {}, - }) - .then(() => { - logger.info('Default settings added'); - }); - } - if (config.debug()) { - logger.info('Default setting setup not required'); - } - }); + .select("id") + .where({ id: "default-site" }) + .first(); + + if (!row?.id) { + await settingModel + .query() + .insert({ + id: "default-site", + name: "Default Site", + description: "What to show when Nginx is hit with an unknown Host", + value: "congratulations", + meta: {}, + }); + logger.info("Default settings added"); + } }; /** @@ -106,44 +102,44 @@ const setupDefaultSettings = () => { * * @returns {Promise} */ -const setupCertbotPlugins = () => { - return certificateModel +const setupCertbotPlugins = async () => { + const certificates = await certificateModel .query() - .where('is_deleted', 0) - .andWhere('provider', 'letsencrypt') - .then((certificates) => { - if (certificates && certificates.length) { - let plugins = []; - let promises = []; + .where("is_deleted", 0) + .andWhere("provider", "letsencrypt"); - certificates.map(function (certificate) { - if (certificate.meta && certificate.meta.dns_challenge === true) { - if (plugins.indexOf(certificate.meta.dns_provider) === -1) { - plugins.push(certificate.meta.dns_provider); - } + if (certificates?.length) { + const plugins = []; + const promises = []; - // Make sure credentials file exists - const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id; - // Escape single quotes and backslashes - const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\'); - const credentials_cmd = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }'; - promises.push(utils.exec(credentials_cmd)); - } - }); + certificates.map((certificate) => { + if (certificate.meta && certificate.meta.dns_challenge === true) { + if (plugins.indexOf(certificate.meta.dns_provider) === -1) { + plugins.push(certificate.meta.dns_provider); + } - return certbot.installPlugins(plugins) - .then(() => { - if (promises.length) { - return Promise.all(promises) - .then(() => { - logger.info('Added Certbot plugins ' + plugins.join(', ')); - }); - } - }); + // Make sure credentials file exists + const credentials_loc = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; + // Escape single quotes and backslashes + if (typeof certificate.meta.dns_provider_credentials === "string") { + const escapedCredentials = certificate.meta.dns_provider_credentials + .replaceAll("'", "\\'") + .replaceAll("\\", "\\\\"); + const credentials_cmd = `[ -f '${credentials_loc}' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo '${escapedCredentials}' > '${credentials_loc}' && chmod 600 '${credentials_loc}'; }`; + promises.push(utils.exec(credentials_cmd)); + } } + return true; }); -}; + await installPlugins(plugins); + + if (promises.length) { + await Promise.all(promises); + logger.info(`Added Certbot plugins ${plugins.join(", ")}`); + } + } +}; /** * Starts a timer to call run the logrotation binary every two days @@ -154,20 +150,17 @@ const setupLogrotation = () => { const runLogrotate = async () => { try { - await utils.exec('logrotate /etc/logrotate.d/nginx-proxy-manager'); - logger.info('Logrotate completed.'); - } catch (e) { logger.warn(e); } + await utils.exec("logrotate /etc/logrotate.d/nginx-proxy-manager"); + logger.info("Logrotate completed."); + } catch (e) { + logger.warn(e); + } }; - logger.info('Logrotate Timer initialized'); + logger.info("Logrotate Timer initialized"); setInterval(runLogrotate, intervalTimeout); // And do this now as well return runLogrotate(); }; -module.exports = function () { - return setupDefaultUser() - .then(setupDefaultSettings) - .then(setupCertbotPlugins) - .then(setupLogrotation); -}; +export default () => setupDefaultUser().then(setupDefaultSettings).then(setupCertbotPlugins).then(setupLogrotation); diff --git a/backend/validate-schema.js b/backend/validate-schema.js old mode 100644 new mode 100755 index 71a05c81..b1870665 --- a/backend/validate-schema.js +++ b/backend/validate-schema.js @@ -1,16 +1,19 @@ -const SwaggerParser = require('@apidevtools/swagger-parser'); -const chalk = require('chalk'); -const schema = require('./schema'); -const log = console.log; +#!/usr/bin/node -schema.getCompiledSchema().then(async (swaggerJSON) => { +import SwaggerParser from "@apidevtools/swagger-parser"; +import chalk from "chalk"; +import { getCompiledSchema } from "./schema/index.js"; + +const log = console.log; + +getCompiledSchema().then(async (swaggerJSON) => { try { const api = await SwaggerParser.validate(swaggerJSON); - console.log('API name: %s, Version: %s', api.info.title, api.info.version); - log(chalk.green('❯ Schema is valid')); + console.log("API name: %s, Version: %s", api.info.title, api.info.version); + log(chalk.green("❯ Schema is valid")); } catch (e) { console.error(e); - log(chalk.red('❯', e.message), '\n'); + log(chalk.red("❯", e.message), "\n"); process.exit(1); } }); diff --git a/backend/yarn.lock b/backend/yarn.lock index bae734b4..fdfe874e 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2,19 +2,19 @@ # yarn lockfile v1 -"@apidevtools/json-schema-ref-parser@9.0.6": - version "9.0.6" - resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c" - integrity sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg== +"@apidevtools/json-schema-ref-parser@11.7.2": + version "11.7.2" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz#cdf3e0aded21492364a70e193b45b7cf4177f031" + integrity sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA== dependencies: "@jsdevtools/ono" "^7.1.3" - call-me-maybe "^1.0.1" - js-yaml "^3.13.1" + "@types/json-schema" "^7.0.15" + js-yaml "^4.1.0" "@apidevtools/json-schema-ref-parser@^11.7.0": - version "11.7.0" - resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.0.tgz#228d72018a0e7cbee744b677eaa01a8968f302d9" - integrity sha512-pRrmXMCwnmrkS3MLgAIW5dXRzeTv6GLjkjb4HmxNnvAKXN1Nfzp4KmGADBQvlVUcqi+a5D+hfGDLLnd5NnYxog== + version "11.9.3" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz#0e0c9061fc41cf03737d499a4e6a8299fdd2bfa7" + integrity sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ== dependencies: "@jsdevtools/ono" "^7.1.3" "@types/json-schema" "^7.0.15" @@ -31,83 +31,86 @@ integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== "@apidevtools/swagger-parser@^10.1.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.1.0.tgz#a987d71e5be61feb623203be0c96e5985b192ab6" - integrity sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw== + version "10.1.1" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.1.1.tgz#e29bf17cf94b487a340e06784e9fbe20cb671c45" + integrity sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA== dependencies: - "@apidevtools/json-schema-ref-parser" "9.0.6" + "@apidevtools/json-schema-ref-parser" "11.7.2" "@apidevtools/openapi-schemas" "^2.1.0" "@apidevtools/swagger-methods" "^3.0.2" "@jsdevtools/ono" "^7.1.3" - ajv "^8.6.3" + ajv "^8.17.1" ajv-draft-04 "^1.0.0" - call-me-maybe "^1.0.1" + call-me-maybe "^1.0.2" -"@eslint-community/eslint-utils@^4.2.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz#a556790523a351b4e47e9d385f47265eaaf9780a" - integrity sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA== - dependencies: - eslint-visitor-keys "^3.3.0" +"@biomejs/biome@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.3.2.tgz#aeeb5f12c39571a18f36a919be63ba7dbc7b290a" + integrity sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg== + optionalDependencies: + "@biomejs/cli-darwin-arm64" "2.3.2" + "@biomejs/cli-darwin-x64" "2.3.2" + "@biomejs/cli-linux-arm64" "2.3.2" + "@biomejs/cli-linux-arm64-musl" "2.3.2" + "@biomejs/cli-linux-x64" "2.3.2" + "@biomejs/cli-linux-x64-musl" "2.3.2" + "@biomejs/cli-win32-arm64" "2.3.2" + "@biomejs/cli-win32-x64" "2.3.2" -"@eslint-community/regexpp@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403" - integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ== +"@biomejs/cli-darwin-arm64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.2.tgz#93f866161abe32e702987ccbddf492c1aabe016f" + integrity sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew== -"@eslint/eslintrc@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.1.tgz#7888fe7ec8f21bc26d646dbd2c11cd776e21192d" - integrity sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.5.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" +"@biomejs/cli-darwin-x64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.2.tgz#9c3dffdac12e4f4d8db7680ca20f58ace1f38c23" + integrity sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA== -"@eslint/js@8.36.0": - version "8.36.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.36.0.tgz#9837f768c03a1e4a30bd304a64fb8844f0e72efe" - integrity sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg== +"@biomejs/cli-linux-arm64-musl@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.2.tgz#a0424d2fe355cc43c375b3fbf3e42d39b7221d0e" + integrity sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw== + +"@biomejs/cli-linux-arm64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.2.tgz#f85717c04d420ede20523d173a1fc10df60d4d37" + integrity sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw== + +"@biomejs/cli-linux-x64-musl@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.2.tgz#d3e114c744c32d2c50a77c13476bd941819c92d8" + integrity sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA== + +"@biomejs/cli-linux-x64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.2.tgz#f66ce85d2d757d45e6edecce04753a805bd816f0" + integrity sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA== + +"@biomejs/cli-win32-arm64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.2.tgz#b46f8b47a3d97e766cc5ad5eb67d90eeb230b2cb" + integrity sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg== + +"@biomejs/cli-win32-x64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.2.tgz#a14f5e220dd496705278315ee3e5e028dd657344" + integrity sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ== "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@humanwhocodes/config-array@^0.11.8": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" - integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== - dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.5" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - "@jsdevtools/ono@^7.1.3": version "7.1.3" resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== -"@mapbox/node-pre-gyp@^1.0.0": - version "1.0.10" - resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" - integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== +"@mapbox/node-pre-gyp@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== dependencies: detect-libc "^2.0.0" https-proxy-agent "^5.0.0" @@ -119,27 +122,6 @@ semver "^7.3.5" tar "^6.1.11" -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - "@npmcli/fs@^1.0.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" @@ -156,28 +138,11 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== - "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -188,7 +153,7 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.5, accepts@~1.3.8: +accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -196,16 +161,6 @@ accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^8.8.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== - agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -214,12 +169,10 @@ agent-base@6, agent-base@^6.0.2: debug "4" agentkeepalive@^4.1.3: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" - integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== dependencies: - debug "^4.1.0" - depd "^2.0.0" humanize-ms "^1.2.1" aggregate-error@^3.0.0: @@ -235,17 +188,7 @@ ajv-draft-04@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.17.1, ajv@^8.6.3: +ajv@^8.17.1, ajv@^8.6.2: version "8.17.1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== @@ -255,43 +198,6 @@ ajv@^8.17.1, ajv@^8.6.3: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -ajv@^8.6.2: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-align@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" - integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== - dependencies: - string-width "^3.0.0" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -305,30 +211,24 @@ ansi-styles@^3.2.1: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: - "@types/color-name" "^1.1.1" color-convert "^2.0.1" -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - "aproba@^1.0.3 || ^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" - integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.1.0.tgz#75500a190313d95c64e871e7e4284c6ac219f0b1" + integrity sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew== archiver-utils@^2.1.0: version "2.1.0" @@ -346,16 +246,32 @@ archiver-utils@^2.1.0: normalize-path "^3.0.0" readable-stream "^2.0.0" +archiver-utils@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-3.0.4.tgz#a0d201f1cf8fce7af3b5a05aea0a337329e96ec7" + integrity sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw== + dependencies: + glob "^7.2.3" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + archiver@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba" - integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg== + version "5.3.2" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.2.tgz#99991d5957e53bd0303a392979276ac4ddccf3b0" + integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw== dependencies: archiver-utils "^2.1.0" - async "^3.2.0" + async "^3.2.4" buffer-crc32 "^0.2.1" readable-stream "^3.6.0" - readdir-glob "^1.0.0" + readdir-glob "^1.1.2" tar-stream "^2.2.0" zip-stream "^4.1.0" @@ -375,21 +291,6 @@ are-we-there-yet@^3.0.0: delegates "^1.0.0" readable-stream "^3.6.0" -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -398,19 +299,19 @@ argparse@^2.0.1: array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== asn1@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== dependencies: safer-buffer "~2.1.0" -async@^3.2.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== +async@^3.2.4: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== aws-ssl-profiles@^1.1.1: version "1.1.2" @@ -418,9 +319,9 @@ aws-ssl-profiles@^1.1.1: integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g== balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.3.1: version "1.5.1" @@ -430,20 +331,27 @@ base64-js@^1.3.1: batchflow@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/batchflow/-/batchflow-0.4.0.tgz#7d419df79b6b7587b06f9ea34f96ccef6f74e5b5" - integrity sha1-fUGd95trdYewb56jT5bM72905bU= + integrity sha512-XwQQoCGPUjdLWzmpAvRNZc91wnBYuKLmj52d9LLZ1Ww06ow5RBqBt8kUmU9/3ZvPq88j7Elh3V4cEhgNKXbIlQ== bcrypt@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.0.tgz#051407c7cd5ffbfb773d541ca3760ea0754e37e2" - integrity sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg== + version "5.1.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2" + integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww== dependencies: - node-addon-api "^3.0.0" - node-pre-gyp "0.15.0" + "@mapbox/node-pre-gyp" "^1.0.11" + node-addon-api "^5.0.0" binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" bl@^4.0.3: version "4.1.0" @@ -455,9 +363,9 @@ bl@^4.0.3: readable-stream "^3.4.0" blueimp-md5@^2.16.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.17.0.tgz#f4fcac088b115f7b4045f19f5da59e9d01b1bb96" - integrity sha512-x5PKJHY5rHQYaADj6NwPUR2QRCUVSggPzrUKkeENpj871o9l9IefJbO2jkT5UvYykeOK9dx0VmkIo6dZ+vThYw== + version "2.19.0" + resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0" + integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== body-parser@1.20.3, body-parser@^1.20.3: version "1.20.3" @@ -477,20 +385,6 @@ body-parser@1.20.3, body-parser@^1.20.3: type-is "~1.6.18" unpipe "1.0.0" -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - brace-expansion@^1.1.7: version "1.1.12" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" @@ -499,6 +393,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -509,12 +410,12 @@ braces@~3.0.2: buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== -buffer-equal-constant-time@1.0.1: +buffer-equal-constant-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== buffer@^5.5.0: version "5.7.1" @@ -524,17 +425,12 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -busboy@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" - integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw== +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== dependencies: - dicer "0.3.0" - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + streamsearch "^1.1.0" bytes@3.1.2: version "3.1.2" @@ -565,46 +461,33 @@ cacache@^15.2.0: tar "^6.0.2" unique-filename "^1.1.1" -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" es-errors "^1.3.0" function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +call-me-maybe@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" + integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== -camelcase@^5.0.0, camelcase@^5.3.1: +camelcase@^5.0.0: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -chalk@4.1.2, chalk@^4.0.0: +chalk@4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -621,30 +504,22 @@ chalk@^2.3.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@^3.2.2: - version "3.4.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" - integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== - dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.4.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.1.2" + fsevents "~2.3.2" -chownr@^1.1.4: +chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -654,21 +529,11 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-boxes@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" - integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== - cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -678,18 +543,6 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -707,7 +560,7 @@ color-convert@^2.0.1: color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" @@ -725,26 +578,26 @@ colorette@2.0.19: integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== commander@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.0.tgz#71797971162cd3cf65f0b9d24eb28f8d303acdf1" - integrity sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA== + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== commander@^9.1.0: version "9.5.0" resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== -compress-commons@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" - integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ== +compress-commons@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.2.tgz#6542e59cb63e1f46a8b21b0e06f9a32e4c8b06df" + integrity sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg== dependencies: buffer-crc32 "^0.2.13" crc32-stream "^4.0.2" normalize-path "^3.0.0" readable-stream "^3.6.0" -compressible@~2.0.16: +compressible@~2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== @@ -752,39 +605,27 @@ compressible@~2.0.16: mime-db ">= 1.43.0 < 2" compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + version "1.8.1" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79" + integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" + bytes "3.1.2" + compressible "~2.0.18" debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" + negotiator "~0.6.4" + on-headers "~1.1.0" + safe-buffer "5.2.1" vary "~1.1.2" concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== content-disposition@0.5.4: version "0.5.4" @@ -801,121 +642,92 @@ content-type@~1.0.4, content-type@~1.0.5: cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" - integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== crc-32@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" - integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA== - dependencies: - exit-on-epipe "~1.0.1" - printj "~1.1.0" + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== crc32-stream@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" - integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== + version "4.0.3" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.3.tgz#85dd677eb78fa7cad1ba17cc506a597d41fc6f33" + integrity sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw== dependencies: crc-32 "^1.2.0" readable-stream "^3.4.0" -cross-spawn@^7.0.2: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - db-errors@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/db-errors/-/db-errors-0.2.3.tgz#a6a38952e00b20e790f2695a6446b3c65497ffa2" integrity sha512-OOgqgDuCavHXjYSJoV2yGhv6SeG8nk42aoCSoyXLZUH7VwFG27rxbavU1z+VrZbZjphw5UkDQwUlD21MwZpUng== -debug@2.6.9, debug@^2.2.0: +debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3: +debug@4, debug@^4.3.3: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +debug@4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== dependencies: - mimic-response "^1.0.0" + mimic-response "^3.1.0" deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== denque@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== -depd@2.0.0, depd@^2.0.0: +depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -925,41 +737,19 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-libc@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8" + integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== -dicer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" - integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== dependencies: - streamsearch "0.1.2" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== - dependencies: - is-obj "^2.0.0" - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" ecdsa-sig-formatter@1.0.11: version "1.0.11" @@ -971,18 +761,13 @@ ecdsa-sig-formatter@1.0.11: ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== email-validator@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -991,7 +776,7 @@ emoji-regex@^8.0.0: encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== encodeurl@~2.0.0: version "2.0.0" @@ -1006,9 +791,9 @@ encoding@^0.1.12: iconv-lite "^0.6.2" end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + version "1.4.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== dependencies: once "^1.4.0" @@ -1029,249 +814,118 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-plugin-align-assignments@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-align-assignments/-/eslint-plugin-align-assignments-1.1.2.tgz#83e1a8a826d4adf29e82b52d0bb39c88b301b576" - integrity sha512-I1ZJgk9EjHfGVU9M2Ex8UkVkkjLL5Y9BS6VNnQHq79eHj2H4/Cgxf36lQSUTLgm2ntB03A2NtF+zg9fyi5vChg== - -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== - -eslint@^8.36.0: - version "8.36.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.36.0.tgz#1bd72202200a5492f91803b113fb8a83b11285cf" - integrity sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.1" - "@eslint/js" "8.36.0" - "@humanwhocodes/config-array" "^0.11.8" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-visitor-keys "^3.3.0" - espree "^9.5.0" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - grapheme-splitter "^1.0.4" - ignore "^5.2.0" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-sdsl "^4.1.4" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.1" - strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" - text-table "^0.2.0" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== esm@^3.2.25: version "3.2.25" resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^9.5.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.0.tgz#3646d4e3f58907464edba852fa047e6a27bdf113" - integrity sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw== - dependencies: - acorn "^8.8.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -exit-on-epipe@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" - integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -express-fileupload@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.1.9.tgz#e798e9318394ed5083e56217ad6cda576da465d2" - integrity sha512-f2w0aoe7lj3NeD8a4MXmYQsqir3Z66I08l9AKq04QbFUAjeZNmPwTlR5Lx2NGwSu/PslsAjGC38MWzo5tTjoBg== +express-fileupload@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.5.2.tgz#4da70ba6f2ffd4c736eab0776445865a9dbd9bfa" + integrity sha512-wxUJn2vTHvj/kZCVmc5/bJO15C7aSMyHeuXYY3geKpeKibaAoQGcEv5+sM6nHS2T7VF+QHS4hTWPiY2mKofEdg== dependencies: - busboy "^0.3.1" + busboy "^1.6.0" express@^4.20.0: - version "4.20.0" - resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48" - integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw== + version "4.21.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" + integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== dependencies: accepts "~1.3.8" array-flatten "1.1.1" body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.6.0" + cookie "0.7.1" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.2.0" + finalhandler "1.3.1" fresh "0.5.2" http-errors "2.0.0" merge-descriptors "1.0.3" methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.10" + path-to-regexp "0.1.12" proxy-addr "~2.0.7" - qs "6.11.0" + qs "6.13.0" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.19.0" - serve-static "1.16.0" + serve-static "1.16.2" setprototypeof "1.2.0" statuses "2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - fast-uri@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.2.tgz#d78b298cf70fd3b752fd951175a3da6a7b48f024" - integrity sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row== - -fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== - dependencies: - reusify "^1.0.4" + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== fill-range@^7.1.1: version "7.1.1" @@ -1280,13 +934,13 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== dependencies: debug "2.6.9" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" on-finished "2.4.1" parseurl "~1.3.3" @@ -1296,7 +950,7 @@ finalhandler@1.2.0: find-up@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== dependencies: locate-path "^2.0.0" @@ -1308,27 +962,6 @@ find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== - forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -1337,20 +970,13 @@ forwarded@0.2.0: fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-minipass@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -1361,17 +987,12 @@ fs-minipass@^2.0.0: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: version "1.1.2" @@ -1407,20 +1028,6 @@ gauge@^4.0.3: strip-ansi "^6.0.1" wide-align "^1.1.5" -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - generate-function@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" @@ -1433,141 +1040,78 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" es-errors "^1.3.0" + es-object-atoms "^1.1.1" function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" getopts@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== -glob-parent@~5.1.0: +glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob@^7.1.3, glob@^7.1.4, glob@^7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.4: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -global-dirs@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" - integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== - dependencies: - ini "^1.3.5" - -globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== - dependencies: - type-fest "^0.20.2" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -graceful-fs@^4.1.15, graceful-fs@^4.1.2: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== - -graceful-fs@^4.2.0: - version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" - integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== - -graceful-fs@^4.2.6: +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - -gravatar@^1.8.0: - version "1.8.1" - resolved "https://registry.yarnpkg.com/gravatar/-/gravatar-1.8.1.tgz#743bbdf3185c3433172e00e0e6ff5f6b30c58997" - integrity sha512-18frnfVp4kRYkM/eQW32Mfwlsh/KMbwd3S6nkescBZHioobflFEFHsvM71qZAkUSLNifyi2uoI+TuGxJAnQIOA== +gravatar@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/gravatar/-/gravatar-1.8.2.tgz#f298642b1562ed685af2ae938dbe31ec0c542cc1" + integrity sha512-GdRwLM3oYpFQKy47MKuluw9hZ2gaCtiKPbDGdcDEuYDKlc8eNnW27KYL9LVbIDzEsx88WtDWQm2ClBcsgBnj6w== dependencies: blueimp-md5 "^2.16.0" email-validator "^2.0.4" @@ -1577,58 +1121,34 @@ gravatar@^1.8.0: has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has-proto@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-unicode@^2.0.0, has-unicode@^2.0.1: +has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hasown@^2.0.0: +hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" -http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== +http-cache-semantics@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" + integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== http-errors@2.0.0: version "2.0.0" @@ -1665,20 +1185,27 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -iconv-lite@0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2, iconv-lite@^0.6.3: +iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.0.tgz#c50cd80e6746ca8115eb98743afa81aa0e147a3e" + integrity sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -1687,45 +1214,12 @@ ieee754@^1.1.13: ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" - integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= - -ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== - dependencies: - minimatch "^3.0.4" - -ignore@^5.2.0: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -import-fresh@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== indent-string@^4.0.0: version "4.0.0" @@ -1740,7 +1234,7 @@ infer-owner@^1.0.4: inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" @@ -1753,9 +1247,9 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== -ini@^1.3.5, ini@~1.3.0: +ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -1765,10 +1259,10 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -ip@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" - integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== +ip-address@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.0.1.tgz#a8180b783ce7788777d796286d61bce4276818ed" + integrity sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA== ipaddr.js@1.9.1: version "1.9.1" @@ -1778,7 +1272,7 @@ ipaddr.js@1.9.1: is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-binary-path@~2.1.0: version "2.1.0" @@ -1787,136 +1281,59 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: - ci-info "^2.0.0" - -is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== - dependencies: - has "^1.0.3" + hasown "^2.0.2" is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-glob@^4.0.3: +is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" -is-installed-globally@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== - dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" - is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" - integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== - -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-property@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -js-sdsl@^4.1.4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711" - integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== - -js-yaml@^3.13.1: - version "3.14.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" - integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== js-yaml@^4.1.0: version "4.1.0" @@ -1925,47 +1342,38 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -jsonwebtoken@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" - integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== +jsonwebtoken@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== dependencies: jws "^3.2.2" - lodash "^4.17.21" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" ms "^2.1.1" - semver "^7.3.8" + semver "^7.5.4" jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + version "1.4.2" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" + integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== dependencies: - buffer-equal-constant-time "1.0.1" + buffer-equal-constant-time "^1.0.1" ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" @@ -1977,13 +1385,6 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - knex@2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/knex/-/knex-2.4.2.tgz#a34a289d38406dc19a0447a78eeaf2d16ebedd61" @@ -2004,28 +1405,13 @@ knex@2.4.2: tarn "^3.0.2" tildify "2.0.0" -latest-version@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== dependencies: readable-stream "^2.0.5" -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - liquidjs@10.6.1: version "10.6.1" resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.6.1.tgz#b401662cb8f0cca59b42f79fc08e411c86d92dab" @@ -2036,7 +1422,7 @@ liquidjs@10.6.1: load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== dependencies: graceful-fs "^4.1.2" parse-json "^4.0.0" @@ -2046,7 +1432,7 @@ load-json-file@^4.0.0: locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== dependencies: p-locate "^2.0.0" path-exists "^3.0.0" @@ -2058,42 +1444,60 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== lodash.difference@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" - integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= + integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= + integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== lodash@^4.17.21: version "4.17.21" @@ -2101,19 +1505,9 @@ lodash@^4.17.21: integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== long@^5.2.1: - version "5.2.3" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" - integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== - -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== lru-cache@^6.0.0: version "6.0.0" @@ -2127,10 +1521,10 @@ lru-cache@^7.14.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -lru-cache@^8.0.0: - version "8.0.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-8.0.5.tgz#983fe337f3e176667f8e567cfcce7cb064ea214e" - integrity sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA== +lru.min@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.2.tgz#01ce1d72cc50c7faf8bd1f809ebf05d4331021eb" + integrity sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg== make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" @@ -2161,10 +1555,15 @@ make-fetch-happen@^9.1.0: socks-proxy-agent "^6.0.0" ssri "^8.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== merge-descriptors@1.0.3: version "1.0.3" @@ -2174,26 +1573,19 @@ merge-descriptors@1.0.3: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -mime-db@1.44.0, "mime-db@>= 1.43.0 < 2": - version "1.44.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@~2.1.24: - version "2.1.27" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== - dependencies: - mime-db "1.44.0" +"mime-db@>= 1.43.0 < 2": + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@~2.1.34: +mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -2205,26 +1597,26 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.0.5, minimatch@^3.1.2: +minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5: +minimatch@^5.1.0: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.3: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -2268,14 +1660,6 @@ minipass-sized@^1.0.3: dependencies: minipass "^3.0.0" -minipass@^2.6.0, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: version "3.3.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" @@ -2283,17 +1667,10 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: dependencies: yallist "^4.0.0" -minipass@^4.0.0: - version "4.2.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.5.tgz#9e0e5256f1e3513f8c34691dd68549e85b2c8ceb" - integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q== - -minizlib@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" @@ -2303,49 +1680,47 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -mkdirp@^0.5.3, mkdirp@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment@^2.29.4: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== +moment@^2.30.1: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mysql2@^3.11.1: - version "3.11.1" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.11.1.tgz#edfb856e2176fcf43d2cc066dd4959e9fc76ea85" - integrity sha512-Oc8Zffd0gpIJnJ/NOMp6IiiJJDdWc7nmWpS+UE3K9feTpYia8XkbgL6EaOJYz52f6+2pAoC0eAQqUzal4lnNGQ== +mysql2@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.15.3.tgz#f0348d9c7401bb98cb1f45ffc5a773b109f70808" + integrity sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg== dependencies: aws-ssl-profiles "^1.1.1" denque "^2.1.0" generate-function "^2.3.1" - iconv-lite "^0.6.3" + iconv-lite "^0.7.0" long "^5.2.1" - lru-cache "^8.0.0" + lru.min "^1.0.0" named-placeholders "^1.1.3" seq-queue "^0.0.5" sqlstring "^2.3.2" @@ -2357,39 +1732,42 @@ named-placeholders@^1.1.3: dependencies: lru-cache "^7.14.1" -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +napi-build-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" + integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== -needle@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.0.tgz#e6fc4b3cc6c25caed7554bd613a5cf0bac8c31c0" - integrity sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - -negotiator@0.6.3, negotiator@^0.6.2: +negotiator@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== -node-addon-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.0.tgz#812446a1001a54f71663bed188314bba07e09247" - integrity sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg== +negotiator@^0.6.2, negotiator@~0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== -node-addon-api@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" - integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== +node-abi@^3.3.0: + version "3.78.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.78.0.tgz#fd0ecbd0aa89857b98da06bd3909194abb0821ba" + integrity sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ== + dependencies: + semver "^7.3.5" + +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== node-fetch@^2.6.7: - version "2.6.9" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" - integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg== + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" @@ -2409,23 +1787,7 @@ node-gyp@8.x: tar "^6.1.2" which "^2.0.2" -node-pre-gyp@0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz#c2fc383276b74c7ffa842925241553e8b40f1087" - integrity sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.3" - needle "^2.5.0" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - -node-rsa@^1.0.8: +node-rsa@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.1.1.tgz#efd9ad382097782f506153398496f79e4464434d" integrity sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw== @@ -2433,28 +1795,20 @@ node-rsa@^1.0.8: asn1 "^0.2.4" nodemon@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" - integrity sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ== + version "2.0.22" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.22.tgz#182c45c3a78da486f673d6c1702e00728daf5258" + integrity sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ== dependencies: - chokidar "^3.2.2" - debug "^3.2.6" + chokidar "^3.5.2" + debug "^3.2.7" ignore-by-default "^1.0.1" - minimatch "^3.0.4" - pstree.remy "^1.1.7" + minimatch "^3.1.2" + pstree.remy "^1.1.8" semver "^5.7.1" + simple-update-notifier "^1.0.7" supports-color "^5.5.0" touch "^3.1.0" - undefsafe "^2.0.2" - update-notifier "^4.0.0" - -nopt@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" + undefsafe "^2.0.5" nopt@^5.0.0: version "5.0.0" @@ -2463,54 +1817,11 @@ nopt@^5.0.0: dependencies: abbrev "1" -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= - dependencies: - abbrev "1" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^4.1.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" - integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== - -npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-packlist@^1.1.6: - version "1.4.8" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" - integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-normalize-package-bin "^1.0.1" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - npmlog@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" @@ -2531,20 +1842,15 @@ npmlog@^6.0.0: gauge "^4.0.3" set-blocking "^2.0.0" -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== objection@3.0.1: version "3.0.1" @@ -2561,53 +1867,18 @@ on-finished@2.4.1: dependencies: ee-first "1.1.1" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== +on-headers@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" + integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.3" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -2622,17 +1893,10 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== dependencies: p-limit "^1.1.0" @@ -2643,13 +1907,6 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" @@ -2660,34 +1917,17 @@ p-map@^4.0.0: p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== dependencies: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" @@ -2700,7 +1940,7 @@ parseurl@~1.3.3: path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== path-exists@^4.0.0: version "4.0.0" @@ -2710,62 +1950,57 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.10: - version "0.1.10" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" - integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== +path-to-regexp@0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== path@^0.12.7: version "0.12.7" resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" - integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8= + integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q== dependencies: process "^0.11.1" util "^0.10.3" -pg-cloudflare@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" - integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== +pg-cloudflare@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz#a1f3d226bab2c45ae75ea54d65ec05ac6cfafbef" + integrity sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg== pg-connection-string@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== -pg-connection-string@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37" - integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA== +pg-connection-string@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.9.1.tgz#bb1fd0011e2eb76ac17360dc8fa183b2d3465238" + integrity sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w== pg-int8@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-pool@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec" - integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g== +pg-pool@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.10.1.tgz#481047c720be2d624792100cac1816f8850d31b2" + integrity sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg== -pg-protocol@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93" - integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ== +pg-protocol@^1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.10.3.tgz#ac9e4778ad3f84d0c5670583bab976ea0a34f69f" + integrity sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ== -pg-types@^2.1.0: +pg-types@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== @@ -2776,20 +2011,20 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg@^8.13.1: - version "8.13.1" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.13.1.tgz#6498d8b0a87ff76c2df7a32160309d3168c0c080" - integrity sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ== +pg@^8.16.3: + version "8.16.3" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.16.3.tgz#160741d0b44fdf64680e45374b06d632e86c99fd" + integrity sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw== dependencies: - pg-connection-string "^2.7.0" - pg-pool "^3.7.0" - pg-protocol "^1.7.0" - pg-types "^2.1.0" - pgpass "1.x" + pg-connection-string "^2.9.1" + pg-pool "^3.10.1" + pg-protocol "^1.10.3" + pg-types "2.2.0" + pgpass "1.0.5" optionalDependencies: - pg-cloudflare "^1.1.1" + pg-cloudflare "^1.2.7" -pgpass@1.x: +pgpass@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== @@ -2797,19 +2032,19 @@ pgpass@1.x: split2 "^4.1.0" picomatch@^2.0.4, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== pkg-conf@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" - integrity sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg= + integrity sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g== dependencies: find-up "^2.0.0" load-json-file "^4.0.0" @@ -2836,25 +2071,23 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - -prettier@^2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" - integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== - -printj@~1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" - integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== +prebuild-install@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" + integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^2.0.0" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" process-nextick-args@~2.0.0: version "2.0.1" @@ -2864,7 +2097,7 @@ process-nextick-args@~2.0.0: process@^0.11.1: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== promise-inflight@^1.0.1: version "1.0.1" @@ -2887,38 +2120,19 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -pstree.remy@^1.1.7: +pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + version "3.0.3" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" + integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== dependencies: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -pupa@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" - integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== - dependencies: - escape-goat "^2.0.0" - -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - qs@6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" @@ -2929,12 +2143,7 @@ qs@6.13.0: querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== range-parser@~1.2.1: version "1.2.1" @@ -2951,7 +2160,7 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.2.7, rc@^1.2.8: +rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -2961,10 +2170,10 @@ rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== +readable-stream@^2.0.0, readable-stream@^2.0.5: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -2975,25 +2184,25 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6: util-deprecate "~1.0.1" readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" util-deprecate "^1.0.1" -readdir-glob@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" - integrity sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA== +readdir-glob@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" + integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== dependencies: - minimatch "^3.0.4" + minimatch "^5.1.0" -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -3004,24 +2213,10 @@ rechoir@^0.8.0: dependencies: resolve "^1.20.0" -registry-auth-token@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.0.tgz#1d37dffda72bbecd0f581e4715540213a65eb7da" - integrity sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w== - dependencies: - rc "^1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-from-string@^2.0.2: version "2.0.2" @@ -3033,49 +2228,25 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve@^1.20.0: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.16.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^2.6.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -3083,75 +2254,40 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver@^5.3.0, semver@^5.7.1: +semver@^5.7.1: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.8: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" +semver@^7.3.5, semver@^7.5.4: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" +semver@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== send@0.19.0: version "0.19.0" @@ -3177,66 +2313,67 @@ seq-queue@^0.0.5: resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== -serve-static@1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92" - integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA== +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== dependencies: - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.18.0" + send "0.19.0" -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" + object-inspect "^1.13.3" -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" -signal-exit@^3.0.7: +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.0.6: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.0, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -3250,6 +2387,27 @@ signale@1.4.0: figures "^2.0.0" pkg-conf "^2.1.0" +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-update-notifier@^1.0.7: + version "1.1.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" + integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== + dependencies: + semver "~7.0.0" + smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" @@ -3265,11 +2423,11 @@ socks-proxy-agent@^6.0.0: socks "^2.6.2" socks@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + version "2.8.7" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea" + integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== dependencies: - ip "^2.0.0" + ip-address "^10.0.1" smart-buffer "^4.2.0" split2@^4.1.0: @@ -3277,18 +2435,14 @@ split2@^4.1.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -sqlite3@5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.6.tgz#1d4fbc90fe4fbd51e952e0a90fd8f6c2b9098e97" - integrity sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw== +sqlite3@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" + integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog== dependencies: - "@mapbox/node-pre-gyp" "^1.0.0" - node-addon-api "^4.2.0" + bindings "^1.5.0" + node-addon-api "^7.0.0" + prebuild-install "^7.1.1" tar "^6.1.11" optionalDependencies: node-gyp "8.x" @@ -3310,29 +2464,12 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -streamsearch@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" - integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -3341,24 +2478,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -3373,35 +2492,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -3411,17 +2502,12 @@ strip-ansi@^6.0.1: strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" @@ -3431,9 +2517,9 @@ supports-color@^5.3.0, supports-color@^5.5.0: has-flag "^3.0.0" supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" @@ -3442,7 +2528,17 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tar-stream@^2.2.0: +tar-fs@^2.0.0: + version "2.1.4" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.4.tgz#800824dbf4ef06ded9afea4acafe71c67c76b930" + integrity sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4, tar-stream@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -3453,27 +2549,14 @@ tar-stream@^2.2.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.4.2: - version "4.4.19" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" - integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== - dependencies: - chownr "^1.1.4" - fs-minipass "^1.2.7" - minipass "^2.9.0" - minizlib "^1.3.3" - mkdirp "^0.5.5" - safe-buffer "^5.2.1" - yallist "^3.1.1" - tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: - version "6.1.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" - integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^4.0.0" + minipass "^5.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" @@ -3486,7 +2569,7 @@ tarn@^3.0.2: temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" - integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= + integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== temp-write@^4.0.0: version "4.0.0" @@ -3499,26 +2582,11 @@ temp-write@^4.0.0: temp-dir "^1.0.0" uuid "^3.3.2" -term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - tildify@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -3532,33 +2600,21 @@ toidentifier@1.0.1: integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== touch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" - integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== - dependencies: - nopt "~1.0.10" + version "3.1.1" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== dependencies: - prelude-ls "^1.2.1" - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + safe-buffer "^5.0.1" type-is@~1.6.18: version "1.6.18" @@ -3568,19 +2624,10 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -undefsafe@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" - integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== - dependencies: - debug "^2.2.0" +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== unique-filename@^1.1.1: version "1.1.1" @@ -3596,55 +2643,15 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -update-notifier@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" - integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== - dependencies: - boxen "^4.2.0" - chalk "^3.0.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.0.0" - pupa "^2.0.1" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== util@^0.10.3: version "0.10.4" @@ -3656,7 +2663,7 @@ util@^0.10.3: utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== uuid@^3.3.2: version "3.4.0" @@ -3666,7 +2673,7 @@ uuid@^3.3.2: vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== webidl-conversions@^3.0.0: version "3.0.1" @@ -3682,24 +2689,17 @@ whatwg-url@^5.0.0: webidl-conversions "^3.0.0" which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which@^2.0.1, which@^2.0.2: +which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" @@ -3707,18 +2707,6 @@ wide-align@^1.1.2, wide-align@^1.1.5: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -word-wrap@^1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" - integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== - wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -3731,22 +2719,7 @@ wrap-ansi@^6.2.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== xtend@^4.0.0: version "4.0.2" @@ -3754,14 +2727,9 @@ xtend@^4.0.0: integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" - integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== - -yallist@^3.0.0, yallist@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== yallist@^4.0.0: version "4.0.0" @@ -3793,16 +2761,11 @@ yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - zip-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" - integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A== + version "4.1.1" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.1.tgz#1337fe974dbaffd2fa9a1ba09662a66932bd7135" + integrity sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ== dependencies: - archiver-utils "^2.1.0" - compress-commons "^4.1.0" + archiver-utils "^3.0.4" + compress-commons "^4.1.2" readable-stream "^3.6.0" diff --git a/docker/Dockerfile b/docker/Dockerfile index 0603e2de..913f79d5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -39,7 +39,6 @@ EXPOSE 80 81 443 COPY backend /app COPY frontend/dist /app/frontend -COPY global /app/global WORKDIR /app RUN yarn install \ diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index dcb1f1f9..45d97a32 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -15,7 +15,7 @@ ENV SUPPRESS_NO_CONFIG_WARNING=1 \ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ && apt-get update \ - && apt-get install -y jq python3-pip logrotate \ + && apt-get install -y jq python3-pip logrotate moreutils \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/docker/docker-compose.ci.postgres.yml b/docker/docker-compose.ci.postgres.yml index c4468c68..e9eb4bc2 100644 --- a/docker/docker-compose.ci.postgres.yml +++ b/docker/docker-compose.ci.postgres.yml @@ -1,17 +1,16 @@ # WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production. services: - cypress: environment: - CYPRESS_stack: 'postgres' + CYPRESS_stack: "postgres" fullstack: environment: - DB_POSTGRES_HOST: 'db-postgres' - DB_POSTGRES_PORT: '5432' - DB_POSTGRES_USER: 'npm' - DB_POSTGRES_PASSWORD: 'npmpass' - DB_POSTGRES_NAME: 'npm' + DB_POSTGRES_HOST: "db-postgres" + DB_POSTGRES_PORT: "5432" + DB_POSTGRES_USER: "npm" + DB_POSTGRES_PASSWORD: "npmpass" + DB_POSTGRES_NAME: "npm" depends_on: - db-postgres - authentik @@ -19,11 +18,11 @@ services: - authentik-ldap db-postgres: - image: postgres:latest + image: postgres:17 environment: - POSTGRES_USER: 'npm' - POSTGRES_PASSWORD: 'npmpass' - POSTGRES_DB: 'npm' + POSTGRES_USER: "npm" + POSTGRES_PASSWORD: "npmpass" + POSTGRES_DB: "npm" volumes: - psql_vol:/var/lib/postgresql/data - ./ci/postgres:/docker-entrypoint-initdb.d @@ -31,11 +30,11 @@ services: - fulltest authentik-redis: - image: 'redis:alpine' + image: "redis:alpine" command: --save 60 1 --loglevel warning restart: unless-stopped healthcheck: - test: ['CMD-SHELL', 'redis-cli ping | grep PONG'] + test: ["CMD-SHELL", "redis-cli ping | grep PONG"] start_period: 20s interval: 30s retries: 5 @@ -66,9 +65,9 @@ services: authentik-ldap: image: ghcr.io/goauthentik/ldap:2024.10.1 environment: - AUTHENTIK_HOST: 'http://authentik:9000' - AUTHENTIK_INSECURE: 'true' - AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp' + AUTHENTIK_HOST: "http://authentik:9000" + AUTHENTIK_INSECURE: "true" + AUTHENTIK_TOKEN: "wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp" restart: unless-stopped depends_on: - authentik diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index 280a0546..8070aa42 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -7,7 +7,9 @@ services: fullstack: image: "${IMAGE}:${BRANCH_LOWER}-ci-${BUILD_NUMBER}" environment: + TZ: "${TZ:-Australia/Brisbane}" DEBUG: 'true' + CI: 'true' FORCE_COLOR: 1 # Required for DNS Certificate provisioning in CI LE_SERVER: 'https://ca.internal/acme/acme/directory' diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 5abe057b..57c30e07 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -1,6 +1,5 @@ # WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production. services: - fullstack: image: npm2dev:core container_name: npm2dev.core @@ -18,13 +17,14 @@ services: - website2.example.com - website3.example.com environment: + TZ: "${TZ:-Australia/Brisbane}" PUID: 1000 PGID: 1000 FORCE_COLOR: 1 # specifically for dev: - DEBUG: 'true' - DEVELOPMENT: 'true' - LE_STAGING: 'true' + DEBUG: "true" + DEVELOPMENT: "true" + LE_STAGING: "true" # db: # DB_MYSQL_HOST: 'db' # DB_MYSQL_PORT: '3306' @@ -32,23 +32,23 @@ services: # DB_MYSQL_PASSWORD: 'npm' # DB_MYSQL_NAME: 'npm' # db-postgres: - DB_POSTGRES_HOST: 'db-postgres' - DB_POSTGRES_PORT: '5432' - DB_POSTGRES_USER: 'npm' - DB_POSTGRES_PASSWORD: 'npmpass' - DB_POSTGRES_NAME: 'npm' + DB_POSTGRES_HOST: "db-postgres" + DB_POSTGRES_PORT: "5432" + DB_POSTGRES_USER: "npm" + DB_POSTGRES_PASSWORD: "npmpass" + DB_POSTGRES_NAME: "npm" # DB_SQLITE_FILE: "/data/database.sqlite" # DISABLE_IPV6: "true" # Required for DNS Certificate provisioning testing: - LE_SERVER: 'https://ca.internal/acme/acme/directory' - REQUESTS_CA_BUNDLE: '/etc/ssl/certs/NginxProxyManager.crt' + LE_SERVER: "https://ca.internal/acme/acme/directory" + REQUESTS_CA_BUNDLE: "/etc/ssl/certs/NginxProxyManager.crt" volumes: - npm_data:/data - le_data:/etc/letsencrypt - - './dev/resolv.conf:/etc/resolv.conf:ro' + - "./dev/resolv.conf:/etc/resolv.conf:ro" - ../backend:/app - - ../frontend:/app/frontend - - ../global:/app/global + - ../frontend:/frontend + - "/etc/localtime:/etc/localtime:ro" healthcheck: test: ["CMD", "/usr/bin/check-health"] interval: 10s @@ -69,22 +69,24 @@ services: networks: - nginx_proxy_manager environment: - MYSQL_ROOT_PASSWORD: 'npm' - MYSQL_DATABASE: 'npm' - MYSQL_USER: 'npm' - MYSQL_PASSWORD: 'npm' + TZ: "${TZ:-Australia/Brisbane}" + MYSQL_ROOT_PASSWORD: "npm" + MYSQL_DATABASE: "npm" + MYSQL_USER: "npm" + MYSQL_PASSWORD: "npm" volumes: - db_data:/var/lib/mysql + - "/etc/localtime:/etc/localtime:ro" db-postgres: - image: postgres:latest + image: postgres:17 container_name: npm2dev.db-postgres networks: - nginx_proxy_manager environment: - POSTGRES_USER: 'npm' - POSTGRES_PASSWORD: 'npmpass' - POSTGRES_DB: 'npm' + POSTGRES_USER: "npm" + POSTGRES_PASSWORD: "npmpass" + POSTGRES_DB: "npm" volumes: - psql_data:/var/lib/postgresql/data - ./ci/postgres:/docker-entrypoint-initdb.d @@ -93,8 +95,8 @@ services: image: jc21/testca container_name: npm2dev.stepca volumes: - - './dev/resolv.conf:/etc/resolv.conf:ro' - - '/etc/localtime:/etc/localtime:ro' + - "./dev/resolv.conf:/etc/resolv.conf:ro" + - "/etc/localtime:/etc/localtime:ro" networks: nginx_proxy_manager: aliases: @@ -115,7 +117,7 @@ services: - 3082:80 environment: URL: "http://npm:81/api/schema" - PORT: '80' + PORT: "80" depends_on: - fullstack @@ -123,9 +125,9 @@ services: image: ubuntu/squid container_name: npm2dev.squid volumes: - - './dev/squid.conf:/etc/squid/squid.conf:ro' - - './dev/resolv.conf:/etc/resolv.conf:ro' - - '/etc/localtime:/etc/localtime:ro' + - "./dev/squid.conf:/etc/squid/squid.conf:ro" + - "./dev/resolv.conf:/etc/resolv.conf:ro" + - "/etc/localtime:/etc/localtime:ro" networks: - nginx_proxy_manager ports: @@ -135,18 +137,18 @@ services: image: pschiffe/pdns-mysql:4.8 container_name: npm2dev.pdns volumes: - - '/etc/localtime:/etc/localtime:ro' + - "/etc/localtime:/etc/localtime:ro" environment: - PDNS_master: 'yes' - PDNS_api: 'yes' - PDNS_api_key: 'npm' - PDNS_webserver: 'yes' - PDNS_webserver_address: '0.0.0.0' - PDNS_webserver_password: 'npm' - PDNS_webserver-allow-from: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8' - PDNS_version_string: 'anonymous' + PDNS_master: "yes" + PDNS_api: "yes" + PDNS_api_key: "npm" + PDNS_webserver: "yes" + PDNS_webserver_address: "0.0.0.0" + PDNS_webserver_password: "npm" + PDNS_webserver-allow-from: "127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8" + PDNS_version_string: "anonymous" PDNS_default_ttl: 1500 - PDNS_allow_axfr_ips: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8' + PDNS_allow_axfr_ips: "127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8" PDNS_gmysql_host: pdns-db PDNS_gmysql_port: 3306 PDNS_gmysql_user: pdns @@ -164,14 +166,14 @@ services: image: mariadb container_name: npm2dev.pdns-db environment: - MYSQL_ROOT_PASSWORD: 'pdns' - MYSQL_DATABASE: 'pdns' - MYSQL_USER: 'pdns' - MYSQL_PASSWORD: 'pdns' + MYSQL_ROOT_PASSWORD: "pdns" + MYSQL_DATABASE: "pdns" + MYSQL_USER: "pdns" + MYSQL_PASSWORD: "pdns" volumes: - - 'pdns_mysql:/var/lib/mysql' - - '/etc/localtime:/etc/localtime:ro' - - './dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro' + - "pdns_mysql:/var/lib/mysql" + - "/etc/localtime:/etc/localtime:ro" + - "./dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro" networks: - nginx_proxy_manager @@ -182,25 +184,25 @@ services: context: ../ dockerfile: test/cypress/Dockerfile environment: - HTTP_PROXY: 'squid:3128' - HTTPS_PROXY: 'squid:3128' + HTTP_PROXY: "squid:3128" + HTTPS_PROXY: "squid:3128" volumes: - - '../test/results:/results' - - './dev/resolv.conf:/etc/resolv.conf:ro' - - '/etc/localtime:/etc/localtime:ro' + - "../test/results:/results" + - "./dev/resolv.conf:/etc/resolv.conf:ro" + - "/etc/localtime:/etc/localtime:ro" command: cypress run --browser chrome --config-file=cypress/config/ci.js networks: - nginx_proxy_manager authentik-redis: - image: 'redis:alpine' + image: "redis:alpine" container_name: npm2dev.authentik-redis command: --save 60 1 --loglevel warning networks: - nginx_proxy_manager restart: unless-stopped healthcheck: - test: ['CMD-SHELL', 'redis-cli ping | grep PONG'] + test: ["CMD-SHELL", "redis-cli ping | grep PONG"] start_period: 20s interval: 30s retries: 5 @@ -242,9 +244,9 @@ services: networks: - nginx_proxy_manager environment: - AUTHENTIK_HOST: 'http://authentik:9000' - AUTHENTIK_INSECURE: 'true' - AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp' + AUTHENTIK_HOST: "http://authentik:9000" + AUTHENTIK_INSECURE: "true" + AUTHENTIK_TOKEN: "wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp" restart: unless-stopped depends_on: - authentik diff --git a/docker/rootfs/etc/nginx/conf.d/dev.conf b/docker/rootfs/etc/nginx/conf.d/dev.conf index edbdec8a..67efc0f8 100644 --- a/docker/rootfs/etc/nginx/conf.d/dev.conf +++ b/docker/rootfs/etc/nginx/conf.d/dev.conf @@ -12,6 +12,7 @@ server { location /api/ { add_header X-Served-By $host; + proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; @@ -23,7 +24,14 @@ server { } location / { - index index.html; - try_files $uri $uri.html $uri/ /index.html; + add_header X-Served-By $host; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header X-Forwarded-Scheme $scheme; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_pass http://127.0.0.1:5173; } } diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run index 4f203555..91ed3fa8 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run @@ -7,15 +7,15 @@ set -e if [ "$DEVELOPMENT" = 'true' ]; then . /usr/bin/common.sh - cd /app/frontend || exit 1 + cd /frontend || exit 1 HOME=$NPMHOME export HOME - mkdir -p /app/frontend/dist - chown -R "$PUID:$PGID" /app/frontend/dist + mkdir -p /frontend/dist + chown -R "$PUID:$PGID" /frontend/dist log_info 'Starting frontend ...' s6-setuidgid "$PUID:$PGID" yarn install - exec s6-setuidgid "$PUID:$PGID" yarn watch + exec s6-setuidgid "$PUID:$PGID" yarn dev else exit 0 fi diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index cd485237..2e3cda7d 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -24,4 +24,5 @@ .inline-img img { display: inline; + margin-right: 8px; } diff --git a/docs/package.json b/docs/package.json index 3e3dcba2..d4523250 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,7 +5,7 @@ "preview": "vitepress preview" }, "devDependencies": { - "vitepress": "^1.4.0" + "vitepress": "^1.6.4" }, "dependencies": {} } diff --git a/docs/src/advanced-config/index.md b/docs/src/advanced-config/index.md index 4a7c260e..e4a9594e 100644 --- a/docs/src/advanced-config/index.md +++ b/docs/src/advanced-config/index.md @@ -228,3 +228,13 @@ To enable the geoip2 module, you can create the custom configuration file `/data load_module /usr/lib/nginx/modules/ngx_http_geoip2_module.so; load_module /usr/lib/nginx/modules/ngx_stream_geoip2_module.so; ``` + +## Auto Initial User Creation + +Setting these environment variables will create the default user on startup, skipping the UI first user setup screen: + +``` + environment: + INITIAL_ADMIN_EMAIL: my@example.com + INITIAL_ADMIN_PASSWORD: mypassword1 +``` diff --git a/docs/src/faq/index.md b/docs/src/faq/index.md index ea375f47..b3668562 100644 --- a/docs/src/faq/index.md +++ b/docs/src/faq/index.md @@ -23,4 +23,10 @@ Your best bet is to ask the [Reddit community for support](https://www.reddit.co ## When adding username and password access control to a proxy host, I can no longer login into the app. -Having an Access Control List (ACL) with username and password requires the browser to always send this username and password in the `Authorization` header on each request. If your proxied app also requires authentication (like Nginx Proxy Manager itself), most likely the app will also use the `Authorization` header to transmit this information, as this is the standardized header meant for this kind of information. However having multiples of the same headers is not allowed in the [internet standard](https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2) and almost all apps do not support multiple values in the `Authorization` header. Hence one of the two logins will be broken. This can only be fixed by either removing one of the logins or by changing the app to use other non-standard headers for authorization. +Having an Access Control List (ACL) with username and password requires the browser to always send this username +and password in the `Authorization` header on each request. If your proxied app also requires authentication (like +Nginx Proxy Manager itself), most likely the app will also use the `Authorization` header to transmit this information, +as this is the standardized header meant for this kind of information. However having multiples of the same headers +is not allowed in the [internet standard](https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2) and almost all apps +do not support multiple values in the `Authorization` header. Hence one of the two logins will be broken. This can +only be fixed by either removing one of the logins or by changing the app to use other non-standard headers for authorization. diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index fcf176fa..a9de63a9 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -35,7 +35,7 @@ so that the barrier for entry here is low. ## Features -- Beautiful and Secure Admin Interface based on [Tabler](https://tabler.github.io/) +- Beautiful and Secure Admin Interface based on [Tabler](https://tabler.io/) - Easily create forwarding domains, redirections, streams and 404 hosts without knowing anything about Nginx - Free SSL using Let's Encrypt or provide your own custom SSL certificates - Access Lists and basic HTTP Authentication for your hosts @@ -66,6 +66,8 @@ services: app: image: 'jc21/nginx-proxy-manager:latest' restart: unless-stopped + environment: + TZ: "Australia/Brisbane" ports: - '80:80' - '81:81' @@ -80,26 +82,16 @@ This is the bare minimum configuration required. See the [documentation](https:/ 3. Bring up your stack by running ```bash -docker-compose up -d - -# If using docker-compose-plugin docker compose up -d ``` 4. Log in to the Admin UI When your docker container is running, connect to it on port `81` for the admin interface. -Sometimes this can take a little bit because of the entropy of keys. [http://127.0.0.1:81](http://127.0.0.1:81) -Default Admin User: -``` -Email: admin@example.com -Password: changeme -``` - -Immediately after logging in with this default user you will be asked to modify your details and change your password. +This startup can take a minute depending on your hardware. ## Contributing diff --git a/docs/src/public/screenshots/access-lists.png b/docs/src/public/screenshots/access-lists.png deleted file mode 100644 index 058c6012..00000000 Binary files a/docs/src/public/screenshots/access-lists.png and /dev/null differ diff --git a/docs/src/public/screenshots/audit-log.png b/docs/src/public/screenshots/audit-log.png deleted file mode 100644 index 82de4ad0..00000000 Binary files a/docs/src/public/screenshots/audit-log.png and /dev/null differ diff --git a/docs/src/public/screenshots/certificates.png b/docs/src/public/screenshots/certificates.png deleted file mode 100644 index 222c79d2..00000000 Binary files a/docs/src/public/screenshots/certificates.png and /dev/null differ diff --git a/docs/src/public/screenshots/custom-settings.png b/docs/src/public/screenshots/custom-settings.png deleted file mode 100644 index 6ea4d58f..00000000 Binary files a/docs/src/public/screenshots/custom-settings.png and /dev/null differ diff --git a/docs/src/public/screenshots/dark/01_first-user.png b/docs/src/public/screenshots/dark/01_first-user.png new file mode 100644 index 00000000..f585c859 Binary files /dev/null and b/docs/src/public/screenshots/dark/01_first-user.png differ diff --git a/docs/src/public/screenshots/dark/02_login.png b/docs/src/public/screenshots/dark/02_login.png new file mode 100644 index 00000000..0ce7fd40 Binary files /dev/null and b/docs/src/public/screenshots/dark/02_login.png differ diff --git a/docs/src/public/screenshots/dark/03_dashboard.png b/docs/src/public/screenshots/dark/03_dashboard.png new file mode 100644 index 00000000..22aedb3c Binary files /dev/null and b/docs/src/public/screenshots/dark/03_dashboard.png differ diff --git a/docs/src/public/screenshots/dark/04_proxy-hosts.png b/docs/src/public/screenshots/dark/04_proxy-hosts.png new file mode 100644 index 00000000..363c7083 Binary files /dev/null and b/docs/src/public/screenshots/dark/04_proxy-hosts.png differ diff --git a/docs/src/public/screenshots/dark/05_redirection_hosts.png b/docs/src/public/screenshots/dark/05_redirection_hosts.png new file mode 100644 index 00000000..dd9eebba Binary files /dev/null and b/docs/src/public/screenshots/dark/05_redirection_hosts.png differ diff --git a/docs/src/public/screenshots/dark/06_streams.png b/docs/src/public/screenshots/dark/06_streams.png new file mode 100644 index 00000000..feb4fa46 Binary files /dev/null and b/docs/src/public/screenshots/dark/06_streams.png differ diff --git a/docs/src/public/screenshots/dark/07_404_hosts.png b/docs/src/public/screenshots/dark/07_404_hosts.png new file mode 100644 index 00000000..431c69d2 Binary files /dev/null and b/docs/src/public/screenshots/dark/07_404_hosts.png differ diff --git a/docs/src/public/screenshots/dark/08_access-lists.png b/docs/src/public/screenshots/dark/08_access-lists.png new file mode 100644 index 00000000..33a19bc4 Binary files /dev/null and b/docs/src/public/screenshots/dark/08_access-lists.png differ diff --git a/docs/src/public/screenshots/dark/09_certificates.png b/docs/src/public/screenshots/dark/09_certificates.png new file mode 100644 index 00000000..e870ae52 Binary files /dev/null and b/docs/src/public/screenshots/dark/09_certificates.png differ diff --git a/docs/src/public/screenshots/dark/10_users.png b/docs/src/public/screenshots/dark/10_users.png new file mode 100644 index 00000000..34ed0b54 Binary files /dev/null and b/docs/src/public/screenshots/dark/10_users.png differ diff --git a/docs/src/public/screenshots/dark/11_audit-logs.png b/docs/src/public/screenshots/dark/11_audit-logs.png new file mode 100644 index 00000000..bce75b12 Binary files /dev/null and b/docs/src/public/screenshots/dark/11_audit-logs.png differ diff --git a/docs/src/public/screenshots/dark/12_settings.png b/docs/src/public/screenshots/dark/12_settings.png new file mode 100644 index 00000000..adc518f3 Binary files /dev/null and b/docs/src/public/screenshots/dark/12_settings.png differ diff --git a/docs/src/public/screenshots/dark/13_add-proxy_host.png b/docs/src/public/screenshots/dark/13_add-proxy_host.png new file mode 100644 index 00000000..4b5522c7 Binary files /dev/null and b/docs/src/public/screenshots/dark/13_add-proxy_host.png differ diff --git a/docs/src/public/screenshots/dark/14_add_proxy_host_dns.png b/docs/src/public/screenshots/dark/14_add_proxy_host_dns.png new file mode 100644 index 00000000..199708dc Binary files /dev/null and b/docs/src/public/screenshots/dark/14_add_proxy_host_dns.png differ diff --git a/docs/src/public/screenshots/dashboard.png b/docs/src/public/screenshots/dashboard.png deleted file mode 100644 index fce2711a..00000000 Binary files a/docs/src/public/screenshots/dashboard.png and /dev/null differ diff --git a/docs/src/public/screenshots/dead-hosts.png b/docs/src/public/screenshots/dead-hosts.png deleted file mode 100644 index 0c36de3f..00000000 Binary files a/docs/src/public/screenshots/dead-hosts.png and /dev/null differ diff --git a/docs/src/public/screenshots/light/01_first-user.png b/docs/src/public/screenshots/light/01_first-user.png new file mode 100644 index 00000000..c0a48d67 Binary files /dev/null and b/docs/src/public/screenshots/light/01_first-user.png differ diff --git a/docs/src/public/screenshots/light/02_login.png b/docs/src/public/screenshots/light/02_login.png new file mode 100644 index 00000000..86574aa9 Binary files /dev/null and b/docs/src/public/screenshots/light/02_login.png differ diff --git a/docs/src/public/screenshots/light/03_dashboard.png b/docs/src/public/screenshots/light/03_dashboard.png new file mode 100644 index 00000000..38057e8c Binary files /dev/null and b/docs/src/public/screenshots/light/03_dashboard.png differ diff --git a/docs/src/public/screenshots/light/04_proxy-hosts.png b/docs/src/public/screenshots/light/04_proxy-hosts.png new file mode 100644 index 00000000..e1a45d96 Binary files /dev/null and b/docs/src/public/screenshots/light/04_proxy-hosts.png differ diff --git a/docs/src/public/screenshots/light/05_redirection_hosts.png b/docs/src/public/screenshots/light/05_redirection_hosts.png new file mode 100644 index 00000000..e90a8844 Binary files /dev/null and b/docs/src/public/screenshots/light/05_redirection_hosts.png differ diff --git a/docs/src/public/screenshots/light/06_streams.png b/docs/src/public/screenshots/light/06_streams.png new file mode 100644 index 00000000..79657aa7 Binary files /dev/null and b/docs/src/public/screenshots/light/06_streams.png differ diff --git a/docs/src/public/screenshots/light/07_404_hosts.png b/docs/src/public/screenshots/light/07_404_hosts.png new file mode 100644 index 00000000..cca39f1b Binary files /dev/null and b/docs/src/public/screenshots/light/07_404_hosts.png differ diff --git a/docs/src/public/screenshots/light/08_access-lists.png b/docs/src/public/screenshots/light/08_access-lists.png new file mode 100644 index 00000000..84ee9104 Binary files /dev/null and b/docs/src/public/screenshots/light/08_access-lists.png differ diff --git a/docs/src/public/screenshots/light/09_certificates.png b/docs/src/public/screenshots/light/09_certificates.png new file mode 100644 index 00000000..0ed24266 Binary files /dev/null and b/docs/src/public/screenshots/light/09_certificates.png differ diff --git a/docs/src/public/screenshots/light/10_users.png b/docs/src/public/screenshots/light/10_users.png new file mode 100644 index 00000000..7e949808 Binary files /dev/null and b/docs/src/public/screenshots/light/10_users.png differ diff --git a/docs/src/public/screenshots/light/11_audit-logs.png b/docs/src/public/screenshots/light/11_audit-logs.png new file mode 100644 index 00000000..19de8975 Binary files /dev/null and b/docs/src/public/screenshots/light/11_audit-logs.png differ diff --git a/docs/src/public/screenshots/light/12_settings.png b/docs/src/public/screenshots/light/12_settings.png new file mode 100644 index 00000000..2afa273d Binary files /dev/null and b/docs/src/public/screenshots/light/12_settings.png differ diff --git a/docs/src/public/screenshots/light/13_add-proxy_host.png b/docs/src/public/screenshots/light/13_add-proxy_host.png new file mode 100644 index 00000000..eeeb1fcb Binary files /dev/null and b/docs/src/public/screenshots/light/13_add-proxy_host.png differ diff --git a/docs/src/public/screenshots/light/14_add_proxy_host_dns.png b/docs/src/public/screenshots/light/14_add_proxy_host_dns.png new file mode 100644 index 00000000..50b10885 Binary files /dev/null and b/docs/src/public/screenshots/light/14_add_proxy_host_dns.png differ diff --git a/docs/src/public/screenshots/login.png b/docs/src/public/screenshots/login.png deleted file mode 100644 index 99b12e19..00000000 Binary files a/docs/src/public/screenshots/login.png and /dev/null differ diff --git a/docs/src/public/screenshots/permissions.png b/docs/src/public/screenshots/permissions.png deleted file mode 100644 index 317d02ca..00000000 Binary files a/docs/src/public/screenshots/permissions.png and /dev/null differ diff --git a/docs/src/public/screenshots/proxy-hosts-add.png b/docs/src/public/screenshots/proxy-hosts-add.png deleted file mode 100644 index e003c0b7..00000000 Binary files a/docs/src/public/screenshots/proxy-hosts-add.png and /dev/null differ diff --git a/docs/src/public/screenshots/proxy-hosts.png b/docs/src/public/screenshots/proxy-hosts.png deleted file mode 100644 index 7485c0f6..00000000 Binary files a/docs/src/public/screenshots/proxy-hosts.png and /dev/null differ diff --git a/docs/src/public/screenshots/redirection-hosts.png b/docs/src/public/screenshots/redirection-hosts.png deleted file mode 100644 index 5b61b57a..00000000 Binary files a/docs/src/public/screenshots/redirection-hosts.png and /dev/null differ diff --git a/docs/src/screenshots/index.md b/docs/src/screenshots/index.md index 8bd21028..2e15fef6 100644 --- a/docs/src/screenshots/index.md +++ b/docs/src/screenshots/index.md @@ -4,17 +4,44 @@ outline: deep # Screenshots +### Light Mode + ::: raw
- Login - Dashboard - Proxy Hosts - Add Proxy Host - Redirection Hosts - 404 Hosts - User Permissions - Certificates - Audit Log - Custom Settings + Setup + Login + Dashboard + Proxy Hosts + Redirection Hosts + Streams + 404 Hosts + Access Lists + Certificates + Users + Audit Logs + Settings + Add Proxy Host + Add Proxy Host with DNS +
+::: + +### Dark Mode + +::: raw +
+ Setup + Login + Dashboard + Proxy Hosts + Redirection Hosts + Streams + 404 Hosts + Access Lists + Certificates + Users + Audit Logs + Settings + Add Proxy Host + Add Proxy Host with DNS
::: diff --git a/docs/src/setup/index.md b/docs/src/setup/index.md index c2296da7..998508dd 100644 --- a/docs/src/setup/index.md +++ b/docs/src/setup/index.md @@ -13,6 +13,7 @@ services: app: image: 'jc21/nginx-proxy-manager:latest' restart: unless-stopped + ports: # These ports are in format : - '80:80' # Public HTTP Port @@ -21,7 +22,9 @@ services: # Add any other Stream port you want to expose # - '21:21' # FTP - #environment: + environment: + TZ: "Australia/Brisbane" + # Uncomment this if you want to change the location of # the SQLite DB file within the container # DB_SQLITE_FILE: "/data/database.sqlite" @@ -65,12 +68,17 @@ services: # Add any other Stream port you want to expose # - '21:21' # FTP environment: + TZ: "Australia/Brisbane" # Mysql/Maria connection parameters: DB_MYSQL_HOST: "db" DB_MYSQL_PORT: 3306 DB_MYSQL_USER: "npm" DB_MYSQL_PASSWORD: "npm" DB_MYSQL_NAME: "npm" + # Optional SSL (see section below) + # DB_MYSQL_SSL: 'true' + # DB_MYSQL_SSL_REJECT_UNAUTHORIZED: 'true' + # DB_MYSQL_SSL_VERIFY_IDENTITY: 'true' # Uncomment this if IPv6 is not enabled on your host # DISABLE_IPV6: 'true' volumes: @@ -98,6 +106,16 @@ Please note, that `DB_MYSQL_*` environment variables will take precedent over `D ::: +### Optional: MySQL / MariaDB SSL + +You can enable TLS for the MySQL/MariaDB connection with these environment variables: + +- DB_MYSQL_SSL: Enable SSL when set to true. If unset or false, SSL disabled (previous default behaviour). +- DB_MYSQL_SSL_REJECT_UNAUTHORIZED: (default: true) Validate the server certificate chain. Set to false to allow self‑signed/unknown CA. +- DB_MYSQL_SSL_VERIFY_IDENTITY: (default: true) Performs host name / identity verification. + +Enabling SSL using a self-signed cert (not recommended for production). + ## Using Postgres database Similar to the MySQL server setup: @@ -115,6 +133,7 @@ services: # Add any other Stream port you want to expose # - '21:21' # FTP environment: + TZ: "Australia/Brisbane" # Postgres parameters: DB_POSTGRES_HOST: 'db' DB_POSTGRES_PORT: '5432' @@ -130,13 +149,13 @@ services: - db db: - image: postgres:latest + image: postgres:17 environment: POSTGRES_USER: 'npm' POSTGRES_PASSWORD: 'npmpass' POSTGRES_DB: 'npm' volumes: - - ./postgres:/var/lib/postgresql/data + - ./postgresql:/var/lib/postgresql ``` ::: warning @@ -173,21 +192,3 @@ After the app is running for the first time, the following will happen: 3. A default admin user will be created This process can take a couple of minutes depending on your machine. - -## Default Administrator User - -``` -Email: admin@example.com -Password: changeme -``` - -Immediately after logging in with this default user you will be asked to modify your details and change your password. You can change defaults with: - - -``` - environment: - INITIAL_ADMIN_EMAIL: my@example.com - INITIAL_ADMIN_PASSWORD: mypassword1 -``` - - diff --git a/docs/yarn.lock b/docs/yarn.lock index c95905f6..985626b6 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -1065,9 +1065,9 @@ vfile@^6.0.0: vfile-message "^4.0.0" vite@^5.4.8: - version "5.4.19" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.19.tgz#20efd060410044b3ed555049418a5e7d1998f959" - integrity sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA== + version "5.4.21" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.21.tgz#84a4f7c5d860b071676d39ba513c0d598fdc7027" + integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw== dependencies: esbuild "^0.21.3" postcss "^8.4.43" diff --git a/frontend/.babelrc b/frontend/.babelrc deleted file mode 100644 index 54071ecd..00000000 --- a/frontend/.babelrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "targets": { - "browsers": [ - "Chrome >= 65" - ] - }, - "debug": false, - "modules": false, - "useBuiltIns": "usage" - } - ] - ] -} \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index c8f4b4f9..8b7e5021 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,4 +1,22 @@ -dist +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + node_modules -webpack_stats.html -yarn-error.log +dist +dist-ssr +*.local + +# Editor directories and files +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/biome.json b/frontend/biome.json new file mode 100644 index 00000000..298b88b5 --- /dev/null +++ b/frontend/biome.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false, + "includes": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "!**/dist/**/*" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "indentWidth": 4, + "lineWidth": 120, + "formatWithErrors": true + }, + "assist": { + "actions": { + "source": { + "organizeImports": { + "level": "on", + "options": { + "groups": [ + ":BUN:", + ":NODE:", + [ + "npm:*", + "npm:*/**" + ], + ":PACKAGE_WITH_PROTOCOL:", + ":URL:", + ":PACKAGE:", + [ + "/src/*", + "/src/**" + ], + [ + "/**" + ], + [ + "#*", + "#*/**" + ], + ":PATH:" + ] + } + } + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "useUniqueElementIds": "off" + }, + "suspicious": { + "noExplicitAny": "off", + "noArrayIndexKey": "off" + }, + "performance": { + "noDelete": "off" + }, + "nursery": "off", + "a11y": { + "useSemanticElements": "off", + "useValidAnchor": "off" + }, + "style": { + "noParameterAssign": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSelfClosingElements": "error", + "useSingleVarDeclarator": "error", + "noUnusedTemplateLiteral": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error", + "noUselessElse": "error" + } + } + } +} diff --git a/frontend/check-locales.cjs b/frontend/check-locales.cjs new file mode 100755 index 00000000..03a8be14 --- /dev/null +++ b/frontend/check-locales.cjs @@ -0,0 +1,159 @@ +#!/usr/bin/env node + +// This file does a few things to ensure that the Locales are present and valid: +// - Ensures that the name of the locale exists in the language list +// - Ensures that each locale contains the translations used in the application +// - Ensures that there are no unused translations in the locale files +// - Also checks the error messages returned by the backend + +const allLocales = [ + ["en", "en-US"], + ["fa", "fa-IR"], +]; + +const ignoreUnused = [ + /^.*$/, +]; + +const { spawnSync } = require("child_process"); +const fs = require("fs"); + +const tmp = require("tmp"); + +// Parse backend errors +const BACKEND_ERRORS_FILE = "../backend/internal/errors/errors.go"; +const BACKEND_ERRORS = []; +/* +try { + const backendErrorsContent = fs.readFileSync(BACKEND_ERRORS_FILE, "utf8"); + const backendErrorsContentRes = [ + ...backendErrorsContent.matchAll(/(?:errors|eris)\.New\("([^"]+)"\)/g), + ]; + backendErrorsContentRes.map((item) => { + BACKEND_ERRORS.push("error." + item[1]); + return null; + }); +} catch (err) { + console.log("\x1b[31m%s\x1b[0m", err); + process.exit(1); +} +*/ + +// get all translations used in frontend code +const tmpobj = tmp.fileSync({ postfix: ".json" }); +spawnSync("yarn", ["locale-extract", "--out-file", tmpobj.name]); + +const allLocalesInProject = require(tmpobj.name); + +// get list og language names and locales +const langList = require("./src/locale/src/lang-list.json"); + +// store a list of all validation errors +const allErrors = []; +const allWarnings = []; +const allKeys = []; + +const checkLangList = (fullCode) => { + const key = "locale-" + fullCode; + if (typeof langList[key] === "undefined") { + allErrors.push( + "ERROR: `" + key + "` language does not exist in lang-list.json", + ); + } +}; + +const compareLocale = (locale) => { + const projectLocaleKeys = Object.keys(allLocalesInProject); + // Check that locale contains the items used in the codebase + projectLocaleKeys.map((key) => { + if (typeof locale.data[key] === "undefined") { + allErrors.push( + "ERROR: `" + locale[0] + "` does not contain item: `" + key + "`", + ); + } + return null; + }); + // Check that locale contains all error.* items + BACKEND_ERRORS.forEach((key) => { + if (typeof locale.data[key] === "undefined") { + allErrors.push( + "ERROR: `" + locale[0] + "` does not contain item: `" + key + "`", + ); + } + return null; + }); + + // Check that locale does not contain items not used in the codebase + const localeKeys = Object.keys(locale.data); + localeKeys.map((key) => { + let ignored = false; + ignoreUnused.map((regex) => { + if (key.match(regex)) { + ignored = true; + } + return null; + }); + + if (!ignored && typeof allLocalesInProject[key] === "undefined") { + // ensure this key doesn't exist in the backend errors either + if (!BACKEND_ERRORS.includes(key)) { + allErrors.push( + "ERROR: `" + locale[0] + "` contains unused item: `" + key + "`", + ); + } + } + + // Add this key to allKeys + if (allKeys.indexOf(key) === -1) { + allKeys.push(key); + } + return null; + }); +}; + +// Checks for any keys missing from this locale, that +// have been defined in any other locales +const checkForMissing = (locale) => { + allKeys.forEach((key) => { + if (typeof locale.data[key] === "undefined") { + allWarnings.push( + "WARN: `" + locale[0] + "` does not contain item: `" + key + "`", + ); + } + return null; + }); +}; + +// Local all locale data +allLocales.map((locale, idx) => { + checkLangList(locale[1]); + allLocales[idx].data = require("./src/locale/src/" + locale[0] + ".json"); + return null; +}); + +// Verify all locale data +allLocales.map((locale) => { + compareLocale(locale); + checkForMissing(locale); + return null; +}); + +if (allErrors.length) { + allErrors.map((err) => { + console.log("\x1b[31m%s\x1b[0m", err); + return null; + }); +} +if (allWarnings.length) { + allWarnings.map((err) => { + console.log("\x1b[33m%s\x1b[0m", err); + return null; + }); +} + +if (allErrors.length) { + process.exit(1); +} + +console.log("\x1b[32m%s\x1b[0m", "Locale check passed"); +process.exit(0); diff --git a/frontend/fonts/feather b/frontend/fonts/feather deleted file mode 120000 index 440203ba..00000000 --- a/frontend/fonts/feather +++ /dev/null @@ -1 +0,0 @@ -../node_modules/tabler-ui/dist/assets/fonts/feather \ No newline at end of file diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff deleted file mode 100644 index 96d8768e..00000000 Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff and /dev/null differ diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2 deleted file mode 100644 index e97a2218..00000000 Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2 and /dev/null differ diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff deleted file mode 100644 index 0829caef..00000000 Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff and /dev/null differ diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 deleted file mode 100644 index 7c901cd8..00000000 Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 and /dev/null differ diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff deleted file mode 100644 index 99652481..00000000 Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff and /dev/null differ diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 deleted file mode 100644 index 343e5ba8..00000000 Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 and /dev/null differ diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff deleted file mode 100644 index 92c3260e..00000000 Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff and /dev/null differ diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 deleted file mode 100644 index d552543b..00000000 Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 and /dev/null differ diff --git a/frontend/html/index.ejs b/frontend/html/index.ejs deleted file mode 100644 index ae08b012..00000000 --- a/frontend/html/index.ejs +++ /dev/null @@ -1,9 +0,0 @@ -<% var title = 'Nginx Proxy Manager' %> -<%- include partials/header.ejs %> - -
- -
- - -<%- include partials/footer.ejs %> diff --git a/frontend/html/login.ejs b/frontend/html/login.ejs deleted file mode 100644 index bc4b9a27..00000000 --- a/frontend/html/login.ejs +++ /dev/null @@ -1,9 +0,0 @@ -<% var title = 'Login – Nginx Proxy Manager' %> -<%- include partials/header.ejs %> - -
- -
- - -<%- include partials/footer.ejs %> diff --git a/frontend/html/partials/footer.ejs b/frontend/html/partials/footer.ejs deleted file mode 100644 index 7fb2bd61..00000000 --- a/frontend/html/partials/footer.ejs +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/frontend/html/partials/header.ejs b/frontend/html/partials/header.ejs deleted file mode 100644 index cabb9df2..00000000 --- a/frontend/html/partials/header.ejs +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - <%- title %> - - - - - - - - - - - - - - diff --git a/frontend/images b/frontend/images deleted file mode 120000 index 37c31854..00000000 --- a/frontend/images +++ /dev/null @@ -1 +0,0 @@ -./node_modules/tabler-ui/dist/assets/images \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 00000000..80818044 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,44 @@ + + + + + + Nginx Proxy Manager + + + + + + + + + + + + + +
+ + + + diff --git a/frontend/js/app/api.js b/frontend/js/app/api.js deleted file mode 100644 index 6e33a6dc..00000000 --- a/frontend/js/app/api.js +++ /dev/null @@ -1,757 +0,0 @@ -const $ = require('jquery'); -const _ = require('underscore'); -const Tokens = require('./tokens'); - -/** - * @param {String} message - * @param {*} debug - * @param {Number} code - * @constructor - */ -const ApiError = function (message, debug, code) { - let temp = Error.call(this, message); - temp.name = this.name = 'ApiError'; - this.stack = temp.stack; - this.message = temp.message; - this.debug = debug; - this.code = code; -}; - -ApiError.prototype = Object.create(Error.prototype, { - constructor: { - value: ApiError, - writable: true, - configurable: true - } -}); - -/** - * - * @param {String} verb - * @param {String} path - * @param {Object} [data] - * @param {Object} [options] - * @returns {Promise} - */ -function fetch(verb, path, data, options) { - options = options || {}; - - return new Promise(function (resolve, reject) { - let api_url = '/api/'; - let url = api_url + path; - let token = Tokens.getTopToken(); - - if ((typeof options.contentType === 'undefined' || options.contentType.match(/json/im)) && typeof data === 'object') { - data = JSON.stringify(data); - } - - $.ajax({ - url: url, - data: typeof data === 'object' ? JSON.stringify(data) : data, - type: verb, - dataType: 'json', - contentType: options.contentType || 'application/json; charset=UTF-8', - processData: options.processData || true, - crossDomain: true, - timeout: options.timeout ? options.timeout : 180000, - xhrFields: { - withCredentials: true - }, - - beforeSend: function (xhr) { - xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); - }, - - success: function (data, textStatus, response) { - let total = response.getResponseHeader('X-Dataset-Total'); - if (total !== null) { - resolve({ - data: data, - pagination: { - total: parseInt(total, 10), - offset: parseInt(response.getResponseHeader('X-Dataset-Offset'), 10), - limit: parseInt(response.getResponseHeader('X-Dataset-Limit'), 10) - } - }); - } else { - resolve(response); - } - }, - - error: function (xhr, status, error_thrown) { - let code = 400; - - if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { - error_thrown = xhr.responseJSON.error.message; - code = xhr.responseJSON.error.code || 500; - } - - reject(new ApiError(error_thrown, xhr.responseText, code)); - } - }); - }); -} - -/** - * - * @param {Array} expand - * @returns {String} - */ -function makeExpansionString(expand) { - let items = []; - _.forEach(expand, function (exp) { - items.push(encodeURIComponent(exp)); - }); - - return items.join(','); -} - -/** - * @param {String} path - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ -function getAllObjects(path, expand, query) { - let params = []; - - if (typeof expand === 'object' && expand !== null && expand.length) { - params.push('expand=' + makeExpansionString(expand)); - } - - if (typeof query === 'string') { - params.push('query=' + query); - } - - return fetch('get', path + (params.length ? '?' + params.join('&') : '')); -} - -function FileUpload(path, fd) { - return new Promise((resolve, reject) => { - let xhr = new XMLHttpRequest(); - let token = Tokens.getTopToken(); - - xhr.open('POST', '/api/' + path); - xhr.overrideMimeType('text/plain'); - xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); - xhr.send(fd); - - xhr.onreadystatechange = function () { - if (this.readyState === XMLHttpRequest.DONE) { - if (xhr.status !== 200 && xhr.status !== 201) { - try { - reject(new Error('Upload failed: ' + JSON.parse(xhr.responseText).error.message)); - } catch (err) { - reject(new Error('Upload failed: ' + xhr.status)); - } - } else { - resolve(xhr.responseText); - } - } - }; - }); -} - -//ref : https://codepen.io/chrisdpratt/pen/RKxJNo -function DownloadFile(verb, path, filename) { - return new Promise(function (resolve, reject) { - let api_url = '/api/'; - let url = api_url + path; - let token = Tokens.getTopToken(); - - $.ajax({ - url: url, - type: verb, - crossDomain: true, - xhrFields: { - withCredentials: true, - responseType: 'blob' - }, - - beforeSend: function (xhr) { - xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); - }, - - success: function (data) { - var a = document.createElement('a'); - var url = window.URL.createObjectURL(data); - a.href = url; - a.download = filename; - document.body.append(a); - a.click(); - a.remove(); - window.URL.revokeObjectURL(url); - }, - - error: function (xhr, status, error_thrown) { - let code = 400; - - if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { - error_thrown = xhr.responseJSON.error.message; - code = xhr.responseJSON.error.code || 500; - } - - reject(new ApiError(error_thrown, xhr.responseText, code)); - } - }); - }); -} - -module.exports = { - status: function () { - return fetch('get', ''); - }, - - Tokens: { - - /** - * @param {String} identity - * @param {String} secret - * @param {Boolean} [wipe] Will wipe the stack before adding to it again if login was successful - * @returns {Promise} - */ - login: function (identity, secret, wipe) { - return fetch('post', 'tokens', {identity: identity, secret: secret}) - .then(response => { - if (response.token) { - if (wipe) { - Tokens.clearTokens(); - } - - // Set storage token - Tokens.addToken(response.token); - return response.token; - } else { - Tokens.clearTokens(); - throw(new Error('No token returned')); - } - }); - }, - - /** - * @returns {Promise} - */ - refresh: function () { - return fetch('get', 'tokens') - .then(response => { - if (response.token) { - Tokens.setCurrentToken(response.token); - return response.token; - } else { - Tokens.clearTokens(); - throw(new Error('No token returned')); - } - }); - } - }, - - Users: { - - /** - * @param {Number|String} user_id - * @param {Array} [expand] - * @returns {Promise} - */ - getById: function (user_id, expand) { - return fetch('get', 'users/' + user_id + (typeof expand === 'object' && expand.length ? '?expand=' + makeExpansionString(expand) : '')); - }, - - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('users', expand, query); - }, - - /** - * @param {Object} data - * @returns {Promise} - */ - create: function (data) { - return fetch('post', 'users', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'users/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'users/' + id); - }, - - /** - * - * @param {Number} id - * @param {Object} auth - * @returns {Promise} - */ - setPassword: function (id, auth) { - return fetch('put', 'users/' + id + '/auth', auth); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - loginAs: function (id) { - return fetch('post', 'users/' + id + '/login'); - }, - - /** - * - * @param {Number} id - * @param {Object} perms - * @returns {Promise} - */ - setPermissions: function (id, perms) { - return fetch('put', 'users/' + id + '/permissions', perms); - } - }, - - Nginx: { - - ProxyHosts: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/proxy-hosts', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/proxy-hosts', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/proxy-hosts/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/proxy-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/proxy-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/proxy-hosts/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/proxy-hosts/' + id + '/disable'); - } - }, - - RedirectionHosts: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/redirection-hosts', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/redirection-hosts', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/redirection-hosts/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/redirection-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/redirection-hosts/' + id); - }, - - /** - * @param {Number} id - * @param {FormData} form_data - * @params {Promise} - */ - setCerts: function (id, form_data) { - return FileUpload('nginx/redirection-hosts/' + id + '/certificates', form_data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/redirection-hosts/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/redirection-hosts/' + id + '/disable'); - } - }, - - Streams: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/streams', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/streams', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/streams/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/streams/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/streams/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/streams/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/streams/' + id + '/disable'); - } - }, - - DeadHosts: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/dead-hosts', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/dead-hosts', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/dead-hosts/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/dead-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/dead-hosts/' + id); - }, - - /** - * @param {Number} id - * @param {FormData} form_data - * @params {Promise} - */ - setCerts: function (id, form_data) { - return FileUpload('nginx/dead-hosts/' + id + '/certificates', form_data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/dead-hosts/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/dead-hosts/' + id + '/disable'); - } - }, - - AccessLists: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/access-lists', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/access-lists', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/access-lists/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/access-lists/' + id); - } - }, - - Certificates: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/certificates', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - - const timeout = 180000 + (data && data.meta && data.meta.propagation_seconds ? Number(data.meta.propagation_seconds) * 1000 : 0); - return fetch('post', 'nginx/certificates', data, {timeout}); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/certificates/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/certificates/' + id); - }, - - /** - * @param {Number} id - * @param {FormData} form_data - * @params {Promise} - */ - upload: function (id, form_data) { - return FileUpload('nginx/certificates/' + id + '/upload', form_data); - }, - - /** - * @param {FormData} form_data - * @params {Promise} - */ - validate: function (form_data) { - return FileUpload('nginx/certificates/validate', form_data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - renew: function (id, timeout = 180000) { - return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout}); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - testHttpChallenge: function (domains) { - return fetch('get', 'nginx/certificates/test-http?' + new URLSearchParams({ - domains: JSON.stringify(domains), - })); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - download: function (id) { - return DownloadFile('get', "nginx/certificates/" + id + "/download", "certificate.zip") - } - } - }, - - AuditLog: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('audit-log', expand, query); - } - }, - - Reports: { - - /** - * @returns {Promise} - */ - getHostStats: function () { - return fetch('get', 'reports/hosts'); - } - }, - - Settings: { - - /** - * @param {String} setting_id - * @returns {Promise} - */ - getById: function (setting_id) { - return fetch('get', 'settings/' + setting_id); - }, - - /** - * @returns {Promise} - */ - getAll: function () { - return getAllObjects('settings'); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'settings/' + id, data); - } - } -}; diff --git a/frontend/js/app/audit-log/list/item.ejs b/frontend/js/app/audit-log/list/item.ejs deleted file mode 100644 index 84743c8d..00000000 --- a/frontend/js/app/audit-log/list/item.ejs +++ /dev/null @@ -1,80 +0,0 @@ - -
- -
- - -
- <% if (user.is_deleted) { - %> - <%- user.name %> - <% - } else { - %> - <%- user.name %> - <% - } - %> -
- - -
- <% - var items = []; - switch (object_type) { - case 'proxy-host': - %> <% - items = meta.domain_names; - break; - case 'redirection-host': - %> <% - items = meta.domain_names; - break; - case 'stream': - %> <% - items.push(meta.incoming_port); - break; - case 'dead-host': - %> <% - items = meta.domain_names; - break; - case 'access-list': - %> <% - items.push(meta.name); - break; - case 'user': - %> <% - items.push(meta.name); - break; - case 'certificate': - %> <% - if (meta.provider === 'letsencrypt') { - items = meta.domain_names; - } else { - items.push(meta.nice_name); - } - break; - } - %> <%- i18n('audit-log', action, {name: i18n('audit-log', object_type)}) %> - — - <% - if (items && items.length) { - items.map(function(item) { - %> - <%- item %> - <% - }); - } else { - %> - #<%- object_id %> - <% - } - %> -
-
- <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %> -
- - - <%- i18n('audit-log', 'view-meta') %> - diff --git a/frontend/js/app/audit-log/list/item.js b/frontend/js/app/audit-log/list/item.js deleted file mode 100644 index 862ffc22..00000000 --- a/frontend/js/app/audit-log/list/item.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const Controller = require('../../controller'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - meta: 'a.meta' - }, - - events: { - 'click @ui.meta': function (e) { - e.preventDefault(); - Controller.showAuditMeta(this.model); - } - }, - - templateContext: { - more: function() { - switch (this.object_type) { - case 'redirection-host': - case 'stream': - case 'proxy-host': - return this.meta.domain_names.join(', '); - } - - return '#' + (this.object_id || '?'); - } - } -}); diff --git a/frontend/js/app/audit-log/list/main.ejs b/frontend/js/app/audit-log/list/main.ejs deleted file mode 100644 index ec3cf2a2..00000000 --- a/frontend/js/app/audit-log/list/main.ejs +++ /dev/null @@ -1,9 +0,0 @@ - -   - User - Event -   - - - - diff --git a/frontend/js/app/audit-log/list/main.js b/frontend/js/app/audit-log/list/main.js deleted file mode 100644 index 9d3e26fb..00000000 --- a/frontend/js/app/audit-log/list/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/audit-log/main.ejs b/frontend/js/app/audit-log/main.ejs deleted file mode 100644 index 8d182b59..00000000 --- a/frontend/js/app/audit-log/main.ejs +++ /dev/null @@ -1,25 +0,0 @@ -
-
-
-

<%- i18n('audit-log', 'title') %>

-
- -
-
-
-
-
-
- -
-
- -
-
diff --git a/frontend/js/app/audit-log/main.js b/frontend/js/app/audit-log/main.js deleted file mode 100644 index 0d03c5ca..00000000 --- a/frontend/js/app/audit-log/main.js +++ /dev/null @@ -1,82 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const AuditLogModel = require('../../models/audit-log'); -const ListView = require('./list/main'); -const template = require('./main.ejs'); -const ErrorView = require('../error/main'); -const EmptyView = require('../empty/main'); - -module.exports = Mn.View.extend({ - id: 'audit-log', - template: template, - - ui: { - list_region: '.list-region', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.AuditLog.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new AuditLogModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showAuditLog(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - this.showChildView('list_region', new EmptyView({ - title: App.i18n('audit-log', 'empty'), - subtitle: App.i18n('audit-log', 'empty-subtitle') - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['user'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - onRender: function () { - let view = this; - - view.fetch(['user']) - .then(response => { - if (!view.isDestroyed() && response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/audit-log/meta.ejs b/frontend/js/app/audit-log/meta.ejs deleted file mode 100644 index 98a2d973..00000000 --- a/frontend/js/app/audit-log/meta.ejs +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/frontend/js/app/audit-log/meta.js b/frontend/js/app/audit-log/meta.js deleted file mode 100644 index 815cdfac..00000000 --- a/frontend/js/app/audit-log/meta.js +++ /dev/null @@ -1,7 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./meta.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog wide' -}); diff --git a/frontend/js/app/cache.js b/frontend/js/app/cache.js deleted file mode 100644 index 6d1fbc4f..00000000 --- a/frontend/js/app/cache.js +++ /dev/null @@ -1,10 +0,0 @@ -const UserModel = require('../models/user'); - -let cache = { - User: new UserModel.Model(), - locale: 'en', - version: null -}; - -module.exports = cache; - diff --git a/frontend/js/app/controller.js b/frontend/js/app/controller.js deleted file mode 100644 index ebddd780..00000000 --- a/frontend/js/app/controller.js +++ /dev/null @@ -1,441 +0,0 @@ -const Backbone = require('backbone'); -const Cache = require('./cache'); -const Tokens = require('./tokens'); - -module.exports = { - - /** - * @param {String} route - * @param {Object} [options] - * @returns {Boolean} - */ - navigate: function (route, options) { - options = options || {}; - Backbone.history.navigate(route.toString(), options); - return true; - }, - - /** - * Login - */ - showLogin: function () { - window.location = '/login'; - }, - - /** - * Users - */ - showUsers: function () { - const controller = this; - if (Cache.User.isAdmin()) { - require(['./main', './users/main'], (App, View) => { - controller.navigate('/users'); - App.UI.showAppContent(new View()); - }); - } else { - this.showDashboard(); - } - }, - - /** - * User Form - * - * @param [model] - */ - showUserForm: function (model) { - if (Cache.User.isAdmin()) { - require(['./main', './user/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * User Permissions Form - * - * @param model - */ - showUserPermissions: function (model) { - if (Cache.User.isAdmin()) { - require(['./main', './user/permissions'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * User Password Form - * - * @param model - */ - showUserPasswordForm: function (model) { - if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) { - require(['./main', './user/password'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * User Delete Confirm - * - * @param model - */ - showUserDeleteConfirm: function (model) { - if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) { - require(['./main', './user/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Dashboard - */ - showDashboard: function () { - const controller = this; - require(['./main', './dashboard/main'], (App, View) => { - controller.navigate('/'); - App.UI.showAppContent(new View()); - }); - }, - - /** - * Nginx Proxy Hosts - */ - showNginxProxy: function () { - if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) { - const controller = this; - - require(['./main', './nginx/proxy/main'], (App, View) => { - controller.navigate('/nginx/proxy'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Proxy Host Form - * - * @param [model] - */ - showNginxProxyForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { - require(['./main', './nginx/proxy/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Proxy Host Delete Confirm - * - * @param model - */ - showNginxProxyDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { - require(['./main', './nginx/proxy/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Redirection Hosts - */ - showNginxRedirection: function () { - if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) { - const controller = this; - require(['./main', './nginx/redirection/main'], (App, View) => { - controller.navigate('/nginx/redirection'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Redirection Host Form - * - * @param [model] - */ - showNginxRedirectionForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { - require(['./main', './nginx/redirection/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Proxy Redirection Delete Confirm - * - * @param model - */ - showNginxRedirectionDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { - require(['./main', './nginx/redirection/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Stream Hosts - */ - showNginxStream: function () { - if (Cache.User.isAdmin() || Cache.User.canView('streams')) { - const controller = this; - require(['./main', './nginx/stream/main'], (App, View) => { - controller.navigate('/nginx/stream'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Stream Form - * - * @param [model] - */ - showNginxStreamForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { - require(['./main', './nginx/stream/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Stream Delete Confirm - * - * @param model - */ - showNginxStreamDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { - require(['./main', './nginx/stream/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Dead Hosts - */ - showNginxDead: function () { - if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) { - const controller = this; - require(['./main', './nginx/dead/main'], (App, View) => { - controller.navigate('/nginx/404'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Dead Host Form - * - * @param [model] - */ - showNginxDeadForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { - require(['./main', './nginx/dead/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Dead Host Delete Confirm - * - * @param model - */ - showNginxDeadDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { - require(['./main', './nginx/dead/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Help Dialog - * - * @param {String} title - * @param {String} content - */ - showHelp: function (title, content) { - require(['./main', './help/main'], function (App, View) { - App.UI.showModalDialog(new View({title: title, content: content})); - }); - }, - - /** - * Nginx Access - */ - showNginxAccess: function () { - if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) { - const controller = this; - require(['./main', './nginx/access/main'], (App, View) => { - controller.navigate('/nginx/access'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Access List Form - * - * @param [model] - */ - showNginxAccessListForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { - require(['./main', './nginx/access/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Access List Delete Confirm - * - * @param model - */ - showNginxAccessListDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { - require(['./main', './nginx/access/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Certificates - */ - showNginxCertificates: function () { - if (Cache.User.isAdmin() || Cache.User.canView('certificates')) { - const controller = this; - require(['./main', './nginx/certificates/main'], (App, View) => { - controller.navigate('/nginx/certificates'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Certificate Form - * - * @param [model] - */ - showNginxCertificateForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Certificate Renew - * - * @param model - */ - showNginxCertificateRenew: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/renew'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Certificate Delete Confirm - * - * @param model - */ - showNginxCertificateDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Certificate Test Reachability - * - * @param model - */ - showNginxCertificateTestReachability: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/test'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Audit Log - */ - showAuditLog: function () { - const controller = this; - if (Cache.User.isAdmin()) { - require(['./main', './audit-log/main'], (App, View) => { - controller.navigate('/audit-log'); - App.UI.showAppContent(new View()); - }); - } else { - this.showDashboard(); - } - }, - - /** - * Audit Log Metadata - * - * @param model - */ - showAuditMeta: function (model) { - if (Cache.User.isAdmin()) { - require(['./main', './audit-log/meta'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Settings - */ - showSettings: function () { - const controller = this; - if (Cache.User.isAdmin()) { - require(['./main', './settings/main'], (App, View) => { - controller.navigate('/settings'); - App.UI.showAppContent(new View()); - }); - } else { - this.showDashboard(); - } - }, - - /** - * Settings Item Form - * - * @param model - */ - showSettingForm: function (model) { - if (Cache.User.isAdmin()) { - if (model.get('id') === 'default-site') { - require(['./main', './settings/default-site/main'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - } - }, - - /** - * Logout - */ - logout: function () { - Tokens.dropTopToken(); - this.showLogin(); - } -}; diff --git a/frontend/js/app/dashboard/main.ejs b/frontend/js/app/dashboard/main.ejs deleted file mode 100644 index c00aa6d0..00000000 --- a/frontend/js/app/dashboard/main.ejs +++ /dev/null @@ -1,67 +0,0 @@ - - -<% if (columns) { %> -
- <% if (canShow('proxy_hosts')) { %> - - <% } %> - - <% if (canShow('redirection_hosts')) { %> - - <% } %> - - <% if (canShow('streams')) { %> - - <% } %> - - <% if (canShow('dead_hosts')) { %> - - <% } %> -
-<% } %> diff --git a/frontend/js/app/dashboard/main.js b/frontend/js/app/dashboard/main.js deleted file mode 100644 index ba4a99a6..00000000 --- a/frontend/js/app/dashboard/main.js +++ /dev/null @@ -1,90 +0,0 @@ -const Mn = require('backbone.marionette'); -const Cache = require('../cache'); -const Controller = require('../controller'); -const Api = require('../api'); -const Helpers = require('../../lib/helpers'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - template: template, - id: 'dashboard', - columns: 0, - - stats: {}, - - ui: { - links: 'a' - }, - - events: { - 'click @ui.links': function (e) { - e.preventDefault(); - Controller.navigate($(e.currentTarget).attr('href'), true); - } - }, - - templateContext: function () { - const view = this; - - return { - getUserName: function () { - return Cache.User.get('nickname') || Cache.User.get('name'); - }, - - getHostStat: function (type) { - if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') { - return Helpers.niceNumber(view.stats.hosts[type]); - } - - return '-'; - }, - - canShow: function (perm) { - return Cache.User.isAdmin() || Cache.User.canView(perm); - }, - - columns: view.columns - }; - }, - - onRender: function () { - const view = this; - if (typeof view.stats.hosts === 'undefined') { - Api.Reports.getHostStats() - .then(response => { - if (!view.isDestroyed()) { - view.stats.hosts = response; - view.render(); - } - }) - .catch(err => { - console.log(err); - }); - } - }, - - /** - * @param {Object} [model] - */ - preRender: function (model) { - this.columns = 0; - - // calculate the available columns based on permissions for the objects - // and store as a variable - const perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts']; - - perms.map(perm => { - this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0; - }); - - // Prevent double rendering on initial calls - if (typeof model !== 'undefined') { - this.render(); - } - }, - - initialize: function () { - this.preRender(); - this.listenTo(Cache.User, 'change', this.preRender); - } -}); diff --git a/frontend/js/app/empty/main.ejs b/frontend/js/app/empty/main.ejs deleted file mode 100644 index 11633dfc..00000000 --- a/frontend/js/app/empty/main.ejs +++ /dev/null @@ -1,11 +0,0 @@ -<% if (title) { %> -

<%- title %>

-<% } - -if (subtitle) { %> -

<%- subtitle %>

-<% } - -if (link) { %> - <%- link %> -<% } %> diff --git a/frontend/js/app/empty/main.js b/frontend/js/app/empty/main.js deleted file mode 100644 index 74998d65..00000000 --- a/frontend/js/app/empty/main.js +++ /dev/null @@ -1,33 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - className: 'text-center m-7', - template: template, - - options: { - btn_color: 'teal' - }, - - ui: { - action: 'a' - }, - - events: { - 'click @ui.action': function (e) { - e.preventDefault(); - this.getOption('action')(); - } - }, - - templateContext: function () { - return { - title: this.getOption('title'), - subtitle: this.getOption('subtitle'), - link: this.getOption('link'), - action: typeof this.getOption('action') === 'function', - btn_color: this.getOption('btn_color') - }; - } - -}); diff --git a/frontend/js/app/error/main.ejs b/frontend/js/app/error/main.ejs deleted file mode 100644 index f7fd709b..00000000 --- a/frontend/js/app/error/main.ejs +++ /dev/null @@ -1,7 +0,0 @@ - -<%= code ? '' + code + ' — ' : '' %> -<%- message %> - -<% if (retry) { %> -

<%- i18n('str', 'try-again') %> -<% } %> diff --git a/frontend/js/app/error/main.js b/frontend/js/app/error/main.js deleted file mode 100644 index 6fa85fc8..00000000 --- a/frontend/js/app/error/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'alert alert-icon alert-warning m-5', - - ui: { - retry: 'a.retry' - }, - - events: { - 'click @ui.retry': function (e) { - e.preventDefault(); - this.getOption('retry')(); - } - }, - - templateContext: function () { - return { - message: this.getOption('message'), - code: this.getOption('code'), - retry: typeof this.getOption('retry') === 'function' - }; - } - -}); diff --git a/frontend/js/app/help/main.ejs b/frontend/js/app/help/main.ejs deleted file mode 100644 index 6fb79e66..00000000 --- a/frontend/js/app/help/main.ejs +++ /dev/null @@ -1,12 +0,0 @@ - diff --git a/frontend/js/app/help/main.js b/frontend/js/app/help/main.js deleted file mode 100644 index b0f54374..00000000 --- a/frontend/js/app/help/main.js +++ /dev/null @@ -1,16 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog wide', - - templateContext: function () { - let content = this.getOption('content').split("\n"); - - return { - title: this.getOption('title'), - content: '

' + content.join('

') + '

' - }; - } -}); diff --git a/frontend/js/app/i18n.js b/frontend/js/app/i18n.js deleted file mode 100644 index c63cdc07..00000000 --- a/frontend/js/app/i18n.js +++ /dev/null @@ -1,23 +0,0 @@ -const Cache = ('./cache'); -const messages = require('../i18n/messages.json'); - -/** - * @param {String} namespace - * @param {String} key - * @param {Object} [data] - */ -module.exports = function (namespace, key, data) { - let locale = Cache.locale; - // check that the locale exists - if (typeof messages[locale] === 'undefined') { - locale = 'en'; - } - - if (typeof messages[locale][namespace] !== 'undefined' && typeof messages[locale][namespace][key] !== 'undefined') { - return messages[locale][namespace][key](data); - } else if (locale !== 'en' && typeof messages['en'][namespace] !== 'undefined' && typeof messages['en'][namespace][key] !== 'undefined') { - return messages['en'][namespace][key](data); - } - - return '(MISSING: ' + namespace + '/' + key + ')'; -}; diff --git a/frontend/js/app/main.js b/frontend/js/app/main.js deleted file mode 100644 index e85b4f62..00000000 --- a/frontend/js/app/main.js +++ /dev/null @@ -1,155 +0,0 @@ -const _ = require('underscore'); -const Backbone = require('backbone'); -const Mn = require('../lib/marionette'); -const Cache = require('./cache'); -const Controller = require('./controller'); -const Router = require('./router'); -const Api = require('./api'); -const Tokens = require('./tokens'); -const UI = require('./ui/main'); -const i18n = require('./i18n'); - -const App = Mn.Application.extend({ - - Cache: Cache, - Api: Api, - UI: null, - i18n: i18n, - Controller: Controller, - - region: { - el: '#app', - replaceElement: true - }, - - onStart: function (app, options) { - console.log(i18n('main', 'welcome')); - - // Check if token is coming through - if (this.getParam('token')) { - Tokens.addToken(this.getParam('token')); - } - - // Check if we are still logged in by refreshing the token - Api.status() - .then(result => { - Cache.version = [result.version.major, result.version.minor, result.version.revision].join('.'); - }) - .then(Api.Tokens.refresh) - .then(this.bootstrap) - .then(() => { - console.info(i18n('main', 'logged-in', Cache.User.attributes)); - this.bootstrapTimer(); - this.refreshTokenTimer(); - - this.UI = new UI(); - this.UI.on('render', () => { - new Router(options); - Backbone.history.start({pushState: true}); - - // Ask the admin use to change their details - if (Cache.User.get('email') === 'admin@example.com') { - Controller.showUserForm(Cache.User); - } - }); - - this.getRegion().show(this.UI); - }) - .catch(err => { - console.warn('Not logged in:', err.message); - Controller.showLogin(); - }); - }, - - History: { - replace: function (data) { - window.history.replaceState(_.extend(window.history.state || {}, data), document.title); - }, - - get: function (attr) { - return window.history.state ? window.history.state[attr] : undefined; - } - }, - - getParam: function (name) { - name = name.replace(/[\[\]]/g, '\\$&'); - let url = window.location.href; - let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); - let results = regex.exec(url); - - if (!results) { - return null; - } - - if (!results[2]) { - return ''; - } - - return decodeURIComponent(results[2].replace(/\+/g, ' ')); - }, - - /** - * Get user and other base info to start prime the cache and the application - * - * @returns {Promise} - */ - bootstrap: function () { - return Api.Users.getById('me', ['permissions']) - .then(response => { - Cache.User.set(response); - Tokens.setCurrentName(response.nickname || response.name); - }); - }, - - /** - * Bootstraps the user from time to time - */ - bootstrapTimer: function () { - setTimeout(() => { - Api.status() - .then(result => { - let version = [result.version.major, result.version.minor, result.version.revision].join('.'); - if (version !== Cache.version) { - document.location.reload(); - } - }) - .then(this.bootstrap) - .then(() => { - this.bootstrapTimer(); - }) - .catch(err => { - if (err.message !== 'timeout' && err.code && err.code !== 400) { - console.log(err); - console.error(err.message); - console.info('Not logged in?'); - Controller.showLogin(); - } else { - this.bootstrapTimer(); - } - }); - }, 30 * 1000); // 30 seconds - }, - - refreshTokenTimer: function () { - setTimeout(() => { - return Api.Tokens.refresh() - .then(this.bootstrap) - .then(() => { - this.refreshTokenTimer(); - }) - .catch(err => { - if (err.message !== 'timeout' && err.code && err.code !== 400) { - console.log(err); - console.error(err.message); - console.info('Not logged in?'); - Controller.showLogin(); - } else { - this.refreshTokenTimer(); - } - }); - }, 10 * 60 * 1000); - } -}); - -const app = new App(); -module.exports = app; diff --git a/frontend/js/app/nginx/access/delete.ejs b/frontend/js/app/nginx/access/delete.ejs deleted file mode 100644 index 3833549a..00000000 --- a/frontend/js/app/nginx/access/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/access/delete.js b/frontend/js/app/nginx/access/delete.js deleted file mode 100644 index 4af91ab1..00000000 --- a/frontend/js/app/nginx/access/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.AccessLists.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxAccess(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/access/form.ejs b/frontend/js/app/nginx/access/form.ejs deleted file mode 100644 index 79220b14..00000000 --- a/frontend/js/app/nginx/access/form.ejs +++ /dev/null @@ -1,108 +0,0 @@ - diff --git a/frontend/js/app/nginx/access/form.js b/frontend/js/app/nginx/access/form.js deleted file mode 100644 index bb075548..00000000 --- a/frontend/js/app/nginx/access/form.js +++ /dev/null @@ -1,153 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const AccessListModel = require('../../../models/access-list'); -const template = require('./form.ejs'); -const ItemView = require('./form/item'); -const ClientView = require('./form/client'); - -require('jquery-serializejson'); - -const ItemsView = Mn.CollectionView.extend({ - childView: ItemView -}); - -const ClientsView = Mn.CollectionView.extend({ - childView: ClientView -}); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - items_region: '.items', - clients_region: '.clients', - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - access_add: 'button.access_add', - auth_add: 'button.auth_add' - }, - - regions: { - items_region: '@ui.items_region', - clients_region: '@ui.clients_region' - }, - - events: { - 'click @ui.save': function (e) { - e.preventDefault(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let form_data = this.ui.form.serializeJSON(); - let items_data = []; - let clients_data = []; - - form_data.username.map(function (val, idx) { - if (val.trim().length) { - items_data.push({ - username: val.trim(), - password: form_data.password[idx] - }); - } - }); - - form_data.address.map(function (val, idx) { - if (val.trim().length) { - clients_data.push({ - address: val.trim(), - directive: form_data.directive[idx] - }) - } - }); - - if (!items_data.length && !clients_data.length) { - alert('You must specify at least 1 Authorization or Access rule'); - return; - } - - let data = { - name: form_data.name, - satisfy_any: !!form_data.satisfy_any, - pass_auth: !!form_data.pass_auth, - items: items_data, - clients: clients_data - }; - - console.log(data); - - let method = App.Api.Nginx.AccessLists.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.AccessLists.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxAccess(); - } - }); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - }, - 'click @ui.access_add': function (e) { - e.preventDefault(); - - let clients = this.model.get('clients'); - clients.push({}); - this.showChildView('clients_region', new ClientsView({ - collection: new Backbone.Collection(clients) - })); - }, - 'click @ui.auth_add': function (e) { - e.preventDefault(); - - let items = this.model.get('items'); - items.push({}); - this.showChildView('items_region', new ItemsView({ - collection: new Backbone.Collection(items) - })); - } - }, - - onRender: function () { - let items = this.model.get('items'); - let clients = this.model.get('clients'); - - // Ensure at least one field is shown initally - if (!items.length) items.push({}); - if (!clients.length) clients.push({}); - - this.showChildView('items_region', new ItemsView({ - collection: new Backbone.Collection(items) - })); - - this.showChildView('clients_region', new ClientsView({ - collection: new Backbone.Collection(clients) - })); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new AccessListModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/access/form/client.ejs b/frontend/js/app/nginx/access/form/client.ejs deleted file mode 100644 index 6b767b83..00000000 --- a/frontend/js/app/nginx/access/form/client.ejs +++ /dev/null @@ -1,13 +0,0 @@ -
-
- -
-
-
-
- -
-
diff --git a/frontend/js/app/nginx/access/form/client.js b/frontend/js/app/nginx/access/form/client.js deleted file mode 100644 index b4c00e2e..00000000 --- a/frontend/js/app/nginx/access/form/client.js +++ /dev/null @@ -1,7 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./client.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'row' -}); diff --git a/frontend/js/app/nginx/access/form/item.ejs b/frontend/js/app/nginx/access/form/item.ejs deleted file mode 100644 index c2435ecb..00000000 --- a/frontend/js/app/nginx/access/form/item.ejs +++ /dev/null @@ -1,10 +0,0 @@ -
-
- -
-
-
-
- -
-
diff --git a/frontend/js/app/nginx/access/form/item.js b/frontend/js/app/nginx/access/form/item.js deleted file mode 100644 index f15238dc..00000000 --- a/frontend/js/app/nginx/access/form/item.js +++ /dev/null @@ -1,7 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'row' -}); diff --git a/frontend/js/app/nginx/access/list/item.ejs b/frontend/js/app/nginx/access/list/item.ejs deleted file mode 100644 index fe043c98..00000000 --- a/frontend/js/app/nginx/access/list/item.ejs +++ /dev/null @@ -1,42 +0,0 @@ - -
- -
- - -
- <%- name %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - - <%- i18n('access-lists', 'item-count', {count: items.length || 0}) %> - - - <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> - - - <% if (satisfy_any) { %> - <%- i18n('str', 'any') %> - <%} else { %> - <%- i18n('str', 'all') %> - <% } %> - - - <%- i18n('access-lists', 'proxy-host-count', {count: proxy_host_count}) %> - -<% if (canManage) { %> - - - -<% } %> diff --git a/frontend/js/app/nginx/access/list/item.js b/frontend/js/app/nginx/access/list/item.js deleted file mode 100644 index 4f68aead..00000000 --- a/frontend/js/app/nginx/access/list/item.js +++ /dev/null @@ -1,33 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - edit: 'a.edit', - delete: 'a.delete' - }, - - events: { - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxAccessListForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxAccessListDeleteConfirm(this.model); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('access_lists') - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/access/list/main.ejs b/frontend/js/app/nginx/access/list/main.ejs deleted file mode 100644 index 7988e0c2..00000000 --- a/frontend/js/app/nginx/access/list/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ - -   - <%- i18n('str', 'name') %> - <%- i18n('access-lists', 'authorization') %> - <%- i18n('access-lists', 'access') %> - <%- i18n('access-lists', 'satisfy') %> - <%- i18n('proxy-hosts', 'title') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/access/list/main.js b/frontend/js/app/nginx/access/list/main.js deleted file mode 100644 index 577a77ef..00000000 --- a/frontend/js/app/nginx/access/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('access_lists') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/access/main.ejs b/frontend/js/app/nginx/access/main.ejs deleted file mode 100644 index 97585936..00000000 --- a/frontend/js/app/nginx/access/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('access-lists', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('access-lists', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/access/main.js b/frontend/js/app/nginx/access/main.js deleted file mode 100644 index 513f5865..00000000 --- a/frontend/js/app/nginx/access/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const AccessListModel = require('../../../models/access-list'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-access', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.AccessLists.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new AccessListModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxAccess(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('access_lists'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('access-lists', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('access-lists', 'add') : null, - btn_color: 'teal', - permission: 'access_lists', - action: function () { - App.Controller.showNginxAccessListForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxAccessListForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('access-lists', 'help-title'), App.i18n('access-lists', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'items', 'clients'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('access_lists') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'items', 'clients']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/certificates-list-item.ejs b/frontend/js/app/nginx/certificates-list-item.ejs deleted file mode 100644 index aa4b53ad..00000000 --- a/frontend/js/app/nginx/certificates-list-item.ejs +++ /dev/null @@ -1,18 +0,0 @@ -
- <% if (id === 'new') { %> -
- <%- i18n('all-hosts', 'new-cert') %> -
- <%- i18n('all-hosts', 'with-le') %> - <% } else if (id > 0) { %> -
- <%- provider === 'other' ? nice_name : domain_names.join(', ') %> -
- <%- i18n('ssl', provider) %> – Expires: <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> - <% } else { %> -
- <%- i18n('all-hosts', 'none') %> -
- <%- i18n('all-hosts', 'no-ssl') %> - <% } %> -
diff --git a/frontend/js/app/nginx/certificates/delete.ejs b/frontend/js/app/nginx/certificates/delete.ejs deleted file mode 100644 index b4e06866..00000000 --- a/frontend/js/app/nginx/certificates/delete.ejs +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/delete.js b/frontend/js/app/nginx/certificates/delete.js deleted file mode 100644 index 89a2e5e8..00000000 --- a/frontend/js/app/nginx/certificates/delete.js +++ /dev/null @@ -1,34 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.save.addClass('btn-loading'); - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - - App.Api.Nginx.Certificates.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxCertificates(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/certificates/form.ejs b/frontend/js/app/nginx/certificates/form.ejs deleted file mode 100644 index 6adb4b01..00000000 --- a/frontend/js/app/nginx/certificates/form.ejs +++ /dev/null @@ -1,185 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/form.js b/frontend/js/app/nginx/certificates/form.js deleted file mode 100644 index cab09155..00000000 --- a/frontend/js/app/nginx/certificates/form.js +++ /dev/null @@ -1,294 +0,0 @@ -const _ = require('underscore'); -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const CertificateModel = require('../../../models/certificate'); -const template = require('./form.ejs'); -const i18n = require('../../i18n'); -const dns_providers = sortProvidersAlphabetically(require('../../../../../global/certbot-dns-plugins')); - -require('jquery-serializejson'); -require('selectize'); - -function sortProvidersAlphabetically(obj) { - return Object.entries(obj) - .sort((a,b) => a[1].name.toLowerCase() > b[1].name.toLowerCase()) - .reduce((result, entry) => { - result[entry[0]] = entry[1]; - return result; - }, {}); -} - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - max_file_size: 102400, - - ui: { - form: 'form', - loader_content: '.loader-content', - non_loader_content: '.non-loader-content', - le_error_info: '#le-error-info', - domain_names: 'input[name="domain_names"]', - test_domains_container: '.test-domains-container', - test_domains_button: '.test-domains', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - other_certificate: '#other_certificate', - other_certificate_label: '#other_certificate_label', - other_certificate_key: '#other_certificate_key', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - other_certificate_key_label: '#other_certificate_key_label', - other_intermediate_certificate: '#other_intermediate_certificate', - other_intermediate_certificate_label: '#other_intermediate_certificate_label' - }, - - events: { - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - this.ui.test_domains_container.hide(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - this.ui.test_domains_container.show(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - $(this).removeClass('btn-loading'); - return; - } - - let data = this.ui.form.serializeJSON(); - data.provider = this.model.get('provider'); - let ssl_files = []; - - if (data.provider === 'letsencrypt') { - if (typeof data.meta === 'undefined') data.meta = {}; - - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.split(',').map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - - // Manipulate - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - } else if (data.provider === 'other' && !this.model.hasSslFiles()) { - // check files are attached - if (!this.ui.other_certificate[0].files.length || !this.ui.other_certificate[0].files[0].size) { - alert('Certificate file is not attached'); - return; - } else { - if (this.ui.other_certificate[0].files[0].size > this.max_file_size) { - alert('Certificate file is too large (> 100kb)'); - return; - } - ssl_files.push({name: 'certificate', file: this.ui.other_certificate[0].files[0]}); - } - - if (!this.ui.other_certificate_key[0].files.length || !this.ui.other_certificate_key[0].files[0].size) { - alert('Certificate key file is not attached'); - return; - } else { - if (this.ui.other_certificate_key[0].files[0].size > this.max_file_size) { - alert('Certificate key file is too large (> 100kb)'); - return; - } - ssl_files.push({name: 'certificate_key', file: this.ui.other_certificate_key[0].files[0]}); - } - - if (this.ui.other_intermediate_certificate[0].files.length && this.ui.other_intermediate_certificate[0].files[0].size) { - if (this.ui.other_intermediate_certificate[0].files[0].size > this.max_file_size) { - alert('Intermediate Certificate file is too large (> 100kb)'); - return; - } - ssl_files.push({name: 'intermediate_certificate', file: this.ui.other_intermediate_certificate[0].files[0]}); - } - } - - this.ui.loader_content.show(); - this.ui.non_loader_content.hide(); - - // compile file data - let form_data = new FormData(); - if (data.provider === 'other' && ssl_files.length) { - ssl_files.map(function (file) { - form_data.append(file.name, file.file); - }); - } - - new Promise(resolve => { - if (data.provider === 'other') { - resolve(App.Api.Nginx.Certificates.validate(form_data)); - } else { - resolve(); - } - }) - .then(() => { - return App.Api.Nginx.Certificates.create(data); - }) - .then(result => { - this.model.set(result); - - // Now upload the certs if we need to - if (data.provider === 'other') { - return App.Api.Nginx.Certificates.upload(this.model.get('id'), form_data) - .then(result => { - this.model.set('meta', _.assign({}, this.model.get('meta'), result)); - }); - } - }) - .then(() => { - App.UI.closeModal(function () { - App.Controller.showNginxCertificates(); - }); - }) - .catch(err => { - let more_info = ''; - if (err.code === 500 && err.debug) { - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.loader_content.hide(); - this.ui.non_loader_content.show(); - }); - }, - 'click @ui.test_domains_button': function (e) { - e.preventDefault(); - const domainNames = this.ui.domain_names[0].value.split(','); - if (domainNames && domainNames.length > 0) { - this.model.set('domain_names', domainNames); - this.model.set('back_to_add', true); - App.Controller.showNginxCertificateTestReachability(this.model); - } - }, - 'change @ui.domain_names': function(e){ - const domainNames = e.target.value.split(','); - if (domainNames && domainNames.length > 0) { - this.ui.test_domains_button.prop('disabled', false); - } else { - this.ui.test_domains_button.prop('disabled', true); - } - }, - 'change @ui.other_certificate_key': function(e){ - this.setFileName("other_certificate_key_label", e) - }, - 'change @ui.other_certificate': function(e){ - this.setFileName("other_certificate_label", e) - }, - 'change @ui.other_intermediate_certificate': function(e){ - this.setFileName("other_intermediate_certificate_label", e) - } - }, - setFileName(ui, e){ - this.getUI(ui).text(e.target.files[0].name) - }, - templateContext: { - getLetsencryptEmail: function () { - return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email'); - }, - getLetsencryptAgree: function () { - return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false; - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 100, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.loader_content.hide(); - this.ui.le_error_info.hide(); - if (this.ui.domain_names[0]) { - const domainNames = this.ui.domain_names[0].value.split(','); - if (!domainNames || domainNames.length === 0 || (domainNames.length === 1 && domainNames[0] === "")) { - this.ui.test_domains_button.prop('disabled', true); - } - } - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new CertificateModel.Model({provider: 'letsencrypt'}); - } - } -}); diff --git a/frontend/js/app/nginx/certificates/list/item.ejs b/frontend/js/app/nginx/certificates/list/item.ejs deleted file mode 100644 index 179a8195..00000000 --- a/frontend/js/app/nginx/certificates/list/item.ejs +++ /dev/null @@ -1,68 +0,0 @@ - -
- -
- - -
- <% - if (provider === 'letsencrypt') { - domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - } else { - %><%- nice_name %><% - } - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - - <%- i18n('ssl', provider) %><% if (meta.dns_provider) { %> - <%- dns_providers[meta.dns_provider].name %><% } %> - - - <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> - - - <% if (active_domain_names().length > 0) { %> - <%- i18n('certificates', 'in-use') %> - <% } else { %> - <%- i18n('certificates', 'inactive') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/certificates/list/item.js b/frontend/js/app/nginx/certificates/list/item.js deleted file mode 100644 index b9a927ad..00000000 --- a/frontend/js/app/nginx/certificates/list/item.js +++ /dev/null @@ -1,68 +0,0 @@ -const Mn = require('backbone.marionette'); -const moment = require('moment'); -const App = require('../../../main'); -const template = require('./item.ejs'); -const dns_providers = require('../../../../../../global/certbot-dns-plugins'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - host_link: '.host-link', - renew: 'a.renew', - delete: 'a.delete', - download: 'a.download', - test: 'a.test' - }, - - events: { - 'click @ui.renew': function (e) { - e.preventDefault(); - App.Controller.showNginxCertificateRenew(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxCertificateDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - }, - - 'click @ui.download': function (e) { - e.preventDefault(); - App.Api.Nginx.Certificates.download(this.model.get('id')); - }, - - 'click @ui.test': function (e) { - e.preventDefault(); - App.Controller.showNginxCertificateTestReachability(this.model); - }, - }, - - templateContext: function () { - return { - canManage: App.Cache.User.canManage('certificates'), - isExpired: function () { - return moment(this.expires_on).isBefore(moment()); - }, - dns_providers: dns_providers, - active_domain_names: function () { - const { proxy_hosts = [], redirect_hosts = [], dead_hosts = [] } = this; - return [...proxy_hosts, ...redirect_hosts, ...dead_hosts].reduce((acc, host) => { - acc.push(...(host.domain_names || [])); - return acc; - }, []); - } - }; - }, - - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/certificates/list/main.ejs b/frontend/js/app/nginx/certificates/list/main.ejs deleted file mode 100644 index 329b5843..00000000 --- a/frontend/js/app/nginx/certificates/list/main.ejs +++ /dev/null @@ -1,13 +0,0 @@ - -   - <%- i18n('str', 'name') %> - <%- i18n('all-hosts', 'cert-provider') %> - <%- i18n('str', 'expires') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/certificates/list/main.js b/frontend/js/app/nginx/certificates/list/main.js deleted file mode 100644 index d96b43e8..00000000 --- a/frontend/js/app/nginx/certificates/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('certificates') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/certificates/main.ejs b/frontend/js/app/nginx/certificates/main.ejs deleted file mode 100644 index dbd6fa85..00000000 --- a/frontend/js/app/nginx/certificates/main.ejs +++ /dev/null @@ -1,36 +0,0 @@ -
-
-
-

<%- i18n('certificates', 'title') %>

-
- - - <% if (showAddButton) { %> - - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/certificates/main.js b/frontend/js/app/nginx/certificates/main.js deleted file mode 100644 index 3f9f022e..00000000 --- a/frontend/js/app/nginx/certificates/main.js +++ /dev/null @@ -1,109 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const CertificateModel = require('../../../models/certificate'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-certificates', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.Certificates.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new CertificateModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxCertificates(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('certificates'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('certificates', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('certificates', 'add') : null, - btn_color: 'pink', - permission: 'certificates', - action: function () { - App.Controller.showNginxCertificateForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - let model = new CertificateModel.Model({provider: $(e.currentTarget).data('cert')}); - App.Controller.showNginxCertificateForm(model); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('certificates', 'help-title'), App.i18n('certificates', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('certificates') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/certificates/renew.ejs b/frontend/js/app/nginx/certificates/renew.ejs deleted file mode 100644 index 4af186d0..00000000 --- a/frontend/js/app/nginx/certificates/renew.ejs +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/renew.js b/frontend/js/app/nginx/certificates/renew.js deleted file mode 100644 index 73632881..00000000 --- a/frontend/js/app/nginx/certificates/renew.js +++ /dev/null @@ -1,31 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./renew.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - waiting: '.waiting', - error: '.error', - close: 'button.cancel' - }, - - onRender: function () { - this.ui.error.hide(); - - App.Api.Nginx.Certificates.renew(this.model.get('id')) - .then((result) => { - this.model.set(result); - setTimeout(() => { - App.UI.closeModal(); - }, 1000); - }) - .catch((err) => { - this.ui.waiting.hide(); - this.ui.error.text(err.message).show(); - this.ui.close.prop('disabled', false); - }); - } -}); diff --git a/frontend/js/app/nginx/certificates/test.ejs b/frontend/js/app/nginx/certificates/test.ejs deleted file mode 100644 index 6661f625..00000000 --- a/frontend/js/app/nginx/certificates/test.ejs +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/test.js b/frontend/js/app/nginx/certificates/test.js deleted file mode 100644 index 0886d26f..00000000 --- a/frontend/js/app/nginx/certificates/test.js +++ /dev/null @@ -1,75 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./test.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - waiting: '.waiting', - error: '.error', - success: '.success', - close: 'button.cancel' - }, - - events: { - 'click @ui.close': function (e) { - e.preventDefault(); - if (this.model.get('back_to_add')) { - App.Controller.showNginxCertificateForm(this.model); - } else { - App.UI.closeModal(); - } - }, - }, - - onRender: function () { - this.ui.error.hide(); - this.ui.success.hide(); - - App.Api.Nginx.Certificates.testHttpChallenge(this.model.get('domain_names')) - .then((result) => { - let allOk = true; - let text = ''; - - for (const domain in result) { - const status = result[domain]; - if (status === 'ok') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-ok')}

`; - } else { - allOk = false; - if (status === 'no-host') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-not-resolved')}

`; - } else if (status === 'failed') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-failed-to-check')}

`; - } else if (status === '404') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-404')}

`; - } else if (status === 'wrong-data') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-wrong-data')}

`; - } else if (status.startsWith('other:')) { - const code = status.substring(6); - text += `

${domain}: ${App.i18n('certificates', 'reachability-other', {code})}

`; - } else { - // This should never happen - text += `

${domain}: ?

`; - } - } - } - - this.ui.waiting.hide(); - if (allOk) { - this.ui.success.html(text).show(); - } else { - this.ui.error.html(text).show(); - } - this.ui.close.prop('disabled', false); - }) - .catch((e) => { - console.error(e); - this.ui.waiting.hide(); - this.ui.error.text(App.i18n('certificates', 'reachability-failed-to-reach-api')).show(); - this.ui.close.prop('disabled', false); - }); - } -}); diff --git a/frontend/js/app/nginx/dead/delete.ejs b/frontend/js/app/nginx/dead/delete.ejs deleted file mode 100644 index 4bebb436..00000000 --- a/frontend/js/app/nginx/dead/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/dead/delete.js b/frontend/js/app/nginx/dead/delete.js deleted file mode 100644 index d497d068..00000000 --- a/frontend/js/app/nginx/dead/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.DeadHosts.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxDead(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/dead/form.ejs b/frontend/js/app/nginx/dead/form.ejs deleted file mode 100644 index de3b87d8..00000000 --- a/frontend/js/app/nginx/dead/form.ejs +++ /dev/null @@ -1,206 +0,0 @@ - diff --git a/frontend/js/app/nginx/dead/form.js b/frontend/js/app/nginx/dead/form.js deleted file mode 100644 index a091717d..00000000 --- a/frontend/js/app/nginx/dead/form.js +++ /dev/null @@ -1,286 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const DeadHostModel = require('../../../models/dead-host'); -const template = require('./form.ejs'); -const certListItemTemplate = require('../certificates-list-item.ejs'); -const Helpers = require('../../../lib/helpers'); -const i18n = require('../../i18n'); -const dns_providers = require('../../../../../global/certbot-dns-plugins'); - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - letsencrypt: '.letsencrypt' - }, - - events: { - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.dns_challenge_content.hide(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - - - let enabled = id === 'new' || parseInt(id, 10) > 0; - - let inputs = this.ui.ssl_forced.add(this.ui.http2_support); - inputs - .prop('disabled', !enabled) - .parents('.form-group') - .css('opacity', enabled ? 1 : 0.5); - - if (!enabled) { - inputs.prop('checked', false); - } - - inputs.trigger('change'); - }, - - 'change @ui.ssl_forced': function () { - let checked = this.ui.ssl_forced.prop('checked'); - this.ui.hsts_enabled - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_enabled.prop('checked', false); - } - - this.ui.hsts_enabled.trigger('change'); - }, - - 'change @ui.hsts_enabled': function () { - let checked = this.ui.hsts_enabled.prop('checked'); - this.ui.hsts_subdomains - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_subdomains.prop('checked', false); - } - }, - - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Manipulate - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.http2_support = !!data.http2_support; - data.ssl_forced = !!data.ssl_forced; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.DeadHosts.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.DeadHosts.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - this.ui.save.addClass('btn-loading'); - - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxDead(); - } - }); - }) - .catch(err => { - let more_info = ''; - if(err.code === 500 && err.debug){ - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - // Domain names - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 100, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new DeadHostModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/dead/list/item.ejs b/frontend/js/app/nginx/dead/list/item.ejs deleted file mode 100644 index dede3b63..00000000 --- a/frontend/js/app/nginx/dead/list/item.ejs +++ /dev/null @@ -1,54 +0,0 @@ - -
- -
- - -
- <% domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/dead/list/item.js b/frontend/js/app/nginx/dead/list/item.js deleted file mode 100644 index a477dbfa..00000000 --- a/frontend/js/app/nginx/dead/list/item.js +++ /dev/null @@ -1,61 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete', - host_link: '.host-link' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.DeadHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.DeadHosts.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxDeadForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxDeadDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('dead_hosts'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/dead/list/main.ejs b/frontend/js/app/nginx/dead/list/main.ejs deleted file mode 100644 index e018a74b..00000000 --- a/frontend/js/app/nginx/dead/list/main.ejs +++ /dev/null @@ -1,12 +0,0 @@ - -   - <%- i18n('str', 'source') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/dead/list/main.js b/frontend/js/app/nginx/dead/list/main.js deleted file mode 100644 index 57931419..00000000 --- a/frontend/js/app/nginx/dead/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('dead_hosts') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/dead/main.ejs b/frontend/js/app/nginx/dead/main.ejs deleted file mode 100644 index 4c5d1ad1..00000000 --- a/frontend/js/app/nginx/dead/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('dead-hosts', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('dead-hosts', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/dead/main.js b/frontend/js/app/nginx/dead/main.js deleted file mode 100644 index e4d0c010..00000000 --- a/frontend/js/app/nginx/dead/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const DeadHostModel = require('../../../models/dead-host'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-dead', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.DeadHosts.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new DeadHostModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxDead(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('dead_hosts'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('dead-hosts', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('dead-hosts', 'add') : null, - btn_color: 'danger', - permission: 'dead_hosts', - action: function () { - App.Controller.showNginxDeadForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxDeadForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('dead-hosts', 'help-title'), App.i18n('dead-hosts', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'certificate'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('dead_hosts') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/proxy/access-list-item.ejs b/frontend/js/app/nginx/proxy/access-list-item.ejs deleted file mode 100644 index e5a7e116..00000000 --- a/frontend/js/app/nginx/proxy/access-list-item.ejs +++ /dev/null @@ -1,13 +0,0 @@ -
- <% if (id > 0) { %> -
- <%- name %> -
- <%- i18n('access-lists', 'item-count', {count: items.length || 0}) %>, <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %> - <% } else { %> -
- <%- i18n('access-lists', 'public') %> -
- <%- i18n('access-lists', 'public-sub') %> - <% } %> -
diff --git a/frontend/js/app/nginx/proxy/delete.ejs b/frontend/js/app/nginx/proxy/delete.ejs deleted file mode 100644 index 74da297c..00000000 --- a/frontend/js/app/nginx/proxy/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/proxy/delete.js b/frontend/js/app/nginx/proxy/delete.js deleted file mode 100644 index 63a8e020..00000000 --- a/frontend/js/app/nginx/proxy/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.ProxyHosts.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxProxy(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/proxy/form.ejs b/frontend/js/app/nginx/proxy/form.ejs deleted file mode 100644 index 8e7a2a2d..00000000 --- a/frontend/js/app/nginx/proxy/form.ejs +++ /dev/null @@ -1,281 +0,0 @@ - diff --git a/frontend/js/app/nginx/proxy/form.js b/frontend/js/app/nginx/proxy/form.js deleted file mode 100644 index 4437a6dd..00000000 --- a/frontend/js/app/nginx/proxy/form.js +++ /dev/null @@ -1,369 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const ProxyHostModel = require('../../../models/proxy-host'); -const ProxyLocationModel = require('../../../models/proxy-host-location'); -const template = require('./form.ejs'); -const certListItemTemplate = require('../certificates-list-item.ejs'); -const accessListItemTemplate = require('./access-list-item.ejs'); -const CustomLocation = require('./location'); -const Helpers = require('../../../lib/helpers'); -const i18n = require('../../i18n'); -const dns_providers = require('../../../../../global/certbot-dns-plugins'); - - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - locationsCollection: new ProxyLocationModel.Collection(), - - ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - forward_host: 'input[name="forward_host"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - add_location_btn: 'button.add_location', - locations_container: '.locations_container', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - access_list_select: 'select[name="access_list_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - forward_scheme: 'select[name="forward_scheme"]', - letsencrypt: '.letsencrypt' - }, - - regions: { - locations_regions: '@ui.locations_container' - }, - - events: { - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.dns_challenge_content.hide(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - - let enabled = id === 'new' || parseInt(id, 10) > 0; - - let inputs = this.ui.ssl_forced.add(this.ui.http2_support); - inputs - .prop('disabled', !enabled) - .parents('.form-group') - .css('opacity', enabled ? 1 : 0.5); - - if (!enabled) { - inputs.prop('checked', false); - } - - inputs.trigger('change'); - }, - - 'change @ui.ssl_forced': function () { - let checked = this.ui.ssl_forced.prop('checked'); - this.ui.hsts_enabled - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_enabled.prop('checked', false); - } - - this.ui.hsts_enabled.trigger('change'); - }, - - 'change @ui.hsts_enabled': function () { - let checked = this.ui.hsts_enabled.prop('checked'); - this.ui.hsts_subdomains - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_subdomains.prop('checked', false); - } - }, - - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.add_location_btn': function (e) { - e.preventDefault(); - - const model = new ProxyLocationModel.Model(); - this.locationsCollection.add(model); - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Add locations - data.locations = []; - this.locationsCollection.models.forEach((location) => { - data.locations.push(location.toJSON()); - }); - - // Serialize collects path from custom locations - // This field must be removed from root object - delete data.path; - - // Manipulate - data.forward_port = parseInt(data.forward_port, 10); - data.block_exploits = !!data.block_exploits; - data.caching_enabled = !!data.caching_enabled; - data.allow_websocket_upgrade = !!data.allow_websocket_upgrade; - data.http2_support = !!data.http2_support; - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.ssl_forced = !!data.ssl_forced; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.ProxyHosts.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.ProxyHosts.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - this.ui.save.addClass('btn-loading'); - - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxProxy(); - } - }); - }) - .catch(err => { - let more_info = ''; - if(err.code === 500 && err.debug){ - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - this.ui.ssl_forced.trigger('change'); - this.ui.hsts_enabled.trigger('change'); - - // Domain names - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 100, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - - // Access Lists - this.ui.access_list_select.selectize({ - valueField: 'id', - labelField: 'name', - searchField: ['name'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return accessListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.AccessLists.getAll(['items', 'clients']) - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.access_list_select[0].selectize.setValue(view.model.get('access_list_id')); - } - }); - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new ProxyHostModel.Model(); - } - - this.locationsCollection = new ProxyLocationModel.Collection(); - - // Custom locations - this.showChildView('locations_regions', new CustomLocation.LocationCollectionView({ - collection: this.locationsCollection - })); - - // Check wether there are any location defined - if (options.model && Array.isArray(options.model.attributes.locations)) { - options.model.attributes.locations.forEach((location) => { - let m = new ProxyLocationModel.Model(location); - this.locationsCollection.add(m); - }); - } - } -}); diff --git a/frontend/js/app/nginx/proxy/list/item.ejs b/frontend/js/app/nginx/proxy/list/item.ejs deleted file mode 100644 index 3eeaa6d2..00000000 --- a/frontend/js/app/nginx/proxy/list/item.ejs +++ /dev/null @@ -1,60 +0,0 @@ - -
- -
- - -
- <% domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- forward_scheme %>://<%- forward_host %>:<%- forward_port %>
- - -
<%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
- - -
<%- access_list_id ? access_list.name : i18n('str', 'public') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/proxy/list/item.js b/frontend/js/app/nginx/proxy/list/item.js deleted file mode 100644 index 37d199b4..00000000 --- a/frontend/js/app/nginx/proxy/list/item.js +++ /dev/null @@ -1,61 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete', - host_link: '.host-link' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.ProxyHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.ProxyHosts.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxProxyForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxProxyDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('proxy_hosts'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/proxy/list/main.ejs b/frontend/js/app/nginx/proxy/list/main.ejs deleted file mode 100644 index 6de5b9c6..00000000 --- a/frontend/js/app/nginx/proxy/list/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ - -   - <%- i18n('str', 'source') %> - <%- i18n('str', 'destination') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'access') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/proxy/list/main.js b/frontend/js/app/nginx/proxy/list/main.js deleted file mode 100644 index 09e984e6..00000000 --- a/frontend/js/app/nginx/proxy/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('proxy_hosts') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/proxy/location-item.ejs b/frontend/js/app/nginx/proxy/location-item.ejs deleted file mode 100644 index 466cb9ba..00000000 --- a/frontend/js/app/nginx/proxy/location-item.ejs +++ /dev/null @@ -1,64 +0,0 @@ -
-
-
-
-
- -
-
-
- - location - - -
-
-
-
- -
-
-
-
-
-
-
- - -
-
-
-
- - - <%- i18n('proxy-hosts', 'custom-forward-host-help') %> -
-
-
-
- - -
-
-
-
-
-
- -
-
-
- - - <%- i18n('locations', 'delete') %> - -
-
diff --git a/frontend/js/app/nginx/proxy/location.js b/frontend/js/app/nginx/proxy/location.js deleted file mode 100644 index e9513a48..00000000 --- a/frontend/js/app/nginx/proxy/location.js +++ /dev/null @@ -1,54 +0,0 @@ -const locationItemTemplate = require('./location-item.ejs'); -const Mn = require('backbone.marionette'); -const App = require('../../main'); - -const LocationView = Mn.View.extend({ - template: locationItemTemplate, - className: 'location_block', - - ui: { - toggle: 'input[type="checkbox"]', - config: '.config', - delete: '.location-delete' - }, - - events: { - 'change @ui.toggle': function(el) { - if (el.target.checked) { - this.ui.config.show(); - } else { - this.ui.config.hide(); - } - }, - - 'change .model': function (e) { - const map = {}; - map[e.target.name] = e.target.value; - this.model.set(map); - }, - - 'click @ui.delete': function () { - this.model.destroy(); - } - }, - - onRender: function() { - $(this.ui.config).hide(); - }, - - templateContext: function() { - return { - i18n: App.i18n - } - } -}); - -const LocationCollectionView = Mn.CollectionView.extend({ - className: 'locations_container', - childView: LocationView -}); - -module.exports = { - LocationCollectionView, - LocationView -} \ No newline at end of file diff --git a/frontend/js/app/nginx/proxy/main.ejs b/frontend/js/app/nginx/proxy/main.ejs deleted file mode 100644 index 4ecb9036..00000000 --- a/frontend/js/app/nginx/proxy/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('proxy-hosts', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('proxy-hosts', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/proxy/main.js b/frontend/js/app/nginx/proxy/main.js deleted file mode 100644 index baf67101..00000000 --- a/frontend/js/app/nginx/proxy/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const ProxyHostModel = require('../../../models/proxy-host'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-proxy', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.ProxyHosts.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new ProxyHostModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxProxy(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('proxy_hosts'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('proxy-hosts', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('proxy-hosts', 'add') : null, - btn_color: 'success', - permission: 'proxy_hosts', - action: function () { - App.Controller.showNginxProxyForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxProxyForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('proxy-hosts', 'help-title'), App.i18n('proxy-hosts', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'access_list', 'certificate'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('proxy_hosts') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'access_list', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/redirection/delete.ejs b/frontend/js/app/nginx/redirection/delete.ejs deleted file mode 100644 index 782d8435..00000000 --- a/frontend/js/app/nginx/redirection/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/redirection/delete.js b/frontend/js/app/nginx/redirection/delete.js deleted file mode 100644 index 6d2862f6..00000000 --- a/frontend/js/app/nginx/redirection/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.RedirectionHosts.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxRedirection(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/redirection/form.ejs b/frontend/js/app/nginx/redirection/form.ejs deleted file mode 100644 index f32178bc..00000000 --- a/frontend/js/app/nginx/redirection/form.ejs +++ /dev/null @@ -1,253 +0,0 @@ - diff --git a/frontend/js/app/nginx/redirection/form.js b/frontend/js/app/nginx/redirection/form.js deleted file mode 100644 index 5d061ff7..00000000 --- a/frontend/js/app/nginx/redirection/form.js +++ /dev/null @@ -1,288 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const RedirectionHostModel = require('../../../models/redirection-host'); -const template = require('./form.ejs'); -const certListItemTemplate = require('../certificates-list-item.ejs'); -const Helpers = require('../../../lib/helpers'); -const i18n = require('../../i18n'); -const dns_providers = require('../../../../../global/certbot-dns-plugins'); - - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - letsencrypt: '.letsencrypt' - }, - - events: { - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.dns_challenge_content.hide(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - - let enabled = id === 'new' || parseInt(id, 10) > 0; - - let inputs = this.ui.ssl_forced.add(this.ui.http2_support); - inputs - .prop('disabled', !enabled) - .parents('.form-group') - .css('opacity', enabled ? 1 : 0.5); - - if (!enabled) { - inputs.prop('checked', false); - } - - inputs.trigger('change'); - }, - - 'change @ui.ssl_forced': function () { - let checked = this.ui.ssl_forced.prop('checked'); - this.ui.hsts_enabled - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_enabled.prop('checked', false); - } - - this.ui.hsts_enabled.trigger('change'); - }, - - 'change @ui.hsts_enabled': function () { - let checked = this.ui.hsts_enabled.prop('checked'); - this.ui.hsts_subdomains - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_subdomains.prop('checked', false); - } - }, - - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Manipulate - data.block_exploits = !!data.block_exploits; - data.preserve_path = !!data.preserve_path; - data.http2_support = !!data.http2_support; - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.ssl_forced = !!data.ssl_forced; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.RedirectionHosts.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.RedirectionHosts.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - this.ui.save.addClass('btn-loading'); - - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxRedirection(); - } - }); - }) - .catch(err => { - let more_info = ''; - if(err.code === 500 && err.debug){ - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - // Domain names - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 100, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new RedirectionHostModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/redirection/list/item.ejs b/frontend/js/app/nginx/redirection/list/item.ejs deleted file mode 100644 index dff186fd..00000000 --- a/frontend/js/app/nginx/redirection/list/item.ejs +++ /dev/null @@ -1,63 +0,0 @@ - -
- -
- - -
- <% domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- forward_http_code %>
- - -
<%- forward_scheme == '$scheme' ? 'auto' : forward_scheme %>
- - -
<%- forward_domain_name %>
- - -
<%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> diff --git a/frontend/js/app/nginx/redirection/list/item.js b/frontend/js/app/nginx/redirection/list/item.js deleted file mode 100644 index 05adc251..00000000 --- a/frontend/js/app/nginx/redirection/list/item.js +++ /dev/null @@ -1,61 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete', - host_link: '.host-link' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.RedirectionHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.RedirectionHosts.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxRedirectionForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxRedirectionDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('redirection_hosts'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/redirection/list/main.ejs b/frontend/js/app/nginx/redirection/list/main.ejs deleted file mode 100644 index 8b6930d6..00000000 --- a/frontend/js/app/nginx/redirection/list/main.ejs +++ /dev/null @@ -1,15 +0,0 @@ - -   - <%- i18n('str', 'source') %> - <%- i18n('redirection-hosts', 'forward-http-status-code') %> - <%- i18n('redirection-hosts', 'forward-scheme') %> - <%- i18n('str', 'destination') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/redirection/list/main.js b/frontend/js/app/nginx/redirection/list/main.js deleted file mode 100644 index d368cf6a..00000000 --- a/frontend/js/app/nginx/redirection/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('redirection_hosts') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/redirection/main.ejs b/frontend/js/app/nginx/redirection/main.ejs deleted file mode 100644 index 87e28229..00000000 --- a/frontend/js/app/nginx/redirection/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('redirection-hosts', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('redirection-hosts', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/redirection/main.js b/frontend/js/app/nginx/redirection/main.js deleted file mode 100644 index 1f5351a7..00000000 --- a/frontend/js/app/nginx/redirection/main.js +++ /dev/null @@ -1,107 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const RedirectionHostModel = require('../../../models/redirection-host'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-redirection', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.RedirectionHosts.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new RedirectionHostModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxRedirection(); - } - })); - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('redirection_hosts'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('redirection-hosts', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('redirection-hosts', 'add') : null, - btn_color: 'yellow', - permission: 'redirection_hosts', - action: function () { - App.Controller.showNginxRedirectionForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxRedirectionForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('redirection-hosts', 'help-title'), App.i18n('redirection-hosts', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'certificate'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('proxy_hosts') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/stream/delete.ejs b/frontend/js/app/nginx/stream/delete.ejs deleted file mode 100644 index d7ba3a21..00000000 --- a/frontend/js/app/nginx/stream/delete.ejs +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/frontend/js/app/nginx/stream/delete.js b/frontend/js/app/nginx/stream/delete.js deleted file mode 100644 index 71eff18c..00000000 --- a/frontend/js/app/nginx/stream/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.Streams.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxStream(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/stream/form.ejs b/frontend/js/app/nginx/stream/form.ejs deleted file mode 100644 index 800945f3..00000000 --- a/frontend/js/app/nginx/stream/form.ejs +++ /dev/null @@ -1,194 +0,0 @@ - diff --git a/frontend/js/app/nginx/stream/form.js b/frontend/js/app/nginx/stream/form.js deleted file mode 100644 index cd012f9b..00000000 --- a/frontend/js/app/nginx/stream/form.js +++ /dev/null @@ -1,225 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const StreamModel = require('../../../models/stream'); -const template = require('./form.ejs'); -const dns_providers = require('../../../../../global/certbot-dns-plugins'); - -require('jquery-serializejson'); -require('jquery-mask-plugin'); -require('selectize'); -const Helpers = require("../../../lib/helpers"); -const certListItemTemplate = require("../certificates-list-item.ejs"); -const i18n = require("../../i18n"); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - forwarding_host: 'input[name="forwarding_host"]', - type_error: '.forward-type-error', - buttons: '.modal-footer button', - switches: '.custom-switch-input', - cancel: 'button.cancel', - save: 'button.save', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - domain_names: 'input[name="domain_names"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - letsencrypt: '.letsencrypt' - }, - - events: { - 'change @ui.switches': function () { - this.ui.type_error.hide(); - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - if (!data.tcp_forwarding && !data.udp_forwarding) { - this.ui.type_error.show(); - return; - } - - // Manipulate - data.incoming_port = parseInt(data.incoming_port, 10); - data.forwarding_port = parseInt(data.forwarding_port, 10); - data.tcp_forwarding = !!data.tcp_forwarding; - data.udp_forwarding = !!data.udp_forwarding; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = true; - - if (data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.Streams.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.Streams.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxStream(); - } - }); - }) - .catch(err => { - let more_info = ''; - if (err.code === 500 && err.debug) { - try { - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch (e) { - } - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
` : ''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - }, - - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.domain_names.prop('required', 'required'); - - this.ui.dns_challenge_switch - .prop('disabled', true) - .parents('.form-group') - .css('opacity', 0.5); - - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new StreamModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/stream/list/item.ejs b/frontend/js/app/nginx/stream/list/item.ejs deleted file mode 100644 index 936247ef..00000000 --- a/frontend/js/app/nginx/stream/list/item.ejs +++ /dev/null @@ -1,59 +0,0 @@ - -
- -
- - -
- <%- incoming_port %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- forwarding_host %>:<%- forwarding_port %>
- - -
- <% if (certificate) { %> - <%- i18n('streams', 'tcp+ssl') %> - <% } - else if (tcp_forwarding) { %> - <%- i18n('streams', 'tcp') %> - <% } - if (udp_forwarding) { %> - <%- i18n('streams', 'udp') %> - <% } %> -
- - -
<%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('all-hosts', 'none') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/stream/list/item.js b/frontend/js/app/nginx/stream/list/item.js deleted file mode 100644 index a6892ee2..00000000 --- a/frontend/js/app/nginx/stream/list/item.js +++ /dev/null @@ -1,54 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.Streams[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.Streams.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxStreamForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxStreamDeleteConfirm(this.model); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('streams'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/stream/list/main.ejs b/frontend/js/app/nginx/stream/list/main.ejs deleted file mode 100644 index 57ab6b2a..00000000 --- a/frontend/js/app/nginx/stream/list/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ - -   - <%- i18n('streams', 'incoming-port') %> - <%- i18n('str', 'destination') %> - <%- i18n('streams', 'protocol') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/stream/list/main.js b/frontend/js/app/nginx/stream/list/main.js deleted file mode 100644 index 36be621d..00000000 --- a/frontend/js/app/nginx/stream/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('streams') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/stream/main.ejs b/frontend/js/app/nginx/stream/main.ejs deleted file mode 100644 index 7dc0dbe8..00000000 --- a/frontend/js/app/nginx/stream/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('streams', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('streams', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/stream/main.js b/frontend/js/app/nginx/stream/main.js deleted file mode 100644 index 83bdc15e..00000000 --- a/frontend/js/app/nginx/stream/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const StreamModel = require('../../../models/stream'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-stream', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.Streams.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new StreamModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxStream(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('streams'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('streams', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('streams', 'add') : null, - btn_color: 'blue', - permission: 'streams', - action: function () { - App.Controller.showNginxStreamForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxStreamForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('streams', 'help-title'), App.i18n('streams', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('streams') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/router.js b/frontend/js/app/router.js deleted file mode 100644 index a036bfc5..00000000 --- a/frontend/js/app/router.js +++ /dev/null @@ -1,19 +0,0 @@ -const AppRouter = require('marionette.approuter'); -const Controller = require('./controller'); - -module.exports = AppRouter.default.extend({ - controller: Controller, - appRoutes: { - users: 'showUsers', - logout: 'logout', - 'nginx/proxy': 'showNginxProxy', - 'nginx/redirection': 'showNginxRedirection', - 'nginx/404': 'showNginxDead', - 'nginx/stream': 'showNginxStream', - 'nginx/access': 'showNginxAccess', - 'nginx/certificates': 'showNginxCertificates', - 'audit-log': 'showAuditLog', - 'settings': 'showSettings', - '*default': 'showDashboard' - } -}); diff --git a/frontend/js/app/settings/default-site/main.ejs b/frontend/js/app/settings/default-site/main.ejs deleted file mode 100644 index d74ac0bd..00000000 --- a/frontend/js/app/settings/default-site/main.ejs +++ /dev/null @@ -1,57 +0,0 @@ - diff --git a/frontend/js/app/settings/default-site/main.js b/frontend/js/app/settings/default-site/main.js deleted file mode 100644 index 06a45b8b..00000000 --- a/frontend/js/app/settings/default-site/main.js +++ /dev/null @@ -1,69 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./main.ejs'); - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - options: '.option-item', - value: 'input[name="value"]', - redirect: '.redirect-input', - html: '.html-content' - }, - - events: { - 'change @ui.value': function (e) { - let val = this.ui.value.filter(':checked').val(); - this.ui.options.hide(); - this.ui.options.filter('.option-' + val).show(); - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - - let val = this.ui.value.filter(':checked').val(); - - // Clear redirect field before validation - if (val !== 'redirect') { - this.ui.redirect.val('').attr('required', false); - } else { - this.ui.redirect.attr('required', true); - } - - this.ui.html.attr('required', val === 'html'); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - data.id = this.model.get('id'); - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - App.Api.Settings.update(data) - .then(result => { - view.model.set(result); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - onRender: function () { - this.ui.value.trigger('change'); - } -}); diff --git a/frontend/js/app/settings/list/item.ejs b/frontend/js/app/settings/list/item.ejs deleted file mode 100644 index 1623c4dc..00000000 --- a/frontend/js/app/settings/list/item.ejs +++ /dev/null @@ -1,21 +0,0 @@ - -
<%- i18n('settings', 'default-site') %>
-
- <%- i18n('settings', 'default-site-description') %> -
- - -
- <% if (id === 'default-site') { %> - <%- i18n('settings', 'default-site-' + value) %> - <% } %> -
- - - - \ No newline at end of file diff --git a/frontend/js/app/settings/list/item.js b/frontend/js/app/settings/list/item.js deleted file mode 100644 index 03f9ac05..00000000 --- a/frontend/js/app/settings/list/item.js +++ /dev/null @@ -1,23 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - edit: 'a.edit' - }, - - events: { - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showSettingForm(this.model); - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/settings/list/main.ejs b/frontend/js/app/settings/list/main.ejs deleted file mode 100644 index c96e923a..00000000 --- a/frontend/js/app/settings/list/main.ejs +++ /dev/null @@ -1,8 +0,0 @@ - - <%- i18n('str', 'name') %> - <%- i18n('str', 'value') %> -   - - - - diff --git a/frontend/js/app/settings/list/main.js b/frontend/js/app/settings/list/main.js deleted file mode 100644 index 9d3e26fb..00000000 --- a/frontend/js/app/settings/list/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/settings/main.ejs b/frontend/js/app/settings/main.ejs deleted file mode 100644 index 2b02769f..00000000 --- a/frontend/js/app/settings/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ -
-
-
-

<%- i18n('settings', 'title') %>

-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/settings/main.js b/frontend/js/app/settings/main.js deleted file mode 100644 index 96b2941f..00000000 --- a/frontend/js/app/settings/main.js +++ /dev/null @@ -1,48 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const SettingModel = require('../../models/setting'); -const ListView = require('./list/main'); -const ErrorView = require('../error/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'settings', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - dimmer: '.dimmer' - }, - - regions: { - list_region: '@ui.list_region' - }, - - onRender: function () { - let view = this; - - App.Api.Settings.getAll() - .then(response => { - if (!view.isDestroyed() && response && response.length) { - view.showChildView('list_region', new ListView({ - collection: new SettingModel.Collection(response) - })); - } - }) - .catch(err => { - view.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showSettings(); - } - })); - - console.error(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/tokens.js b/frontend/js/app/tokens.js deleted file mode 100644 index 4a56bcab..00000000 --- a/frontend/js/app/tokens.js +++ /dev/null @@ -1,126 +0,0 @@ -const STORAGE_NAME = 'nginx-proxy-manager-tokens'; - -/** - * @returns {Array} - */ -const getStorageTokens = function () { - let json = window.localStorage.getItem(STORAGE_NAME); - if (json) { - try { - return JSON.parse(json); - } catch (err) { - return []; - } - } - - return []; -}; - -/** - * @param {Array} tokens - */ -const setStorageTokens = function (tokens) { - window.localStorage.setItem(STORAGE_NAME, JSON.stringify(tokens)); -}; - -const Tokens = { - - /** - * @returns {Number} - */ - getTokenCount: () => { - return getStorageTokens().length; - }, - - /** - * @returns {Object} t,n - */ - getTopToken: () => { - let tokens = getStorageTokens(); - if (tokens && tokens.length) { - return tokens[0]; - } - - return null; - }, - - /** - * @returns {String} - */ - getNextTokenName: () => { - let tokens = getStorageTokens(); - if (tokens && tokens.length > 1 && typeof tokens[1] !== 'undefined' && typeof tokens[1].n !== 'undefined') { - return tokens[1].n; - } - - return null; - }, - - /** - * - * @param {String} token - * @param {String} [name] - * @returns {Number} - */ - addToken: (token, name) => { - // Get top token and if it's the same, ignore this call - let top = Tokens.getTopToken(); - if (!top || top.t !== token) { - let tokens = getStorageTokens(); - tokens.unshift({t: token, n: name || null}); - setStorageTokens(tokens); - } - - return Tokens.getTokenCount(); - }, - - /** - * @param {String} token - * @returns {Boolean} - */ - setCurrentToken: token => { - let tokens = getStorageTokens(); - if (tokens.length) { - tokens[0].t = token; - setStorageTokens(tokens); - return true; - } - - return false; - }, - - /** - * @param {String} name - * @returns {Boolean} - */ - setCurrentName: name => { - let tokens = getStorageTokens(); - if (tokens.length) { - tokens[0].n = name; - setStorageTokens(tokens); - return true; - } - - return false; - }, - - /** - * @returns {Number} - */ - dropTopToken: () => { - let tokens = getStorageTokens(); - tokens.shift(); - setStorageTokens(tokens); - return tokens.length; - }, - - /** - * - */ - clearTokens: () => { - window.localStorage.removeItem(STORAGE_NAME); - } - -}; - -module.exports = Tokens; diff --git a/frontend/js/app/ui/footer/main.ejs b/frontend/js/app/ui/footer/main.ejs deleted file mode 100644 index 99c2630a..00000000 --- a/frontend/js/app/ui/footer/main.ejs +++ /dev/null @@ -1,16 +0,0 @@ -
- -
- <%- i18n('main', 'version', {version: getVersion()}) %> - <%= i18n('footer', 'copy', {url: 'https://jc21.com?utm_source=nginx-proxy-manager'}) %> - <%= i18n('footer', 'theme', {url: 'https://tabler.github.io/?utm_source=nginx-proxy-manager'}) %> -
-
diff --git a/frontend/js/app/ui/footer/main.js b/frontend/js/app/ui/footer/main.js deleted file mode 100644 index 73f515e6..00000000 --- a/frontend/js/app/ui/footer/main.js +++ /dev/null @@ -1,14 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); -const Cache = require('../../cache'); - -module.exports = Mn.View.extend({ - className: 'container', - template: template, - - templateContext: { - getVersion: function () { - return Cache.version || '0.0.0'; - } - } -}); diff --git a/frontend/js/app/ui/header/main.ejs b/frontend/js/app/ui/header/main.ejs deleted file mode 100644 index 18ed2b6a..00000000 --- a/frontend/js/app/ui/header/main.ejs +++ /dev/null @@ -1,34 +0,0 @@ - diff --git a/frontend/js/app/ui/header/main.js b/frontend/js/app/ui/header/main.js deleted file mode 100644 index 9779b45c..00000000 --- a/frontend/js/app/ui/header/main.js +++ /dev/null @@ -1,67 +0,0 @@ -const $ = require('jquery'); -const Mn = require('backbone.marionette'); -const i18n = require('../../i18n'); -const Cache = require('../../cache'); -const Controller = require('../../controller'); -const Tokens = require('../../tokens'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'header', - className: 'header', - template: template, - - ui: { - link: 'a', - details: 'a.edit-details', - password: 'a.change-password' - }, - - events: { - 'click @ui.details': function (e) { - e.preventDefault(); - Controller.showUserForm(Cache.User); - }, - - 'click @ui.password': function (e) { - e.preventDefault(); - Controller.showUserPasswordForm(Cache.User); - }, - - 'click @ui.link': function (e) { - e.preventDefault(); - let href = $(e.currentTarget).attr('href'); - - switch (href) { - case '/': - Controller.showDashboard(); - break; - case '/logout': - Controller.logout(); - break; - } - } - }, - - templateContext: { - getUserField: function (field, default_val) { - return Cache.User.get(field) || default_val; - }, - - getRole: function () { - return i18n('roles', Cache.User.isAdmin() ? 'admin' : 'user'); - }, - - getLogoutText: function () { - if (Tokens.getTokenCount() > 1) { - return i18n('main', 'sign-in-as', {name: Tokens.getNextTokenName()}); - } - - return i18n('str', 'sign-out'); - } - }, - - initialize: function () { - this.listenTo(Cache.User, 'change', this.render); - } -}); diff --git a/frontend/js/app/ui/main.ejs b/frontend/js/app/ui/main.ejs deleted file mode 100644 index b62c3acd..00000000 --- a/frontend/js/app/ui/main.ejs +++ /dev/null @@ -1,21 +0,0 @@ -
- -
-
- -
-
-
- -
- -
- - diff --git a/frontend/js/app/ui/main.js b/frontend/js/app/ui/main.js deleted file mode 100644 index c90c61d5..00000000 --- a/frontend/js/app/ui/main.js +++ /dev/null @@ -1,98 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); -const HeaderView = require('./header/main'); -const MenuView = require('./menu/main'); -const FooterView = require('./footer/main'); -const Cache = require('../cache'); - -module.exports = Mn.View.extend({ - id: 'app', - className: 'page', - template: template, - modal_setup: false, - - modal: null, - - ui: { - modal: '#modal-dialog' - }, - - regions: { - header_region: { - el: '#header', - replaceElement: true - }, - menu_region: { - el: '#menu', - replaceElement: true - }, - footer_region: '.footer', - app_content_region: '#app-content', - modal_region: '#modal-dialog' - }, - - /** - * @param {Object} view - */ - showAppContent: function (view) { - this.showChildView('app_content_region', view); - }, - - /** - * @param {Object} view - * @param {Function} [show_callback] - * @param {Function} [shown_callback] - */ - showModalDialog: function (view, show_callback, shown_callback) { - this.showChildView('modal_region', view); - let modal = this.getRegion('modal_region').$el.modal('show'); - - modal.on('hidden.bs.modal', function (/*e*/) { - if (show_callback) { - modal.off('show.bs.modal', show_callback); - } - - if (shown_callback) { - modal.off('shown.bs.modal', shown_callback); - } - - modal.off('hidden.bs.modal'); - view.destroy(); - }); - - if (show_callback) { - modal.on('show.bs.modal', show_callback); - } - - if (shown_callback) { - modal.on('shown.bs.modal', shown_callback); - } - }, - - /** - * - * @param {Function} [hidden_callback] - */ - closeModal: function (hidden_callback) { - let modal = this.getRegion('modal_region').$el.modal('hide'); - - if (hidden_callback) { - modal.on('hidden.bs.modal', hidden_callback); - } - }, - - onRender: function () { - this.showChildView('header_region', new HeaderView({ - model: Cache.User - })); - - this.showChildView('menu_region', new MenuView()); - this.showChildView('footer_region', new FooterView()); - }, - - reset: function () { - this.getRegion('header_region').reset(); - this.getRegion('footer_region').reset(); - this.getRegion('modal_region').reset(); - } -}); diff --git a/frontend/js/app/ui/menu/main.ejs b/frontend/js/app/ui/menu/main.ejs deleted file mode 100644 index 671b4e3b..00000000 --- a/frontend/js/app/ui/menu/main.ejs +++ /dev/null @@ -1,52 +0,0 @@ -
-
-
- -
-
-
diff --git a/frontend/js/app/ui/menu/main.js b/frontend/js/app/ui/menu/main.js deleted file mode 100644 index dabe26d3..00000000 --- a/frontend/js/app/ui/menu/main.js +++ /dev/null @@ -1,39 +0,0 @@ -const $ = require('jquery'); -const Mn = require('backbone.marionette'); -const Controller = require('../../controller'); -const Cache = require('../../cache'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'menu', - className: 'header collapse d-lg-flex p-0', - template: template, - - ui: { - links: 'a' - }, - - events: { - 'click @ui.links': function (e) { - let href = $(e.currentTarget).attr('href'); - if (href !== '#') { - e.preventDefault(); - Controller.navigate(href, true); - } - } - }, - - templateContext: { - isAdmin: function () { - return Cache.User.isAdmin(); - }, - - canShow: function (perm) { - return Cache.User.isAdmin() || Cache.User.canView(perm); - } - }, - - initialize: function () { - this.listenTo(Cache.User, 'change', this.render); - } -}); diff --git a/frontend/js/app/user/delete.ejs b/frontend/js/app/user/delete.ejs deleted file mode 100644 index c10532ef..00000000 --- a/frontend/js/app/user/delete.ejs +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/frontend/js/app/user/delete.js b/frontend/js/app/user/delete.js deleted file mode 100644 index e8ed5c32..00000000 --- a/frontend/js/app/user/delete.js +++ /dev/null @@ -1,34 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./delete.ejs'); -const App = require('../main'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Users.delete(this.model.get('id')) - .then(() => { - App.Controller.showUsers(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/user/form.ejs b/frontend/js/app/user/form.ejs deleted file mode 100644 index 9ba84438..00000000 --- a/frontend/js/app/user/form.ejs +++ /dev/null @@ -1,58 +0,0 @@ - diff --git a/frontend/js/app/user/form.js b/frontend/js/app/user/form.js deleted file mode 100644 index 617a75fc..00000000 --- a/frontend/js/app/user/form.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const UserModel = require('../../models/user'); -const template = require('./form.ejs'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - error: '.secret-error' - }, - - events: { - - 'submit @ui.form': function (e) { - e.preventDefault(); - this.ui.error.hide(); - let view = this; - let data = this.ui.form.serializeJSON(); - - let show_password = this.model.get('email') === 'admin@example.com'; - - // admin@example.com is not allowed - if (data.email === 'admin@example.com') { - this.ui.error.text(App.i18n('users', 'default_error')).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - return; - } - - // Manipulate - data.roles = []; - if ((this.model.get('id') === App.Cache.User.get('id') && this.model.isAdmin()) || (typeof data.is_admin !== 'undefined' && data.is_admin)) { - data.roles.push('admin'); - delete data.is_admin; - } - - data.is_disabled = typeof data.is_disabled !== 'undefined' ? !!data.is_disabled : false; - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - let method = App.Api.Users.create; - - if (this.model.get('id')) { - // edit - method = App.Api.Users.update; - data.id = this.model.get('id'); - } - - method(data) - .then(result => { - if (result.id === App.Cache.User.get('id')) { - App.Cache.User.set(result); - } - - if (view.model.get('id') !== App.Cache.User.get('id')) { - App.Controller.showUsers(); - } - - view.model.set(result); - App.UI.closeModal(function () { - if (method === App.Api.Users.create) { - // Show permissions dialog immediately - App.Controller.showUserPermissions(view.model); - } else if (show_password) { - App.Controller.showUserPasswordForm(view.model); - } - }); - }) - .catch(err => { - this.ui.error.text(err.message).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - templateContext: function () { - let view = this; - - return { - isSelf: function () { - return view.model.get('id') === App.Cache.User.get('id'); - }, - - isAdmin: function () { - return App.Cache.User.isAdmin(); - }, - - isAdminUser: function () { - return view.model.isAdmin(); - }, - - isDisabled: function () { - return view.model.isDisabled(); - } - }; - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new UserModel.Model(); - } - } -}); diff --git a/frontend/js/app/user/password.ejs b/frontend/js/app/user/password.ejs deleted file mode 100644 index a45cc7ed..00000000 --- a/frontend/js/app/user/password.ejs +++ /dev/null @@ -1,31 +0,0 @@ - diff --git a/frontend/js/app/user/password.js b/frontend/js/app/user/password.js deleted file mode 100644 index 84030750..00000000 --- a/frontend/js/app/user/password.js +++ /dev/null @@ -1,69 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const template = require('./password.ejs'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - newSecretError: '.new-secret-error', - generalError: '#error-info', - }, - - events: { - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.newSecretError.hide(); - this.ui.generalError.hide(); - let form = this.ui.form.serializeJSON(); - - if (form.new_password1 !== form.new_password2) { - this.ui.newSecretError.text('Passwords do not match!').show(); - return; - } - - let data = { - type: 'password', - current: form.current_password, - secret: form.new_password1 - }; - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - App.Api.Users.setPassword(this.model.get('id'), data) - .then(() => { - App.UI.closeModal(); - App.Controller.showUsers(); - }) - .catch(err => { - // Change error message to make it a little clearer - if (err.message === 'Invalid password') { - err.message = 'Current password is invalid'; - } - this.ui.generalError.text(err.message).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - isSelf: function () { - return App.Cache.User.get('id') === this.model.get('id'); - }, - - templateContext: function () { - return { - isSelf: this.isSelf.bind(this) - }; - }, - - onRender: function () { - this.ui.newSecretError.hide(); - this.ui.generalError.hide(); - }, -}); diff --git a/frontend/js/app/user/permissions.ejs b/frontend/js/app/user/permissions.ejs deleted file mode 100644 index b6161796..00000000 --- a/frontend/js/app/user/permissions.ejs +++ /dev/null @@ -1,68 +0,0 @@ - diff --git a/frontend/js/app/user/permissions.js b/frontend/js/app/user/permissions.js deleted file mode 100644 index af8049ce..00000000 --- a/frontend/js/app/user/permissions.js +++ /dev/null @@ -1,95 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const UserModel = require('../../models/user'); -const template = require('./permissions.ejs'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - error: '.secret-error' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Manipulate - if (view.model.isAdmin()) { - // Force some attributes for admin - data = _.assign({}, data, { - access_lists: 'manage', - dead_hosts: 'manage', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - streams: 'manage', - certificates: 'manage' - }); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - - App.Api.Users.setPermissions(view.model.get('id'), data) - .then(() => { - if (view.model.get('id') === App.Cache.User.get('id')) { - App.Cache.User.set({permissions: data}); - } - - view.model.set({permissions: data}); - App.UI.closeModal(); - }) - .catch(err => { - this.ui.error.text(err.message).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - templateContext: function () { - let perms = this.model.get('permissions'); - let is_admin = this.model.isAdmin(); - - return { - getPerm: function (key) { - if (perms !== null && typeof perms[key] !== 'undefined') { - return perms[key]; - } - - return null; - }, - - getPermProps: function (key, item, forced_admin) { - if (forced_admin && is_admin) { - return 'checked disabled'; - } else if (is_admin) { - return 'disabled'; - } else if (perms !== null && typeof perms[key] !== 'undefined' && perms[key] === item) { - return 'checked'; - } - - return ''; - }, - - isAdmin: function () { - return is_admin; - } - }; - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new UserModel.Model(); - } - } -}); diff --git a/frontend/js/app/users/list/item.ejs b/frontend/js/app/users/list/item.ejs deleted file mode 100644 index fab5585b..00000000 --- a/frontend/js/app/users/list/item.ejs +++ /dev/null @@ -1,45 +0,0 @@ - -
- -
- - -
<%- name %>
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- email %>
- - -
- <% - var r = []; - roles.map(function(role) { - if (role) { - r.push(i18n('roles', role)); - } - }); - %> - <%- r.join(', ') %> -
- - - - diff --git a/frontend/js/app/users/list/item.js b/frontend/js/app/users/list/item.js deleted file mode 100644 index 4645a5c4..00000000 --- a/frontend/js/app/users/list/item.js +++ /dev/null @@ -1,68 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const Tokens = require('../../tokens'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - edit: 'a.edit-user', - permissions: 'a.edit-permissions', - password: 'a.set-password', - login: 'a.login', - delete: 'a.delete-user' - }, - - events: { - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showUserForm(this.model); - }, - - 'click @ui.permissions': function (e) { - e.preventDefault(); - App.Controller.showUserPermissions(this.model); - }, - - 'click @ui.password': function (e) { - e.preventDefault(); - App.Controller.showUserPasswordForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showUserDeleteConfirm(this.model); - }, - - 'click @ui.login': function (e) { - e.preventDefault(); - - if (App.Cache.User.get('id') !== this.model.get('id')) { - this.ui.login.prop('disabled', true).addClass('btn-disabled'); - - App.Api.Users.loginAs(this.model.get('id')) - .then(res => { - Tokens.addToken(res.token, res.user.nickname || res.user.name); - window.location = '/'; - window.location.reload(); - }) - .catch(err => { - alert(err.message); - this.ui.login.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } - }, - - templateContext: { - isSelf: function () { - return App.Cache.User.get('id') === this.id; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/users/list/main.ejs b/frontend/js/app/users/list/main.ejs deleted file mode 100644 index c85c9cb1..00000000 --- a/frontend/js/app/users/list/main.ejs +++ /dev/null @@ -1,10 +0,0 @@ - -   - <%- i18n('str', 'name') %> - <%- i18n('str', 'email') %> - <%- i18n('str', 'roles') %> -   - - - - diff --git a/frontend/js/app/users/list/main.js b/frontend/js/app/users/list/main.js deleted file mode 100644 index 9d3e26fb..00000000 --- a/frontend/js/app/users/list/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/users/main.ejs b/frontend/js/app/users/main.ejs deleted file mode 100644 index 892cb83f..00000000 --- a/frontend/js/app/users/main.ejs +++ /dev/null @@ -1,26 +0,0 @@ -
-
-
-

<%- i18n('users', 'title') %>

-
- - <%- i18n('users', 'add') %> -
-
-
-
-
-
- -
-
- -
-
diff --git a/frontend/js/app/users/main.js b/frontend/js/app/users/main.js deleted file mode 100644 index 42cb41ef..00000000 --- a/frontend/js/app/users/main.js +++ /dev/null @@ -1,78 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const UserModel = require('../../models/user'); -const ListView = require('./list/main'); -const ErrorView = require('../error/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'users', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Users.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new UserModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showUsers(); - } - })); - - console.error(err); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showUserForm(new UserModel.Model()); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['permissions'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - onRender: function () { - let view = this; - - view.fetch(['permissions']) - .then(response => { - if (!view.isDestroyed() && response && response.length) { - view.showData(response); - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/i18n/messages.json b/frontend/js/i18n/messages.json deleted file mode 100644 index d409942f..00000000 --- a/frontend/js/i18n/messages.json +++ /dev/null @@ -1,301 +0,0 @@ -{ - "en": { - "str": { - "email-address": "Email address", - "username": "Username", - "password": "Password", - "sign-in": "Sign in", - "sign-out": "Sign out", - "try-again": "Try again", - "name": "Name", - "email": "Email", - "roles": "Roles", - "created-on": "Created: {date}", - "save": "Save", - "cancel": "Cancel", - "close": "Close", - "enable": "Enable", - "disable": "Disable", - "sure": "Yes I'm Sure", - "disabled": "Disabled", - "choose-file": "Choose file", - "source": "Source", - "destination": "Destination", - "ssl": "SSL", - "access": "Access", - "public": "Public", - "edit": "Edit", - "delete": "Delete", - "logs": "Logs", - "status": "Status", - "online": "Online", - "offline": "Offline", - "unknown": "Unknown", - "expires": "Expires", - "value": "Value", - "please-wait": "Please wait...", - "all": "All", - "any": "Any" - }, - "login": { - "title": "Login to your account" - }, - "main": { - "app": "Nginx Proxy Manager", - "version": "v{version}", - "welcome": "Welcome to Nginx Proxy Manager", - "logged-in": "You are logged in as {name}", - "unknown-error": "Error loading stuff. Please reload the app.", - "unknown-user": "Unknown User", - "sign-in-as": "Sign back in as {name}" - }, - "roles": { - "title": "Roles", - "admin": "Administrator", - "user": "Apache Helicopter" - }, - "menu": { - "dashboard": "Dashboard", - "hosts": "Hosts" - }, - "footer": { - "fork-me": "Fork me on Github", - "copy": "© 2025 jc21.com.", - "theme": "Theme by Tabler" - }, - "dashboard": { - "title": "Hi {name}" - }, - "all-hosts": { - "empty-subtitle": "{manage, select, true{Why don't you create one?} other{And you don't have permission to create one.}}", - "details": "Details", - "enable-ssl": "Enable SSL", - "force-ssl": "Force SSL", - "http2-support": "HTTP/2 Support", - "domain-names": "Domain Names", - "cert-provider": "Certificate Provider", - "block-exploits": "Block Common Exploits", - "caching-enabled": "Cache Assets", - "ssl-certificate": "SSL Certificate", - "none": "None", - "new-cert": "Request a new SSL Certificate", - "with-le": "with Let's Encrypt", - "no-ssl": "This host will not use HTTPS", - "advanced": "Advanced", - "advanced-warning": "Enter your custom Nginx configuration here at your own risk!", - "advanced-config": "Custom Nginx Configuration", - "advanced-config-var-headline": "These proxy details are available as nginx variables:", - "advanced-config-header-info": "Please note, that any add_header or set_header directives added here will not be used by nginx. You will have to add a custom location '/' and add the header in the custom config there.", - "hsts-enabled": "HSTS Enabled", - "hsts-subdomains": "HSTS Subdomains", - "locations": "Custom locations" - }, - "locations": { - "new_location": "Add location", - "path": "/path", - "location_label": "Define location", - "delete": "Delete" - }, - "ssl": { - "letsencrypt": "Let's Encrypt", - "other": "Custom", - "none": "HTTP only", - "letsencrypt-email": "Email Address for Let's Encrypt", - "letsencrypt-agree": "I Agree to the Let's Encrypt Terms of Service", - "delete-ssl": "The SSL certificates attached will NOT be removed, they will need to be removed manually.", - "hosts-warning": "These domains must be already configured to point to this installation", - "no-wildcard-without-dns": "Cannot request Let's Encrypt Certificate for wildcard domains when not using DNS challenge", - "dns-challenge": "Use a DNS Challenge", - "certbot-warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.", - "dns-provider": "DNS Provider", - "please-choose": "Please Choose...", - "credentials-file-content": "Credentials File Content", - "credentials-file-content-info": "This plugin requires a configuration file containing an API token or other credentials to your provider", - "stored-as-plaintext-info": "This data will be stored as plaintext in the database and in a file!", - "propagation-seconds": "Propagation Seconds", - "propagation-seconds-info": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.", - "processing-info": "Processing... This might take a few minutes.", - "passphrase-protection-support-info": "Key files protected with a passphrase are not supported." - }, - "proxy-hosts": { - "title": "Proxy Hosts", - "empty": "There are no Proxy Hosts", - "add": "Add Proxy Host", - "form-title": "{id, select, undefined{New} other{Edit}} Proxy Host", - "forward-scheme": "Scheme", - "forward-host": "Forward Hostname / IP", - "forward-port": "Forward Port", - "delete": "Delete Proxy Host", - "delete-confirm": "Are you sure you want to delete the Proxy host for: {domains}?", - "help-title": "What is a Proxy Host?", - "help-content": "A Proxy Host is the incoming endpoint for a web service that you want to forward.\nIt provides optional SSL termination for your service that might not have SSL support built in.\nProxy Hosts are the most common use for the Nginx Proxy Manager.", - "access-list": "Access List", - "allow-websocket-upgrade": "Websockets Support", - "ignore-invalid-upstream-ssl": "Ignore Invalid SSL", - "custom-forward-host-help": "Add a path for sub-folder forwarding.\nExample: 203.0.113.25/path/", - "search": "Search Host…" - }, - "redirection-hosts": { - "title": "Redirection Hosts", - "empty": "There are no Redirection Hosts", - "add": "Add Redirection Host", - "form-title": "{id, select, undefined{New} other{Edit}} Redirection Host", - "forward-scheme": "Scheme", - "forward-http-status-code": "HTTP Code", - "forward-domain": "Forward Domain", - "preserve-path": "Preserve Path", - "delete": "Delete Redirection Host", - "delete-confirm": "Are you sure you want to delete the Redirection host for: {domains}?", - "help-title": "What is a Redirection Host?", - "help-content": "A Redirection Host will redirect requests from the incoming domain and push the viewer to another domain.\nThe most common reason to use this type of host is when your website changes domains but you still have search engine or referrer links pointing to the old domain.", - "search": "Search Host…" - }, - "dead-hosts": { - "title": "404 Hosts", - "empty": "There are no 404 Hosts", - "add": "Add 404 Host", - "form-title": "{id, select, undefined{New} other{Edit}} 404 Host", - "delete": "Delete 404 Host", - "delete-confirm": "Are you sure you want to delete this 404 Host?", - "help-title": "What is a 404 Host?", - "help-content": "A 404 Host is simply a host setup that shows a 404 page.\nThis can be useful when your domain is listed in search engines and you want to provide a nicer error page or specifically to tell the search indexers that the domain pages no longer exist.\nAnother benefit of having this host is to track the logs for hits to it and view the referrers.", - "search": "Search Host…" - }, - "streams": { - "title": "Streams", - "empty": "There are no Streams", - "add": "Add Stream", - "form-title": "{id, select, undefined{New} other{Edit}} Stream", - "incoming-port": "Incoming Port", - "forwarding-host": "Forward Host", - "forwarding-port": "Forward Port", - "tcp-forwarding": "TCP Forwarding", - "udp-forwarding": "UDP Forwarding", - "forward-type-error": "At least one type of protocol must be enabled", - "protocol": "Protocol", - "tcp": "TCP", - "udp": "UDP", - "delete": "Delete Stream", - "delete-confirm": "Are you sure you want to delete this Stream?", - "help-title": "What is a Stream?", - "help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.", - "search": "Search Incoming Port…", - "ssl-certificate": "SSL Certificate for TCP Forwarding", - "tcp+ssl": "TCP+SSL" - }, - "certificates": { - "title": "SSL Certificates", - "empty": "There are no SSL Certificates", - "add": "Add SSL Certificate", - "form-title": "Add {provider, select, letsencrypt{Let's Encrypt} other{Custom}} Certificate", - "delete": "Delete SSL Certificate", - "delete-confirm": "Are you sure you want to delete this SSL Certificate? Any hosts using it will need to be updated later.", - "help-title": "SSL Certificates", - "help-content": "SSL certificates (correctly known as TLS Certificates) are a form of encryption key which allows your site to be encrypted for the end user.\nNPM uses a service called Let's Encrypt to issue SSL certificates for free.\nIf you have any sort of personal information, passwords, or sensitive data behind NPM, it's probably a good idea to use a certificate.\nNPM also supports DNS authentication for if you're not running your site facing the internet, or if you just want a wildcard certificate.", - "other-certificate": "Certificate", - "other-certificate-key": "Certificate Key", - "other-intermediate-certificate": "Intermediate Certificate", - "force-renew": "Renew Now", - "test-reachability": "Test Server Reachability", - "reachability-title": "Test Server Reachability", - "reachability-info": "Test whether the domains are reachable from the public internet using Site24x7. This is not necessary when using the DNS Challenge.", - "reachability-failed-to-reach-api": "Communication with the API failed, is NPM running correctly?", - "reachability-failed-to-check": "Failed to check the reachability due to a communication error with site24x7.com.", - "reachability-ok": "Your server is reachable and creating certificates should be possible.", - "reachability-404": "There is a server found at this domain but it does not seem to be Nginx Proxy Manager. Please make sure your domain points to the IP where your NPM instance is running.", - "reachability-not-resolved": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router.", - "reachability-wrong-data": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", - "reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", - "download": "Download", - "renew-title": "Renew Let's Encrypt Certificate", - "search": "Search Certificate…", - "in-use" : "In use", - "inactive": "Inactive", - "active-domain_names": "Active domain names" - }, - "access-lists": { - "title": "Access Lists", - "empty": "There are no Access Lists", - "add": "Add Access List", - "form-title": "{id, select, undefined{New} other{Edit}} Access List", - "delete": "Delete Access List", - "delete-confirm": "Are you sure you want to delete this access list?", - "public": "Publicly Accessible", - "public-sub": "No Access Restrictions", - "help-title": "What is an Access List?", - "help-content": "Access Lists provide a blacklist or whitelist of specific client IP addresses along with authentication for the Proxy Hosts via Basic HTTP Authentication.\nYou can configure multiple client rules, usernames and passwords for a single Access List and then apply that to a Proxy Host.\nThis is most useful for forwarded web services that do not have authentication mechanisms built in or that you want to protect from access by unknown clients.", - "item-count": "{count} {count, select, 1{User} other{Users}}", - "client-count": "{count} {count, select, 1{Rule} other{Rules}}", - "proxy-host-count": "{count} {count, select, 1{Proxy Host} other{Proxy Hosts}}", - "delete-has-hosts": "This Access List is associated with {count} Proxy Hosts. They will become publicly available upon deletion.", - "details": "Details", - "authorization": "Authorization", - "access": "Access", - "satisfy": "Satisfy", - "satisfy-any": "Satisfy Any", - "pass-auth": "Pass Auth to Host", - "access-add": "Add", - "auth-add": "Add", - "search": "Search Access…" - }, - "users": { - "title": "Users", - "default_error": "Default email address must be changed", - "add": "Add User", - "nickname": "Nickname", - "full-name": "Full Name", - "edit-details": "Edit Details", - "change-password": "Change Password", - "edit-permissions": "Edit Permissions", - "sign-in-as": "Sign in as User", - "form-title": "{id, select, undefined{New} other{Edit}} User", - "delete": "Delete {name, select, undefined{User} other{{name}}}", - "delete-confirm": "Are you sure you want to delete {name}?", - "password-title": "Change Password{self, select, false{ for {name}} other{}}", - "current-password": "Current Password", - "new-password": "New Password", - "confirm-password": "Confirm Password", - "permissions-title": "Permissions for {name}", - "admin-perms": "This user is an Administrator and some items cannot be altered", - "perms-visibility": "Item Visibility", - "perms-visibility-user": "Created Items Only", - "perms-visibility-all": "All Items", - "perm-manage": "Manage", - "perm-view": "View Only", - "perm-hidden": "Hidden", - "search": "Search User…" - }, - "audit-log": { - "title": "Audit Log", - "empty": "There are no logs.", - "empty-subtitle": "As soon as you or another user changes something, history of those events will show up here.", - "proxy-host": "Proxy Host", - "redirection-host": "Redirection Host", - "dead-host": "404 Host", - "stream": "Stream", - "user": "User", - "certificate": "Certificate", - "access-list": "Access List", - "created": "Created {name}", - "updated": "Updated {name}", - "deleted": "Deleted {name}", - "enabled": "Enabled {name}", - "disabled": "Disabled {name}", - "renewed": "Renewed {name}", - "meta-title": "Details for Event", - "view-meta": "View Details", - "date": "Date", - "search": "Search Log…" - }, - "settings": { - "title": "Settings", - "default-site": "Default Site", - "default-site-description": "What to show when Nginx is hit with an unknown Host", - "default-site-congratulations": "Congratulations Page", - "default-site-404": "404 Page", - "default-site-444": "No Response (444)", - "default-site-html": "Custom Page", - "default-site-redirect": "Redirect" - } - } -} diff --git a/frontend/js/index.js b/frontend/js/index.js deleted file mode 100644 index 3d817d71..00000000 --- a/frontend/js/index.js +++ /dev/null @@ -1,119 +0,0 @@ -// This has to exist here so that Webpack picks it up -import '../scss/styles.scss'; - -window.tabler = { - colors: { - 'blue': '#467fcf', - 'blue-darkest': '#0e1929', - 'blue-darker': '#1c3353', - 'blue-dark': '#3866a6', - 'blue-light': '#7ea5dd', - 'blue-lighter': '#c8d9f1', - 'blue-lightest': '#edf2fa', - 'azure': '#45aaf2', - 'azure-darkest': '#0e2230', - 'azure-darker': '#1c4461', - 'azure-dark': '#3788c2', - 'azure-light': '#7dc4f6', - 'azure-lighter': '#c7e6fb', - 'azure-lightest': '#ecf7fe', - 'indigo': '#6574cd', - 'indigo-darkest': '#141729', - 'indigo-darker': '#282e52', - 'indigo-dark': '#515da4', - 'indigo-light': '#939edc', - 'indigo-lighter': '#d1d5f0', - 'indigo-lightest': '#f0f1fa', - 'purple': '#a55eea', - 'purple-darkest': '#21132f', - 'purple-darker': '#42265e', - 'purple-dark': '#844bbb', - 'purple-light': '#c08ef0', - 'purple-lighter': '#e4cff9', - 'purple-lightest': '#f6effd', - 'pink': '#f66d9b', - 'pink-darkest': '#31161f', - 'pink-darker': '#622c3e', - 'pink-dark': '#c5577c', - 'pink-light': '#f999b9', - 'pink-lighter': '#fcd3e1', - 'pink-lightest': '#fef0f5', - 'red': '#e74c3c', - 'red-darkest': '#2e0f0c', - 'red-darker': '#5c1e18', - 'red-dark': '#b93d30', - 'red-light': '#ee8277', - 'red-lighter': '#f8c9c5', - 'red-lightest': '#fdedec', - 'orange': '#fd9644', - 'orange-darkest': '#331e0e', - 'orange-darker': '#653c1b', - 'orange-dark': '#ca7836', - 'orange-light': '#feb67c', - 'orange-lighter': '#fee0c7', - 'orange-lightest': '#fff5ec', - 'yellow': '#f1c40f', - 'yellow-darkest': '#302703', - 'yellow-darker': '#604e06', - 'yellow-dark': '#c19d0c', - 'yellow-light': '#f5d657', - 'yellow-lighter': '#fbedb7', - 'yellow-lightest': '#fef9e7', - 'lime': '#7bd235', - 'lime-darkest': '#192a0b', - 'lime-darker': '#315415', - 'lime-dark': '#62a82a', - 'lime-light': '#a3e072', - 'lime-lighter': '#d7f2c2', - 'lime-lightest': '#f2fbeb', - 'green': '#5eba00', - 'green-darkest': '#132500', - 'green-darker': '#264a00', - 'green-dark': '#4b9500', - 'green-light': '#8ecf4d', - 'green-lighter': '#cfeab3', - 'green-lightest': '#eff8e6', - 'teal': '#2bcbba', - 'teal-darkest': '#092925', - 'teal-darker': '#11514a', - 'teal-dark': '#22a295', - 'teal-light': '#6bdbcf', - 'teal-lighter': '#bfefea', - 'teal-lightest': '#eafaf8', - 'cyan': '#17a2b8', - 'cyan-darkest': '#052025', - 'cyan-darker': '#09414a', - 'cyan-dark': '#128293', - 'cyan-light': '#5dbecd', - 'cyan-lighter': '#b9e3ea', - 'cyan-lightest': '#e8f6f8', - 'gray': '#868e96', - 'gray-darkest': '#1b1c1e', - 'gray-darker': '#36393c', - 'gray-light': '#aab0b6', - 'gray-lighter': '#dbdde0', - 'gray-lightest': '#f3f4f5', - 'gray-dark': '#343a40', - 'gray-dark-darkest': '#0a0c0d', - 'gray-dark-darker': '#15171a', - 'gray-dark-dark': '#2a2e33', - 'gray-dark-light': '#717579', - 'gray-dark-lighter': '#c2c4c6', - 'gray-dark-lightest': '#ebebec' - } -}; - -String.prototype.toHtmlEntities = function() { - return this.replace(/./gm, function(s) { - // return "&#" + s.charCodeAt(0) + ";"; - return (s.match(/[a-z0-9\s]+/i)) ? s : "&#" + s.charCodeAt(0) + ";"; - }); -}; - -require('tabler-core'); - -const App = require('./app/main'); - -$(document).ready(() => { - App.start(); -}); diff --git a/frontend/js/lib/helpers.js b/frontend/js/lib/helpers.js deleted file mode 100644 index 21ce7424..00000000 --- a/frontend/js/lib/helpers.js +++ /dev/null @@ -1,26 +0,0 @@ -const numeral = require('numeral'); -const moment = require('moment'); - -module.exports = { - - /** - * @param {Integer} number - * @returns {String} - */ - niceNumber: function (number) { - return numeral(number).format('0,0'); - }, - - /** - * @param {String|Number} date - * @param {String} format - * @returns {String} - */ - formatDbDate: function (date, format) { - if (typeof date === 'number') { - return moment.unix(date).format(format); - } - - return moment(date).format(format); - } -}; diff --git a/frontend/js/lib/marionette.js b/frontend/js/lib/marionette.js deleted file mode 100644 index c88368f8..00000000 --- a/frontend/js/lib/marionette.js +++ /dev/null @@ -1,15 +0,0 @@ -const _ = require('underscore'); -const Mn = require('backbone.marionette'); -const i18n = require('../app/i18n'); -const Helpers = require('./helpers'); -const TemplateCache = require('marionette.templatecache'); - -Mn.setRenderer(function (template, data, view) { - data = _.clone(data); - data.i18n = i18n; - data.formatDbDate = Helpers.formatDbDate; - - return TemplateCache.default.render.call(this, template, data, view); -}); - -module.exports = Mn; diff --git a/frontend/js/login.js b/frontend/js/login.js deleted file mode 100644 index 0094e2a2..00000000 --- a/frontend/js/login.js +++ /dev/null @@ -1,5 +0,0 @@ -const App = require('./login/main'); - -$(document).ready(() => { - App.start(); -}); diff --git a/frontend/js/login/main.js b/frontend/js/login/main.js deleted file mode 100644 index 03fdc7e5..00000000 --- a/frontend/js/login/main.js +++ /dev/null @@ -1,14 +0,0 @@ -const Mn = require('backbone.marionette'); -const LoginView = require('./ui/login'); - -const App = Mn.Application.extend({ - region: '#login', - UI: null, - - onStart: function (/*app, options*/) { - this.getRegion().show(new LoginView()); - } -}); - -const app = new App(); -module.exports = app; diff --git a/frontend/js/login/ui/login.ejs b/frontend/js/login/ui/login.ejs deleted file mode 100644 index 693bc050..00000000 --- a/frontend/js/login/ui/login.ejs +++ /dev/null @@ -1,37 +0,0 @@ -
-
- -
-
diff --git a/frontend/js/login/ui/login.js b/frontend/js/login/ui/login.js deleted file mode 100644 index 757eb4e3..00000000 --- a/frontend/js/login/ui/login.js +++ /dev/null @@ -1,42 +0,0 @@ -const $ = require('jquery'); -const Mn = require('backbone.marionette'); -const template = require('./login.ejs'); -const Api = require('../../app/api'); -const i18n = require('../../app/i18n'); - -module.exports = Mn.View.extend({ - template: template, - className: 'page-single', - - ui: { - form: 'form', - identity: 'input[name="identity"]', - secret: 'input[name="secret"]', - error: '.secret-error', - button: 'button' - }, - - events: { - 'submit @ui.form': function (e) { - e.preventDefault(); - this.ui.button.addClass('btn-loading').prop('disabled', true); - this.ui.error.hide(); - - Api.Tokens.login(this.ui.identity.val(), this.ui.secret.val(), true) - .then(() => { - window.location = '/'; - }) - .catch(err => { - this.ui.error.text(err.message).show(); - this.ui.button.removeClass('btn-loading').prop('disabled', false); - }); - } - }, - - templateContext: { - i18n: i18n, - getVersion: function () { - return $('#login').data('version'); - } - } -}); diff --git a/frontend/js/models/access-list.js b/frontend/js/models/access-list.js deleted file mode 100644 index 0c2c4abe..00000000 --- a/frontend/js/models/access-list.js +++ /dev/null @@ -1,25 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - name: '', - items: [], - clients: [], - // The following are expansions: - owner: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/audit-log.js b/frontend/js/models/audit-log.js deleted file mode 100644 index c929a0bd..00000000 --- a/frontend/js/models/audit-log.js +++ /dev/null @@ -1,18 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - name: '' - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/certificate.js b/frontend/js/models/certificate.js deleted file mode 100644 index c7d0b2d9..00000000 --- a/frontend/js/models/certificate.js +++ /dev/null @@ -1,38 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - provider: '', - nice_name: '', - domain_names: [], - expires_on: null, - meta: {}, - // The following are expansions: - owner: null, - proxy_hosts: [], - redirection_hosts: [], - dead_hosts: [] - }; - }, - - /** - * @returns {Boolean} - */ - hasSslFiles: function () { - let meta = this.get('meta'); - return typeof meta['certificate'] !== 'undefined' && meta['certificate'] && typeof meta['certificate_key'] !== 'undefined' && meta['certificate_key']; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/dead-host.js b/frontend/js/models/dead-host.js deleted file mode 100644 index 98ceef29..00000000 --- a/frontend/js/models/dead-host.js +++ /dev/null @@ -1,32 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - domain_names: [], - certificate_id: 0, - ssl_forced: false, - http2_support: false, - hsts_enabled: false, - hsts_subdomains: false, - enabled: true, - meta: {}, - advanced_config: '', - // The following are expansions: - owner: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/proxy-host-location.js b/frontend/js/models/proxy-host-location.js deleted file mode 100644 index 2a35059f..00000000 --- a/frontend/js/models/proxy-host-location.js +++ /dev/null @@ -1,35 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function() { - return { - opened: false, - path: '', - advanced_config: '', - forward_scheme: 'http', - forward_host: '', - forward_port: '80' - } - }, - - toJSON() { - const r = Object.assign({}, this.attributes); - delete r.opened; - return r; - }, - - toggleVisibility: function () { - this.save({ - opened: !this.get('opened') - }); - } -}) - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model - }) -} \ No newline at end of file diff --git a/frontend/js/models/proxy-host.js b/frontend/js/models/proxy-host.js deleted file mode 100644 index b82d09fe..00000000 --- a/frontend/js/models/proxy-host.js +++ /dev/null @@ -1,40 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - domain_names: [], - forward_scheme: 'http', - forward_host: '', - forward_port: null, - access_list_id: 0, - certificate_id: 0, - ssl_forced: false, - hsts_enabled: false, - hsts_subdomains: false, - caching_enabled: false, - allow_websocket_upgrade: false, - block_exploits: false, - http2_support: false, - advanced_config: '', - enabled: true, - meta: {}, - // The following are expansions: - owner: null, - access_list: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/redirection-host.js b/frontend/js/models/redirection-host.js deleted file mode 100644 index 1d0b0de2..00000000 --- a/frontend/js/models/redirection-host.js +++ /dev/null @@ -1,37 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - domain_names: [], - forward_http_code: 0, - forward_scheme: null, - forward_domain_name: '', - preserve_path: true, - certificate_id: 0, - ssl_forced: false, - hsts_enabled: false, - hsts_subdomains: false, - block_exploits: false, - http2_support: false, - advanced_config: '', - enabled: true, - meta: {}, - // The following are expansions: - owner: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/setting.js b/frontend/js/models/setting.js deleted file mode 100644 index c70a4e9c..00000000 --- a/frontend/js/models/setting.js +++ /dev/null @@ -1,22 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - name: '', - description: '', - value: null, - meta: [] - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/stream.js b/frontend/js/models/stream.js deleted file mode 100644 index 390c4fb0..00000000 --- a/frontend/js/models/stream.js +++ /dev/null @@ -1,32 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - incoming_port: null, - forwarding_host: null, - forwarding_port: null, - tcp_forwarding: true, - udp_forwarding: false, - enabled: true, - meta: {}, - certificate_id: 0, - domain_names: [], - // The following are expansions: - owner: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/user.js b/frontend/js/models/user.js deleted file mode 100644 index a8e4ed9e..00000000 --- a/frontend/js/models/user.js +++ /dev/null @@ -1,54 +0,0 @@ -const _ = require('underscore'); -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - name: '', - nickname: '', - email: '', - is_disabled: false, - roles: [], - permissions: null - }; - }, - - /** - * @returns {Boolean} - */ - isAdmin: function () { - return _.indexOf(this.get('roles'), 'admin') !== -1; - }, - - /** - * Checks if the perm has either `view` or `manage` value - * - * @param {String} item - * @returns {Boolean} - */ - canView: function (item) { - let permissions = this.get('permissions'); - return permissions !== null && typeof permissions[item] !== 'undefined' && ['view', 'manage'].indexOf(permissions[item]) !== -1; - }, - - /** - * Checks if the perm has `manage` value - * - * @param {String} item - * @returns {Boolean} - */ - canManage: function (item) { - let permissions = this.get('permissions'); - return permissions !== null && typeof permissions[item] !== 'undefined' && permissions[item] === 'manage'; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/package.json b/frontend/package.json index 769e9036..da140951 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,47 +1,66 @@ { - "name": "nginx-proxy-manager", - "version": "0.0.0", - "description": "A beautiful interface for creating Nginx endpoints", - "main": "js/index.js", - "devDependencies": { - "@babel/core": "^7.9.0", - "babel-core": "^6.26.3", - "babel-loader": "^8.1.0", - "babel-preset-env": "^1.7.0", - "backbone": "^1.4.0", - "backbone.marionette": "^4.1.2", - "copy-webpack-plugin": "^5.1.1", - "css-loader": "^3.5.0", - "ejs-lint": "^1.0.1", - "ejs-loader": "^0.3.6", - "ejs-webpack-loader": "^2.2.2", - "file-loader": "^6.0.0", - "html-webpack-plugin": "^4.0.4", - "imports-loader": "^0.8.0", - "jquery": "^3.5.0", - "jquery-mask-plugin": "^1.14.16", - "jquery-serializejson": "^2.9.0", - "marionette.approuter": "^1.0.2", - "marionette.templatecache": "^1.0.0", - "messageformat": "^2.3.0", - "messageformat-loader": "^0.8.1", - "mini-css-extract-plugin": "^0.9.0", - "moment": "^2.29.4", - "node-sass": "^9.0.0", - "nodemon": "^2.0.2", - "numeral": "^2.0.6", - "sass-loader": "^10.0.0", - "style-loader": "^1.1.3", - "tabler-ui": "git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813", - "underscore": "^1.12.1", - "webpack": "^4.42.1", - "webpack-cli": "^3.3.11", - "webpack-visualizer-plugin": "^0.1.11" - }, - "scripts": { - "build": "webpack --mode production", - "watch": "webpack --watch --mode development" - }, - "author": "Jamie Curnow ", - "license": "MIT" + "name": "nginx-proxy-manager", + "version": "2.0.0", + "type": "module", + "author": "Jamie Curnow ", + "license": "MIT", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "biome lint", + "preview": "vite preview", + "prettier": "biome format --write ./src", + "locale-extract": "formatjs extract 'src/**/*.tsx'", + "locale-compile": "formatjs compile-folder src/locale/src src/locale/lang", + "locale-sort": "./src/locale/scripts/locale-sort.sh", + "test": "vitest" + }, + "dependencies": { + "@tabler/core": "^1.4.0", + "@tabler/icons-react": "^3.35.0", + "@tanstack/react-query": "^5.90.6", + "@tanstack/react-table": "^8.21.3", + "@uiw/react-textarea-code-editor": "^3.1.1", + "classnames": "^2.5.1", + "country-flag-icons": "^1.5.21", + "date-fns": "^4.1.0", + "ez-modal-react": "^1.0.5", + "formik": "^2.4.6", + "generate-password-browser": "^1.1.0", + "humps": "^2.0.1", + "query-string": "^9.3.1", + "react": "^19.2.0", + "react-bootstrap": "^2.10.10", + "react-dom": "^19.2.0", + "react-intl": "^7.1.14", + "react-markdown": "^10.1.0", + "react-router-dom": "^7.9.5", + "react-select": "^5.10.2", + "react-toastify": "^11.0.5", + "rooks": "^9.3.0" + }, + "devDependencies": { + "@biomejs/biome": "^2.3.2", + "@formatjs/cli": "^6.7.4", + "@tanstack/react-query-devtools": "^5.90.2", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.0", + "@types/country-flag-icons": "^1.2.2", + "@types/humps": "^2.0.6", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@types/react-table": "^7.7.20", + "@vitejs/plugin-react": "^5.1.0", + "happy-dom": "^20.0.10", + "postcss": "^8.5.6", + "postcss-simple-vars": "^7.0.1", + "sass": "^1.93.3", + "tmp": "^0.2.5", + "typescript": "5.9.3", + "vite": "^7.1.12", + "vite-plugin-checker": "^0.11.0", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^4.0.6" + } } diff --git a/frontend/app-images/default-avatar.jpg b/frontend/public/images/default-avatar.jpg similarity index 100% rename from frontend/app-images/default-avatar.jpg rename to frontend/public/images/default-avatar.jpg diff --git a/frontend/app-images/favicons/android-chrome-192x192.png b/frontend/public/images/favicon/android-chrome-192x192.png similarity index 100% rename from frontend/app-images/favicons/android-chrome-192x192.png rename to frontend/public/images/favicon/android-chrome-192x192.png diff --git a/frontend/app-images/favicons/android-chrome-512x512.png b/frontend/public/images/favicon/android-chrome-512x512.png similarity index 100% rename from frontend/app-images/favicons/android-chrome-512x512.png rename to frontend/public/images/favicon/android-chrome-512x512.png diff --git a/frontend/app-images/favicons/apple-touch-icon.png b/frontend/public/images/favicon/apple-touch-icon.png similarity index 100% rename from frontend/app-images/favicons/apple-touch-icon.png rename to frontend/public/images/favicon/apple-touch-icon.png diff --git a/frontend/app-images/favicons/browserconfig.xml b/frontend/public/images/favicon/browserconfig.xml similarity index 100% rename from frontend/app-images/favicons/browserconfig.xml rename to frontend/public/images/favicon/browserconfig.xml diff --git a/frontend/app-images/favicons/favicon-16x16.png b/frontend/public/images/favicon/favicon-16x16.png similarity index 100% rename from frontend/app-images/favicons/favicon-16x16.png rename to frontend/public/images/favicon/favicon-16x16.png diff --git a/frontend/app-images/favicons/favicon-32x32.png b/frontend/public/images/favicon/favicon-32x32.png similarity index 100% rename from frontend/app-images/favicons/favicon-32x32.png rename to frontend/public/images/favicon/favicon-32x32.png diff --git a/frontend/app-images/favicons/favicon.ico b/frontend/public/images/favicon/favicon.ico similarity index 100% rename from frontend/app-images/favicons/favicon.ico rename to frontend/public/images/favicon/favicon.ico diff --git a/frontend/app-images/favicons/mstile-150x150.png b/frontend/public/images/favicon/mstile-150x150.png similarity index 100% rename from frontend/app-images/favicons/mstile-150x150.png rename to frontend/public/images/favicon/mstile-150x150.png diff --git a/frontend/app-images/favicons/safari-pinned-tab.svg b/frontend/public/images/favicon/safari-pinned-tab.svg similarity index 100% rename from frontend/app-images/favicons/safari-pinned-tab.svg rename to frontend/public/images/favicon/safari-pinned-tab.svg diff --git a/frontend/app-images/favicons/site.webmanifest b/frontend/public/images/favicon/site.webmanifest similarity index 100% rename from frontend/app-images/favicons/site.webmanifest rename to frontend/public/images/favicon/site.webmanifest diff --git a/frontend/app-images/logo-256.png b/frontend/public/images/logo-256.png similarity index 100% rename from frontend/app-images/logo-256.png rename to frontend/public/images/logo-256.png diff --git a/frontend/public/images/logo-bold-horizontal-grey.svg b/frontend/public/images/logo-bold-horizontal-grey.svg new file mode 100644 index 00000000..c87396f6 --- /dev/null +++ b/frontend/public/images/logo-bold-horizontal-grey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/images/logo-no-text.svg b/frontend/public/images/logo-no-text.svg new file mode 100644 index 00000000..dc3c1163 --- /dev/null +++ b/frontend/public/images/logo-no-text.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/images/logo-text-horizontal-grey.png b/frontend/public/images/logo-text-horizontal-grey.png new file mode 100644 index 00000000..057a83d1 Binary files /dev/null and b/frontend/public/images/logo-text-horizontal-grey.png differ diff --git a/frontend/app-images/logo-text-vertical-grey.png b/frontend/public/images/logo-text-vertical-grey.png similarity index 100% rename from frontend/app-images/logo-text-vertical-grey.png rename to frontend/public/images/logo-text-vertical-grey.png diff --git a/frontend/public/images/unhealthy.svg b/frontend/public/images/unhealthy.svg new file mode 100644 index 00000000..6c39eeb1 --- /dev/null +++ b/frontend/public/images/unhealthy.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/scss/custom.scss b/frontend/scss/custom.scss deleted file mode 100644 index 4037dcf6..00000000 --- a/frontend/scss/custom.scss +++ /dev/null @@ -1,42 +0,0 @@ -$primary-color: #2bcbba; - -.loader { - color: $primary-color; -} - -a { - color: $primary-color; -} - -a:hover { - color: darken($primary-color, 10%); -} - -.dropdown-header { - padding-left: 1rem; -} - -.dropdown-item.active, .dropdown-item:active { - background-color: $primary-color; -} - -.custom-switch-input:checked ~ .custom-switch-indicator { - background: $primary-color; -} - -.min-100 { - min-height: 100px; -} - -.card-options .dropdown-menu a:not(.btn) { - margin-left: 0; -} - -.wrap { - display: flex; - flex-wrap: wrap; -} - -.col-login { - max-width: 48rem; -} \ No newline at end of file diff --git a/frontend/scss/fonts.scss b/frontend/scss/fonts.scss deleted file mode 100644 index f0ec1b73..00000000 --- a/frontend/scss/fonts.scss +++ /dev/null @@ -1,39 +0,0 @@ -/* source-sans-pro-regular - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 400; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} - -/* source-sans-pro-italic - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: italic; - font-weight: 400; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} - -/* source-sans-pro-700italic - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: italic; - font-weight: 700; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} - -/* source-sans-pro-700 - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 700; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} diff --git a/frontend/scss/selectize.scss b/frontend/scss/selectize.scss deleted file mode 100644 index e12d5b69..00000000 --- a/frontend/scss/selectize.scss +++ /dev/null @@ -1,196 +0,0 @@ -.selectize-dropdown-header { - position: relative; - padding: 5px 8px; - background: #f8f8f8; - border-bottom: 1px solid #d0d0d0; - -webkit-border-radius: 3px 3px 0 0; - -moz-border-radius: 3px 3px 0 0; - border-radius: 3px 3px 0 0; -} - -.selectize-dropdown-header-close { - position: absolute; - top: 50%; - right: 8px; - margin-top: -12px; - font-size: 20px !important; - line-height: 20px; - color: #303030; - opacity: 0.4; -} - -.selectize-dropdown-header-close:hover { - color: #000000; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup { - float: left; - border-top: 0 none; - border-right: 1px solid #f2f2f2; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child { - border-right: 0 none; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup:before { - display: none; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup-header { - border-top: 0 none; -} - -.selectize-control.plugin-remove_button [data-value] { - position: relative; - padding-right: 24px !important; -} - -.selectize-control.plugin-remove_button [data-value] .remove { - position: absolute; - top: 0; - right: 0; - bottom: 0; - display: inline-block; - width: 17px; - padding: 2px 0 0 0; - font-size: 12px; - font-weight: bold; - color: inherit; - text-align: center; - text-decoration: none; - vertical-align: middle; - border-left: 1px solid #0073bb; - -webkit-border-radius: 0 2px 2px 0; - -moz-border-radius: 0 2px 2px 0; - border-radius: 0 2px 2px 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.selectize-control.plugin-remove_button [data-value] .remove:hover { - background: rgba(0, 0, 0, 0.05); -} - -.selectize-control.plugin-remove_button [data-value].active .remove { - border-left-color: #00578d; -} - -.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover { - background: none; -} - -.selectize-control.plugin-remove_button .disabled [data-value] .remove { - border-left-color: #aaaaaa; -} - -.selectize-control { - position: relative; -} - -.selectize-dropdown { - font-family: inherit; - font-size: 13px; - -webkit-font-smoothing: inherit; - line-height: 18px; - color: #303030; -} - -.selectize-control.single { - display: inline-block; - cursor: text; - background: #ffffff; -} - -.selectize-dropdown { - position: absolute; - z-index: 10; - margin: -1px 0 0 0; - background: #ffffff; - border: 1px solid #d0d0d0; - border-top: 0 none; - -webkit-border-radius: 0 0 3px 3px; - -moz-border-radius: 0 0 3px 3px; - border-radius: 0 0 3px 3px; - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.selectize-dropdown [data-selectable] { - overflow: hidden; - cursor: pointer; -} - -.selectize-dropdown [data-selectable] .highlight { - background: rgba(125, 168, 208, 0.2); - -webkit-border-radius: 1px; - -moz-border-radius: 1px; - border-radius: 1px; -} - -.selectize-dropdown [data-selectable], -.selectize-dropdown .optgroup-header { - padding: 5px 8px; -} - -.selectize-dropdown .optgroup:first-child .optgroup-header { - border-top: 0 none; -} - -.selectize-dropdown .optgroup-header { - color: #303030; - cursor: default; - background: #ffffff; -} - -.selectize-dropdown .active { - color: #495c68; - background-color: #f5fafd; -} - -.selectize-dropdown .active.create { - color: #495c68; -} - -.selectize-dropdown .create { - color: rgba(48, 48, 48, 0.5); -} - -.selectize-dropdown-content { - max-height: 200px; - overflow-x: hidden; - overflow-y: auto; - - .title { - font-weight: bold; - } - - .description { - padding-left: 16px; - } -} - -.selectize-dropdown .optgroup-header { - padding-top: 7px; - font-size: 0.85em; - font-weight: bold; -} - -.selectize-dropdown .optgroup { - border-top: 1px solid #f0f0f0; -} - -.selectize-dropdown .optgroup:first-child { - border-top: 0 none; -} - -.custom-select { - height: auto; -} diff --git a/frontend/scss/styles.scss b/frontend/scss/styles.scss deleted file mode 100644 index 52733097..00000000 --- a/frontend/scss/styles.scss +++ /dev/null @@ -1,17 +0,0 @@ -@import "~tabler-ui/dist/assets/css/dashboard"; -@import "tabler-extra"; -@import "fonts"; -@import "selectize"; -@import "custom"; - -/* Before any JS content is loaded */ -#app > .loader, #login > .loader, .container > .loader { - position: absolute; - left: 49%; - top: 40%; - display: block; -} - -.no-js-warning { - margin-top: 100px; -} diff --git a/frontend/scss/tabler-extra.scss b/frontend/scss/tabler-extra.scss deleted file mode 100644 index c56aedbc..00000000 --- a/frontend/scss/tabler-extra.scss +++ /dev/null @@ -1,171 +0,0 @@ -$teal: #2bcbba; -$yellow: #f1c40f; -$blue: #467fcf; -$pink: #f66d9b; - -.tag { - margin-bottom: .5em; - margin-right: .5em; -} - -.tag.hover-green:hover, .tag.hover-green:active, .tag.hover-green:focus { - background-color: #5eba00; - cursor: pointer; - color: #fff; -} - -.tag.hover-red:hover, .tag.hover-red:active, .tag.hover-red:focus { - background-color: #cd201f; - cursor: pointer; - color: #fff; -} - -/* For Card bodies where I don't want padding */ -.card-body.no-padding { - padding: 0; -} - -/* For some reason this class doesn't have 'display: flex' when it should. https://preview.tabler.io/docs/buttons.html#list-of-buttons */ -.btn-list { - display: flex; -} - -/* Teal Outline Buttons */ -.btn-outline-teal { - color: $teal; - background-color: transparent; - background-image: none; - border-color: $teal; -} - -.btn-outline-teal:hover { - color: #fff; - background-color: $teal; - border-color: $teal; -} - -.btn-outline-teal:not(:disabled):not(.disabled):active, .btn-outline-teal:not(:disabled):not(.disabled).active, .show > .btn-outline-teal.dropdown-toggle { - color: #fff; - background-color: $teal; - border-color: $teal; -} - -.tag.hover-teal:hover, .tag.hover-teal:active, .tag.hover-teal:focus { - background-color: $teal; - color: #fff; - cursor: pointer; -} - -/* Yellow Outline Buttons */ -.btn-outline-yellow { - color: $yellow; - background-color: transparent; - background-image: none; - border-color: $yellow; -} - -.btn-outline-yellow:hover { - color: #fff; - background-color: $yellow; - border-color: $yellow; -} - -.btn-outline-yellow:not(:disabled):not(.disabled):active, .btn-outline-yellow:not(:disabled):not(.disabled).active, .show > .btn-outline-yellow.dropdown-toggle { - color: #fff; - background-color: $yellow; - border-color: $yellow; -} - -.tag.hover-yellow:hover, .tag.hover-yellow:active, .tag.hover-yellow:focus { - background-color: $yellow; - cursor: pointer; - color: #fff; -} - -/* Blue Outline Buttons */ -.btn-outline-blue { - color: $blue; - background-color: transparent; - background-image: none; - border-color: $blue; -} - -.btn-outline-blue:hover { - color: #fff; - background-color: $blue; - border-color: $blue; -} - -.btn-outline-blue:not(:disabled):not(.disabled):active, .btn-outline-blue:not(:disabled):not(.disabled).active, .show > .btn-outline-blue.dropdown-toggle { - color: #fff; - background-color: $blue; - border-color: $blue; -} - -.tag.hover-blue:hover, .tag.hover-blue:active, .tag.hover-blue:focus { - background-color: $blue; - cursor: pointer; - color: #fff; -} - -/* Pink Outline Buttons */ -.btn-outline-pink { - color: $pink; - background-color: transparent; - background-image: none; - border-color: $pink; -} - -.btn-outline-pink:hover { - color: #fff; - background-color: $pink; - border-color: $pink; -} - -.btn-outline-pink:not(:disabled):not(.disabled):active, .btn-outline-pink:not(:disabled):not(.disabled).active, .show > .btn-outline-pink.dropdown-toggle { - color: #fff; - background-color: $pink; - border-color: $pink; -} - -.tag.hover-pink:hover, .tag.hover-pink:active, .tag.hover-pink:focus { - background-color: $pink; - cursor: pointer; -} - -/* dimmer */ - -.dimmer .loader { - margin-top: 50px; -} - -/* modal tabs */ - -.modal-body.has-tabs { - padding: 0; - - .nav-tabs { - margin: 0; - } - - .tab-content { - padding: 1rem; - } -} - -/* modal wide */ - -@media (min-width: 576px) { - .modal-dialog.wide { - max-width: 700px; - margin: 1.75rem auto; - } -} - - -/* Form mod */ - -textarea.form-control.text-monospace { - font-size: 12px; - font-family: monospace; -} diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 00000000..59e0ed0c --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,97 @@ +:root { + color-scheme: light dark; +} + +.light { + color-scheme: light; +} +.dark { + color-scheme: dark; +} + +.modal-backdrop { + --tblr-backdrop-opacity: 0.8 !important; +} + +.domain-name { + font-family: monospace; +} + +.mr-1 { + margin-right: 0.25rem; +} +.ml-1 { + margin-left: 0.25rem; +} + +.react-select-container { + .react-select__control { + color: var(--tblr-body-color); + background-color: var(--tblr-bg-forms); + border: var(--tblr-border-width) solid var(--tblr-border-color); + + .react-select__input { + color: var(--tblr-body-color) !important; + } + + .react-select__single-value { + color: var(--tblr-body-color); + } + + .react-select__multi-value { + border: 1px solid var(--tblr-border-color); + background-color: var(--tblr-bg-surface-tertiary); + color: var(--tblr-secondary) !important; + + .react-select__multi-value__label { + color: var(--tblr-secondary) !important; + } + } + } + + .react-select__menu { + background-color: var(--tblr-bg-forms); + + .react-select__option { + background: rgba(var(--tblr-primary-rgb), .04); + color: inherit !important; + &.react-select__option--is-focused { + background: rgba(var(--tblr-primary-rgb), .1); + } + + &.react-select__option--is-focused.react-select__option--is-selected { + background: rgba(var(--tblr-primary-rgb), .2); + } + } + } +} + +.textareaMono { + font-family: 'Courier New', Courier, monospace !important; + resize: vertical; +} + +label.row { + cursor: pointer; +} + +.input-group-select { + display: flex; + align-items: center; + padding: 0; + font-size: .875rem; + font-weight: 400; + line-height: 1.25rem; + color: var(--tblr-gray-500); + text-align: center; + white-space: nowrap; + background-color: var(--tblr-bg-surface-secondary); + border: var(--tblr-border-width) solid var(--tblr-border-color); + border-radius: var(--tblr-border-radius); + + .form-select { + border: none; + background-color: var(--tblr-bg-surface-secondary); + border-radius: var(--tblr-border-radius) 0 0 var(--tblr-border-radius); + } +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 00000000..b6f0bba7 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,41 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import EasyModal from "ez-modal-react"; +import { RawIntlProvider } from "react-intl"; +import { ToastContainer } from "react-toastify"; +import { AuthProvider, LocaleProvider, ThemeProvider } from "src/context"; +import { intl } from "src/locale"; +import Router from "src/Router.tsx"; + +// Create a client +const queryClient = new QueryClient(); + +function App() { + return ( + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/frontend/src/Router.tsx b/frontend/src/Router.tsx new file mode 100644 index 00000000..6aa8f089 --- /dev/null +++ b/frontend/src/Router.tsx @@ -0,0 +1,83 @@ +import { lazy, Suspense } from "react"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import { + ErrorNotFound, + LoadingPage, + Page, + SiteContainer, + SiteFooter, + SiteHeader, + SiteMenu, + Unhealthy, +} from "src/components"; +import { useAuthState } from "src/context"; +import { useHealth } from "src/hooks"; + +const Setup = lazy(() => import("src/pages/Setup")); +const Login = lazy(() => import("src/pages/Login")); +const Dashboard = lazy(() => import("src/pages/Dashboard")); +const Settings = lazy(() => import("src/pages/Settings")); +const Certificates = lazy(() => import("src/pages/Certificates")); +const Access = lazy(() => import("src/pages/Access")); +const AuditLog = lazy(() => import("src/pages/AuditLog")); +const Users = lazy(() => import("src/pages/Users")); +const ProxyHosts = lazy(() => import("src/pages/Nginx/ProxyHosts")); +const RedirectionHosts = lazy(() => import("src/pages/Nginx/RedirectionHosts")); +const DeadHosts = lazy(() => import("src/pages/Nginx/DeadHosts")); +const Streams = lazy(() => import("src/pages/Nginx/Streams")); + +function Router() { + const health = useHealth(); + const { authenticated } = useAuthState(); + + if (health.isLoading) { + return ; + } + + if (health.isError || health.data?.status !== "OK") { + return ; + } + + if (!health.data?.setup) { + return ; + } + + if (!authenticated) { + return ( + }> + + + ); + } + + return ( + + +
+ + +
+ + }> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + +
+
+ ); +} + +export default Router; diff --git a/frontend/src/api/backend/base.ts b/frontend/src/api/backend/base.ts new file mode 100644 index 00000000..54eed050 --- /dev/null +++ b/frontend/src/api/backend/base.ts @@ -0,0 +1,164 @@ +import { QueryClient } from "@tanstack/react-query"; +import { camelizeKeys, decamelize, decamelizeKeys } from "humps"; +import queryString, { type StringifiableRecord } from "query-string"; +import AuthStore from "src/modules/AuthStore"; + +const queryClient = new QueryClient(); +const contentTypeHeader = "Content-Type"; + +interface BuildUrlArgs { + url: string; + params?: StringifiableRecord; +} + +function decamelizeParams(params?: StringifiableRecord): StringifiableRecord | undefined { + if (!params) { + return undefined; + } + const result: StringifiableRecord = {}; + for (const [key, value] of Object.entries(params)) { + result[decamelize(key)] = value; + } + + return result; +} + +function buildUrl({ url, params }: BuildUrlArgs) { + const endpoint = url.replace(/^\/|\/$/g, ""); + const baseUrl = `/api/${endpoint}`; + const apiUrl = queryString.stringifyUrl({ + url: baseUrl, + query: decamelizeParams(params), + }); + return apiUrl; +} + +function buildAuthHeader(): Record | undefined { + if (AuthStore.token) { + return { Authorization: `Bearer ${AuthStore.token.token}` }; + } + return {}; +} + +function buildBody(data?: Record): string | undefined { + if (data) { + return JSON.stringify(decamelizeKeys(data)); + } +} + +async function processResponse(response: Response) { + const payload = await response.json(); + if (!response.ok) { + if (response.status === 401) { + // Force logout user and reload the page if Unauthorized + AuthStore.clear(); + queryClient.clear(); + window.location.reload(); + } + throw new Error( + typeof payload.error.messageI18n !== "undefined" ? payload.error.messageI18n : payload.error.message, + ); + } + return camelizeKeys(payload) as any; +} + +interface GetArgs { + url: string; + params?: queryString.StringifiableRecord; +} + +async function baseGet({ url, params }: GetArgs, abortController?: AbortController) { + const apiUrl = buildUrl({ url, params }); + const method = "GET"; + const headers = buildAuthHeader(); + const signal = abortController?.signal; + const response = await fetch(apiUrl, { method, headers, signal }); + return response; +} + +export async function get(args: GetArgs, abortController?: AbortController) { + return processResponse(await baseGet(args, abortController)); +} + +export async function download({ url, params }: GetArgs, filename = "download.file") { + const headers = buildAuthHeader(); + const res = await fetch(buildUrl({ url, params }), { headers }); + const bl = await res.blob(); + const u = window.URL.createObjectURL(bl); + const a = document.createElement("a"); + a.href = u; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(url); +} + +interface PostArgs { + url: string; + params?: queryString.StringifiableRecord; + data?: any; + noAuth?: boolean; +} + +export async function post({ url, params, data, noAuth }: PostArgs, abortController?: AbortController) { + const apiUrl = buildUrl({ url, params }); + const method = "POST"; + + let headers: Record = {}; + if (!noAuth) { + headers = { + ...buildAuthHeader(), + }; + } + + let body: string | FormData | undefined; + // Check if the data is an instance of FormData + // If data is FormData, let the browser set the Content-Type header + if (data instanceof FormData) { + body = data; + } else { + // If data is JSON, set the Content-Type header to 'application/json' + headers = { + ...headers, + [contentTypeHeader]: "application/json", + }; + body = buildBody(data); + } + + const signal = abortController?.signal; + const response = await fetch(apiUrl, { method, headers, body, signal }); + return processResponse(response); +} + +interface PutArgs { + url: string; + params?: queryString.StringifiableRecord; + data?: Record; +} +export async function put({ url, params, data }: PutArgs, abortController?: AbortController) { + const apiUrl = buildUrl({ url, params }); + const method = "PUT"; + const headers = { + ...buildAuthHeader(), + [contentTypeHeader]: "application/json", + }; + const signal = abortController?.signal; + const body = buildBody(data); + const response = await fetch(apiUrl, { method, headers, body, signal }); + return processResponse(response); +} + +interface DeleteArgs { + url: string; + params?: queryString.StringifiableRecord; +} +export async function del({ url, params }: DeleteArgs, abortController?: AbortController) { + const apiUrl = buildUrl({ url, params }); + const method = "DELETE"; + const headers = { + ...buildAuthHeader(), + [contentTypeHeader]: "application/json", + }; + const signal = abortController?.signal; + const response = await fetch(apiUrl, { method, headers, signal }); + return processResponse(response); +} diff --git a/frontend/src/api/backend/createAccessList.ts b/frontend/src/api/backend/createAccessList.ts new file mode 100644 index 00000000..4a17f675 --- /dev/null +++ b/frontend/src/api/backend/createAccessList.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { AccessList } from "./models"; + +export async function createAccessList(item: AccessList): Promise { + return await api.post({ + url: "/nginx/access-lists", + data: item, + }); +} diff --git a/frontend/src/api/backend/createCertificate.ts b/frontend/src/api/backend/createCertificate.ts new file mode 100644 index 00000000..ea81c44e --- /dev/null +++ b/frontend/src/api/backend/createCertificate.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { Certificate } from "./models"; + +export async function createCertificate(item: Certificate): Promise { + return await api.post({ + url: "/nginx/certificates", + data: item, + }); +} diff --git a/frontend/src/api/backend/createDeadHost.ts b/frontend/src/api/backend/createDeadHost.ts new file mode 100644 index 00000000..f2a9f450 --- /dev/null +++ b/frontend/src/api/backend/createDeadHost.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { DeadHost } from "./models"; + +export async function createDeadHost(item: DeadHost): Promise { + return await api.post({ + url: "/nginx/dead-hosts", + data: item, + }); +} diff --git a/frontend/src/api/backend/createProxyHost.ts b/frontend/src/api/backend/createProxyHost.ts new file mode 100644 index 00000000..fcde7cd6 --- /dev/null +++ b/frontend/src/api/backend/createProxyHost.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { ProxyHost } from "./models"; + +export async function createProxyHost(item: ProxyHost): Promise { + return await api.post({ + url: "/nginx/proxy-hosts", + data: item, + }); +} diff --git a/frontend/src/api/backend/createRedirectionHost.ts b/frontend/src/api/backend/createRedirectionHost.ts new file mode 100644 index 00000000..a797f819 --- /dev/null +++ b/frontend/src/api/backend/createRedirectionHost.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { RedirectionHost } from "./models"; + +export async function createRedirectionHost(item: RedirectionHost): Promise { + return await api.post({ + url: "/nginx/redirection-hosts", + data: item, + }); +} diff --git a/frontend/src/api/backend/createStream.ts b/frontend/src/api/backend/createStream.ts new file mode 100644 index 00000000..adad2a1e --- /dev/null +++ b/frontend/src/api/backend/createStream.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { Stream } from "./models"; + +export async function createStream(item: Stream): Promise { + return await api.post({ + url: "/nginx/streams", + data: item, + }); +} diff --git a/frontend/src/api/backend/createUser.ts b/frontend/src/api/backend/createUser.ts new file mode 100644 index 00000000..c5ea306d --- /dev/null +++ b/frontend/src/api/backend/createUser.ts @@ -0,0 +1,24 @@ +import * as api from "./base"; +import type { User } from "./models"; + +export interface AuthOptions { + type: string; + secret: string; +} + +export interface NewUser { + name: string; + nickname: string; + email: string; + isDisabled?: boolean; + auth?: AuthOptions; + roles?: string[]; +} + +export async function createUser(item: NewUser, noAuth?: boolean): Promise { + return await api.post({ + url: "/users", + data: item, + noAuth, + }); +} diff --git a/frontend/src/api/backend/deleteAccessList.ts b/frontend/src/api/backend/deleteAccessList.ts new file mode 100644 index 00000000..838c940b --- /dev/null +++ b/frontend/src/api/backend/deleteAccessList.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function deleteAccessList(id: number): Promise { + return await api.del({ + url: `/nginx/access-lists/${id}`, + }); +} diff --git a/frontend/src/api/backend/deleteCertificate.ts b/frontend/src/api/backend/deleteCertificate.ts new file mode 100644 index 00000000..2257316c --- /dev/null +++ b/frontend/src/api/backend/deleteCertificate.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function deleteCertificate(id: number): Promise { + return await api.del({ + url: `/nginx/certificates/${id}`, + }); +} diff --git a/frontend/src/api/backend/deleteDeadHost.ts b/frontend/src/api/backend/deleteDeadHost.ts new file mode 100644 index 00000000..8e72ca84 --- /dev/null +++ b/frontend/src/api/backend/deleteDeadHost.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function deleteDeadHost(id: number): Promise { + return await api.del({ + url: `/nginx/dead-hosts/${id}`, + }); +} diff --git a/frontend/src/api/backend/deleteProxyHost.ts b/frontend/src/api/backend/deleteProxyHost.ts new file mode 100644 index 00000000..7b7f2d82 --- /dev/null +++ b/frontend/src/api/backend/deleteProxyHost.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function deleteProxyHost(id: number): Promise { + return await api.del({ + url: `/nginx/proxy-hosts/${id}`, + }); +} diff --git a/frontend/src/api/backend/deleteRedirectionHost.ts b/frontend/src/api/backend/deleteRedirectionHost.ts new file mode 100644 index 00000000..7c594b68 --- /dev/null +++ b/frontend/src/api/backend/deleteRedirectionHost.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function deleteRedirectionHost(id: number): Promise { + return await api.del({ + url: `/nginx/redirection-hosts/${id}`, + }); +} diff --git a/frontend/src/api/backend/deleteStream.ts b/frontend/src/api/backend/deleteStream.ts new file mode 100644 index 00000000..db9e11f1 --- /dev/null +++ b/frontend/src/api/backend/deleteStream.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function deleteStream(id: number): Promise { + return await api.del({ + url: `/nginx/streams/${id}`, + }); +} diff --git a/frontend/src/api/backend/deleteUser.ts b/frontend/src/api/backend/deleteUser.ts new file mode 100644 index 00000000..2a04a6e9 --- /dev/null +++ b/frontend/src/api/backend/deleteUser.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function deleteUser(id: number): Promise { + return await api.del({ + url: `/users/${id}`, + }); +} diff --git a/frontend/src/api/backend/downloadCertificate.ts b/frontend/src/api/backend/downloadCertificate.ts new file mode 100644 index 00000000..079f3359 --- /dev/null +++ b/frontend/src/api/backend/downloadCertificate.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function downloadCertificate(id: number): Promise { + await api.download( + { + url: `/nginx/certificates/${id}/download`, + }, + `certificate-${id}.zip`, + ); +} diff --git a/frontend/src/api/backend/expansions.ts b/frontend/src/api/backend/expansions.ts new file mode 100644 index 00000000..e098a490 --- /dev/null +++ b/frontend/src/api/backend/expansions.ts @@ -0,0 +1,6 @@ +export type AccessListExpansion = "owner" | "items" | "clients"; +export type AuditLogExpansion = "user"; +export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts" | "streams"; +export type HostExpansion = "owner" | "certificate"; +export type ProxyHostExpansion = "owner" | "access_list" | "certificate"; +export type UserExpansion = "permissions"; diff --git a/frontend/src/api/backend/getAccessList.ts b/frontend/src/api/backend/getAccessList.ts new file mode 100644 index 00000000..a1afd350 --- /dev/null +++ b/frontend/src/api/backend/getAccessList.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { AccessListExpansion } from "./expansions"; +import type { AccessList } from "./models"; + +export async function getAccessList(id: number, expand?: AccessListExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/access-lists/${id}`, + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getAccessLists.ts b/frontend/src/api/backend/getAccessLists.ts new file mode 100644 index 00000000..515f1e21 --- /dev/null +++ b/frontend/src/api/backend/getAccessLists.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { AccessListExpansion } from "./expansions"; +import type { AccessList } from "./models"; + +export async function getAccessLists(expand?: AccessListExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/access-lists", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getAuditLog.ts b/frontend/src/api/backend/getAuditLog.ts new file mode 100644 index 00000000..d2d01f70 --- /dev/null +++ b/frontend/src/api/backend/getAuditLog.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { AuditLogExpansion } from "./expansions"; +import type { AuditLog } from "./models"; + +export async function getAuditLog(id: number, expand?: AuditLogExpansion[], params = {}): Promise { + return await api.get({ + url: `/audit-log/${id}`, + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getAuditLogs.ts b/frontend/src/api/backend/getAuditLogs.ts new file mode 100644 index 00000000..bbdb2f16 --- /dev/null +++ b/frontend/src/api/backend/getAuditLogs.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { AuditLogExpansion } from "./expansions"; +import type { AuditLog } from "./models"; + +export async function getAuditLogs(expand?: AuditLogExpansion[], params = {}): Promise { + return await api.get({ + url: "/audit-log", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getCertificate.ts b/frontend/src/api/backend/getCertificate.ts new file mode 100644 index 00000000..13de898d --- /dev/null +++ b/frontend/src/api/backend/getCertificate.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { CertificateExpansion } from "./expansions"; +import type { Certificate } from "./models"; + +export async function getCertificate(id: number, expand?: CertificateExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/certificates/${id}`, + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getCertificateDNSProviders.ts b/frontend/src/api/backend/getCertificateDNSProviders.ts new file mode 100644 index 00000000..03e3afa2 --- /dev/null +++ b/frontend/src/api/backend/getCertificateDNSProviders.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { DNSProvider } from "./models"; + +export async function getCertificateDNSProviders(params = {}): Promise { + return await api.get({ + url: "/nginx/certificates/dns-providers", + params, + }); +} diff --git a/frontend/src/api/backend/getCertificates.ts b/frontend/src/api/backend/getCertificates.ts new file mode 100644 index 00000000..d6d215b5 --- /dev/null +++ b/frontend/src/api/backend/getCertificates.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { CertificateExpansion } from "./expansions"; +import type { Certificate } from "./models"; + +export async function getCertificates(expand?: CertificateExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/certificates", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getDeadHost.ts b/frontend/src/api/backend/getDeadHost.ts new file mode 100644 index 00000000..d6c062b9 --- /dev/null +++ b/frontend/src/api/backend/getDeadHost.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { HostExpansion } from "./expansions"; +import type { DeadHost } from "./models"; + +export async function getDeadHost(id: number, expand?: HostExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/dead-hosts/${id}`, + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getDeadHosts.ts b/frontend/src/api/backend/getDeadHosts.ts new file mode 100644 index 00000000..1ca410e4 --- /dev/null +++ b/frontend/src/api/backend/getDeadHosts.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { HostExpansion } from "./expansions"; +import type { DeadHost } from "./models"; + +export async function getDeadHosts(expand?: HostExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/dead-hosts", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getHealth.ts b/frontend/src/api/backend/getHealth.ts new file mode 100644 index 00000000..055681ce --- /dev/null +++ b/frontend/src/api/backend/getHealth.ts @@ -0,0 +1,8 @@ +import * as api from "./base"; +import type { HealthResponse } from "./responseTypes"; + +export async function getHealth(): Promise { + return await api.get({ + url: "/", + }); +} diff --git a/frontend/src/api/backend/getHostsReport.ts b/frontend/src/api/backend/getHostsReport.ts new file mode 100644 index 00000000..938d3a74 --- /dev/null +++ b/frontend/src/api/backend/getHostsReport.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function getHostsReport(): Promise> { + return await api.get({ + url: "/reports/hosts", + }); +} diff --git a/frontend/src/api/backend/getProxyHost.ts b/frontend/src/api/backend/getProxyHost.ts new file mode 100644 index 00000000..911b89c3 --- /dev/null +++ b/frontend/src/api/backend/getProxyHost.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { ProxyHostExpansion } from "./expansions"; +import type { ProxyHost } from "./models"; + +export async function getProxyHost(id: number, expand?: ProxyHostExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/proxy-hosts/${id}`, + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getProxyHosts.ts b/frontend/src/api/backend/getProxyHosts.ts new file mode 100644 index 00000000..ba9c864a --- /dev/null +++ b/frontend/src/api/backend/getProxyHosts.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { ProxyHostExpansion } from "./expansions"; +import type { ProxyHost } from "./models"; + +export async function getProxyHosts(expand?: ProxyHostExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/proxy-hosts", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getRedirectionHost.ts b/frontend/src/api/backend/getRedirectionHost.ts new file mode 100644 index 00000000..01df988a --- /dev/null +++ b/frontend/src/api/backend/getRedirectionHost.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { HostExpansion } from "./expansions"; +import type { RedirectionHost } from "./models"; + +export async function getRedirectionHost(id: number, expand?: HostExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/redirection-hosts/${id}`, + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getRedirectionHosts.ts b/frontend/src/api/backend/getRedirectionHosts.ts new file mode 100644 index 00000000..63e292f7 --- /dev/null +++ b/frontend/src/api/backend/getRedirectionHosts.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { HostExpansion } from "./expansions"; +import type { RedirectionHost } from "./models"; + +export async function getRedirectionHosts(expand?: HostExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/redirection-hosts", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getSetting.ts b/frontend/src/api/backend/getSetting.ts new file mode 100644 index 00000000..daa54fee --- /dev/null +++ b/frontend/src/api/backend/getSetting.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { Setting } from "./models"; + +export async function getSetting(id: string, expand?: string[], params = {}): Promise { + return await api.get({ + url: `/settings/${id}`, + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getSettings.ts b/frontend/src/api/backend/getSettings.ts new file mode 100644 index 00000000..b8e992c6 --- /dev/null +++ b/frontend/src/api/backend/getSettings.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { Setting } from "./models"; + +export async function getSettings(expand?: string[], params = {}): Promise { + return await api.get({ + url: "/settings", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getStream.ts b/frontend/src/api/backend/getStream.ts new file mode 100644 index 00000000..82e10a04 --- /dev/null +++ b/frontend/src/api/backend/getStream.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { HostExpansion } from "./expansions"; +import type { Stream } from "./models"; + +export async function getStream(id: number, expand?: HostExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/streams/${id}`, + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getStreams.ts b/frontend/src/api/backend/getStreams.ts new file mode 100644 index 00000000..b5e9379b --- /dev/null +++ b/frontend/src/api/backend/getStreams.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { HostExpansion } from "./expansions"; +import type { Stream } from "./models"; + +export async function getStreams(expand?: HostExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/streams", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getToken.ts b/frontend/src/api/backend/getToken.ts new file mode 100644 index 00000000..600f0529 --- /dev/null +++ b/frontend/src/api/backend/getToken.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { TokenResponse } from "./responseTypes"; + +export async function getToken(identity: string, secret: string): Promise { + return await api.post({ + url: "/tokens", + data: { identity, secret }, + }); +} diff --git a/frontend/src/api/backend/getUser.ts b/frontend/src/api/backend/getUser.ts new file mode 100644 index 00000000..a006782a --- /dev/null +++ b/frontend/src/api/backend/getUser.ts @@ -0,0 +1,14 @@ +import * as api from "./base"; +import type { UserExpansion } from "./expansions"; +import type { User } from "./models"; + +export async function getUser(id: number | string = "me", expand?: UserExpansion[], params = {}): Promise { + const userId = id ? id : "me"; + return await api.get({ + url: `/users/${userId}`, + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getUsers.ts b/frontend/src/api/backend/getUsers.ts new file mode 100644 index 00000000..dab584f4 --- /dev/null +++ b/frontend/src/api/backend/getUsers.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { UserExpansion } from "./expansions"; +import type { User } from "./models"; + +export async function getUsers(expand?: UserExpansion[], params = {}): Promise { + return await api.get({ + url: "/users", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/helpers.ts b/frontend/src/api/backend/helpers.ts new file mode 100644 index 00000000..8d977319 --- /dev/null +++ b/frontend/src/api/backend/helpers.ts @@ -0,0 +1,54 @@ +import { decamelize } from "humps"; + +/** + * This will convert a react-table sort object into + * a string that the backend api likes: + * name.asc,id.desc + */ +export function tableSortToAPI(sortBy: any): string | undefined { + if (sortBy?.length > 0) { + const strs: string[] = []; + sortBy.map((item: any) => { + strs.push(`${decamelize(item.id)}.${item.desc ? "desc" : "asc"}`); + return undefined; + }); + return strs.join(","); + } + return; +} + +/** + * This will convert a react-table filters object into + * a string that the backend api likes: + * name:contains=jam + */ +export function tableFiltersToAPI(filters: any[]): { [key: string]: string } { + const items: { [key: string]: string } = {}; + if (filters?.length > 0) { + filters.map((item: any) => { + items[`${decamelize(item.id)}:${item.value.modifier}`] = item.value.value; + return undefined; + }); + } + return items; +} + +/** + * Builds a filters object by removing entries with undefined, null, or empty string values. + * + */ +export function buildFilters(filters?: Record) { + if (!filters) { + return filters; + } + const result: Record = {}; + for (const key in filters) { + const value = filters[key]; + // If the value is undefined, null, or an empty string, skip it + if (value === undefined || value === null || value === "") { + continue; + } + result[key] = value.toString(); + } + return result; +} diff --git a/frontend/src/api/backend/index.ts b/frontend/src/api/backend/index.ts new file mode 100644 index 00000000..6d42a6f4 --- /dev/null +++ b/frontend/src/api/backend/index.ts @@ -0,0 +1,61 @@ +export * from "./createAccessList"; +export * from "./createCertificate"; +export * from "./createDeadHost"; +export * from "./createProxyHost"; +export * from "./createRedirectionHost"; +export * from "./createStream"; +export * from "./createUser"; +export * from "./deleteAccessList"; +export * from "./deleteCertificate"; +export * from "./deleteDeadHost"; +export * from "./deleteProxyHost"; +export * from "./deleteRedirectionHost"; +export * from "./deleteStream"; +export * from "./deleteUser"; +export * from "./downloadCertificate"; +export * from "./expansions"; +export * from "./getAccessList"; +export * from "./getAccessLists"; +export * from "./getAuditLog"; +export * from "./getAuditLogs"; +export * from "./getCertificate"; +export * from "./getCertificateDNSProviders"; +export * from "./getCertificates"; +export * from "./getDeadHost"; +export * from "./getDeadHosts"; +export * from "./getHealth"; +export * from "./getHostsReport"; +export * from "./getProxyHost"; +export * from "./getProxyHosts"; +export * from "./getRedirectionHost"; +export * from "./getRedirectionHosts"; +export * from "./getSetting"; +export * from "./getSettings"; +export * from "./getStream"; +export * from "./getStreams"; +export * from "./getToken"; +export * from "./getUser"; +export * from "./getUsers"; +export * from "./helpers"; +export * from "./loginAsUser"; +export * from "./models"; +export * from "./refreshToken"; +export * from "./renewCertificate"; +export * from "./responseTypes"; +export * from "./setPermissions"; +export * from "./testHttpCertificate"; +export * from "./toggleDeadHost"; +export * from "./toggleProxyHost"; +export * from "./toggleRedirectionHost"; +export * from "./toggleStream"; +export * from "./toggleUser"; +export * from "./updateAccessList"; +export * from "./updateAuth"; +export * from "./updateDeadHost"; +export * from "./updateProxyHost"; +export * from "./updateRedirectionHost"; +export * from "./updateSetting"; +export * from "./updateStream"; +export * from "./updateUser"; +export * from "./uploadCertificate"; +export * from "./validateCertificate"; diff --git a/frontend/src/api/backend/loginAsUser.ts b/frontend/src/api/backend/loginAsUser.ts new file mode 100644 index 00000000..2ade379d --- /dev/null +++ b/frontend/src/api/backend/loginAsUser.ts @@ -0,0 +1,8 @@ +import * as api from "./base"; +import type { LoginAsTokenResponse } from "./responseTypes"; + +export async function loginAsUser(id: number): Promise { + return await api.post({ + url: `/users/${id}/login`, + }); +} diff --git a/frontend/src/api/backend/models.ts b/frontend/src/api/backend/models.ts new file mode 100644 index 00000000..d63d47ae --- /dev/null +++ b/frontend/src/api/backend/models.ts @@ -0,0 +1,209 @@ +export interface AppVersion { + major: number; + minor: number; + revision: number; +} + +export interface UserPermissions { + id?: number; + createdOn?: string; + modifiedOn?: string; + userId?: number; + visibility: string; + proxyHosts: string; + redirectionHosts: string; + deadHosts: string; + streams: string; + accessLists: string; + certificates: string; +} + +export interface User { + id: number; + createdOn: string; + modifiedOn: string; + isDisabled: boolean; + email: string; + name: string; + nickname: string; + avatar: string; + roles: string[]; + permissions?: UserPermissions; +} + +export interface AuditLog { + id: number; + createdOn: string; + modifiedOn: string; + userId: number; + objectType: string; + objectId: number; + action: string; + meta: Record; + // Expansions: + user?: User; +} + +export interface AccessList { + id?: number; + createdOn?: string; + modifiedOn?: string; + ownerUserId: number; + name: string; + meta: Record; + satisfyAny: boolean; + passAuth: boolean; + proxyHostCount?: number; + // Expansions: + owner?: User; + items?: AccessListItem[]; + clients?: AccessListClient[]; +} + +export interface AccessListItem { + id?: number; + createdOn?: string; + modifiedOn?: string; + accessListId?: number; + username: string; + password: string; + meta?: Record; + hint?: string; +} + +export type AccessListClient = { + id?: number; + createdOn?: string; + modifiedOn?: string; + accessListId?: number; + address: string; + directive: "allow" | "deny"; + meta?: Record; +}; + +export interface Certificate { + id: number; + createdOn: string; + modifiedOn: string; + ownerUserId: number; + provider: string; + niceName: string; + domainNames: string[]; + expiresOn: string; + meta: Record; + owner?: User; + proxyHosts?: ProxyHost[]; + deadHosts?: DeadHost[]; + redirectionHosts?: RedirectionHost[]; +} + +export interface ProxyLocation { + path: string; + advancedConfig: string; + forwardScheme: string; + forwardHost: string; + forwardPort: number; +} + +export interface ProxyHost { + id: number; + createdOn: string; + modifiedOn: string; + ownerUserId: number; + domainNames: string[]; + forwardScheme: string; + forwardHost: string; + forwardPort: number; + accessListId: number; + certificateId: number; + sslForced: boolean; + cachingEnabled: boolean; + blockExploits: boolean; + advancedConfig: string; + meta: Record; + allowWebsocketUpgrade: boolean; + http2Support: boolean; + enabled: boolean; + locations?: ProxyLocation[]; + hstsEnabled: boolean; + hstsSubdomains: boolean; + // Expansions: + owner?: User; + accessList?: AccessList; + certificate?: Certificate; +} + +export interface DeadHost { + id: number; + createdOn: string; + modifiedOn: string; + ownerUserId: number; + domainNames: string[]; + certificateId: number; + sslForced: boolean; + advancedConfig: string; + meta: Record; + http2Support: boolean; + enabled: boolean; + hstsEnabled: boolean; + hstsSubdomains: boolean; + // Expansions: + owner?: User; + certificate?: Certificate; +} + +export interface RedirectionHost { + id: number; + createdOn: string; + modifiedOn: string; + ownerUserId: number; + domainNames: string[]; + forwardDomainName: string; + preservePath: boolean; + certificateId: number; + sslForced: boolean; + blockExploits: boolean; + advancedConfig: string; + meta: Record; + http2Support: boolean; + forwardScheme: string; + forwardHttpCode: number; + enabled: boolean; + hstsEnabled: boolean; + hstsSubdomains: boolean; + // Expansions: + owner?: User; + certificate?: Certificate; +} + +export interface Stream { + id: number; + createdOn: string; + modifiedOn: string; + ownerUserId: number; + incomingPort: number; + forwardingHost: string; + forwardingPort: number; + tcpForwarding: boolean; + udpForwarding: boolean; + meta: Record; + enabled: boolean; + certificateId: number; + // Expansions: + owner?: User; + certificate?: Certificate; +} + +export interface Setting { + id: string; + name?: string; + description?: string; + value: string; + meta?: Record; +} + +export interface DNSProvider { + id: string; + name: string; + credentials: string; +} diff --git a/frontend/src/api/backend/refreshToken.ts b/frontend/src/api/backend/refreshToken.ts new file mode 100644 index 00000000..de1848c2 --- /dev/null +++ b/frontend/src/api/backend/refreshToken.ts @@ -0,0 +1,8 @@ +import * as api from "./base"; +import type { TokenResponse } from "./responseTypes"; + +export async function refreshToken(): Promise { + return await api.get({ + url: "/tokens", + }); +} diff --git a/frontend/src/api/backend/renewCertificate.ts b/frontend/src/api/backend/renewCertificate.ts new file mode 100644 index 00000000..0f3d082d --- /dev/null +++ b/frontend/src/api/backend/renewCertificate.ts @@ -0,0 +1,8 @@ +import * as api from "./base"; +import type { Certificate } from "./models"; + +export async function renewCertificate(id: number): Promise { + return await api.post({ + url: `/nginx/certificates/${id}/renew`, + }); +} diff --git a/frontend/src/api/backend/responseTypes.ts b/frontend/src/api/backend/responseTypes.ts new file mode 100644 index 00000000..7169fc54 --- /dev/null +++ b/frontend/src/api/backend/responseTypes.ts @@ -0,0 +1,21 @@ +import type { AppVersion, User } from "./models"; + +export interface HealthResponse { + status: string; + version: AppVersion; + setup: boolean; +} + +export interface TokenResponse { + expires: number; + token: string; +} + +export interface ValidatedCertificateResponse { + certificate: Record; + certificateKey: boolean; +} + +export interface LoginAsTokenResponse extends TokenResponse { + user: User; +} diff --git a/frontend/src/api/backend/setPermissions.ts b/frontend/src/api/backend/setPermissions.ts new file mode 100644 index 00000000..47fa6306 --- /dev/null +++ b/frontend/src/api/backend/setPermissions.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; +import type { UserPermissions } from "./models"; + +export async function setPermissions(userId: number, data: UserPermissions): Promise { + // Remove readonly fields + return await api.put({ + url: `/users/${userId}/permissions`, + data, + }); +} diff --git a/frontend/src/api/backend/testHttpCertificate.ts b/frontend/src/api/backend/testHttpCertificate.ts new file mode 100644 index 00000000..23dfa4b7 --- /dev/null +++ b/frontend/src/api/backend/testHttpCertificate.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function testHttpCertificate(domains: string[]): Promise> { + return await api.post({ + url: "/nginx/certificates/test-http", + data: { + domains, + }, + }); +} diff --git a/frontend/src/api/backend/toggleDeadHost.ts b/frontend/src/api/backend/toggleDeadHost.ts new file mode 100644 index 00000000..71a780ef --- /dev/null +++ b/frontend/src/api/backend/toggleDeadHost.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function toggleDeadHost(id: number, enabled: boolean): Promise { + return await api.post({ + url: `/nginx/dead-hosts/${id}/${enabled ? "enable" : "disable"}`, + }); +} diff --git a/frontend/src/api/backend/toggleProxyHost.ts b/frontend/src/api/backend/toggleProxyHost.ts new file mode 100644 index 00000000..376e7881 --- /dev/null +++ b/frontend/src/api/backend/toggleProxyHost.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function toggleProxyHost(id: number, enabled: boolean): Promise { + return await api.post({ + url: `/nginx/proxy-hosts/${id}/${enabled ? "enable" : "disable"}`, + }); +} diff --git a/frontend/src/api/backend/toggleRedirectionHost.ts b/frontend/src/api/backend/toggleRedirectionHost.ts new file mode 100644 index 00000000..0cfa5733 --- /dev/null +++ b/frontend/src/api/backend/toggleRedirectionHost.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function toggleRedirectionHost(id: number, enabled: boolean): Promise { + return await api.post({ + url: `/nginx/redirection-hosts/${id}/${enabled ? "enable" : "disable"}`, + }); +} diff --git a/frontend/src/api/backend/toggleStream.ts b/frontend/src/api/backend/toggleStream.ts new file mode 100644 index 00000000..2b71f720 --- /dev/null +++ b/frontend/src/api/backend/toggleStream.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function toggleStream(id: number, enabled: boolean): Promise { + return await api.post({ + url: `/nginx/streams/${id}/${enabled ? "enable" : "disable"}`, + }); +} diff --git a/frontend/src/api/backend/toggleUser.ts b/frontend/src/api/backend/toggleUser.ts new file mode 100644 index 00000000..d2a24c94 --- /dev/null +++ b/frontend/src/api/backend/toggleUser.ts @@ -0,0 +1,10 @@ +import type { User } from "./models"; +import { updateUser } from "./updateUser"; + +export async function toggleUser(id: number, enabled: boolean): Promise { + await updateUser({ + id, + isDisabled: !enabled, + } as User); + return true; +} diff --git a/frontend/src/api/backend/updateAccessList.ts b/frontend/src/api/backend/updateAccessList.ts new file mode 100644 index 00000000..7a23566b --- /dev/null +++ b/frontend/src/api/backend/updateAccessList.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { AccessList } from "./models"; + +export async function updateAccessList(item: AccessList): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put({ + url: `/nginx/access-lists/${id}`, + data: data, + }); +} diff --git a/frontend/src/api/backend/updateAuth.ts b/frontend/src/api/backend/updateAuth.ts new file mode 100644 index 00000000..5b14b076 --- /dev/null +++ b/frontend/src/api/backend/updateAuth.ts @@ -0,0 +1,18 @@ +import * as api from "./base"; +import type { User } from "./models"; + +export async function updateAuth(userId: number | "me", newPassword: string, current?: string): Promise { + const data = { + type: "password", + current: current, + secret: newPassword, + }; + if (userId === "me") { + data.current = current; + } + + return await api.put({ + url: `/users/${userId}/auth`, + data, + }); +} diff --git a/frontend/src/api/backend/updateDeadHost.ts b/frontend/src/api/backend/updateDeadHost.ts new file mode 100644 index 00000000..1eb5631b --- /dev/null +++ b/frontend/src/api/backend/updateDeadHost.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { DeadHost } from "./models"; + +export async function updateDeadHost(item: DeadHost): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put({ + url: `/nginx/dead-hosts/${id}`, + data: data, + }); +} diff --git a/frontend/src/api/backend/updateProxyHost.ts b/frontend/src/api/backend/updateProxyHost.ts new file mode 100644 index 00000000..e7ee3d90 --- /dev/null +++ b/frontend/src/api/backend/updateProxyHost.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { ProxyHost } from "./models"; + +export async function updateProxyHost(item: ProxyHost): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put({ + url: `/nginx/proxy-hosts/${id}`, + data: data, + }); +} diff --git a/frontend/src/api/backend/updateRedirectionHost.ts b/frontend/src/api/backend/updateRedirectionHost.ts new file mode 100644 index 00000000..4cc36f1e --- /dev/null +++ b/frontend/src/api/backend/updateRedirectionHost.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { RedirectionHost } from "./models"; + +export async function updateRedirectionHost(item: RedirectionHost): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put({ + url: `/nginx/redirection-hosts/${id}`, + data: data, + }); +} diff --git a/frontend/src/api/backend/updateSetting.ts b/frontend/src/api/backend/updateSetting.ts new file mode 100644 index 00000000..bcb94059 --- /dev/null +++ b/frontend/src/api/backend/updateSetting.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { Setting } from "./models"; + +export async function updateSetting(item: Setting): Promise { + // Remove readonly fields + const { id, ...data } = item; + + return await api.put({ + url: `/settings/${id}`, + data: data, + }); +} diff --git a/frontend/src/api/backend/updateStream.ts b/frontend/src/api/backend/updateStream.ts new file mode 100644 index 00000000..508bcece --- /dev/null +++ b/frontend/src/api/backend/updateStream.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { Stream } from "./models"; + +export async function updateStream(item: Stream): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put({ + url: `/nginx/streams/${id}`, + data: data, + }); +} diff --git a/frontend/src/api/backend/updateUser.ts b/frontend/src/api/backend/updateUser.ts new file mode 100644 index 00000000..d063e127 --- /dev/null +++ b/frontend/src/api/backend/updateUser.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { User } from "./models"; + +export async function updateUser(item: User): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put({ + url: `/users/${id}`, + data: data, + }); +} diff --git a/frontend/src/api/backend/uploadCertificate.ts b/frontend/src/api/backend/uploadCertificate.ts new file mode 100644 index 00000000..b28c4e09 --- /dev/null +++ b/frontend/src/api/backend/uploadCertificate.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { Certificate } from "./models"; + +export async function uploadCertificate(id: number, data: FormData): Promise { + return await api.post({ + url: `/nginx/certificates/${id}/upload`, + data, + }); +} diff --git a/frontend/src/api/backend/validateCertificate.ts b/frontend/src/api/backend/validateCertificate.ts new file mode 100644 index 00000000..d404e514 --- /dev/null +++ b/frontend/src/api/backend/validateCertificate.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { ValidatedCertificateResponse } from "./responseTypes"; + +export async function validateCertificate(data: FormData): Promise { + return await api.post({ + url: "/nginx/certificates/validate", + data, + }); +} diff --git a/frontend/src/components/Button.tsx b/frontend/src/components/Button.tsx new file mode 100644 index 00000000..6d02f931 --- /dev/null +++ b/frontend/src/components/Button.tsx @@ -0,0 +1,64 @@ +import cn from "classnames"; +import type { ReactNode } from "react"; + +interface Props { + children: ReactNode; + className?: string; + type?: "button" | "submit"; + actionType?: "primary" | "secondary" | "success" | "warning" | "danger" | "info" | "light" | "dark"; + variant?: "ghost" | "outline" | "pill" | "square" | "action"; + size?: "sm" | "md" | "lg" | "xl"; + fullWidth?: boolean; + isLoading?: boolean; + disabled?: boolean; + color?: + | "blue" + | "azure" + | "indigo" + | "purple" + | "pink" + | "red" + | "orange" + | "yellow" + | "lime" + | "green" + | "teal" + | "cyan"; + onClick?: () => void; +} +function Button({ + children, + className, + onClick, + type, + actionType, + variant, + size, + color, + fullWidth, + isLoading, + disabled, +}: Props) { + const myOnClick = () => { + !isLoading && onClick && onClick(); + }; + + const cns = cn( + "btn", + className, + actionType && `btn-${actionType}`, + variant && `btn-${variant}`, + size && `btn-${size}`, + color && `btn-${color}`, + fullWidth && "w-100", + isLoading && "btn-loading", + ); + + return ( + + ); +} + +export { Button }; diff --git a/frontend/src/components/EmptyData.tsx b/frontend/src/components/EmptyData.tsx new file mode 100644 index 00000000..46a9664c --- /dev/null +++ b/frontend/src/components/EmptyData.tsx @@ -0,0 +1,63 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; +import cn from "classnames"; +import type { ReactNode } from "react"; +import { Button, HasPermission } from "src/components"; +import { T } from "src/locale"; +import { type ADMIN, MANAGE, type Permission, type Section } from "src/modules/Permissions"; + +interface Props { + tableInstance: ReactTable; + onNew?: () => void; + isFiltered?: boolean; + object: string; + objects: string; + color?: string; + customAddBtn?: ReactNode; + permissionSection?: Section | typeof ADMIN; + permission?: Permission; +} +function EmptyData({ + tableInstance, + onNew, + isFiltered, + object, + objects, + color = "primary", + customAddBtn, + permissionSection, + permission, +}: Props) { + return ( + + +
+ {isFiltered ? ( +

+ +

+ ) : ( + <> +

+ +

+ +

+ +

+ {customAddBtn ? ( + customAddBtn + ) : ( + + )} +
+ + )} +
+ + + ); +} + +export { EmptyData }; diff --git a/frontend/src/components/ErrorNotFound.tsx b/frontend/src/components/ErrorNotFound.tsx new file mode 100644 index 00000000..a162d1cd --- /dev/null +++ b/frontend/src/components/ErrorNotFound.tsx @@ -0,0 +1,25 @@ +import { useNavigate } from "react-router-dom"; +import { Button } from "src/components"; +import { T } from "src/locale"; + +export function ErrorNotFound() { + const navigate = useNavigate(); + + return ( +
+
+

+ +

+

+ +

+
+ +
+
+
+ ); +} diff --git a/frontend/src/components/Flag.tsx b/frontend/src/components/Flag.tsx new file mode 100644 index 00000000..420f6c8f --- /dev/null +++ b/frontend/src/components/Flag.tsx @@ -0,0 +1,24 @@ +import { IconWorld } from "@tabler/icons-react"; +import { hasFlag } from "country-flag-icons"; +// @ts-expect-error Creating a typing for a subfolder is not easily possible +import Flags from "country-flag-icons/react/3x2"; + +interface FlagProps { + className?: string; + countryCode: string; +} +function Flag({ className, countryCode }: FlagProps) { + countryCode = countryCode.toUpperCase(); + if (countryCode === "EN") { + return ; + } + + if (hasFlag(countryCode)) { + const FlagElement = Flags[countryCode] as any; + return ; + } + console.error(`No flag for country ${countryCode} found!`); + return null; +} + +export { Flag }; diff --git a/frontend/src/components/Form/AccessClientFields.tsx b/frontend/src/components/Form/AccessClientFields.tsx new file mode 100644 index 00000000..820907dc --- /dev/null +++ b/frontend/src/components/Form/AccessClientFields.tsx @@ -0,0 +1,131 @@ +import { IconX } from "@tabler/icons-react"; +import cn from "classnames"; +import { useFormikContext } from "formik"; +import { useState } from "react"; +import type { AccessListClient } from "src/api/backend"; +import { T } from "src/locale"; + +interface Props { + initialValues: AccessListClient[]; + name?: string; +} +export function AccessClientFields({ initialValues, name = "clients" }: Props) { + const [values, setValues] = useState(initialValues || []); + const { setFieldValue } = useFormikContext(); + + const blankClient: AccessListClient = { directive: "allow", address: "" }; + + if (values?.length === 0) { + setValues([blankClient]); + } + + const handleAdd = () => { + setValues([...values, blankClient]); + }; + + const handleRemove = (idx: number) => { + const newValues = values.filter((_: AccessListClient, i: number) => i !== idx); + if (newValues.length === 0) { + newValues.push(blankClient); + } + setValues(newValues); + setFormField(newValues); + }; + + const handleChange = (idx: number, field: string, fieldValue: string) => { + const newValues = values.map((v: AccessListClient, i: number) => + i === idx ? { ...v, [field]: fieldValue } : v, + ); + setValues(newValues); + setFormField(newValues); + }; + + const setFormField = (newValues: AccessListClient[]) => { + const filtered = newValues.filter((v: AccessListClient) => v?.address?.trim() !== ""); + setFieldValue(name, filtered); + }; + + return ( + <> +

+ +

+ {values.map((client: AccessListClient, idx: number) => ( +
+
+
+ + + + handleChange(idx, "address", e.target.value)} + placeholder="192.168.1.100 or 192.168.1.0/24 or 2001:0db8::/32" + /> +
+
+ +
+ ))} +
+ +
+
+

+ +

+
+
+ + + + +
+
+
+ + ); +} diff --git a/frontend/src/components/Form/AccessField.tsx b/frontend/src/components/Form/AccessField.tsx new file mode 100644 index 00000000..c72ef588 --- /dev/null +++ b/frontend/src/components/Form/AccessField.tsx @@ -0,0 +1,99 @@ +import { IconLock, IconLockOpen2 } from "@tabler/icons-react"; +import { Field, useFormikContext } from "formik"; +import type { ReactNode } from "react"; +import Select, { type ActionMeta, components, type OptionProps } from "react-select"; +import type { AccessList } from "src/api/backend"; +import { useAccessLists } from "src/hooks"; +import { DateTimeFormat, intl, T } from "src/locale"; + +interface AccessOption { + readonly value: number; + readonly label: string; + readonly subLabel: string; + readonly icon: ReactNode; +} + +const Option = (props: OptionProps) => { + return ( + +
+
+ {props.data.icon} {props.data.label} +
+
{props.data.subLabel}
+
+
+ ); +}; + +interface Props { + id?: string; + name?: string; + label?: string; +} +export function AccessField({ name = "accessListId", label = "access-list", id = "accessListId" }: Props) { + const { isLoading, isError, error, data } = useAccessLists(["owner", "items", "clients"]); + const { setFieldValue } = useFormikContext(); + + const handleChange = (newValue: any, _actionMeta: ActionMeta) => { + setFieldValue(name, newValue?.value); + }; + + const options: AccessOption[] = + data?.map((item: AccessList) => ({ + value: item.id || 0, + label: item.name, + subLabel: intl.formatMessage( + { id: "access-list.subtitle" }, + { + users: item?.items?.length, + rules: item?.clients?.length, + date: item?.createdOn ? DateTimeFormat(item?.createdOn) : "N/A", + }, + ), + icon: , + })) || []; + + // Public option + options?.unshift({ + value: 0, + label: intl.formatMessage({ id: "access-list.public" }), + subLabel: intl.formatMessage({ id: "access-list.public.subtitle" }), + icon: , + }); + + return ( + + {({ field, form }: any) => ( +
+ + {isLoading ?
: null} + {isError ?
{`${error}`}
: null} + {!isLoading && !isError ? ( + handleChange(idx, "username", e.target.value)} + /> +
+
+ iv.username === item.username).length > 0 + ? "••••••••" + : "" + } + onChange={(e) => handleChange(idx, "password", e.target.value)} + /> +
+ +
+ ))} +
+ +
+ + ); +} diff --git a/frontend/src/components/Form/DNSProviderFields.module.css b/frontend/src/components/Form/DNSProviderFields.module.css new file mode 100644 index 00000000..fdf04b01 --- /dev/null +++ b/frontend/src/components/Form/DNSProviderFields.module.css @@ -0,0 +1,8 @@ +.dnsChallengeWarning { + border: 1px solid var(--tblr-orange-lt); + padding: 1rem; + border-radius: 0.375rem; + margin-top: 1rem; + background-color: var(--tblr-cyan-lt); +} + diff --git a/frontend/src/components/Form/DNSProviderFields.tsx b/frontend/src/components/Form/DNSProviderFields.tsx new file mode 100644 index 00000000..f323942e --- /dev/null +++ b/frontend/src/components/Form/DNSProviderFields.tsx @@ -0,0 +1,132 @@ +import { IconAlertTriangle } from "@tabler/icons-react"; +import CodeEditor from "@uiw/react-textarea-code-editor"; +import { Field, useFormikContext } from "formik"; +import { useState } from "react"; +import Select, { type ActionMeta } from "react-select"; +import type { DNSProvider } from "src/api/backend"; +import { useDnsProviders } from "src/hooks"; +import { T } from "src/locale"; +import styles from "./DNSProviderFields.module.css"; + +interface DNSProviderOption { + readonly value: string; + readonly label: string; + readonly credentials: string; +} + +interface Props { + showBoundaryBox?: boolean; +} +export function DNSProviderFields({ showBoundaryBox = false }: Props) { + const { values, setFieldValue } = useFormikContext(); + const { data: dnsProviders, isLoading } = useDnsProviders(); + const [dnsProviderId, setDnsProviderId] = useState(null); + + const v: any = values || {}; + + const handleChange = (newValue: any, _actionMeta: ActionMeta) => { + setFieldValue("meta.dnsProvider", newValue?.value); + setFieldValue("meta.dnsProviderCredentials", newValue?.credentials); + setDnsProviderId(newValue?.value); + }; + + const options: DNSProviderOption[] = + dnsProviders?.map((p: DNSProvider) => ({ + value: p.id, + label: p.name, + credentials: p.credentials, + })) || []; + + return ( +
+

+ + +

+ + + {({ field }: any) => ( +
+ + + + + +
+ )} +
+ + ) : null} +
+ ); +} diff --git a/frontend/src/components/Form/DomainNamesField.tsx b/frontend/src/components/Form/DomainNamesField.tsx new file mode 100644 index 00000000..bdba2f2e --- /dev/null +++ b/frontend/src/components/Form/DomainNamesField.tsx @@ -0,0 +1,85 @@ +import { Field, useFormikContext } from "formik"; +import type { ReactNode } from "react"; +import type { ActionMeta, MultiValue } from "react-select"; +import CreatableSelect from "react-select/creatable"; +import { intl, T } from "src/locale"; +import { validateDomain, validateDomains } from "src/modules/Validations"; + +type SelectOption = { + label: string; + value: string; + color?: string; +}; + +interface Props { + id?: string; + maxDomains?: number; + isWildcardPermitted?: boolean; + dnsProviderWildcardSupported?: boolean; + name?: string; + label?: string; + onChange?: (domains: string[]) => void; +} +export function DomainNamesField({ + name = "domainNames", + label = "domain-names", + id = "domainNames", + maxDomains, + isWildcardPermitted = false, + dnsProviderWildcardSupported = false, + onChange, +}: Props) { + const { setFieldValue } = useFormikContext(); + + const handleChange = (v: MultiValue, _actionMeta: ActionMeta) => { + const doms = v?.map((i: SelectOption) => { + return i.value; + }); + setFieldValue(name, doms); + onChange?.(doms); + }; + + const helperTexts: ReactNode[] = []; + if (maxDomains) { + helperTexts.push(); + } + if (!isWildcardPermitted) { + helperTexts.push(); + } else if (!dnsProviderWildcardSupported) { + helperTexts.push(); + } + + return ( + + {({ field, form }: any) => ( +
+ + ({ label: d, value: d }))} + /> + {form.errors[field.name] && form.touched[field.name] ? ( + {form.errors[field.name]} + ) : helperTexts.length ? ( + helperTexts.map((i, idx) => ( + + {i} + + )) + ) : null} +
+ )} +
+ ); +} diff --git a/frontend/src/components/Form/LocationsFields.module.css b/frontend/src/components/Form/LocationsFields.module.css new file mode 100644 index 00000000..4b48ef3c --- /dev/null +++ b/frontend/src/components/Form/LocationsFields.module.css @@ -0,0 +1,3 @@ +.locationCard { + border-color: light-dark(var(--tblr-gray-200), var(--tblr-gray-700)) !important; +} diff --git a/frontend/src/components/Form/LocationsFields.tsx b/frontend/src/components/Form/LocationsFields.tsx new file mode 100644 index 00000000..4240b1f9 --- /dev/null +++ b/frontend/src/components/Form/LocationsFields.tsx @@ -0,0 +1,185 @@ +import { IconSettings } from "@tabler/icons-react"; +import CodeEditor from "@uiw/react-textarea-code-editor"; +import cn from "classnames"; +import { useFormikContext } from "formik"; +import { useState } from "react"; +import type { ProxyLocation } from "src/api/backend"; +import { intl, T } from "src/locale"; +import styles from "./LocationsFields.module.css"; + +interface Props { + initialValues: ProxyLocation[]; + name?: string; +} +export function LocationsFields({ initialValues, name = "locations" }: Props) { + const [values, setValues] = useState(initialValues || []); + const { setFieldValue } = useFormikContext(); + const [advVisible, setAdvVisible] = useState([]); + + const blankItem: ProxyLocation = { + path: "", + advancedConfig: "", + forwardScheme: "http", + forwardHost: "", + forwardPort: 80, + }; + + const toggleAdvVisible = (idx: number) => { + setAdvVisible(advVisible.includes(idx) ? advVisible.filter((i) => i !== idx) : [...advVisible, idx]); + }; + + const handleAdd = () => { + setValues([...values, blankItem]); + }; + + const handleRemove = (idx: number) => { + const newValues = values.filter((_: ProxyLocation, i: number) => i !== idx); + setValues(newValues); + setFormField(newValues); + }; + + const handleChange = (idx: number, field: string, fieldValue: string) => { + const newValues = values.map((v: ProxyLocation, i: number) => (i === idx ? { ...v, [field]: fieldValue } : v)); + setValues(newValues); + setFormField(newValues); + }; + + const setFormField = (newValues: ProxyLocation[]) => { + const filtered = newValues.filter((v: ProxyLocation) => v?.path?.trim() !== ""); + setFieldValue(name, filtered); + }; + + if (values.length === 0) { + return ( +
+ +
+ ); + } + + return ( + <> + {values.map((item: ProxyLocation, idx: number) => ( +
+
+
+
+
+ Location + handleChange(idx, "path", e.target.value)} + /> +
+
+
+ +
+
+
+
+
+ + +
+
+
+
+ + handleChange(idx, "forwardHost", e.target.value)} + /> +
+
+
+
+ + handleChange(idx, "forwardPort", e.target.value)} + /> +
+
+
+ {advVisible.includes(idx) && ( +
+ handleChange(idx, "advancedConfig", e.target.value)} + style={{ + fontFamily: + "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace", + borderRadius: "0.3rem", + minHeight: "170px", + }} + /> +
+ )} + +
+
+ ))} +
+ +
+ + ); +} diff --git a/frontend/src/components/Form/NginxConfigField.tsx b/frontend/src/components/Form/NginxConfigField.tsx new file mode 100644 index 00000000..208a3848 --- /dev/null +++ b/frontend/src/components/Form/NginxConfigField.tsx @@ -0,0 +1,41 @@ +import CodeEditor from "@uiw/react-textarea-code-editor"; +import { Field } from "formik"; +import { intl, T } from "src/locale"; + +interface Props { + id?: string; + name?: string; + label?: string; +} +export function NginxConfigField({ + name = "advancedConfig", + label = "nginx-config.label", + id = "advancedConfig", +}: Props) { + return ( + + {({ field }: any) => ( +
+ + +
+ )} +
+ ); +} diff --git a/frontend/src/components/Form/SSLCertificateField.tsx b/frontend/src/components/Form/SSLCertificateField.tsx new file mode 100644 index 00000000..c4767509 --- /dev/null +++ b/frontend/src/components/Form/SSLCertificateField.tsx @@ -0,0 +1,138 @@ +import { IconShield } from "@tabler/icons-react"; +import { Field, useFormikContext } from "formik"; +import Select, { type ActionMeta, components, type OptionProps } from "react-select"; +import type { Certificate } from "src/api/backend"; +import { useCertificates } from "src/hooks"; +import { DateTimeFormat, intl, T } from "src/locale"; + +interface CertOption { + readonly value: number | "new"; + readonly label: string; + readonly subLabel: string; + readonly icon: React.ReactNode; +} + +const Option = (props: OptionProps) => { + return ( + +
+
+ {props.data.icon} {props.data.label} +
+
{props.data.subLabel}
+
+
+ ); +}; + +interface Props { + id?: string; + name?: string; + label?: string; + required?: boolean; + allowNew?: boolean; + forHttp?: boolean; // the sslForced, http2Support, hstsEnabled, hstsSubdomains fields +} +export function SSLCertificateField({ + name = "certificateId", + label = "ssl-certificate", + id = "certificateId", + required, + allowNew, + forHttp = true, +}: Props) { + const { isLoading, isError, error, data } = useCertificates(); + const { values, setFieldValue } = useFormikContext(); + const v: any = values || {}; + + const handleChange = (newValue: any, _actionMeta: ActionMeta) => { + setFieldValue(name, newValue?.value); + const { + sslForced, + http2Support, + hstsEnabled, + hstsSubdomains, + dnsChallenge, + dnsProvider, + dnsProviderCredentials, + propagationSeconds, + } = v; + if (forHttp && !newValue?.value) { + sslForced && setFieldValue("sslForced", false); + http2Support && setFieldValue("http2Support", false); + hstsEnabled && setFieldValue("hstsEnabled", false); + hstsSubdomains && setFieldValue("hstsSubdomains", false); + } + if (newValue?.value !== "new") { + dnsChallenge && setFieldValue("dnsChallenge", undefined); + dnsProvider && setFieldValue("dnsProvider", undefined); + dnsProviderCredentials && setFieldValue("dnsProviderCredentials", undefined); + propagationSeconds && setFieldValue("propagationSeconds", undefined); + } + }; + + const options: CertOption[] = + data?.map((cert: Certificate) => ({ + value: cert.id, + label: cert.niceName, + subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A" })}`, + icon: , + })) || []; + + // Prepend the Add New option + if (allowNew) { + options?.unshift({ + value: "new", + label: intl.formatMessage({ id: "certificates.request.title" }), + subLabel: intl.formatMessage({ id: "certificates.request.subtitle" }), + icon: , + }); + } + + // Prepend the None option + if (!required) { + options?.unshift({ + value: 0, + label: intl.formatMessage({ id: "certificate.none.title" }), + subLabel: forHttp + ? intl.formatMessage({ id: "certificate.none.subtitle.for-http" }) + : intl.formatMessage({ id: "certificate.none.subtitle" }), + icon: , + }); + } + + return ( + + {({ field, form }: any) => ( +
+ + {isLoading ?
: null} + {isError ?
{`${error}`}
: null} + {!isLoading && !isError ? ( + handleToggleChange(e, field.name)} + disabled={!hasCertificate} + /> + + + + + )} + +
+
+ + {({ field }: any) => ( + + )} + +
+
+
+
+ + {({ field }: any) => ( + + )} + +
+
+ + {({ field }: any) => ( + + )} + +
+
+ + ); + + return ( +
+ {forHttp ? getHttpOptions() : null} + {newCertificate ? ( + <> + + {({ field }: any) => ( + + )} + + {requireDomainNames ? : null} + {dnsChallenge ? : null} + + ) : null} +
+ ); +} diff --git a/frontend/src/components/Form/index.ts b/frontend/src/components/Form/index.ts new file mode 100644 index 00000000..f218b2e3 --- /dev/null +++ b/frontend/src/components/Form/index.ts @@ -0,0 +1,9 @@ +export * from "./AccessClientFields"; +export * from "./AccessField"; +export * from "./BasicAuthFields"; +export * from "./DNSProviderFields"; +export * from "./DomainNamesField"; +export * from "./LocationsFields"; +export * from "./NginxConfigField"; +export * from "./SSLCertificateField"; +export * from "./SSLOptionsFields"; diff --git a/frontend/src/components/HasPermission.tsx b/frontend/src/components/HasPermission.tsx new file mode 100644 index 00000000..c038066b --- /dev/null +++ b/frontend/src/components/HasPermission.tsx @@ -0,0 +1,52 @@ +import type { ReactNode } from "react"; +import Alert from "react-bootstrap/Alert"; +import { Loading, LoadingPage } from "src/components"; +import { useUser } from "src/hooks"; +import { T } from "src/locale"; +import { type ADMIN, hasPermission, type Permission, type Section } from "src/modules/Permissions"; + +interface Props { + section?: Section | typeof ADMIN; + permission: Permission; + hideError?: boolean; + children?: ReactNode; + pageLoading?: boolean; + loadingNoLogo?: boolean; +} +function HasPermission({ + section, + permission, + children, + hideError = false, + pageLoading = false, + loadingNoLogo = false, +}: Props) { + const { data, isLoading } = useUser("me"); + + if (!section) { + return <>{children}; + } + + if (isLoading) { + if (hideError) { + return null; + } + if (pageLoading) { + return ; + } + return ; + } + + const allowed = hasPermission(section, permission, data?.permissions, data?.roles); + if (allowed) { + return <>{children}; + } + + return !hideError ? ( + + + + ) : null; +} + +export { HasPermission }; diff --git a/frontend/src/components/Loading.module.css b/frontend/src/components/Loading.module.css new file mode 100644 index 00000000..7a13ded3 --- /dev/null +++ b/frontend/src/components/Loading.module.css @@ -0,0 +1,3 @@ +.logo { + max-height: 100px; +} diff --git a/frontend/src/components/Loading.tsx b/frontend/src/components/Loading.tsx new file mode 100644 index 00000000..14d96e6d --- /dev/null +++ b/frontend/src/components/Loading.tsx @@ -0,0 +1,23 @@ +import type { ReactNode } from "react"; +import { T } from "src/locale"; +import styles from "./Loading.module.css"; + +interface Props { + label?: string | ReactNode; + noLogo?: boolean; +} +export function Loading({ label, noLogo }: Props) { + return ( +
+ {noLogo ? null : ( +
+ +
+ )} +
{label || }
+
+
+
+
+ ); +} diff --git a/frontend/src/components/LoadingPage.tsx b/frontend/src/components/LoadingPage.tsx new file mode 100644 index 00000000..7d3bec1e --- /dev/null +++ b/frontend/src/components/LoadingPage.tsx @@ -0,0 +1,15 @@ +import { Loading, Page } from "src/components"; + +interface Props { + label?: string; + noLogo?: boolean; +} +export function LoadingPage({ label, noLogo }: Props) { + return ( + +
+ +
+
+ ); +} diff --git a/frontend/src/components/LocalePicker.module.css b/frontend/src/components/LocalePicker.module.css new file mode 100644 index 00000000..6f39e7fa --- /dev/null +++ b/frontend/src/components/LocalePicker.module.css @@ -0,0 +1,8 @@ +.btn { + color: light-dark(var(--tblr-dark), var(--tblr-light)) !important; + + &:hover { + border: var(--tblr-btn-border-width) solid transparent !important; + background: color-mix(in srgb, var(--tblr-btn-hover-bg) 10%, transparent) !important; + } +} diff --git a/frontend/src/components/LocalePicker.tsx b/frontend/src/components/LocalePicker.tsx new file mode 100644 index 00000000..aaa1dc53 --- /dev/null +++ b/frontend/src/components/LocalePicker.tsx @@ -0,0 +1,47 @@ +import cn from "classnames"; +import { Flag } from "src/components"; +import { useLocaleState } from "src/context"; +import { useTheme } from "src/hooks"; +import { changeLocale, getFlagCodeForLocale, localeOptions, T } from "src/locale"; +import styles from "./LocalePicker.module.css"; + +function LocalePicker() { + const { locale, setLocale } = useLocaleState(); + const { getTheme } = useTheme(); + + const changeTo = (lang: string) => { + changeLocale(lang); + setLocale(lang); + location.reload(); + }; + + const classes = ["btn", "dropdown-toggle", "btn-sm", styles.btn]; + const cns = cn(...classes, getTheme() === "dark" ? "btn-ghost-dark" : "btn-ghost-light"); + + return ( +
+ +
+ {localeOptions.map((item) => { + return ( + { + e.preventDefault(); + changeTo(item[0]); + }} + > + + + ); + })} +
+
+ ); +} + +export { LocalePicker }; diff --git a/frontend/src/components/NavLink.tsx b/frontend/src/components/NavLink.tsx new file mode 100644 index 00000000..b6a414af --- /dev/null +++ b/frontend/src/components/NavLink.tsx @@ -0,0 +1,29 @@ +import { useNavigate } from "react-router-dom"; + +interface Props { + children: React.ReactNode; + to?: string; + isDropdownItem?: boolean; + onClick?: () => void; +} +export function NavLink({ children, to, isDropdownItem, onClick }: Props) { + const navigate = useNavigate(); + + return ( + { + e.preventDefault(); + if (onClick) { + onClick(); + } + if (to) { + navigate(to); + } + }} + > + {children} + + ); +} diff --git a/frontend/src/components/Page.module.css b/frontend/src/components/Page.module.css new file mode 100644 index 00000000..00086bba --- /dev/null +++ b/frontend/src/components/Page.module.css @@ -0,0 +1,5 @@ +.page { + display: grid; + grid-template-rows: auto 1fr auto; /* Header, Main Content, Footer */ + min-height: 100vh; +} diff --git a/frontend/src/components/Page.tsx b/frontend/src/components/Page.tsx new file mode 100644 index 00000000..a96a6b1f --- /dev/null +++ b/frontend/src/components/Page.tsx @@ -0,0 +1,10 @@ +import cn from "classnames"; +import styles from "./Page.module.css"; + +interface Props { + children: React.ReactNode; + className?: string; +} +export function Page({ children, className }: Props) { + return
{children}
; +} diff --git a/frontend/src/components/SiteContainer.tsx b/frontend/src/components/SiteContainer.tsx new file mode 100644 index 00000000..1722d88a --- /dev/null +++ b/frontend/src/components/SiteContainer.tsx @@ -0,0 +1,6 @@ +interface Props { + children: React.ReactNode; +} +export function SiteContainer({ children }: Props) { + return
{children}
; +} diff --git a/frontend/src/components/SiteFooter.tsx b/frontend/src/components/SiteFooter.tsx new file mode 100644 index 00000000..a1778395 --- /dev/null +++ b/frontend/src/components/SiteFooter.tsx @@ -0,0 +1,64 @@ +import { useHealth } from "src/hooks"; +import { T } from "src/locale"; + +export function SiteFooter() { + const health = useHealth(); + + const getVersion = () => { + if (!health.data) { + return ""; + } + const v = health.data.version; + return `v${v.major}.${v.minor}.${v.revision}`; + }; + + return ( + + ); +} diff --git a/frontend/src/components/SiteHeader.module.css b/frontend/src/components/SiteHeader.module.css new file mode 100644 index 00000000..a88f81c9 --- /dev/null +++ b/frontend/src/components/SiteHeader.module.css @@ -0,0 +1,8 @@ +.logo { + font-size: 1.1rem; + font-weight: 500; + + img { + margin-right: 0.8rem; + } +} diff --git a/frontend/src/components/SiteHeader.tsx b/frontend/src/components/SiteHeader.tsx new file mode 100644 index 00000000..07d52f98 --- /dev/null +++ b/frontend/src/components/SiteHeader.tsx @@ -0,0 +1,114 @@ +import { IconLock, IconLogout, IconUser } from "@tabler/icons-react"; +import { LocalePicker, NavLink, ThemeSwitcher } from "src/components"; +import { useAuthState } from "src/context"; +import { useUser } from "src/hooks"; +import { T } from "src/locale"; +import { showChangePasswordModal, showUserModal } from "src/modals"; +import styles from "./SiteHeader.module.css"; + +export function SiteHeader() { + const { data: currentUser } = useUser("me"); + const isAdmin = currentUser?.roles.includes("admin"); + const { logout } = useAuthState(); + + return ( +
+
+ ); +} diff --git a/frontend/src/components/SiteMenu.tsx b/frontend/src/components/SiteMenu.tsx new file mode 100644 index 00000000..3a0b0065 --- /dev/null +++ b/frontend/src/components/SiteMenu.tsx @@ -0,0 +1,211 @@ +import { + IconBook, + IconDeviceDesktop, + IconHome, + IconLock, + IconSettings, + IconShield, + IconUser, +} from "@tabler/icons-react"; +import cn from "classnames"; +import React from "react"; +import { HasPermission, NavLink } from "src/components"; +import { T } from "src/locale"; +import { + ACCESS_LISTS, + ADMIN, + CERTIFICATES, + DEAD_HOSTS, + type MANAGE, + PROXY_HOSTS, + REDIRECTION_HOSTS, + type Section, + STREAMS, + VIEW, +} from "src/modules/Permissions"; + +interface MenuItem { + label: string; + icon?: React.ElementType; + to?: string; + items?: MenuItem[]; + permissionSection?: Section | typeof ADMIN; + permission?: typeof VIEW | typeof MANAGE; +} + +const menuItems: MenuItem[] = [ + { + to: "/", + icon: IconHome, + label: "dashboard", + }, + { + icon: IconDeviceDesktop, + label: "hosts", + items: [ + { + to: "/nginx/proxy", + label: "proxy-hosts", + permissionSection: PROXY_HOSTS, + permission: VIEW, + }, + { + to: "/nginx/redirection", + label: "redirection-hosts", + permissionSection: REDIRECTION_HOSTS, + permission: VIEW, + }, + { + to: "/nginx/stream", + label: "streams", + permissionSection: STREAMS, + permission: VIEW, + }, + { + to: "/nginx/404", + label: "dead-hosts", + permissionSection: DEAD_HOSTS, + permission: VIEW, + }, + ], + }, + { + to: "/access", + icon: IconLock, + label: "access-lists", + permissionSection: ACCESS_LISTS, + permission: VIEW, + }, + { + to: "/certificates", + icon: IconShield, + label: "certificates", + permissionSection: CERTIFICATES, + permission: VIEW, + }, + { + to: "/users", + icon: IconUser, + label: "users", + permissionSection: ADMIN, + }, + { + to: "/audit-log", + icon: IconBook, + label: "auditlogs", + permissionSection: ADMIN, + }, + { + to: "/settings", + icon: IconSettings, + label: "settings", + permissionSection: ADMIN, + }, +]; + +const getMenuItem = (item: MenuItem, onClick?: () => void) => { + if (item.items && item.items.length > 0) { + return getMenuDropown(item, onClick); + } + + return ( + +
  • + + + {item.icon && React.createElement(item.icon, { height: 24, width: 24 })} + + + + + +
  • +
    + ); +}; + +const getMenuDropown = (item: MenuItem, onClick?: () => void) => { + const cns = cn("nav-item", "dropdown"); + return ( + +
  • + +
    + {item.items?.map((subitem, idx) => { + return ( + + + + + + ); + })} +
    +
  • +
    + ); +}; + +export function SiteMenu() { + // This is hacky AF. But that's the price of using a non-react UI kit. + const closeMenus = () => { + const navMenus = document.querySelectorAll(".nav-item.dropdown"); + navMenus.forEach((menu) => { + menu.classList.remove("show"); + const dropdown = menu.querySelector(".dropdown-menu"); + if (dropdown) { + dropdown.classList.remove("show"); + } + }); + }; + + return ( +
    +
    +
    +
    +
    +
    +
      + {menuItems.length > 0 && + menuItems.map((item) => { + return getMenuItem(item, closeMenus); + })} +
    +
    +
    +
    +
    +
    +
    + ); +} diff --git a/frontend/src/components/Table/EmptyRow.tsx b/frontend/src/components/Table/EmptyRow.tsx new file mode 100644 index 00000000..2daee952 --- /dev/null +++ b/frontend/src/components/Table/EmptyRow.tsx @@ -0,0 +1,16 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; + +interface Props { + tableInstance: ReactTable; +} +function EmptyRow({ tableInstance }: Props) { + return ( + + +

    There are no items

    + + + ); +} + +export { EmptyRow }; diff --git a/frontend/src/components/Table/Formatter/AccessListformatter.tsx b/frontend/src/components/Table/Formatter/AccessListformatter.tsx new file mode 100644 index 00000000..b5830fd9 --- /dev/null +++ b/frontend/src/components/Table/Formatter/AccessListformatter.tsx @@ -0,0 +1,24 @@ +import type { AccessList } from "src/api/backend"; +import { T } from "src/locale"; +import { showAccessListModal } from "src/modals"; + +interface Props { + access?: AccessList; +} +export function AccessListFormatter({ access }: Props) { + if (!access) { + return ; + } + return ( + + ); +} diff --git a/frontend/src/components/Table/Formatter/CertificateFormatter.tsx b/frontend/src/components/Table/Formatter/CertificateFormatter.tsx new file mode 100644 index 00000000..d793dae8 --- /dev/null +++ b/frontend/src/components/Table/Formatter/CertificateFormatter.tsx @@ -0,0 +1,9 @@ +import type { Certificate } from "src/api/backend"; +import { T } from "src/locale"; + +interface Props { + certificate?: Certificate; +} +export function CertificateFormatter({ certificate }: Props) { + return ; +} diff --git a/frontend/src/components/Table/Formatter/CertificateInUseFormatter.tsx b/frontend/src/components/Table/Formatter/CertificateInUseFormatter.tsx new file mode 100644 index 00000000..a49374d6 --- /dev/null +++ b/frontend/src/components/Table/Formatter/CertificateInUseFormatter.tsx @@ -0,0 +1,82 @@ +import OverlayTrigger from "react-bootstrap/OverlayTrigger"; +import Popover from "react-bootstrap/Popover"; +import type { DeadHost, ProxyHost, RedirectionHost, Stream } from "src/api/backend"; +import { TrueFalseFormatter } from "src/components"; +import { T } from "src/locale"; + +const getSection = (title: string, items: ProxyHost[] | RedirectionHost[] | DeadHost[]) => { + if (items.length === 0) { + return null; + } + return ( + <> +
    + + + +
    + {items.map((host) => ( +
    + {host.domainNames.join(", ")} +
    + ))} + + ); +}; + +const getSectionStream = (items: Stream[]) => { + if (items.length === 0) { + return null; + } + return ( + <> +
    + + + +
    + {items.map((stream) => ( +
    + {stream.forwardingHost}:{stream.forwardingPort} +
    + ))} + + ); +}; + +interface Props { + proxyHosts: ProxyHost[]; + redirectionHosts: RedirectionHost[]; + deadHosts: DeadHost[]; + streams: Stream[]; +} +export function CertificateInUseFormatter({ proxyHosts, redirectionHosts, deadHosts, streams }: Props) { + const totalCount = proxyHosts?.length + redirectionHosts?.length + deadHosts?.length + streams?.length; + if (totalCount === 0) { + return ; + } + + proxyHosts.sort(); + redirectionHosts.sort(); + deadHosts.sort(); + streams.sort(); + + const popover = ( + + + {getSection("proxy-hosts", proxyHosts)} + {getSection("redirection-hosts", redirectionHosts)} + {getSection("dead-hosts", deadHosts)} + {getSectionStream(streams)} + + + ); + + return ( + +
    + +
    +
    + ); +} diff --git a/frontend/src/components/Table/Formatter/DateFormatter.tsx b/frontend/src/components/Table/Formatter/DateFormatter.tsx new file mode 100644 index 00000000..e8c2c6c7 --- /dev/null +++ b/frontend/src/components/Table/Formatter/DateFormatter.tsx @@ -0,0 +1,18 @@ +import cn from "classnames"; +import { differenceInDays, isPast, parseISO } from "date-fns"; +import { DateTimeFormat } from "src/locale"; + +interface Props { + value: string; + highlightPast?: boolean; + highlistNearlyExpired?: boolean; +} +export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: Props) { + const dateIsPast = isPast(parseISO(value)); + const days = differenceInDays(parseISO(value), new Date()); + const cl = cn({ + "text-danger": highlightPast && dateIsPast, + "text-warning": highlistNearlyExpired && !dateIsPast && days <= 30 && days >= 0, + }); + return {DateTimeFormat(value)}; +} diff --git a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx new file mode 100644 index 00000000..8560eae2 --- /dev/null +++ b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx @@ -0,0 +1,61 @@ +import cn from "classnames"; +import type { ReactNode } from "react"; +import { DateTimeFormat, T } from "src/locale"; + +interface Props { + domains: string[]; + createdOn?: string; + niceName?: string; + provider?: string; + color?: string; +} + +const DomainLink = ({ domain, color }: { domain: string; color?: string }) => { + // when domain contains a wildcard, make the link go nowhere. + let onClick: ((e: React.MouseEvent) => void) | undefined; + if (domain.includes("*")) { + onClick = (e: React.MouseEvent) => e.preventDefault(); + } + return ( + + {domain} + + ); +}; + +export function DomainsFormatter({ domains, createdOn, niceName, provider, color }: Props) { + const elms: ReactNode[] = []; + if (domains.length === 0 && !niceName) { + elms.push( + + Unknown + , + ); + } + if (niceName && provider !== "letsencrypt") { + elms.push( + + {niceName} + , + ); + } + + domains.map((domain: string) => elms.push()); + + return ( +
    +
    {...elms}
    + {createdOn ? ( +
    + +
    + ) : null} +
    + ); +} diff --git a/frontend/src/components/Table/Formatter/EmailFormatter.tsx b/frontend/src/components/Table/Formatter/EmailFormatter.tsx new file mode 100644 index 00000000..3371c66a --- /dev/null +++ b/frontend/src/components/Table/Formatter/EmailFormatter.tsx @@ -0,0 +1,10 @@ +interface Props { + email: string; +} +export function EmailFormatter({ email }: Props) { + return ( + + {email} + + ); +} diff --git a/frontend/src/components/Table/Formatter/EventFormatter.tsx b/frontend/src/components/Table/Formatter/EventFormatter.tsx new file mode 100644 index 00000000..f6f7787d --- /dev/null +++ b/frontend/src/components/Table/Formatter/EventFormatter.tsx @@ -0,0 +1,79 @@ +import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconShield, IconUser } from "@tabler/icons-react"; +import cn from "classnames"; +import type { AuditLog } from "src/api/backend"; +import { DateTimeFormat, T } from "src/locale"; + +const getEventValue = (event: AuditLog) => { + switch (event.objectType) { + case "access-list": + case "user": + return event.meta?.name; + case "proxy-host": + case "redirection-host": + case "dead-host": + return event.meta?.domainNames?.join(", ") || "N/A"; + case "stream": + return event.meta?.incomingPort || "N/A"; + case "certificate": + return event.meta?.domainNames?.join(", ") || event.meta?.niceName || "N/A"; + default: + return `UNKNOWN EVENT TYPE: ${event.objectType}`; + } +}; + +const getColorForAction = (action: string) => { + switch (action) { + case "created": + return "text-lime"; + case "deleted": + return "text-red"; + default: + return "text-blue"; + } +}; + +const getIcon = (row: AuditLog) => { + const c = cn(getColorForAction(row.action), "me-1"); + let ico = null; + switch (row.objectType) { + case "user": + ico = ; + break; + case "proxy-host": + ico = ; + break; + case "redirection-host": + ico = ; + break; + case "dead-host": + ico = ; + break; + case "stream": + ico = ; + break; + case "access-list": + ico = ; + break; + case "certificate": + ico = ; + break; + } + + return ico; +}; + +interface Props { + row: AuditLog; +} +export function EventFormatter({ row }: Props) { + return ( +
    +
    + {getIcon(row)} + +   — {getEventValue(row)} +
    +
    {DateTimeFormat(row.createdOn)}
    +
    + ); +} diff --git a/frontend/src/components/Table/Formatter/GravatarFormatter.tsx b/frontend/src/components/Table/Formatter/GravatarFormatter.tsx new file mode 100644 index 00000000..9b7bed95 --- /dev/null +++ b/frontend/src/components/Table/Formatter/GravatarFormatter.tsx @@ -0,0 +1,17 @@ +interface Props { + url: string; + name?: string; +} +export function GravatarFormatter({ url, name }: Props) { + return ( +
    + +
    + ); +} diff --git a/frontend/src/components/Table/Formatter/RolesFormatter.tsx b/frontend/src/components/Table/Formatter/RolesFormatter.tsx new file mode 100644 index 00000000..2e025605 --- /dev/null +++ b/frontend/src/components/Table/Formatter/RolesFormatter.tsx @@ -0,0 +1,20 @@ +import { T } from "src/locale"; + +interface Props { + roles: string[]; +} +export function RolesFormatter({ roles }: Props) { + const r = roles || []; + if (r.length === 0) { + r[0] = "standard-user"; + } + return ( + <> + {r.map((role: string) => ( + + + + ))} + + ); +} diff --git a/frontend/src/components/Table/Formatter/TrueFalseFormatter.tsx b/frontend/src/components/Table/Formatter/TrueFalseFormatter.tsx new file mode 100644 index 00000000..63339502 --- /dev/null +++ b/frontend/src/components/Table/Formatter/TrueFalseFormatter.tsx @@ -0,0 +1,24 @@ +import cn from "classnames"; +import { T } from "src/locale"; + +interface Props { + value: boolean; + trueLabel?: string; + trueColor?: string; + falseLabel?: string; + falseColor?: string; +} +export function TrueFalseFormatter({ + value, + trueLabel = "enabled", + trueColor = "lime", + falseLabel = "disabled", + falseColor = "red", +}: Props) { + return ( + + + + + ); +} diff --git a/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx new file mode 100644 index 00000000..e4e7fb27 --- /dev/null +++ b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx @@ -0,0 +1,21 @@ +import { DateTimeFormat, T } from "src/locale"; + +interface Props { + value: string; + createdOn?: string; + disabled?: boolean; +} +export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) { + return ( +
    +
    +
    {value}
    +
    + {createdOn ? ( +
    + +
    + ) : null} +
    + ); +} diff --git a/frontend/src/components/Table/Formatter/index.ts b/frontend/src/components/Table/Formatter/index.ts new file mode 100644 index 00000000..04a47193 --- /dev/null +++ b/frontend/src/components/Table/Formatter/index.ts @@ -0,0 +1,11 @@ +export * from "./AccessListformatter"; +export * from "./CertificateFormatter"; +export * from "./CertificateInUseFormatter"; +export * from "./DateFormatter"; +export * from "./DomainsFormatter"; +export * from "./EmailFormatter"; +export * from "./EventFormatter"; +export * from "./GravatarFormatter"; +export * from "./RolesFormatter"; +export * from "./TrueFalseFormatter"; +export * from "./ValueWithDateFormatter"; diff --git a/frontend/src/components/Table/TableBody.tsx b/frontend/src/components/Table/TableBody.tsx new file mode 100644 index 00000000..d8fe9bf3 --- /dev/null +++ b/frontend/src/components/Table/TableBody.tsx @@ -0,0 +1,37 @@ +import { flexRender } from "@tanstack/react-table"; +import type { TableLayoutProps } from "src/components"; +import { EmptyRow } from "./EmptyRow"; + +function TableBody(props: TableLayoutProps) { + const { tableInstance, extraStyles, emptyState } = props; + const rows = tableInstance.getRowModel().rows; + + if (rows.length === 0) { + return ( + + {emptyState ? emptyState : } + + ); + } + + return ( + + {rows.map((row: any) => { + return ( + + {row.getVisibleCells().map((cell: any) => { + const { className } = (cell.column.columnDef.meta as any) ?? {}; + return ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ); + })} + + ); + })} + + ); +} + +export { TableBody }; diff --git a/frontend/src/components/Table/TableHeader.tsx b/frontend/src/components/Table/TableHeader.tsx new file mode 100644 index 00000000..bf627ce9 --- /dev/null +++ b/frontend/src/components/Table/TableHeader.tsx @@ -0,0 +1,26 @@ +import type { TableLayoutProps } from "src/components"; + +function TableHeader(props: TableLayoutProps) { + const { tableInstance } = props; + const headerGroups = tableInstance.getHeaderGroups(); + + return ( + + {headerGroups.map((headerGroup: any) => ( + + {headerGroup.headers.map((header: any) => { + const { column } = header; + const { className } = (column.columnDef.meta as any) ?? {}; + return ( + + {typeof column.columnDef.header === "string" ? `${column.columnDef.header}` : null} + + ); + })} + + ))} + + ); +} + +export { TableHeader }; diff --git a/frontend/src/components/Table/TableHelpers.ts b/frontend/src/components/Table/TableHelpers.ts new file mode 100644 index 00000000..0df329c8 --- /dev/null +++ b/frontend/src/components/Table/TableHelpers.ts @@ -0,0 +1,64 @@ +export interface TablePagination { + limit: number; + offset: number; + total: number; +} + +export interface TableSortBy { + id: string; + desc: boolean; +} + +export interface TableFilter { + id: string; + value: any; +} + +const tableEvents = { + FILTERS_CHANGED: "FILTERS_CHANGED", + PAGE_CHANGED: "PAGE_CHANGED", + PAGE_SIZE_CHANGED: "PAGE_SIZE_CHANGED", + TOTAL_COUNT_CHANGED: "TOTAL_COUNT_CHANGED", + SORT_CHANGED: "SORT_CHANGED", +}; + +const tableEventReducer = (state: any, { type, payload }: any) => { + let offset = state.offset; + switch (type) { + case tableEvents.PAGE_CHANGED: + return { + ...state, + offset: payload * state.limit, + }; + case tableEvents.PAGE_SIZE_CHANGED: + return { + ...state, + limit: payload, + }; + case tableEvents.TOTAL_COUNT_CHANGED: + return { + ...state, + total: payload, + }; + case tableEvents.SORT_CHANGED: + return { + ...state, + sortBy: payload, + }; + case tableEvents.FILTERS_CHANGED: + if (state.filters !== payload) { + // this actually was a legit change + // sets to page 1 when filter is modified + offset = 0; + } + return { + ...state, + filters: payload, + offset, + }; + default: + throw new Error(`Unhandled action type: ${type}`); + } +}; + +export { tableEvents, tableEventReducer }; diff --git a/frontend/src/components/Table/TableLayout.tsx b/frontend/src/components/Table/TableLayout.tsx new file mode 100644 index 00000000..c23f50b3 --- /dev/null +++ b/frontend/src/components/Table/TableLayout.tsx @@ -0,0 +1,22 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; +import { TableBody } from "./TableBody"; +import { TableHeader } from "./TableHeader"; + +interface TableLayoutProps { + tableInstance: ReactTable; + emptyState?: React.ReactNode; + extraStyles?: { + row: (rowData: TFields) => any | undefined; + }; +} +function TableLayout(props: TableLayoutProps) { + const hasRows = props.tableInstance.getRowModel().rows.length > 0; + return ( + + {hasRows ? : null} + +
    + ); +} + +export { TableLayout, type TableLayoutProps }; diff --git a/frontend/src/components/Table/index.ts b/frontend/src/components/Table/index.ts new file mode 100644 index 00000000..d471d45d --- /dev/null +++ b/frontend/src/components/Table/index.ts @@ -0,0 +1,4 @@ +export * from "./Formatter"; +export * from "./TableHeader"; +export * from "./TableHelpers"; +export * from "./TableLayout"; diff --git a/frontend/src/components/ThemeSwitcher.module.css b/frontend/src/components/ThemeSwitcher.module.css new file mode 100644 index 00000000..2233d2f8 --- /dev/null +++ b/frontend/src/components/ThemeSwitcher.module.css @@ -0,0 +1,15 @@ +.darkBtn { + color: var(--tblr-light) !important; + &:hover { + border: var(--tblr-btn-border-width) solid transparent !important; + background: color-mix(in srgb, var(--tblr-btn-hover-bg) 10%, transparent) !important; + } +} + +.lightBtn { + color: var(--tblr-dark) !important; + &:hover { + border: var(--tblr-btn-border-width) solid transparent !important; + background: color-mix(in srgb, var(--tblr-btn-hover-bg) 10%, transparent) !important; + } +} diff --git a/frontend/src/components/ThemeSwitcher.tsx b/frontend/src/components/ThemeSwitcher.tsx new file mode 100644 index 00000000..9cb66978 --- /dev/null +++ b/frontend/src/components/ThemeSwitcher.tsx @@ -0,0 +1,41 @@ +import { IconMoon, IconSun } from "@tabler/icons-react"; +import cn from "classnames"; +import { Button } from "src/components"; +import { useTheme } from "src/hooks"; +import styles from "./ThemeSwitcher.module.css"; + +interface Props { + className?: string; +} +function ThemeSwitcher({ className }: Props) { + const { setTheme } = useTheme(); + + return ( +
    + + +
    + ); +} + +export { ThemeSwitcher }; diff --git a/frontend/src/components/Unhealthy.tsx b/frontend/src/components/Unhealthy.tsx new file mode 100644 index 00000000..d4b6e33c --- /dev/null +++ b/frontend/src/components/Unhealthy.tsx @@ -0,0 +1,17 @@ +import { Page } from "src/components"; + +export function Unhealthy() { + return ( + +
    +
    +
    + +
    +

    The API is not healthy.

    +

    We'll keep checking and hope to be back soon!

    +
    +
    +
    + ); +} diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts new file mode 100644 index 00000000..861f637e --- /dev/null +++ b/frontend/src/components/index.ts @@ -0,0 +1,18 @@ +export * from "./Button"; +export * from "./EmptyData"; +export * from "./ErrorNotFound"; +export * from "./Flag"; +export * from "./Form"; +export * from "./HasPermission"; +export * from "./Loading"; +export * from "./LoadingPage"; +export * from "./LocalePicker"; +export * from "./NavLink"; +export * from "./Page"; +export * from "./SiteContainer"; +export * from "./SiteFooter"; +export * from "./SiteHeader"; +export * from "./SiteMenu"; +export * from "./Table"; +export * from "./ThemeSwitcher"; +export * from "./Unhealthy"; diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx new file mode 100644 index 00000000..a0284f60 --- /dev/null +++ b/frontend/src/context/AuthContext.tsx @@ -0,0 +1,86 @@ +import { useQueryClient } from "@tanstack/react-query"; +import { createContext, type ReactNode, useContext, useState } from "react"; +import { useIntervalWhen } from "rooks"; +import { getToken, loginAsUser, refreshToken, type TokenResponse } from "src/api/backend"; +import AuthStore from "src/modules/AuthStore"; + +// Context +export interface AuthContextType { + authenticated: boolean; + login: (username: string, password: string) => Promise; + loginAs: (id: number) => Promise; + logout: () => void; + token?: string; +} + +const initalValue = null; +const AuthContext = createContext(initalValue); + +// Provider +interface Props { + children?: ReactNode; + tokenRefreshInterval?: number; +} +function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props) { + const queryClient = useQueryClient(); + const [authenticated, setAuthenticated] = useState(AuthStore.hasActiveToken()); + + const handleTokenUpdate = (response: TokenResponse) => { + AuthStore.set(response); + setAuthenticated(true); + }; + + const login = async (identity: string, secret: string) => { + const response = await getToken(identity, secret); + handleTokenUpdate(response); + }; + + const loginAs = async (id: number) => { + const response = await loginAsUser(id); + AuthStore.add(response); + queryClient.clear(); + window.location.reload(); + }; + + const logout = () => { + if (AuthStore.count() >= 2) { + AuthStore.drop(); + queryClient.clear(); + window.location.reload(); + return; + } + AuthStore.clear(); + setAuthenticated(false); + queryClient.clear(); + }; + + const refresh = async () => { + const response = await refreshToken(); + handleTokenUpdate(response); + }; + + useIntervalWhen( + () => { + if (authenticated) { + refresh(); + } + }, + tokenRefreshInterval, + true, + ); + + const value = { authenticated, login, logout, loginAs }; + + return {children}; +} + +function useAuthState() { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuthState must be used within a AuthProvider"); + } + return context; +} + +export { AuthProvider, useAuthState }; +export default AuthContext; diff --git a/frontend/src/context/LocaleContext.tsx b/frontend/src/context/LocaleContext.tsx new file mode 100644 index 00000000..ca30a5f3 --- /dev/null +++ b/frontend/src/context/LocaleContext.tsx @@ -0,0 +1,38 @@ +import { createContext, type ReactNode, useContext, useState } from "react"; +import { getLocale } from "src/locale"; + +// Context +export interface LocaleContextType { + setLocale: (locale: string) => void; + locale?: string; +} + +const initalValue = null; +const LocaleContext = createContext(initalValue); + +// Provider +interface Props { + children?: ReactNode; +} +function LocaleProvider({ children }: Props) { + const [locale, setLocaleValue] = useState(getLocale()); + + const setLocale = async (locale: string) => { + setLocaleValue(locale); + }; + + const value = { locale, setLocale }; + + return {children}; +} + +function useLocaleState() { + const context = useContext(LocaleContext); + if (!context) { + throw new Error("useLocaleState must be used within a LocaleProvider"); + } + return context; +} + +export { LocaleProvider, useLocaleState }; +export default LocaleContext; diff --git a/frontend/src/context/ThemeContext.tsx b/frontend/src/context/ThemeContext.tsx new file mode 100644 index 00000000..e01ad022 --- /dev/null +++ b/frontend/src/context/ThemeContext.tsx @@ -0,0 +1,70 @@ +import type React from "react"; +import { createContext, type ReactNode, useContext, useEffect, useState } from "react"; + +const StorageKey = "tabler-theme"; +export const Light = "light"; +export const Dark = "dark"; + +// Define theme types +export type Theme = "light" | "dark"; + +interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; + setTheme: (theme: Theme) => void; + getTheme: () => Theme; +} + +const ThemeContext = createContext(undefined); + +interface ThemeProviderProps { + children: ReactNode; +} + +const getBrowserDefault = (): Theme => { + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return Dark; + } + return Light; +}; + +export const ThemeProvider: React.FC = ({ children }) => { + const [theme, setThemeState] = useState(() => { + // Try to read theme from localStorage or use 'light' as default + if (typeof window !== "undefined") { + const stored = localStorage.getItem(StorageKey) as Theme | null; + return stored || getBrowserDefault(); + } + return getBrowserDefault(); + }); + + useEffect(() => { + document.body.dataset.theme = theme; + document.body.classList.remove(theme === Light ? Dark : Light); + document.body.classList.add(theme); + localStorage.setItem(StorageKey, theme); + }, [theme]); + + const toggleTheme = () => { + setThemeState((prev) => (prev === Light ? Dark : Light)); + }; + + const setTheme = (newTheme: Theme) => { + setThemeState(newTheme); + }; + + const getTheme = () => { + return theme; + }; + + document.documentElement.setAttribute("data-bs-theme", theme); + return {children}; +}; + +export function useTheme(): ThemeContextType { + const context = useContext(ThemeContext); + if (!context) { + throw new Error("useTheme must be used within a ThemeProvider"); + } + return context; +} diff --git a/frontend/src/context/index.ts b/frontend/src/context/index.ts new file mode 100644 index 00000000..a24e0657 --- /dev/null +++ b/frontend/src/context/index.ts @@ -0,0 +1,3 @@ +export * from "./AuthContext"; +export * from "./LocaleContext"; +export * from "./ThemeContext"; diff --git a/frontend/src/declarations.d.ts b/frontend/src/declarations.d.ts new file mode 100644 index 00000000..18f423bf --- /dev/null +++ b/frontend/src/declarations.d.ts @@ -0,0 +1 @@ +declare module "*.md"; diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts new file mode 100644 index 00000000..1ef445a8 --- /dev/null +++ b/frontend/src/hooks/index.ts @@ -0,0 +1,21 @@ +export * from "./useAccessList"; +export * from "./useAccessLists"; +export * from "./useAuditLog"; +export * from "./useAuditLogs"; +export * from "./useCertificate"; +export * from "./useCertificates"; +export * from "./useDeadHost"; +export * from "./useDeadHosts"; +export * from "./useDnsProviders"; +export * from "./useHealth"; +export * from "./useHostReport"; +export * from "./useProxyHost"; +export * from "./useProxyHosts"; +export * from "./useRedirectionHost"; +export * from "./useRedirectionHosts"; +export * from "./useSetting"; +export * from "./useStream"; +export * from "./useStreams"; +export * from "./useTheme"; +export * from "./useUser"; +export * from "./useUsers"; diff --git a/frontend/src/hooks/useAccessList.ts b/frontend/src/hooks/useAccessList.ts new file mode 100644 index 00000000..1f663858 --- /dev/null +++ b/frontend/src/hooks/useAccessList.ts @@ -0,0 +1,60 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + type AccessList, + type AccessListExpansion, + createAccessList, + getAccessList, + updateAccessList, +} from "src/api/backend"; + +const fetchAccessList = (id: number | "new", expand: AccessListExpansion[] = ["owner"]) => { + if (id === "new") { + return Promise.resolve({ + id: 0, + createdOn: "", + modifiedOn: "", + ownerUserId: 0, + name: "", + satisfyAny: false, + passAuth: false, + meta: {}, + } as AccessList); + } + return getAccessList(id, expand); +}; + +const useAccessList = (id: number | "new", expand?: AccessListExpansion[], options = {}) => { + return useQuery({ + queryKey: ["access-list", id, expand], + queryFn: () => fetchAccessList(id, expand), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetAccessList = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: AccessList) => (values.id ? updateAccessList(values) : createAccessList(values)), + onMutate: (values: AccessList) => { + if (!values.id) { + return; + } + const previousObject = queryClient.getQueryData(["access-list", values.id]); + queryClient.setQueryData(["access-list", values.id], (old: AccessList) => ({ + ...old, + ...values, + })); + return () => queryClient.setQueryData(["access-list", values.id], previousObject); + }, + onError: (_, __, rollback: any) => rollback(), + onSuccess: async ({ id }: AccessList) => { + queryClient.invalidateQueries({ queryKey: ["access-list", id] }); + queryClient.invalidateQueries({ queryKey: ["access-lists"] }); + queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); + queryClient.invalidateQueries({ queryKey: ["proxy-hosts"] }); + }, + }); +}; + +export { useAccessList, useSetAccessList }; diff --git a/frontend/src/hooks/useAccessLists.ts b/frontend/src/hooks/useAccessLists.ts new file mode 100644 index 00000000..cb052f68 --- /dev/null +++ b/frontend/src/hooks/useAccessLists.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type AccessList, type AccessListExpansion, getAccessLists } from "src/api/backend"; + +const fetchAccessLists = (expand?: AccessListExpansion[]) => { + return getAccessLists(expand); +}; + +const useAccessLists = (expand?: AccessListExpansion[], options = {}) => { + return useQuery({ + queryKey: ["access-lists", { expand }], + queryFn: () => fetchAccessLists(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchAccessLists, useAccessLists }; diff --git a/frontend/src/hooks/useAuditLog.ts b/frontend/src/hooks/useAuditLog.ts new file mode 100644 index 00000000..95e08ebc --- /dev/null +++ b/frontend/src/hooks/useAuditLog.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type AuditLog, getAuditLog } from "src/api/backend"; + +const fetchAuditLog = (id: number) => { + return getAuditLog(id, ["user"]); +}; + +const useAuditLog = (id: number, options = {}) => { + return useQuery({ + queryKey: ["audit-log", id], + queryFn: () => fetchAuditLog(id), + staleTime: 5 * 60 * 1000, // 5 minutes + ...options, + }); +}; + +export { useAuditLog }; diff --git a/frontend/src/hooks/useAuditLogs.ts b/frontend/src/hooks/useAuditLogs.ts new file mode 100644 index 00000000..bbe8b506 --- /dev/null +++ b/frontend/src/hooks/useAuditLogs.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type AuditLog, type AuditLogExpansion, getAuditLogs } from "src/api/backend"; + +const fetchAuditLogs = (expand?: AuditLogExpansion[]) => { + return getAuditLogs(expand); +}; + +const useAuditLogs = (expand?: AuditLogExpansion[], options = {}) => { + return useQuery({ + queryKey: ["audit-logs", { expand }], + queryFn: () => fetchAuditLogs(expand), + staleTime: 10 * 1000, + ...options, + }); +}; + +export { fetchAuditLogs, useAuditLogs }; diff --git a/frontend/src/hooks/useCertificate.ts b/frontend/src/hooks/useCertificate.ts new file mode 100644 index 00000000..fc99c840 --- /dev/null +++ b/frontend/src/hooks/useCertificate.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type Certificate, getCertificate } from "src/api/backend"; + +const fetchCertificate = (id: number) => { + return getCertificate(id, ["owner"]); +}; + +const useCertificate = (id: number, options = {}) => { + return useQuery({ + queryKey: ["certificate", id], + queryFn: () => fetchCertificate(id), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +export { useCertificate }; diff --git a/frontend/src/hooks/useCertificates.ts b/frontend/src/hooks/useCertificates.ts new file mode 100644 index 00000000..261c79d8 --- /dev/null +++ b/frontend/src/hooks/useCertificates.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type Certificate, type CertificateExpansion, getCertificates } from "src/api/backend"; + +const fetchCertificates = (expand?: CertificateExpansion[]) => { + return getCertificates(expand); +}; + +const useCertificates = (expand?: CertificateExpansion[], options = {}) => { + return useQuery({ + queryKey: ["certificates", { expand }], + queryFn: () => fetchCertificates(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchCertificates, useCertificates }; diff --git a/frontend/src/hooks/useDeadHost.ts b/frontend/src/hooks/useDeadHost.ts new file mode 100644 index 00000000..dd8355e3 --- /dev/null +++ b/frontend/src/hooks/useDeadHost.ts @@ -0,0 +1,60 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { createDeadHost, type DeadHost, getDeadHost, updateDeadHost } from "src/api/backend"; + +const fetchDeadHost = (id: number | "new") => { + if (id === "new") { + return Promise.resolve({ + id: 0, + createdOn: "", + modifiedOn: "", + ownerUserId: 0, + domainNames: [], + certificateId: 0, + sslForced: false, + advancedConfig: "", + meta: {}, + http2Support: false, + enabled: true, + hstsEnabled: false, + hstsSubdomains: false, + } as DeadHost); + } + return getDeadHost(id, ["owner"]); +}; + +const useDeadHost = (id: number | "new", options = {}) => { + return useQuery({ + queryKey: ["dead-host", id], + queryFn: () => fetchDeadHost(id), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetDeadHost = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: DeadHost) => (values.id ? updateDeadHost(values) : createDeadHost(values)), + onMutate: (values: DeadHost) => { + if (!values.id) { + return; + } + const previousObject = queryClient.getQueryData(["dead-host", values.id]); + queryClient.setQueryData(["dead-host", values.id], (old: DeadHost) => ({ + ...old, + ...values, + })); + return () => queryClient.setQueryData(["dead-host", values.id], previousObject); + }, + onError: (_, __, rollback: any) => rollback(), + onSuccess: async ({ id }: DeadHost) => { + queryClient.invalidateQueries({ queryKey: ["dead-host", id] }); + queryClient.invalidateQueries({ queryKey: ["dead-hosts"] }); + queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); + queryClient.invalidateQueries({ queryKey: ["host-report"] }); + queryClient.invalidateQueries({ queryKey: ["certificates"] }); + }, + }); +}; + +export { useDeadHost, useSetDeadHost }; diff --git a/frontend/src/hooks/useDeadHosts.ts b/frontend/src/hooks/useDeadHosts.ts new file mode 100644 index 00000000..744d28c4 --- /dev/null +++ b/frontend/src/hooks/useDeadHosts.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type DeadHost, getDeadHosts, type HostExpansion } from "src/api/backend"; + +const fetchDeadHosts = (expand?: HostExpansion[]) => { + return getDeadHosts(expand); +}; + +const useDeadHosts = (expand?: HostExpansion[], options = {}) => { + return useQuery({ + queryKey: ["dead-hosts", { expand }], + queryFn: () => fetchDeadHosts(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchDeadHosts, useDeadHosts }; diff --git a/frontend/src/hooks/useDnsProviders.ts b/frontend/src/hooks/useDnsProviders.ts new file mode 100644 index 00000000..ec6a5736 --- /dev/null +++ b/frontend/src/hooks/useDnsProviders.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type DNSProvider, getCertificateDNSProviders } from "src/api/backend"; + +const fetchDnsProviders = () => { + return getCertificateDNSProviders(); +}; + +const useDnsProviders = (options = {}) => { + return useQuery({ + queryKey: ["dns-providers"], + queryFn: () => fetchDnsProviders(), + staleTime: 300 * 1000, + ...options, + }); +}; + +export { fetchDnsProviders, useDnsProviders }; diff --git a/frontend/src/hooks/useHealth.ts b/frontend/src/hooks/useHealth.ts new file mode 100644 index 00000000..6a261c95 --- /dev/null +++ b/frontend/src/hooks/useHealth.ts @@ -0,0 +1,18 @@ +import { useQuery } from "@tanstack/react-query"; +import { getHealth, type HealthResponse } from "src/api/backend"; + +const fetchHealth = () => getHealth(); + +const useHealth = (options = {}) => { + return useQuery({ + queryKey: ["health"], + queryFn: fetchHealth, + refetchOnWindowFocus: false, + retry: 5, + refetchInterval: 15 * 1000, // 15 seconds + staleTime: 14 * 1000, // 14 seconds + ...options, + }); +}; + +export { fetchHealth, useHealth }; diff --git a/frontend/src/hooks/useHostReport.ts b/frontend/src/hooks/useHostReport.ts new file mode 100644 index 00000000..c54096d2 --- /dev/null +++ b/frontend/src/hooks/useHostReport.ts @@ -0,0 +1,18 @@ +import { useQuery } from "@tanstack/react-query"; +import { getHostsReport } from "src/api/backend"; + +const fetchHostReport = () => getHostsReport(); + +const useHostReport = (options = {}) => { + return useQuery, Error>({ + queryKey: ["host-report"], + queryFn: fetchHostReport, + refetchOnWindowFocus: false, + retry: 5, + refetchInterval: 15 * 1000, // 15 seconds + staleTime: 14 * 1000, // 14 seconds + ...options, + }); +}; + +export { fetchHostReport, useHostReport }; diff --git a/frontend/src/hooks/useProxyHost.ts b/frontend/src/hooks/useProxyHost.ts new file mode 100644 index 00000000..e6a2adea --- /dev/null +++ b/frontend/src/hooks/useProxyHost.ts @@ -0,0 +1,67 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { createProxyHost, getProxyHost, type ProxyHost, updateProxyHost } from "src/api/backend"; + +const fetchProxyHost = (id: number | "new") => { + if (id === "new") { + return Promise.resolve({ + id: 0, + createdOn: "", + modifiedOn: "", + ownerUserId: 0, + domainNames: [], + forwardHost: "", + forwardPort: 0, + accessListId: 0, + certificateId: 0, + sslForced: false, + cachingEnabled: false, + blockExploits: false, + advancedConfig: "", + meta: {}, + allowWebsocketUpgrade: false, + http2Support: false, + forwardScheme: "", + enabled: true, + hstsEnabled: false, + hstsSubdomains: false, + } as ProxyHost); + } + return getProxyHost(id, ["owner"]); +}; + +const useProxyHost = (id: number | "new", options = {}) => { + return useQuery({ + queryKey: ["proxy-host", id], + queryFn: () => fetchProxyHost(id), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetProxyHost = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: ProxyHost) => (values.id ? updateProxyHost(values) : createProxyHost(values)), + onMutate: (values: ProxyHost) => { + if (!values.id) { + return; + } + const previousObject = queryClient.getQueryData(["proxy-host", values.id]); + queryClient.setQueryData(["proxy-host", values.id], (old: ProxyHost) => ({ + ...old, + ...values, + })); + return () => queryClient.setQueryData(["proxy-host", values.id], previousObject); + }, + onError: (_, __, rollback: any) => rollback(), + onSuccess: async ({ id }: ProxyHost) => { + queryClient.invalidateQueries({ queryKey: ["proxy-host", id] }); + queryClient.invalidateQueries({ queryKey: ["proxy-hosts"] }); + queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); + queryClient.invalidateQueries({ queryKey: ["host-report"] }); + queryClient.invalidateQueries({ queryKey: ["certificates"] }); + }, + }); +}; + +export { useProxyHost, useSetProxyHost }; diff --git a/frontend/src/hooks/useProxyHosts.ts b/frontend/src/hooks/useProxyHosts.ts new file mode 100644 index 00000000..86366fef --- /dev/null +++ b/frontend/src/hooks/useProxyHosts.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { getProxyHosts, type ProxyHost, type ProxyHostExpansion } from "src/api/backend"; + +const fetchProxyHosts = (expand?: ProxyHostExpansion[]) => { + return getProxyHosts(expand); +}; + +const useProxyHosts = (expand?: ProxyHostExpansion[], options = {}) => { + return useQuery({ + queryKey: ["proxy-hosts", { expand }], + queryFn: () => fetchProxyHosts(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchProxyHosts, useProxyHosts }; diff --git a/frontend/src/hooks/useRedirectionHost.ts b/frontend/src/hooks/useRedirectionHost.ts new file mode 100644 index 00000000..ff212653 --- /dev/null +++ b/frontend/src/hooks/useRedirectionHost.ts @@ -0,0 +1,71 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + createRedirectionHost, + getRedirectionHost, + type RedirectionHost, + updateRedirectionHost, +} from "src/api/backend"; + +const fetchRedirectionHost = (id: number | "new") => { + if (id === "new") { + return Promise.resolve({ + id: 0, + createdOn: "", + modifiedOn: "", + ownerUserId: 0, + domainNames: [], + forwardDomainName: "", + preservePath: false, + certificateId: 0, + sslForced: false, + advancedConfig: "", + meta: {}, + http2Support: false, + forwardScheme: "auto", + forwardHttpCode: 301, + blockExploits: false, + enabled: true, + hstsEnabled: false, + hstsSubdomains: false, + } as RedirectionHost); + } + return getRedirectionHost(id, ["owner"]); +}; + +const useRedirectionHost = (id: number | "new", options = {}) => { + return useQuery({ + queryKey: ["redirection-host", id], + queryFn: () => fetchRedirectionHost(id), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetRedirectionHost = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: RedirectionHost) => + values.id ? updateRedirectionHost(values) : createRedirectionHost(values), + onMutate: (values: RedirectionHost) => { + if (!values.id) { + return; + } + const previousObject = queryClient.getQueryData(["redirection-host", values.id]); + queryClient.setQueryData(["redirection-host", values.id], (old: RedirectionHost) => ({ + ...old, + ...values, + })); + return () => queryClient.setQueryData(["redirection-host", values.id], previousObject); + }, + onError: (_, __, rollback: any) => rollback(), + onSuccess: async ({ id }: RedirectionHost) => { + queryClient.invalidateQueries({ queryKey: ["redirection-host", id] }); + queryClient.invalidateQueries({ queryKey: ["redirection-hosts"] }); + queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); + queryClient.invalidateQueries({ queryKey: ["host-report"] }); + queryClient.invalidateQueries({ queryKey: ["certificates"] }); + }, + }); +}; + +export { useRedirectionHost, useSetRedirectionHost }; diff --git a/frontend/src/hooks/useRedirectionHosts.ts b/frontend/src/hooks/useRedirectionHosts.ts new file mode 100644 index 00000000..3b7eda60 --- /dev/null +++ b/frontend/src/hooks/useRedirectionHosts.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { getRedirectionHosts, type HostExpansion, type RedirectionHost } from "src/api/backend"; + +const fetchRedirectionHosts = (expand?: HostExpansion[]) => { + return getRedirectionHosts(expand); +}; + +const useRedirectionHosts = (expand?: HostExpansion[], options = {}) => { + return useQuery({ + queryKey: ["redirection-hosts", { expand }], + queryFn: () => fetchRedirectionHosts(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchRedirectionHosts, useRedirectionHosts }; diff --git a/frontend/src/hooks/useSetting.ts b/frontend/src/hooks/useSetting.ts new file mode 100644 index 00000000..ce843d65 --- /dev/null +++ b/frontend/src/hooks/useSetting.ts @@ -0,0 +1,40 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { getSetting, type Setting, updateSetting } from "src/api/backend"; + +const fetchSetting = (id: string) => { + return getSetting(id); +}; + +const useSetting = (id: string, options = {}) => { + return useQuery({ + queryKey: ["setting", id], + queryFn: () => fetchSetting(id), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetSetting = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: Setting) => updateSetting(values), + onMutate: (values: Setting) => { + if (!values.id) { + return; + } + const previousObject = queryClient.getQueryData(["setting", values.id]); + queryClient.setQueryData(["setting", values.id], (old: Setting) => ({ + ...old, + ...values, + })); + return () => queryClient.setQueryData(["setting", values.id], previousObject); + }, + onError: (_, __, rollback: any) => rollback(), + onSuccess: async ({ id }: Setting) => { + queryClient.invalidateQueries({ queryKey: ["setting", id] }); + queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); + }, + }); +}; + +export { useSetting, useSetSetting }; diff --git a/frontend/src/hooks/useStream.ts b/frontend/src/hooks/useStream.ts new file mode 100644 index 00000000..dfdddc1a --- /dev/null +++ b/frontend/src/hooks/useStream.ts @@ -0,0 +1,56 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { createStream, getStream, type Stream, updateStream } from "src/api/backend"; + +const fetchStream = (id: number | "new") => { + if (id === "new") { + return Promise.resolve({ + id: 0, + createdOn: "", + modifiedOn: "", + ownerUserId: 0, + tcpForwarding: true, + udpForwarding: false, + meta: {}, + enabled: true, + certificateId: 0, + } as Stream); + } + return getStream(id, ["owner"]); +}; + +const useStream = (id: number | "new", options = {}) => { + return useQuery({ + queryKey: ["stream", id], + queryFn: () => fetchStream(id), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetStream = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: Stream) => (values.id ? updateStream(values) : createStream(values)), + onMutate: (values: Stream) => { + if (!values.id) { + return; + } + const previousObject = queryClient.getQueryData(["stream", values.id]); + queryClient.setQueryData(["stream", values.id], (old: Stream) => ({ + ...old, + ...values, + })); + return () => queryClient.setQueryData(["stream", values.id], previousObject); + }, + onError: (_, __, rollback: any) => rollback(), + onSuccess: async ({ id }: Stream) => { + queryClient.invalidateQueries({ queryKey: ["stream", id] }); + queryClient.invalidateQueries({ queryKey: ["streams"] }); + queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); + queryClient.invalidateQueries({ queryKey: ["host-report"] }); + queryClient.invalidateQueries({ queryKey: ["certificates"] }); + }, + }); +}; + +export { useStream, useSetStream }; diff --git a/frontend/src/hooks/useStreams.ts b/frontend/src/hooks/useStreams.ts new file mode 100644 index 00000000..0f0129de --- /dev/null +++ b/frontend/src/hooks/useStreams.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { getStreams, type HostExpansion, type Stream } from "src/api/backend"; + +const fetchStreams = (expand?: HostExpansion[]) => { + return getStreams(expand); +}; + +const useStreams = (expand?: HostExpansion[], options = {}) => { + return useQuery({ + queryKey: ["streams", { expand }], + queryFn: () => fetchStreams(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchStreams, useStreams }; diff --git a/frontend/src/hooks/useTheme.ts b/frontend/src/hooks/useTheme.ts new file mode 100644 index 00000000..941c0626 --- /dev/null +++ b/frontend/src/hooks/useTheme.ts @@ -0,0 +1,8 @@ +import { Dark, Light, useTheme as useThemeContext } from "src/context"; + +// Simple hook wrapper for clarity and scalability +const useTheme = () => { + return useThemeContext(); +}; + +export { useTheme, Dark, Light }; diff --git a/frontend/src/hooks/useUser.ts b/frontend/src/hooks/useUser.ts new file mode 100644 index 00000000..4f399490 --- /dev/null +++ b/frontend/src/hooks/useUser.ts @@ -0,0 +1,54 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { createUser, getUser, type User, updateUser } from "src/api/backend"; + +const fetchUser = (id: number | string) => { + if (id === "new") { + return Promise.resolve({ + id: 0, + createdOn: "", + modifiedOn: "", + isDisabled: false, + email: "", + name: "", + nickname: "", + roles: [], + avatar: "", + } as User); + } + return getUser(id, ["permissions"]); +}; + +const useUser = (id: string | number, options = {}) => { + return useQuery({ + queryKey: ["user", id], + queryFn: () => fetchUser(id), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetUser = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: User) => (values.id ? updateUser(values) : createUser(values)), + onMutate: (values: User) => { + if (!values.id) { + return; + } + const previousObject = queryClient.getQueryData(["user", values.id]); + queryClient.setQueryData(["user", values.id], (old: User) => ({ + ...old, + ...values, + })); + return () => queryClient.setQueryData(["user", values.id], previousObject); + }, + onError: (_, __, rollback: any) => rollback(), + onSuccess: async ({ id }: User) => { + queryClient.invalidateQueries({ queryKey: ["user", id] }); + queryClient.invalidateQueries({ queryKey: ["users"] }); + queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); + }, + }); +}; + +export { useUser, useSetUser }; diff --git a/frontend/src/hooks/useUsers.ts b/frontend/src/hooks/useUsers.ts new file mode 100644 index 00000000..8c4d802f --- /dev/null +++ b/frontend/src/hooks/useUsers.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { getUsers, type User, type UserExpansion } from "src/api/backend"; + +const fetchUsers = (expand?: UserExpansion[]) => { + return getUsers(expand); +}; + +const useUsers = (expand?: UserExpansion[], options = {}) => { + return useQuery({ + queryKey: ["users", { expand }], + queryFn: () => fetchUsers(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchUsers, useUsers }; diff --git a/frontend/src/locale/DateTimeFormat.ts b/frontend/src/locale/DateTimeFormat.ts new file mode 100644 index 00000000..fb8e66c8 --- /dev/null +++ b/frontend/src/locale/DateTimeFormat.ts @@ -0,0 +1,15 @@ +import { intlFormat, parseISO } from "date-fns"; + +const DateTimeFormat = (isoDate: string) => + intlFormat(parseISO(isoDate), { + weekday: "long", + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric", + hour12: true, + }); + +export { DateTimeFormat }; diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx new file mode 100644 index 00000000..46467d01 --- /dev/null +++ b/frontend/src/locale/IntlProvider.tsx @@ -0,0 +1,79 @@ +import { createIntl, createIntlCache } from "react-intl"; +import langEn from "./lang/en.json"; +import langList from "./lang/lang-list.json"; + +// first item of each array should be the language code, +// not the country code +// Remember when adding to this list, also update check-locales.js script +const localeOptions = [["en", "en-US"]]; + +const loadMessages = (locale?: string): typeof langList & typeof langEn => { + const thisLocale = locale || "en"; + switch (thisLocale.slice(0, 2)) { + default: + return Object.assign({}, langList, langEn); + } +}; + +const getFlagCodeForLocale = (locale?: string) => { + switch (locale) { + default: + return "EN"; + } +}; + +const getLocale = (short = false) => { + let loc = window.localStorage.getItem("locale"); + if (!loc) { + loc = document.documentElement.lang; + } + if (short) { + return loc.slice(0, 2); + } + return loc; +}; + +const cache = createIntlCache(); + +const initialMessages = loadMessages(getLocale()); +let intl = createIntl({ locale: getLocale(), messages: initialMessages }, cache); + +const changeLocale = (locale: string): void => { + const messages = loadMessages(locale); + intl = createIntl({ locale, messages }, cache); + window.localStorage.setItem("locale", locale); + document.documentElement.lang = locale; +}; + +// This is a translation component that wraps the translation in a span with a data +// attribute so devs can inspect the element to see the translation ID +const T = ({ + id, + data, + tData, +}: { + id: string; + data?: Record; + tData?: Record; +}) => { + const translatedData: Record = {}; + if (tData) { + // iterate over tData and translate each value + Object.entries(tData).forEach(([key, value]) => { + translatedData[key] = intl.formatMessage({ id: value }); + }); + } + return ( + + {intl.formatMessage( + { id }, + { + ...data, + ...translatedData, + }, + )} + + ); +}; + +export { localeOptions, getFlagCodeForLocale, getLocale, createIntl, changeLocale, intl, T }; diff --git a/frontend/src/locale/README.md b/frontend/src/locale/README.md new file mode 100644 index 00000000..2c3f9cd1 --- /dev/null +++ b/frontend/src/locale/README.md @@ -0,0 +1,48 @@ +# Internationalisation support + +## Before you start + +It's highly recommended that you spin up a development instance of this project +on your docker capable server. It's pretty easy: + +```bash +git clone https://github.com/NginxProxyManager/nginx-proxy-manager.git +cd nginx-proxy-manager +./scripts/start-dev -f +``` + +Then after a while, you can access http://yourserverip:3081 + +This stack will watch the file system for changes, especially to language files, +and reload the site you have open in the browser. + + +## Adding new translations + +Modify the files in the `src` folder. Follow the conventions already there. + +When the development stack is running, it will sort the locale lang files +for you when you save. + + +## After making changes + +If you're NOT running the development stack, you will need to run +`yarn locale-compile` in the `frontend` folder for +the new translations to be compiled into the `lang` folder. + + +## Adding a whole new language + +There's a fair bit you'll need to touch. Here's a list that may +not be complete by the time you're reading this: + +- frontend/src/locale/src/[yourlang].json +- frontend/src/locale/src/lang-list.json +- frontend/src/locale/src/HelpDoc/* +- frontend/src/locale/IntlProvider.tsx + + +## Checking for missing translations in languages + +Run `node check-locales.cjs` in this frontend folder. diff --git a/frontend/src/locale/index.ts b/frontend/src/locale/index.ts new file mode 100644 index 00000000..6d9ac03c --- /dev/null +++ b/frontend/src/locale/index.ts @@ -0,0 +1,2 @@ +export * from "./DateTimeFormat"; +export * from "./IntlProvider"; diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json new file mode 100644 index 00000000..0c67c8d5 --- /dev/null +++ b/frontend/src/locale/lang/en.json @@ -0,0 +1,214 @@ +{ + "access-list": "Access List", + "access-list.access-count": "{count} {count, plural, one {Rule} other {Rules}}", + "access-list.auth-count": "{count} {count, plural, one {User} other {Users}}", + "access-list.help-rules-last": "When at least 1 rule exists, this deny all rule will be added last", + "access-list.help.rules-order": "Note that the allow and deny directives will be applied in the order they are defined.", + "access-list.pass-auth": "Pass Auth to Upstream", + "access-list.public": "Publicly Accessible", + "access-list.public.subtitle": "No basic auth required", + "access-list.satisfy-any": "Satisfy Any", + "access-list.subtitle": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Rule} other {Rules}} - Created: {date}", + "access-lists": "Access Lists", + "action.add": "Add", + "action.add-location": "Add Location", + "action.close": "Close", + "action.delete": "Delete", + "action.disable": "Disable", + "action.download": "Download", + "action.edit": "Edit", + "action.enable": "Enable", + "action.permissions": "Permissions", + "action.renew": "Renew", + "action.view-details": "View Details", + "auditlogs": "Audit Logs", + "cancel": "Cancel", + "certificate": "Certificate", + "certificate.custom-certificate": "Certificate", + "certificate.custom-certificate-key": "Certificate Key", + "certificate.custom-intermediate": "Intermediate Certificate", + "certificate.in-use": "In Use", + "certificate.none.subtitle": "No certificate assigned", + "certificate.none.subtitle.for-http": "This host will not use HTTPS", + "certificate.none.title": "None", + "certificate.not-in-use": "Not Used", + "certificates": "Certificates", + "certificates.custom": "Custom Certificate", + "certificates.custom.warning": "Key files protected with a passphrase are not supported.", + "certificates.dns.credentials": "Credentials File Content", + "certificates.dns.credentials-note": "This plugin requires a configuration file containing an API token or other credentials for your provider", + "certificates.dns.credentials-warning": "This data will be stored as plaintext in the database and in a file!", + "certificates.dns.propagation-seconds": "Propagation Seconds", + "certificates.dns.propagation-seconds-note": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.", + "certificates.dns.provider": "DNS Provider", + "certificates.dns.warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.", + "certificates.http.reachability-404": "There is a server found at this domain but it does not seem to be Nginx Proxy Manager. Please make sure your domain points to the IP where your NPM instance is running.", + "certificates.http.reachability-failed-to-check": "Failed to check the reachability due to a communication error with site24x7.com.", + "certificates.http.reachability-not-resolved": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router.", + "certificates.http.reachability-ok": "Your server is reachable and creating certificates should be possible.", + "certificates.http.reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", + "certificates.http.reachability-wrong-data": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", + "certificates.http.test-results": "Test Results", + "certificates.http.warning": "These domains must be already configured to point to this installation.", + "certificates.request.subtitle": "with Let's Encrypt", + "certificates.request.title": "Request a new Certificate", + "column.access": "Access", + "column.authorization": "Authorization", + "column.authorizations": "Authorizations", + "column.custom-locations": "Custom Locations", + "column.destination": "Destination", + "column.details": "Details", + "column.email": "Email", + "column.event": "Event", + "column.expires": "Expires", + "column.http-code": "Access", + "column.incoming-port": "Incoming Port", + "column.name": "Name", + "column.protocol": "Protocol", + "column.provider": "Provider", + "column.roles": "Roles", + "column.rules": "Rules", + "column.satisfy": "Satisfy", + "column.satisfy-all": "All", + "column.satisfy-any": "Any", + "column.scheme": "Scheme", + "column.source": "Source", + "column.ssl": "SSL", + "column.status": "Status", + "created-on": "Created: {date}", + "dashboard": "Dashboard", + "dead-host": "404 Host", + "dead-hosts": "404 Hosts", + "dead-hosts.count": "{count} {count, plural, one {404 Host} other {404 Hosts}}", + "disabled": "Disabled", + "domain-names": "Domain Names", + "domain-names.max": "{count} domain names maximum", + "domain-names.placeholder": "Start typing to add domain...", + "domain-names.wildcards-not-permitted": "Wildcards not permitted for this type", + "domain-names.wildcards-not-supported": "Wildcards not supported for this CA", + "domains.force-ssl": "Force SSL", + "domains.hsts-enabled": "HSTS Enabled", + "domains.hsts-subdomains": "HSTS Sub-domains", + "domains.http2-support": "HTTP/2 Support", + "domains.use-dns": "Use DNS Challenge", + "email-address": "Email address", + "empty-search": "No results found", + "empty-subtitle": "Why don't you create one?", + "enabled": "Enabled", + "error.access.at-least-one": "Either one Authorization or one Access Rule is required", + "error.access.duplicate-usernames": "Authorization Usernames must be unique", + "error.invalid-auth": "Invalid email or password", + "error.invalid-domain": "Invalid domain: {domain}", + "error.invalid-email": "Invalid email address", + "error.max-character-length": "Maximum length is {max} character{max, plural, one {} other {s}}", + "error.max-domains": "Too many domains, max is {max}", + "error.maximum": "Maximum is {max}", + "error.min-character-length": "Minimum length is {min} character{min, plural, one {} other {s}}", + "error.minimum": "Minimum is {min}", + "error.passwords-must-match": "Passwords must match", + "error.required": "This is required", + "expires.on": "Expires: {date}", + "footer.github-fork": "Fork me on Github", + "host.flags.block-exploits": "Block Common Exploits", + "host.flags.cache-assets": "Cache Assets", + "host.flags.preserve-path": "Preserve Path", + "host.flags.protocols": "Protocols", + "host.flags.websockets-upgrade": "Websockets Support", + "host.forward-port": "Forward Port", + "host.forward-scheme": "Scheme", + "hosts": "Hosts", + "http-only": "HTTP Only", + "lets-encrypt": "Let's Encrypt", + "lets-encrypt-via-dns": "Let's Encrypt via DNS", + "lets-encrypt-via-http": "Let's Encrypt via HTTP", + "loading": "Loading…", + "login.title": "Login to your account", + "nginx-config.label": "Custom Nginx Configuration", + "nginx-config.placeholder": "# Enter your custom Nginx configuration here at your own risk!", + "no-permission-error": "You do not have access to view this.", + "notfound.action": "Take me home", + "notfound.content": "We are sorry but the page you are looking for was not found", + "notfound.title": "Oops… You just found an error page", + "notification.error": "Error", + "notification.object-deleted": "{object} has been deleted", + "notification.object-disabled": "{object} has been disabled", + "notification.object-enabled": "{object} has been enabled", + "notification.object-renewed": "{object} has been renewed", + "notification.object-saved": "{object} has been saved", + "notification.success": "Success", + "object.actions-title": "{object} #{id}", + "object.add": "Add {object}", + "object.delete": "Delete {object}", + "object.delete.content": "Are you sure you want to delete this {object}?", + "object.edit": "Edit {object}", + "object.empty": "There are no {objects}", + "object.event.created": "Created {object}", + "object.event.deleted": "Deleted {object}", + "object.event.disabled": "Disabled {object}", + "object.event.enabled": "Enabled {object}", + "object.event.renewed": "Renewed {object}", + "object.event.updated": "Updated {object}", + "offline": "Offline", + "online": "Online", + "options": "Options", + "password": "Password", + "password.generate": "Generate random password", + "password.hide": "Hide Password", + "password.show": "Show Password", + "permissions.hidden": "Hidden", + "permissions.manage": "Manage", + "permissions.view": "View Only", + "permissions.visibility.all": "All Items", + "permissions.visibility.title": "Item Visibility", + "permissions.visibility.user": "Created Items Only", + "proxy-host": "Proxy Host", + "proxy-host.forward-host": "Forward Hostname / IP", + "proxy-hosts": "Proxy Hosts", + "proxy-hosts.count": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}", + "public": "Public", + "redirection-host": "Redirection Host", + "redirection-host.forward-domain": "Forward Domain", + "redirection-hosts": "Redirection Hosts", + "redirection-hosts.count": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}", + "role.admin": "Administrator", + "role.standard-user": "Standard User", + "save": "Save", + "setting": "Setting", + "settings": "Settings", + "settings.default-site": "Default Site", + "settings.default-site.404": "404 Page", + "settings.default-site.444": "No Response (444)", + "settings.default-site.congratulations": "Congratulations Page", + "settings.default-site.description": "What to show when Nginx is hit with an unknown Host", + "settings.default-site.html": "Custom HTML", + "settings.default-site.html.placeholder": "", + "settings.default-site.redirect": "Redirect", + "setup.preamble": "Get started by creating your admin account.", + "setup.title": "Welcome!", + "sign-in": "Sign in", + "ssl-certificate": "SSL Certificate", + "stream": "Stream", + "stream.forward-host": "Forward Host", + "stream.incoming-port": "Incoming Port", + "streams": "Streams", + "streams.count": "{count} {count, plural, one {Stream} other {Streams}}", + "streams.tcp": "TCP", + "streams.udp": "UDP", + "test": "Test", + "user": "User", + "user.change-password": "Change Password", + "user.confirm-password": "Confirm Password", + "user.current-password": "Current Password", + "user.edit-profile": "Edit Profile", + "user.full-name": "Full Name", + "user.login-as": "Sign in as {name}", + "user.logout": "Logout", + "user.new-password": "New Password", + "user.nickname": "Nickname", + "user.set-password": "Set Password", + "user.set-permissions": "Set Permissions for {name}", + "user.switch-dark": "Switch to Dark mode", + "user.switch-light": "Switch to Light mode", + "username": "Username", + "users": "Users" +} \ No newline at end of file diff --git a/frontend/src/locale/lang/lang-list.json b/frontend/src/locale/lang/lang-list.json new file mode 100644 index 00000000..4d9de4e5 --- /dev/null +++ b/frontend/src/locale/lang/lang-list.json @@ -0,0 +1,3 @@ +{ + "locale-en-US": "English" +} \ No newline at end of file diff --git a/frontend/src/locale/scripts/locale-sort.sh b/frontend/src/locale/scripts/locale-sort.sh new file mode 100755 index 00000000..916d6e61 --- /dev/null +++ b/frontend/src/locale/scripts/locale-sort.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -e -o pipefail + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$DIR/../src" || exit 1 + +if ! command -v jq &> /dev/null; then + echo "jq could not be found, please install it to sort JSON files." + exit 1 +fi + +# iterate over all json files in the current directory +for file in *.json; do + if [[ -f "$file" ]]; then + if [[ ! -s "$file" ]]; then + echo "Skipping empty file $file" + continue + fi + + if [ "$file" == "lang-list.json" ]; then + continue + fi + + # get content of file before sorting + original_content=$(<"$file") + # compare with sorted content + sorted_content=$(jq --tab --sort-keys . "$file") + if [ "$original_content" == "$sorted_content" ]; then + echo "$file is already sorted" + continue + fi + + echo "Sorting $file" + jq --tab --sort-keys . "$file" | sponge "$file" + fi +done diff --git a/frontend/src/locale/src/HelpDoc/en/AccessLists.md b/frontend/src/locale/src/HelpDoc/en/AccessLists.md new file mode 100644 index 00000000..cef58263 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/en/AccessLists.md @@ -0,0 +1,7 @@ +## What is an Access List? + +Access Lists provide a blacklist or whitelist of specific client IP addresses along with authentication for the Proxy Hosts via Basic HTTP Authentication. + +You can configure multiple client rules, usernames and passwords for a single Access List and then apply that to one or more _Proxy Hosts_. + +This is most useful for forwarded web services that do not have authentication mechanisms built in or when you want to protect from unknown clients. diff --git a/frontend/src/locale/src/HelpDoc/en/Certificates.md b/frontend/src/locale/src/HelpDoc/en/Certificates.md new file mode 100644 index 00000000..d79dd04b --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/en/Certificates.md @@ -0,0 +1,32 @@ +## Certificates Help + +### HTTP Certificate + +A HTTP validated certificate means Let's Encrypt servers will +attempt to reach your domains over HTTP (not HTTPS!) and if successful, they +will issue your certificate. + +For this method, you will have to have a _Proxy Host_ created for your domains(s) that +is accessible with HTTP and pointing to this Nginx installation. After a certificate +has been given, you can modify the _Proxy Host_ to also use this certificate for HTTPS +connections. However, the _Proxy Host_ will still need to be configured for HTTP access +in order for the certificate to renew. + +This process _does not_ support wildcard domains. + +### DNS Certificate + +A DNS validated certificate requires you to use a DNS Provider plugin. This DNS +Provider will be used to create temporary records on your domain and then Let's +Encrypt will query those records to be sure you're the owner and if successful, they +will issue your certificate. + +You do not need a _Proxy Host_ to be created prior to requesting this type of +certificate. Nor do you need to have your _Proxy Host_ configured for HTTP access. + +This process _does_ support wildcard domains. + +### Custom Certificate + +Use this option to upload your own SSL Certificate, as provided by your own +Certificate Authority. diff --git a/frontend/src/locale/src/HelpDoc/en/DeadHosts.md b/frontend/src/locale/src/HelpDoc/en/DeadHosts.md new file mode 100644 index 00000000..ef4f3bc4 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/en/DeadHosts.md @@ -0,0 +1,10 @@ +## What is a 404 Host? + +A 404 Host is simply a host setup that shows a 404 page. + +This can be useful when your domain is listed in search engines and you want +to provide a nicer error page or specifically to tell the search indexers that +the domain pages no longer exist. + +Another benefit of having this host is to track the logs for hits to it and +view the referrers. diff --git a/frontend/src/locale/src/HelpDoc/en/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/en/ProxyHosts.md new file mode 100644 index 00000000..e9630d05 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/en/ProxyHosts.md @@ -0,0 +1,7 @@ +## What is a Proxy Host? + +A Proxy Host is the incoming endpoint for a web service that you want to forward. + +It provides optional SSL termination for your service that might not have SSL support built in. + +Proxy Hosts are the most common use for the Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/en/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/en/RedirectionHosts.md new file mode 100644 index 00000000..e57b1b80 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/en/RedirectionHosts.md @@ -0,0 +1,7 @@ +## What is a Redirection Host? + +A Redirection Host will redirect requests from the incoming domain and push the +viewer to another domain. + +The most common reason to use this type of host is when your website changes +domains but you still have search engine or referrer links pointing to the old domain. diff --git a/frontend/src/locale/src/HelpDoc/en/Streams.md b/frontend/src/locale/src/HelpDoc/en/Streams.md new file mode 100644 index 00000000..358f3e5a --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/en/Streams.md @@ -0,0 +1,6 @@ +## What is a Stream? + +A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP +traffic directly to another computer on the network. + +If you're running game servers, FTP or SSH servers this can come in handy. diff --git a/frontend/src/locale/src/HelpDoc/en/index.ts b/frontend/src/locale/src/HelpDoc/en/index.ts new file mode 100644 index 00000000..a9bb46ba --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/en/index.ts @@ -0,0 +1,6 @@ +export * as AccessLists from "./AccessLists.md"; +export * as Certificates from "./Certificates.md"; +export * as DeadHosts from "./DeadHosts.md"; +export * as ProxyHosts from "./ProxyHosts.md"; +export * as RedirectionHosts from "./RedirectionHosts.md"; +export * as Streams from "./Streams.md"; diff --git a/frontend/src/locale/src/HelpDoc/index.ts b/frontend/src/locale/src/HelpDoc/index.ts new file mode 100644 index 00000000..40ca1b52 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/index.ts @@ -0,0 +1,20 @@ +// import * as de from "./de/index"; +// import * as fa from "./fa/index"; +import * as en from "./en/index"; + +const items: any = { en }; + +const fallbackLang = "en"; + +export const getHelpFile = (lang: string, section: string): string => { + if (typeof items[lang] !== "undefined" && typeof items[lang][section] !== "undefined") { + return items[lang][section].default; + } + // Fallback to English + if (typeof items[fallbackLang] !== "undefined" && typeof items[fallbackLang][section] !== "undefined") { + return items[fallbackLang][section].default; + } + throw new Error(`Cannot load help doc for ${lang}-${section}`); +}; + +export default items; diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json new file mode 100644 index 00000000..57b5ac6d --- /dev/null +++ b/frontend/src/locale/src/en.json @@ -0,0 +1,638 @@ +{ + "access-list": { + "defaultMessage": "Access List" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Rule} other {Rules}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {User} other {Users}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "When at least 1 rule exists, this deny all rule will be added last" + }, + "access-list.help.rules-order": { + "defaultMessage": "Note that the allow and deny directives will be applied in the order they are defined." + }, + "access-list.pass-auth": { + "defaultMessage": "Pass Auth to Upstream" + }, + "access-list.public": { + "defaultMessage": "Publicly Accessible" + }, + "access-list.public.subtitle": { + "defaultMessage": "No basic auth required" + }, + "access-list.satisfy-any": { + "defaultMessage": "Satisfy Any" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Rule} other {Rules}} - Created: {date}" + }, + "access-lists": { + "defaultMessage": "Access Lists" + }, + "action.add": { + "defaultMessage": "Add" + }, + "action.add-location": { + "defaultMessage": "Add Location" + }, + "action.close": { + "defaultMessage": "Close" + }, + "action.delete": { + "defaultMessage": "Delete" + }, + "action.disable": { + "defaultMessage": "Disable" + }, + "action.download": { + "defaultMessage": "Download" + }, + "action.edit": { + "defaultMessage": "Edit" + }, + "action.enable": { + "defaultMessage": "Enable" + }, + "action.permissions": { + "defaultMessage": "Permissions" + }, + "action.renew": { + "defaultMessage": "Renew" + }, + "action.view-details": { + "defaultMessage": "View Details" + }, + "auditlogs": { + "defaultMessage": "Audit Logs" + }, + "cancel": { + "defaultMessage": "Cancel" + }, + "certificate": { + "defaultMessage": "Certificate" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certificate" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Certificate Key" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Intermediate Certificate" + }, + "certificate.in-use": { + "defaultMessage": "In Use" + }, + "certificate.none.subtitle": { + "defaultMessage": "No certificate assigned" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "This host will not use HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "None" + }, + "certificate.not-in-use": { + "defaultMessage": "Not Used" + }, + "certificates": { + "defaultMessage": "Certificates" + }, + "certificates.custom": { + "defaultMessage": "Custom Certificate" + }, + "certificates.custom.warning": { + "defaultMessage": "Key files protected with a passphrase are not supported." + }, + "certificates.dns.credentials": { + "defaultMessage": "Credentials File Content" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "This plugin requires a configuration file containing an API token or other credentials for your provider" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "This data will be stored as plaintext in the database and in a file!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Propagation Seconds" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation." + }, + "certificates.dns.provider": { + "defaultMessage": "DNS Provider" + }, + "certificates.dns.warning": { + "defaultMessage": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation." + }, + "certificates.http.reachability-404": { + "defaultMessage": "There is a server found at this domain but it does not seem to be Nginx Proxy Manager. Please make sure your domain points to the IP where your NPM instance is running." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Failed to check the reachability due to a communication error with site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Your server is reachable and creating certificates should be possible." + }, + "certificates.http.reachability-other": { + "defaultMessage": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running." + }, + "certificates.http.test-results": { + "defaultMessage": "Test Results" + }, + "certificates.http.warning": { + "defaultMessage": "These domains must be already configured to point to this installation." + }, + "certificates.request.subtitle": { + "defaultMessage": "with Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Request a new Certificate" + }, + "column.access": { + "defaultMessage": "Access" + }, + "column.authorization": { + "defaultMessage": "Authorization" + }, + "column.authorizations": { + "defaultMessage": "Authorizations" + }, + "column.custom-locations": { + "defaultMessage": "Custom Locations" + }, + "column.destination": { + "defaultMessage": "Destination" + }, + "column.details": { + "defaultMessage": "Details" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.event": { + "defaultMessage": "Event" + }, + "column.expires": { + "defaultMessage": "Expires" + }, + "column.http-code": { + "defaultMessage": "Access" + }, + "column.incoming-port": { + "defaultMessage": "Incoming Port" + }, + "column.name": { + "defaultMessage": "Name" + }, + "column.protocol": { + "defaultMessage": "Protocol" + }, + "column.provider": { + "defaultMessage": "Provider" + }, + "column.roles": { + "defaultMessage": "Roles" + }, + "column.rules": { + "defaultMessage": "Rules" + }, + "column.satisfy": { + "defaultMessage": "Satisfy" + }, + "column.satisfy-all": { + "defaultMessage": "All" + }, + "column.satisfy-any": { + "defaultMessage": "Any" + }, + "column.scheme": { + "defaultMessage": "Scheme" + }, + "column.source": { + "defaultMessage": "Source" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Status" + }, + "created-on": { + "defaultMessage": "Created: {date}" + }, + "dashboard": { + "defaultMessage": "Dashboard" + }, + "dead-host": { + "defaultMessage": "404 Host" + }, + "dead-hosts": { + "defaultMessage": "404 Hosts" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {404 Host} other {404 Hosts}}" + }, + "disabled": { + "defaultMessage": "Disabled" + }, + "domain-names": { + "defaultMessage": "Domain Names" + }, + "domain-names.max": { + "defaultMessage": "{count} domain names maximum" + }, + "domain-names.placeholder": { + "defaultMessage": "Start typing to add domain..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Wildcards not permitted for this type" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Wildcards not supported for this CA" + }, + "domains.force-ssl": { + "defaultMessage": "Force SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS Enabled" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS Sub-domains" + }, + "domains.http2-support": { + "defaultMessage": "HTTP/2 Support" + }, + "domains.use-dns": { + "defaultMessage": "Use DNS Challenge" + }, + "email-address": { + "defaultMessage": "Email address" + }, + "empty-search": { + "defaultMessage": "No results found" + }, + "empty-subtitle": { + "defaultMessage": "Why don't you create one?" + }, + "enabled": { + "defaultMessage": "Enabled" + }, + "error.access.at-least-one": { + "defaultMessage": "Either one Authorization or one Access Rule is required" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Authorization Usernames must be unique" + }, + "error.invalid-auth": { + "defaultMessage": "Invalid email or password" + }, + "error.invalid-domain": { + "defaultMessage": "Invalid domain: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Invalid email address" + }, + "error.max-character-length": { + "defaultMessage": "Maximum length is {max} character{max, plural, one {} other {s}}" + }, + "error.max-domains": { + "defaultMessage": "Too many domains, max is {max}" + }, + "error.maximum": { + "defaultMessage": "Maximum is {max}" + }, + "error.min-character-length": { + "defaultMessage": "Minimum length is {min} character{min, plural, one {} other {s}}" + }, + "error.minimum": { + "defaultMessage": "Minimum is {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Passwords must match" + }, + "error.required": { + "defaultMessage": "This is required" + }, + "expires.on": { + "defaultMessage": "Expires: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Fork me on Github" + }, + "host.flags.block-exploits": { + "defaultMessage": "Block Common Exploits" + }, + "host.flags.cache-assets": { + "defaultMessage": "Cache Assets" + }, + "host.flags.preserve-path": { + "defaultMessage": "Preserve Path" + }, + "host.flags.protocols": { + "defaultMessage": "Protocols" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Websockets Support" + }, + "host.forward-port": { + "defaultMessage": "Forward Port" + }, + "host.forward-scheme": { + "defaultMessage": "Scheme" + }, + "hosts": { + "defaultMessage": "Hosts" + }, + "http-only": { + "defaultMessage": "HTTP Only" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt via DNS" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt via HTTP" + }, + "loading": { + "defaultMessage": "Loading…" + }, + "login.title": { + "defaultMessage": "Login to your account" + }, + "nginx-config.label": { + "defaultMessage": "Custom Nginx Configuration" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Enter your custom Nginx configuration here at your own risk!" + }, + "no-permission-error": { + "defaultMessage": "You do not have access to view this." + }, + "notfound.action": { + "defaultMessage": "Take me home" + }, + "notfound.content": { + "defaultMessage": "We are sorry but the page you are looking for was not found" + }, + "notfound.title": { + "defaultMessage": "Oops… You just found an error page" + }, + "notification.error": { + "defaultMessage": "Error" + }, + "notification.object-deleted": { + "defaultMessage": "{object} has been deleted" + }, + "notification.object-disabled": { + "defaultMessage": "{object} has been disabled" + }, + "notification.object-enabled": { + "defaultMessage": "{object} has been enabled" + }, + "notification.object-renewed": { + "defaultMessage": "{object} has been renewed" + }, + "notification.object-saved": { + "defaultMessage": "{object} has been saved" + }, + "notification.success": { + "defaultMessage": "Success" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Add {object}" + }, + "object.delete": { + "defaultMessage": "Delete {object}" + }, + "object.delete.content": { + "defaultMessage": "Are you sure you want to delete this {object}?" + }, + "object.edit": { + "defaultMessage": "Edit {object}" + }, + "object.empty": { + "defaultMessage": "There are no {objects}" + }, + "object.event.created": { + "defaultMessage": "Created {object}" + }, + "object.event.deleted": { + "defaultMessage": "Deleted {object}" + }, + "object.event.disabled": { + "defaultMessage": "Disabled {object}" + }, + "object.event.enabled": { + "defaultMessage": "Enabled {object}" + }, + "object.event.renewed": { + "defaultMessage": "Renewed {object}" + }, + "object.event.updated": { + "defaultMessage": "Updated {object}" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "options": { + "defaultMessage": "Options" + }, + "password": { + "defaultMessage": "Password" + }, + "password.generate": { + "defaultMessage": "Generate random password" + }, + "password.hide": { + "defaultMessage": "Hide Password" + }, + "password.show": { + "defaultMessage": "Show Password" + }, + "permissions.hidden": { + "defaultMessage": "Hidden" + }, + "permissions.manage": { + "defaultMessage": "Manage" + }, + "permissions.view": { + "defaultMessage": "View Only" + }, + "permissions.visibility.all": { + "defaultMessage": "All Items" + }, + "permissions.visibility.title": { + "defaultMessage": "Item Visibility" + }, + "permissions.visibility.user": { + "defaultMessage": "Created Items Only" + }, + "proxy-host": { + "defaultMessage": "Proxy Host" + }, + "proxy-host.forward-host": { + "defaultMessage": "Forward Hostname / IP" + }, + "proxy-hosts": { + "defaultMessage": "Proxy Hosts" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}" + }, + "public": { + "defaultMessage": "Public" + }, + "redirection-host": { + "defaultMessage": "Redirection Host" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Forward Domain" + }, + "redirection-hosts": { + "defaultMessage": "Redirection Hosts" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}" + }, + "role.admin": { + "defaultMessage": "Administrator" + }, + "role.standard-user": { + "defaultMessage": "Standard User" + }, + "save": { + "defaultMessage": "Save" + }, + "setting": { + "defaultMessage": "Setting" + }, + "settings": { + "defaultMessage": "Settings" + }, + "settings.default-site": { + "defaultMessage": "Default Site" + }, + "settings.default-site.404": { + "defaultMessage": "404 Page" + }, + "settings.default-site.444": { + "defaultMessage": "No Response (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Congratulations Page" + }, + "settings.default-site.description": { + "defaultMessage": "What to show when Nginx is hit with an unknown Host" + }, + "settings.default-site.html": { + "defaultMessage": "Custom HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Redirect" + }, + "setup.preamble": { + "defaultMessage": "Get started by creating your admin account." + }, + "setup.title": { + "defaultMessage": "Welcome!" + }, + "sign-in": { + "defaultMessage": "Sign in" + }, + "ssl-certificate": { + "defaultMessage": "SSL Certificate" + }, + "stream": { + "defaultMessage": "Stream" + }, + "stream.forward-host": { + "defaultMessage": "Forward Host" + }, + "stream.incoming-port": { + "defaultMessage": "Incoming Port" + }, + "streams": { + "defaultMessage": "Streams" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Stream} other {Streams}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Test" + }, + "user": { + "defaultMessage": "User" + }, + "user.change-password": { + "defaultMessage": "Change Password" + }, + "user.confirm-password": { + "defaultMessage": "Confirm Password" + }, + "user.current-password": { + "defaultMessage": "Current Password" + }, + "user.edit-profile": { + "defaultMessage": "Edit Profile" + }, + "user.full-name": { + "defaultMessage": "Full Name" + }, + "user.login-as": { + "defaultMessage": "Sign in as {name}" + }, + "user.logout": { + "defaultMessage": "Logout" + }, + "user.new-password": { + "defaultMessage": "New Password" + }, + "user.nickname": { + "defaultMessage": "Nickname" + }, + "user.set-password": { + "defaultMessage": "Set Password" + }, + "user.set-permissions": { + "defaultMessage": "Set Permissions for {name}" + }, + "user.switch-dark": { + "defaultMessage": "Switch to Dark mode" + }, + "user.switch-light": { + "defaultMessage": "Switch to Light mode" + }, + "username": { + "defaultMessage": "Username" + }, + "users": { + "defaultMessage": "Users" + } +} diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json new file mode 100644 index 00000000..3f3b2697 --- /dev/null +++ b/frontend/src/locale/src/lang-list.json @@ -0,0 +1,5 @@ +{ + "locale-en-US": { + "defaultMessage": "English" + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 00000000..62c1c481 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "src/App.tsx"; + +import "@tabler/core/dist/css/tabler.min.css"; +import "@tabler/core/dist/js/tabler.min.js"; +import "./App.css"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + , +); diff --git a/frontend/src/modals/AccessListModal.tsx b/frontend/src/modals/AccessListModal.tsx new file mode 100644 index 00000000..79537f5c --- /dev/null +++ b/frontend/src/modals/AccessListModal.tsx @@ -0,0 +1,292 @@ +import cn from "classnames"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import type { AccessList, AccessListClient, AccessListItem } from "src/api/backend"; +import { AccessClientFields, BasicAuthFields, Button, Loading } from "src/components"; +import { useAccessList, useSetAccessList } from "src/hooks"; +import { intl, T } from "src/locale"; +import { validateString } from "src/modules/Validations"; +import { showObjectSuccess } from "src/notifications"; + +const showAccessListModal = (id: number | "new") => { + EasyModal.show(AccessListModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number | "new"; +} +const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => { + const { data, isLoading, error } = useAccessList(id, ["items", "clients"]); + const { mutate: setAccessList } = useSetAccessList(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const validate = (values: any): string | null => { + // either Auths or Clients must be defined + if (values.items?.length === 0 && values.clients?.length === 0) { + return intl.formatMessage({ id: "error.access.at-least-one" }); + } + + // ensure the items don't contain the same username twice + const usernames = values.items.map((i: any) => i.username); + const uniqueUsernames = Array.from(new Set(usernames)); + if (usernames.length !== uniqueUsernames.length) { + return intl.formatMessage({ id: "error.access.duplicate-usernames" }); + } + + return null; + }; + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + + const vErr = validate(values); + if (vErr) { + setErrorMsg(vErr); + return; + } + + setIsSubmitting(true); + setErrorMsg(null); + + const { ...payload } = { + id: id === "new" ? undefined : id, + ...values, + }; + + // Filter out "items" to only use the "username" and "password" fields + payload.items = (values.items || []).map((i: AccessListItem) => ({ + username: i.username, + password: i.password, + })); + + // Filter out "clients" to only use the "directive" and "address" fields + payload.clients = (values.clients || []).map((i: AccessListClient) => ({ + directive: i.directive, + address: i.address, + })); + + setAccessList(payload, { + onError: (err: any) => setErrorMsg(), + onSuccess: () => { + showObjectSuccess("access-list", "saved"); + remove(); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }); + }; + + const toggleClasses = "form-check-input"; + const toggleEnabled = cn(toggleClasses, "bg-cyan"); + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + {!isLoading && data && ( + + {({ setFieldValue }: any) => ( +
    + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
    +
    + +
    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.name ? ( +
    + {form.errors.name && form.touched.name + ? form.errors.name + : null} +
    + ) : null} +
    + )} +
    +
    +

    + +

    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + + + +
    + )} +
    + )} +
    + ); +}); + +export { showAccessListModal }; diff --git a/frontend/src/modals/ChangePasswordModal.tsx b/frontend/src/modals/ChangePasswordModal.tsx new file mode 100644 index 00000000..48221e28 --- /dev/null +++ b/frontend/src/modals/ChangePasswordModal.tsx @@ -0,0 +1,170 @@ +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { updateAuth } from "src/api/backend"; +import { Button } from "src/components"; +import { intl, T } from "src/locale"; +import { validateString } from "src/modules/Validations"; + +const showChangePasswordModal = (id: number | "me") => { + EasyModal.show(ChangePasswordModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number | "me"; +} +const ChangePasswordModal = EasyModal.create(({ id, visible, remove }: Props) => { + const [error, setError] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (values.new !== values.confirm) { + setError(); + setSubmitting(false); + return; + } + + if (isSubmitting) return; + setIsSubmitting(true); + setError(null); + + try { + await updateAuth(id, values.new, values.current); + remove(); + } catch (err: any) { + setError(); + } + setIsSubmitting(false); + setSubmitting(false); + }; + + return ( + + + {() => ( +
    + + + + + + + setError(null)} dismissible> + {error} + +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.name ? ( +
    + {form.errors.current && form.touched.current + ? form.errors.current + : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.new ? ( +
    + {form.errors.new && form.touched.new ? form.errors.new : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + {form.errors.confirm ? ( +
    + {form.errors.confirm && form.touched.confirm + ? form.errors.confirm + : null} +
    + ) : null} + +
    + )} +
    +
    +
    + + + + +
    + )} +
    +
    + ); +}); + +export { showChangePasswordModal }; diff --git a/frontend/src/modals/CustomCertificateModal.tsx b/frontend/src/modals/CustomCertificateModal.tsx new file mode 100644 index 00000000..7fe2f4bd --- /dev/null +++ b/frontend/src/modals/CustomCertificateModal.tsx @@ -0,0 +1,232 @@ +import { IconAlertTriangle } from "@tabler/icons-react"; +import { useQueryClient } from "@tanstack/react-query"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { type Certificate, createCertificate, uploadCertificate, validateCertificate } from "src/api/backend"; +import { Button } from "src/components"; +import { T } from "src/locale"; +import { validateString } from "src/modules/Validations"; +import { showObjectSuccess } from "src/notifications"; + +const showCustomCertificateModal = () => { + EasyModal.show(CustomCertificateModal); +}; + +const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModalProps) => { + const queryClient = useQueryClient(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + try { + const { niceName, provider, certificate, certificateKey, intermediateCertificate } = values; + const formData = new FormData(); + + formData.append("certificate", certificate); + formData.append("certificate_key", certificateKey); + if (intermediateCertificate !== null) { + formData.append("intermediate_certificate", intermediateCertificate); + } + + // Validate + await validateCertificate(formData); + + // Create certificate, as other without anything else + const cert = await createCertificate({ niceName, provider } as Certificate); + + // Upload the certificates to the created certificate + await uploadCertificate(cert.id, formData); + + // Success + showObjectSuccess("certificate", "saved"); + remove(); + } catch (err: any) { + setErrorMsg(); + } + + queryClient.invalidateQueries({ queryKey: ["certificates"] }); + setIsSubmitting(false); + setSubmitting(false); + }; + + return ( + + + {() => ( +
    + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
    +
    +

    + + +

    + + {({ field, form }: any) => ( +
    + + + {form.errors.niceName ? ( +
    + {form.errors.niceName && form.touched.niceName + ? form.errors.niceName + : null} +
    + ) : null} +
    + )} +
    + + {({ field, form }: any) => ( +
    + + { + form.setFieldValue( + field.name, + event.currentTarget.files?.length + ? event.currentTarget.files[0] + : null, + ); + }} + /> + {form.errors.certificateKey ? ( +
    + {form.errors.certificateKey && form.touched.certificateKey + ? form.errors.certificateKey + : null} +
    + ) : null} +
    + )} +
    + + {({ field, form }: any) => ( +
    + + { + form.setFieldValue( + field.name, + event.currentTarget.files?.length + ? event.currentTarget.files[0] + : null, + ); + }} + /> + {form.errors.certificate ? ( +
    + {form.errors.certificate && form.touched.certificate + ? form.errors.certificate + : null} +
    + ) : null} +
    + )} +
    + + {({ field, form }: any) => ( +
    + + { + form.setFieldValue( + field.name, + event.currentTarget.files?.length + ? event.currentTarget.files[0] + : null, + ); + }} + /> + {form.errors.intermediateCertificate ? ( +
    + {form.errors.intermediateCertificate && + form.touched.intermediateCertificate + ? form.errors.intermediateCertificate + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    + + + + +
    + )} +
    +
    + ); +}); + +export { showCustomCertificateModal }; diff --git a/frontend/src/modals/DNSCertificateModal.tsx b/frontend/src/modals/DNSCertificateModal.tsx new file mode 100644 index 00000000..3c97fddd --- /dev/null +++ b/frontend/src/modals/DNSCertificateModal.tsx @@ -0,0 +1,92 @@ +import { useQueryClient } from "@tanstack/react-query"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { createCertificate } from "src/api/backend"; +import { Button, DNSProviderFields, DomainNamesField } from "src/components"; +import { T } from "src/locale"; +import { showObjectSuccess } from "src/notifications"; + +const showDNSCertificateModal = () => { + EasyModal.show(DNSCertificateModal); +}; + +const DNSCertificateModal = EasyModal.create(({ visible, remove }: InnerModalProps) => { + const queryClient = useQueryClient(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + try { + await createCertificate(values); + showObjectSuccess("certificate", "saved"); + remove(); + } catch (err: any) { + setErrorMsg(); + } + queryClient.invalidateQueries({ queryKey: ["certificates"] }); + setIsSubmitting(false); + setSubmitting(false); + }; + + return ( + + + {() => ( +
    + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
    +
    + + +
    +
    +
    + + + + +
    + )} +
    +
    + ); +}); + +export { showDNSCertificateModal }; diff --git a/frontend/src/modals/DeadHostModal.tsx b/frontend/src/modals/DeadHostModal.tsx new file mode 100644 index 00000000..c9dce9a1 --- /dev/null +++ b/frontend/src/modals/DeadHostModal.tsx @@ -0,0 +1,174 @@ +import { IconSettings } from "@tabler/icons-react"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { + Button, + DomainNamesField, + Loading, + NginxConfigField, + SSLCertificateField, + SSLOptionsFields, +} from "src/components"; +import { useDeadHost, useSetDeadHost } from "src/hooks"; +import { T } from "src/locale"; +import { showObjectSuccess } from "src/notifications"; + +const showDeadHostModal = (id: number | "new") => { + EasyModal.show(DeadHostModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number | "new"; +} +const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => { + const { data, isLoading, error } = useDeadHost(id); + const { mutate: setDeadHost } = useSetDeadHost(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + const { ...payload } = { + id: id === "new" ? undefined : id, + ...values, + }; + + setDeadHost(payload, { + onError: (err: any) => setErrorMsg(), + onSuccess: () => { + showObjectSuccess("dead-host", "saved"); + remove(); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }); + }; + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + {!isLoading && data && ( + + {() => ( +
    + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
    +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + + + + +
    + )} +
    + )} +
    + ); +}); + +export { showDeadHostModal }; diff --git a/frontend/src/modals/DeleteConfirmModal.tsx b/frontend/src/modals/DeleteConfirmModal.tsx new file mode 100644 index 00000000..fb89f052 --- /dev/null +++ b/frontend/src/modals/DeleteConfirmModal.tsx @@ -0,0 +1,98 @@ +import { useQueryClient } from "@tanstack/react-query"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { Button } from "src/components"; +import { T } from "src/locale"; + +interface ShowProps { + title?: ReactNode; + tTitle?: string; + children: ReactNode; + onConfirm: () => Promise | void; + invalidations?: any[]; +} + +interface Props extends InnerModalProps, ShowProps {} + +const showDeleteConfirmModal = (props: ShowProps) => { + EasyModal.show(DeleteConfirmModal, props); +}; + +const DeleteConfirmModal = EasyModal.create( + ({ title, tTitle, children, onConfirm, invalidations, visible, remove }: Props) => { + const queryClient = useQueryClient(); + const [error, setError] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async () => { + if (isSubmitting) return; + setIsSubmitting(true); + setError(null); + try { + await onConfirm(); + remove(); + // invalidate caches as requested + invalidations?.forEach((inv) => { + queryClient.invalidateQueries({ queryKey: inv }); + }); + } catch (err: any) { + setError(); + } + setIsSubmitting(false); + }; + + return ( + + + {tTitle ? : title ? title : null} + + + setError(null)} dismissible> + {error} + +
    + + + + + +
    +
    {children}
    +
    + + + + +
    + ); + }, +); + +export { showDeleteConfirmModal }; diff --git a/frontend/src/modals/EventDetailsModal.tsx b/frontend/src/modals/EventDetailsModal.tsx new file mode 100644 index 00000000..7cf68487 --- /dev/null +++ b/frontend/src/modals/EventDetailsModal.tsx @@ -0,0 +1,72 @@ +import CodeEditor from "@uiw/react-textarea-code-editor"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { Button, EventFormatter, GravatarFormatter, Loading } from "src/components"; +import { useAuditLog } from "src/hooks"; +import { T } from "src/locale"; + +const showEventDetailsModal = (id: number) => { + EasyModal.show(EventDetailsModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number; +} +const EventDetailsModal = EasyModal.create(({ id, visible, remove }: Props) => { + const { data, isLoading, error } = useAuditLog(id); + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + {!isLoading && data && ( + <> + + + + + + +
    +
    + +
    +
    + +
    +
    + +
    +
    + + + + + )} +
    + ); +}); + +export { showEventDetailsModal }; diff --git a/frontend/src/modals/HTTPCertificateModal.tsx b/frontend/src/modals/HTTPCertificateModal.tsx new file mode 100644 index 00000000..b996a7d6 --- /dev/null +++ b/frontend/src/modals/HTTPCertificateModal.tsx @@ -0,0 +1,191 @@ +import { IconAlertTriangle } from "@tabler/icons-react"; +import { useQueryClient } from "@tanstack/react-query"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { createCertificate, testHttpCertificate } from "src/api/backend"; +import { Button, DomainNamesField } from "src/components"; +import { T } from "src/locale"; +import { showObjectSuccess } from "src/notifications"; + +const showHTTPCertificateModal = () => { + EasyModal.show(HTTPCertificateModal); +}; + +const HTTPCertificateModal = EasyModal.create(({ visible, remove }: InnerModalProps) => { + const queryClient = useQueryClient(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + const [domains, setDomains] = useState([] as string[]); + const [isTesting, setIsTesting] = useState(false); + const [testResults, setTestResults] = useState(null as Record | null); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + try { + await createCertificate(values); + showObjectSuccess("certificate", "saved"); + remove(); + } catch (err: any) { + setErrorMsg(); + } + queryClient.invalidateQueries({ queryKey: ["certificates"] }); + setIsSubmitting(false); + setSubmitting(false); + }; + + const handleTest = async () => { + setIsTesting(true); + setErrorMsg(null); + setTestResults(null); + try { + const result = await testHttpCertificate(domains); + setTestResults(result); + } catch (err: any) { + setErrorMsg(); + } + setIsTesting(false); + }; + + const parseTestResults = () => { + const elms = []; + for (const domain in testResults) { + const status = testResults[domain]; + if (status === "ok") { + elms.push( +

    + {domain}: +

    , + ); + } else { + if (status === "no-host") { + elms.push( +

    + {domain}: +

    , + ); + } else if (status === "failed") { + elms.push( +

    + {domain}: +

    , + ); + } else if (status === "404") { + elms.push( +

    + {domain}: +

    , + ); + } else if (status === "wrong-data") { + elms.push( +

    + {domain}: +

    , + ); + } else if (status.startsWith("other:")) { + const code = status.substring(6); + elms.push( +

    + {domain}: +

    , + ); + } else { + // This should never happen + elms.push( +

    + {domain}: ? +

    , + ); + } + } + } + + return <>{elms}; + }; + + return ( + + + {() => ( +
    + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
    +
    +

    + + +

    + { + setDomains(doms); + setTestResults(null); + }} + /> +
    + {testResults ? ( +
    +
    + +
    + {parseTestResults()} +
    + ) : null} +
    +
    + + +
    + + +
    +
    +
    + )} +
    +
    + ); +}); + +export { showHTTPCertificateModal }; diff --git a/frontend/src/modals/HelpModal.tsx b/frontend/src/modals/HelpModal.tsx new file mode 100644 index 00000000..f0581614 --- /dev/null +++ b/frontend/src/modals/HelpModal.tsx @@ -0,0 +1,54 @@ +import cn from "classnames"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { useEffect, useState } from "react"; +import Modal from "react-bootstrap/Modal"; +import ReactMarkdown from "react-markdown"; +import { Button } from "src/components"; +import { getLocale, T } from "src/locale"; +import { getHelpFile } from "src/locale/src/HelpDoc"; + +interface Props extends InnerModalProps { + section: string; + color?: string; +} + +const showHelpModal = (section: string, color?: string) => { + EasyModal.show(HelpModal, { section, color }); +}; + +const HelpModal = EasyModal.create(({ section, color, visible, remove }: Props) => { + const [markdownText, setMarkdownText] = useState(""); + const lang = getLocale(true); + + useEffect(() => { + try { + const docFile = getHelpFile(lang, section) as any; + fetch(docFile) + .then((response) => response.text()) + .then(setMarkdownText); + } catch (ex: any) { + setMarkdownText(`**ERROR:** ${ex.message}`); + } + }, [lang, section]); + + return ( + + + {markdownText} + + + + + + ); +}); + +export { showHelpModal }; diff --git a/frontend/src/modals/PermissionsModal.module.css b/frontend/src/modals/PermissionsModal.module.css new file mode 100644 index 00000000..6ef4265f --- /dev/null +++ b/frontend/src/modals/PermissionsModal.module.css @@ -0,0 +1,4 @@ +.active { + border-color: var(--tblr-orange) !important; +} + diff --git a/frontend/src/modals/PermissionsModal.tsx b/frontend/src/modals/PermissionsModal.tsx new file mode 100644 index 00000000..d363de9e --- /dev/null +++ b/frontend/src/modals/PermissionsModal.tsx @@ -0,0 +1,287 @@ +import { useQueryClient } from "@tanstack/react-query"; +import cn from "classnames"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { setPermissions } from "src/api/backend"; +import { Button, Loading } from "src/components"; +import { useUser } from "src/hooks"; +import { T } from "src/locale"; +import styles from "./PermissionsModal.module.css"; + +const showPermissionsModal = (id: number) => { + EasyModal.show(PermissionsModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number; +} +const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => { + const queryClient = useQueryClient(); + const [errorMsg, setErrorMsg] = useState(null); + const { data, isLoading, error } = useUser(id); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + try { + await setPermissions(id, values); + remove(); + queryClient.invalidateQueries({ queryKey: ["users"] }); + queryClient.invalidateQueries({ queryKey: ["user"] }); + } catch (err: any) { + setErrorMsg(); + } + setSubmitting(false); + setIsSubmitting(false); + }; + + const getClasses = (active: boolean) => { + return cn("btn", active ? styles.active : null, { + active, + "bg-orange-lt": active, + }); + }; + + // given the field and clicked permission, intelligently set the value, and + // other values that depends on it. + const handleChange = (form: any, field: any, perm: string) => { + if (field.name === "proxyHosts" && perm !== "hidden" && form.values.accessLists === "hidden") { + form.setFieldValue("accessLists", "view"); + } + // certs are required for proxy and redirection hosts, and streams + if ( + ["proxyHosts", "redirectionHosts", "deadHosts", "streams"].includes(field.name) && + perm !== "hidden" && + form.values.certificates === "hidden" + ) { + form.setFieldValue("certificates", "view"); + } + + form.setFieldValue(field.name, perm); + }; + + const getPermissionButtons = (field: any, form: any) => { + const isManage = field.value === "manage"; + const isView = field.value === "view"; + const isHidden = field.value === "hidden"; + + let hiddenDisabled = false; + if (field.name === "accessLists") { + hiddenDisabled = form.values.proxyHosts !== "hidden"; + } + if (field.name === "certificates") { + hiddenDisabled = + form.values.proxyHosts !== "hidden" || + form.values.redirectionHosts !== "hidden" || + form.values.deadHosts !== "hidden" || + form.values.streams !== "hidden"; + } + + return ( +
    +
    + handleChange(form, field, "manage")} + /> + + handleChange(form, field, "view")} + /> + + handleChange(form, field, "hidden")} + /> + +
    +
    + ); + }; + + const isAdmin = data?.roles.indexOf("admin") !== -1; + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + {!isLoading && data && ( + + {() => ( +
    + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
    + + + {({ field, form }: any) => ( +
    + form.setFieldValue(field.name, "user")} + /> + + form.setFieldValue(field.name, "all")} + /> + +
    + )} +
    +
    + {!isAdmin && ( + <> +
    + + + {({ field, form }: any) => getPermissionButtons(field, form)} + +
    +
    + + + {({ field, form }: any) => getPermissionButtons(field, form)} + +
    +
    + + + {({ field, form }: any) => getPermissionButtons(field, form)} + +
    +
    + + + {({ field, form }: any) => getPermissionButtons(field, form)} + +
    +
    + + + {({ field, form }: any) => getPermissionButtons(field, form)} + +
    +
    + + + {({ field, form }: any) => getPermissionButtons(field, form)} + +
    + + )} +
    + + + + +
    + )} +
    + )} +
    + ); +}); + +export { showPermissionsModal }; diff --git a/frontend/src/modals/ProxyHostModal.tsx b/frontend/src/modals/ProxyHostModal.tsx new file mode 100644 index 00000000..ca322849 --- /dev/null +++ b/frontend/src/modals/ProxyHostModal.tsx @@ -0,0 +1,376 @@ +import { IconSettings } from "@tabler/icons-react"; +import cn from "classnames"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { + AccessField, + Button, + DomainNamesField, + HasPermission, + Loading, + LocationsFields, + NginxConfigField, + SSLCertificateField, + SSLOptionsFields, +} from "src/components"; +import { useProxyHost, useSetProxyHost, useUser } from "src/hooks"; +import { T } from "src/locale"; +import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions"; +import { validateNumber, validateString } from "src/modules/Validations"; +import { showObjectSuccess } from "src/notifications"; + +const showProxyHostModal = (id: number | "new") => { + EasyModal.show(ProxyHostModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number | "new"; +} +const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => { + const { data: currentUser, isLoading: userIsLoading, error: userError } = useUser("me"); + const { data, isLoading, error } = useProxyHost(id); + const { mutate: setProxyHost } = useSetProxyHost(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + const { ...payload } = { + id: id === "new" ? undefined : id, + ...values, + }; + + setProxyHost(payload, { + onError: (err: any) => setErrorMsg(), + onSuccess: () => { + showObjectSuccess("proxy-host", "saved"); + remove(); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }); + }; + + return ( + + {!isLoading && (error || userError) && ( + + {error?.message || userError?.message || "Unknown error"} + + )} + {isLoading || (userIsLoading && )} + {!isLoading && !userIsLoading && data && currentUser && ( + + {() => ( +
    + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
    +
    + +
    +
    +
    +
    + +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.forwardScheme ? ( +
    + {form.errors.forwardScheme && + form.touched.forwardScheme + ? form.errors.forwardScheme + : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.forwardHost ? ( +
    + {form.errors.forwardHost && + form.touched.forwardHost + ? form.errors.forwardHost + : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.forwardPort ? ( +
    + {form.errors.forwardPort && + form.touched.forwardPort + ? form.errors.forwardPort + : null} +
    + ) : null} +
    + )} +
    +
    +
    + +
    +

    + +

    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + + + + + + +
    + )} +
    + )} +
    + ); +}); + +export { showProxyHostModal }; diff --git a/frontend/src/modals/RedirectionHostModal.tsx b/frontend/src/modals/RedirectionHostModal.tsx new file mode 100644 index 00000000..d2a40b56 --- /dev/null +++ b/frontend/src/modals/RedirectionHostModal.tsx @@ -0,0 +1,307 @@ +import { IconSettings } from "@tabler/icons-react"; +import cn from "classnames"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { + Button, + DomainNamesField, + Loading, + NginxConfigField, + SSLCertificateField, + SSLOptionsFields, +} from "src/components"; +import { useRedirectionHost, useSetRedirectionHost } from "src/hooks"; +import { T } from "src/locale"; +import { validateString } from "src/modules/Validations"; +import { showObjectSuccess } from "src/notifications"; + +const showRedirectionHostModal = (id: number | "new") => { + EasyModal.show(RedirectionHostModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number | "new"; +} +const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) => { + const { data, isLoading, error } = useRedirectionHost(id); + const { mutate: setRedirectionHost } = useSetRedirectionHost(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + const { ...payload } = { + id: id === "new" ? undefined : id, + ...values, + }; + + setRedirectionHost(payload, { + onError: (err: any) => setErrorMsg(), + onSuccess: () => { + showObjectSuccess("redirection-host", "saved"); + remove(); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }); + }; + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + {!isLoading && data && ( + + {() => ( +
    + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
    +
    + +
    +
    +
    +
    + +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.forwardScheme ? ( +
    + {form.errors.forwardScheme && + form.touched.forwardScheme + ? form.errors.forwardScheme + : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.forwardDomainName ? ( +
    + {form.errors.forwardDomainName && + form.touched.forwardDomainName + ? form.errors.forwardDomainName + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    +

    + +

    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + + + + +
    + )} +
    + )} +
    + ); +}); + +export { showRedirectionHostModal }; diff --git a/frontend/src/modals/RenewCertificateModal.tsx b/frontend/src/modals/RenewCertificateModal.tsx new file mode 100644 index 00000000..c7e13dba --- /dev/null +++ b/frontend/src/modals/RenewCertificateModal.tsx @@ -0,0 +1,74 @@ +import { useQueryClient } from "@tanstack/react-query"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { type ReactNode, useEffect, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { renewCertificate } from "src/api/backend"; +import { Button, Loading } from "src/components"; +import { useCertificate } from "src/hooks"; +import { T } from "src/locale"; +import { showObjectSuccess } from "src/notifications"; + +interface Props extends InnerModalProps { + id: number; +} + +const showRenewCertificateModal = (id: number) => { + EasyModal.show(RenewCertificateModal, { id }); +}; + +const RenewCertificateModal = EasyModal.create(({ id, visible, remove }: Props) => { + const queryClient = useQueryClient(); + const { data, isLoading, error } = useCertificate(id); + const [errorMsg, setErrorMsg] = useState(null); + const [isFresh, setIsFresh] = useState(true); + const [isSubmitting, setIsSubmitting] = useState(false); + + useEffect(() => { + if (!data || !isFresh || isSubmitting) return; + setIsFresh(false); + setIsSubmitting(true); + + renewCertificate(id) + .then(() => { + showObjectSuccess("certificate", "renewed"); + queryClient.invalidateQueries({ queryKey: ["certificates"] }); + remove(); + }) + .catch((err: any) => { + setErrorMsg(); + }) + .finally(() => { + setIsSubmitting(false); + }); + }, [id, data, isFresh, isSubmitting, remove, queryClient.invalidateQueries]); + + return ( + + + + + + + + + {errorMsg} + + {isLoading && } + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {data && isSubmitting && !errorMsg ?

    Please wait ...

    : null} +
    + + + +
    + ); +}); + +export { showRenewCertificateModal }; diff --git a/frontend/src/modals/SetPasswordModal.tsx b/frontend/src/modals/SetPasswordModal.tsx new file mode 100644 index 00000000..ef6d99be --- /dev/null +++ b/frontend/src/modals/SetPasswordModal.tsx @@ -0,0 +1,138 @@ +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { generate } from "generate-password-browser"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { updateAuth } from "src/api/backend"; +import { Button } from "src/components"; +import { intl, T } from "src/locale"; +import { validateString } from "src/modules/Validations"; + +const showSetPasswordModal = (id: number) => { + EasyModal.show(SetPasswordModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number; +} +const SetPasswordModal = EasyModal.create(({ id, visible, remove }: Props) => { + const [error, setError] = useState(null); + const [showPassword, setShowPassword] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setError(null); + try { + await updateAuth(id, values.new); + remove(); + } catch (err: any) { + setError(); + } + setIsSubmitting(false); + setSubmitting(false); + }; + + return ( + + + {() => ( +
    + + + + + + + setError(null)} dismissible> + {error} + +
    + + {({ field, form }: any) => ( + <> +

    + + { + e.preventDefault(); + form.setFieldValue( + field.name, + generate({ + length: 12, + numbers: true, + }), + ); + setShowPassword(true); + }} + > + + {" "} + —{" "} + { + e.preventDefault(); + setShowPassword(!showPassword); + }} + > + + + +

    +
    + + + {form.errors.new ? ( +
    + {form.errors.new && form.touched.new ? form.errors.new : null} +
    + ) : null} +
    + + )} +
    +
    +
    + + + + +
    + )} +
    +
    + ); +}); + +export { showSetPasswordModal }; diff --git a/frontend/src/modals/StreamModal.tsx b/frontend/src/modals/StreamModal.tsx new file mode 100644 index 00000000..37a109b3 --- /dev/null +++ b/frontend/src/modals/StreamModal.tsx @@ -0,0 +1,325 @@ +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { Button, Loading, SSLCertificateField, SSLOptionsFields } from "src/components"; +import { useSetStream, useStream } from "src/hooks"; +import { T } from "src/locale"; +import { validateNumber, validateString } from "src/modules/Validations"; +import { showObjectSuccess } from "src/notifications"; + +const showStreamModal = (id: number | "new") => { + EasyModal.show(StreamModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number | "new"; +} +const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => { + const { data, isLoading, error } = useStream(id); + const { mutate: setStream } = useSetStream(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + const { ...payload } = { + id: id === "new" ? undefined : id, + ...values, + }; + + setStream(payload, { + onError: (err: any) => setErrorMsg(), + onSuccess: () => { + showObjectSuccess("stream", "saved"); + remove(); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }); + }; + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + {!isLoading && data && ( + + {({ setFieldValue }: any) => ( +
    + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + + +
    +
    + +
    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.incomingPort ? ( +
    + {form.errors.incomingPort && + form.touched.incomingPort + ? form.errors.incomingPort + : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.forwardingHost ? ( +
    + {form.errors.forwardingHost && + form.touched.forwardingHost + ? form.errors.forwardingHost + : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.forwardingPort ? ( +
    + {form.errors.forwardingPort && + form.touched.forwardingPort + ? form.errors.forwardingPort + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    +

    + +

    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + +
    + )} +
    + )} +
    + ); +}); + +export { showStreamModal }; diff --git a/frontend/src/modals/UserModal.tsx b/frontend/src/modals/UserModal.tsx new file mode 100644 index 00000000..06bc38cf --- /dev/null +++ b/frontend/src/modals/UserModal.tsx @@ -0,0 +1,246 @@ +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { Button, Loading } from "src/components"; +import { useSetUser, useUser } from "src/hooks"; +import { intl, T } from "src/locale"; +import { validateEmail, validateString } from "src/modules/Validations"; +import { showObjectSuccess } from "src/notifications"; + +const showUserModal = (id: number | "me" | "new") => { + EasyModal.show(UserModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number | "me" | "new"; +} +const UserModal = EasyModal.create(({ id, visible, remove }: Props) => { + const { data, isLoading, error } = useUser(id); + const { data: currentUser, isLoading: currentIsLoading } = useUser("me"); + const { mutate: setUser } = useSetUser(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + const { ...payload } = { + id: id === "new" ? undefined : id, + roles: [], + ...values, + }; + + if (data?.id === currentUser?.id) { + // Prevent user from locking themselves out + delete payload.isDisabled; + delete payload.roles; + } else if (payload.isAdmin) { + payload.roles = ["admin"]; + } + + // this isn't a real field, just for the form + delete payload.isAdmin; + + setUser(payload, { + onError: (err: any) => setErrorMsg(err.message), + onSuccess: () => { + showObjectSuccess("user", "saved"); + remove(); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }); + }; + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {(isLoading || currentIsLoading) && } + {!isLoading && !currentIsLoading && data && currentUser && ( + + {() => ( +
    + + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.name ? ( +
    + {form.errors.name && form.touched.name + ? form.errors.name + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.nickname ? ( +
    + {form.errors.nickname && form.touched.nickname + ? form.errors.nickname + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.email ? ( +
    + {form.errors.email && form.touched.email + ? form.errors.email + : null} +
    + ) : null} +
    + )} +
    +
    + {currentUser && data && currentUser?.id !== data?.id ? ( +
    +

    + +

    +
    +
    + +
    +
    + +
    +
    +
    + ) : null} +
    + + + + +
    + )} +
    + )} +
    + ); +}); + +export { showUserModal }; diff --git a/frontend/src/modals/index.ts b/frontend/src/modals/index.ts new file mode 100644 index 00000000..4db26738 --- /dev/null +++ b/frontend/src/modals/index.ts @@ -0,0 +1,16 @@ +export * from "./AccessListModal"; +export * from "./ChangePasswordModal"; +export * from "./CustomCertificateModal"; +export * from "./DeadHostModal"; +export * from "./DeleteConfirmModal"; +export * from "./DNSCertificateModal"; +export * from "./EventDetailsModal"; +export * from "./HelpModal"; +export * from "./HTTPCertificateModal"; +export * from "./PermissionsModal"; +export * from "./ProxyHostModal"; +export * from "./RedirectionHostModal"; +export * from "./RenewCertificateModal"; +export * from "./SetPasswordModal"; +export * from "./StreamModal"; +export * from "./UserModal"; diff --git a/frontend/src/modules/AuthStore.ts b/frontend/src/modules/AuthStore.ts new file mode 100644 index 00000000..9978aaa2 --- /dev/null +++ b/frontend/src/modules/AuthStore.ts @@ -0,0 +1,95 @@ +import { getUnixTime, parseISO } from "date-fns"; +import type { TokenResponse } from "src/api/backend"; + +export const TOKEN_KEY = "authentications"; + +export class AuthStore { + // Get all tokens from stack + get tokens() { + const t = localStorage.getItem(TOKEN_KEY); + let tokens = []; + if (t !== null) { + try { + tokens = JSON.parse(t); + } catch (e) { + console.error("Failed to parse tokens from localStorage", e); + } + } + return tokens; + } + + // Get last token from stack + get token() { + const t = this.tokens; + if (t.length) { + return t[t.length - 1]; + } + return null; + } + + // Get expires from last token + get expires() { + const t = this.token; + if (t && typeof t.expires !== "undefined") { + const expires = Number(t.expires); + if (expires && !Number.isNaN(expires)) { + return expires; + } + } + return null; + } + + // Filter out invalid tokens and return true if we find one that is valid + // hasActiveToken() { + // const t = this.tokens; + // return t.length > 0; + // } + // Start from the END of the stack and work backwards + hasActiveToken() { + const t = this.tokens; + if (!t.length) { + return false; + } + + const now = Math.round(Date.now() / 1000); + const oneMinuteBuffer = 60; + for (let i = t.length - 1; i >= 0; i--) { + const dte = getUnixTime(parseISO(t[i].expires)); + const valid = dte - oneMinuteBuffer > now; + if (valid) { + return true; + } + this.drop(); + } + return false; + } + + // Set a single token on the stack + set({ token, expires }: TokenResponse) { + localStorage.setItem(TOKEN_KEY, JSON.stringify([{ token, expires }])); + } + + // Add a token to the END of the stack + add({ token, expires }: TokenResponse) { + const t = this.tokens; + t.push({ token, expires }); + localStorage.setItem(TOKEN_KEY, JSON.stringify(t)); + } + + // Drop a token from the END of the stack + drop() { + const t = this.tokens; + t.splice(-1, 1); + localStorage.setItem(TOKEN_KEY, JSON.stringify(t)); + } + + clear() { + localStorage.removeItem(TOKEN_KEY); + } + + count() { + return this.tokens.length; + } +} + +export default new AuthStore(); diff --git a/frontend/src/modules/Permissions.ts b/frontend/src/modules/Permissions.ts new file mode 100644 index 00000000..2d784213 --- /dev/null +++ b/frontend/src/modules/Permissions.ts @@ -0,0 +1,49 @@ +import type { UserPermissions } from "src/api/backend"; + +export const ADMIN = "admin"; +export const VISIBILITY = "visibility"; +export const PROXY_HOSTS = "proxyHosts"; +export const REDIRECTION_HOSTS = "redirectionHosts"; +export const DEAD_HOSTS = "deadHosts"; +export const STREAMS = "streams"; +export const CERTIFICATES = "certificates"; +export const ACCESS_LISTS = "accessLists"; + +export const MANAGE = "manage"; +export const VIEW = "view"; +export const HIDDEN = "hidden"; + +export const ALL = "all"; +export const USER = "user"; + +export type Section = + | typeof ADMIN + | typeof VISIBILITY + | typeof PROXY_HOSTS + | typeof REDIRECTION_HOSTS + | typeof DEAD_HOSTS + | typeof STREAMS + | typeof CERTIFICATES + | typeof ACCESS_LISTS; + +export type Permission = typeof MANAGE | typeof VIEW; + +const hasPermission = ( + section: Section, + perm: Permission, + userPerms: UserPermissions | undefined, + roles: string[] | undefined, +): boolean => { + if (!userPerms) return false; + if (isAdmin(roles)) return true; + const acceptable = [MANAGE, perm]; + // @ts-expect-error 7053 + const v = typeof userPerms[section] !== "undefined" ? userPerms[section] : HIDDEN; + return acceptable.indexOf(v) !== -1; +}; + +const isAdmin = (roles: string[] | undefined): boolean => { + return roles?.includes("admin") || false; +}; + +export { hasPermission, isAdmin }; diff --git a/frontend/src/modules/Validations.tsx b/frontend/src/modules/Validations.tsx new file mode 100644 index 00000000..facb875b --- /dev/null +++ b/frontend/src/modules/Validations.tsx @@ -0,0 +1,103 @@ +import { intl } from "src/locale"; + +const validateString = (minLength = 0, maxLength = 0) => { + if (minLength <= 0 && maxLength <= 0) { + // this doesn't require translation + console.error("validateString() must be called with a min or max or both values in order to work!"); + } + + return (value: string): string | undefined => { + if (minLength && (typeof value === "undefined" || !value.length)) { + return intl.formatMessage({ id: "error.required" }); + } + if (minLength && value.length < minLength) { + return intl.formatMessage({ id: "error.min-character-length" }, { min: minLength }); + } + if (maxLength && (typeof value === "undefined" || value.length > maxLength)) { + return intl.formatMessage({ id: "error.max-character-length" }, { max: maxLength }); + } + }; +}; + +const validateNumber = (min = -1, max = -1) => { + if (min === -1 && max === -1) { + // this doesn't require translation + console.error("validateNumber() must be called with a min or max or both values in order to work!"); + } + + return (value: string): string | undefined => { + const int: number = +value; + if (min > -1 && !int) { + return intl.formatMessage({ id: "error.required" }); + } + if (min > -1 && int < min) { + return intl.formatMessage({ id: "error.minimum" }, { min }); + } + if (max > -1 && int > max) { + return intl.formatMessage({ id: "error.maximum" }, { max }); + } + }; +}; + +const validateEmail = () => { + return (value: string): string | undefined => { + if (!value.length) { + return intl.formatMessage({ id: "error.required" }); + } + if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) { + return intl.formatMessage({ id: "error.invalid-email" }); + } + }; +}; + +const validateDomain = (allowWildcards = false) => { + return (d: string): boolean => { + const dom = d.trim().toLowerCase(); + + if (dom.length < 3) { + return false; + } + + // Prevent wildcards + if (!allowWildcards && dom.indexOf("*") !== -1) { + return false; + } + + // Prevent duplicate * in domain + if ((dom.match(/\*/g) || []).length > 1) { + return false; + } + + // Prevent some invalid characters + if ((dom.match(/(@|,|!|&|\$|#|%|\^|\(|\))/g) || []).length > 0) { + return false; + } + + // This will match *.com type domains, + return dom.match(/\*\.[^.]+$/m) === null; + }; +}; + +const validateDomains = (allowWildcards = false, maxDomains?: number) => { + const vDom = validateDomain(allowWildcards); + + return (value?: string[]): string | undefined => { + if (!value?.length) { + return intl.formatMessage({ id: "error.required" }); + } + + // Deny if the list of domains is hit + if (maxDomains && value?.length >= maxDomains) { + return intl.formatMessage({ id: "error.max-domains" }, { max: maxDomains }); + } + + // validate each domain + for (let i = 0; i < value?.length; i++) { + if (!vDom(value[i])) { + return intl.formatMessage({ id: "error.invalid-domain" }, { domain: value[i] }); + } + } + }; +}; + +export { validateEmail, validateNumber, validateString, validateDomains, validateDomain }; diff --git a/frontend/src/notifications/Msg.module.css b/frontend/src/notifications/Msg.module.css new file mode 100644 index 00000000..24c55921 --- /dev/null +++ b/frontend/src/notifications/Msg.module.css @@ -0,0 +1,14 @@ +.toaster { + padding: 0; + background: transparent !important; + box-shadow: none !important; + border: none !important; + + &.toast { + border-radius: 0; + box-shadow: none; + font-size: 14px; + padding: 16px 24px; + background: transparent; + } +} diff --git a/frontend/src/notifications/Msg.tsx b/frontend/src/notifications/Msg.tsx new file mode 100644 index 00000000..b68b8671 --- /dev/null +++ b/frontend/src/notifications/Msg.tsx @@ -0,0 +1,36 @@ +import { IconCheck, IconExclamationCircle } from "@tabler/icons-react"; +import cn from "classnames"; +import type { ReactNode } from "react"; + +function Msg({ data }: any) { + const cns = cn("toast", "show", data.type || null); + + let icon: ReactNode = null; + switch (data.type) { + case "success": + icon = ; + break; + case "error": + icon = ; + break; + } + + return ( +
    + {data.title && ( +
    + {icon} {data.title} +
    + )} +
    {data.message}
    +
    + ); +} +export { Msg }; diff --git a/frontend/src/notifications/helpers.tsx b/frontend/src/notifications/helpers.tsx new file mode 100644 index 00000000..07517e49 --- /dev/null +++ b/frontend/src/notifications/helpers.tsx @@ -0,0 +1,38 @@ +import { toast } from "react-toastify"; +import { intl } from "src/locale"; +import { Msg } from "./Msg"; +import styles from "./Msg.module.css"; + +const showSuccess = (message: string) => { + toast(Msg, { + className: styles.toaster, + data: { + type: "success", + title: intl.formatMessage({ id: "notification.success" }), + message, + }, + }); +}; + +const showError = (message: string) => { + toast(, { + data: { + type: "error", + title: intl.formatMessage({ id: "notification.error" }), + message, + }, + }); +}; + +const showObjectSuccess = (obj: string, action: string) => { + showSuccess( + intl.formatMessage( + { + id: `notification.object-${action}`, + }, + { object: intl.formatMessage({ id: obj }) }, + ), + ); +}; + +export { showSuccess, showError, showObjectSuccess }; diff --git a/frontend/src/notifications/index.ts b/frontend/src/notifications/index.ts new file mode 100644 index 00000000..d4e09d7b --- /dev/null +++ b/frontend/src/notifications/index.ts @@ -0,0 +1 @@ +export * from "./helpers"; diff --git a/frontend/src/pages/Access/Table.tsx b/frontend/src/pages/Access/Table.tsx new file mode 100644 index 00000000..b20919ef --- /dev/null +++ b/frontend/src/pages/Access/Table.tsx @@ -0,0 +1,141 @@ +import { IconDotsVertical, IconEdit, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { AccessList } from "src/api/backend"; +import { EmptyData, GravatarFormatter, HasPermission, ValueWithDateFormatter } from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl, T } from "src/locale"; +import { ACCESS_LISTS, MANAGE } from "src/modules/Permissions"; + +interface Props { + data: AccessList[]; + isFiltered?: boolean; + isFetching?: boolean; + onEdit?: (id: number) => void; + onDelete?: (id: number) => void; + onNew?: () => void; +} +export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, onNew }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => , + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "name", + header: intl.formatMessage({ id: "column.name" }), + cell: (info: any) => ( + + ), + }), + columnHelper.accessor((row: any) => row.items, { + id: "items", + header: intl.formatMessage({ id: "column.authorization" }), + cell: (info: any) => , + }), + columnHelper.accessor((row: any) => row.clients, { + id: "clients", + header: intl.formatMessage({ id: "column.access" }), + cell: (info: any) => , + }), + columnHelper.accessor((row: any) => row.satisfyAny, { + id: "satisfyAny", + header: intl.formatMessage({ id: "column.satisfy" }), + cell: (info: any) => , + }), + columnHelper.accessor((row: any) => row.proxyHostCount, { + id: "proxyHostCount", + header: intl.formatMessage({ id: "proxy-hosts" }), + cell: (info: any) => , + }), + columnHelper.display({ + id: "id", + cell: (info: any) => { + return ( + + +
    + + + + { + e.preventDefault(); + onEdit?.(info.row.original.id); + }} + > + + + + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper, onEdit, onDelete], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return ( + + } + /> + ); +} diff --git a/frontend/src/pages/Access/TableWrapper.tsx b/frontend/src/pages/Access/TableWrapper.tsx new file mode 100644 index 00000000..1e028f4a --- /dev/null +++ b/frontend/src/pages/Access/TableWrapper.tsx @@ -0,0 +1,104 @@ +import { IconHelp, IconSearch } from "@tabler/icons-react"; +import { useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { deleteAccessList } from "src/api/backend"; +import { Button, HasPermission, LoadingPage } from "src/components"; +import { useAccessLists } from "src/hooks"; +import { T } from "src/locale"; +import { showAccessListModal, showDeleteConfirmModal, showHelpModal } from "src/modals"; +import { ACCESS_LISTS, MANAGE } from "src/modules/Permissions"; +import { showObjectSuccess } from "src/notifications"; +import Table from "./Table"; + +export default function TableWrapper() { + const [search, setSearch] = useState(""); + const { isFetching, isLoading, isError, error, data } = useAccessLists(["owner", "items", "clients"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + const handleDelete = async (id: number) => { + await deleteAccessList(id); + showObjectSuccess("access-list", "deleted"); + }; + + let filtered = null; + if (search && data) { + filtered = data?.filter((item) => { + return item.name.toLowerCase().includes(search); + }); + } else if (search !== "") { + // this can happen if someone deletes the last item while searching + setSearch(""); + } + + return ( +
    +
    +
    +
    +
    +
    +

    + +

    +
    + +
    +
    + {data?.length ? ( +
    + + + + setSearch(e.target.value.toLowerCase().trim())} + /> +
    + ) : null} + + + {data?.length ? ( + + ) : null} + +
    +
    +
    +
    + showAccessListModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: , + onConfirm: () => handleDelete(id), + invalidations: [["access-lists"], ["access-list", id]], + children: , + }) + } + onNew={() => showAccessListModal("new")} + /> + + + ); +} diff --git a/frontend/src/pages/Access/index.tsx b/frontend/src/pages/Access/index.tsx new file mode 100644 index 00000000..9c4624cb --- /dev/null +++ b/frontend/src/pages/Access/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { ACCESS_LISTS, VIEW } from "src/modules/Permissions"; +import TableWrapper from "./TableWrapper"; + +const Access = () => { + return ( + + + + ); +}; + +export default Access; diff --git a/frontend/src/pages/AuditLog/Table.tsx b/frontend/src/pages/AuditLog/Table.tsx new file mode 100644 index 00000000..379a5f9b --- /dev/null +++ b/frontend/src/pages/AuditLog/Table.tsx @@ -0,0 +1,74 @@ +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { AuditLog } from "src/api/backend"; +import { EventFormatter, GravatarFormatter } from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl, T } from "src/locale"; + +interface Props { + data: AuditLog[]; + isFetching?: boolean; + onSelectItem?: (id: number) => void; +} +export default function Table({ data, isFetching, onSelectItem }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: AuditLog) => row.user, { + id: "user.avatar", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: AuditLog) => row.user?.name, { + id: "user.name", + header: intl.formatMessage({ id: "column.name" }), + }), + columnHelper.accessor((row: AuditLog) => row, { + id: "objectType", + header: intl.formatMessage({ id: "column.event" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", + cell: (info: any) => { + return ( + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper, onSelectItem], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return ; +} diff --git a/frontend/src/pages/AuditLog/TableWrapper.tsx b/frontend/src/pages/AuditLog/TableWrapper.tsx new file mode 100644 index 00000000..a7d3967d --- /dev/null +++ b/frontend/src/pages/AuditLog/TableWrapper.tsx @@ -0,0 +1,36 @@ +import Alert from "react-bootstrap/Alert"; +import { LoadingPage } from "src/components"; +import { useAuditLogs } from "src/hooks"; +import { T } from "src/locale"; +import { showEventDetailsModal } from "src/modals"; +import Table from "./Table"; + +export default function TableWrapper() { + const { isFetching, isLoading, isError, error, data } = useAuditLogs(["user"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + return ( +
    +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    +
    + + + ); +} diff --git a/frontend/src/pages/AuditLog/index.tsx b/frontend/src/pages/AuditLog/index.tsx new file mode 100644 index 00000000..4bdc2d07 --- /dev/null +++ b/frontend/src/pages/AuditLog/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { ADMIN, VIEW } from "src/modules/Permissions"; +import TableWrapper from "./TableWrapper"; + +const AuditLog = () => { + return ( + + + + ); +}; + +export default AuditLog; diff --git a/frontend/src/pages/Certificates/Table.tsx b/frontend/src/pages/Certificates/Table.tsx new file mode 100644 index 00000000..ce816f9b --- /dev/null +++ b/frontend/src/pages/Certificates/Table.tsx @@ -0,0 +1,235 @@ +import { IconDotsVertical, IconDownload, IconRefresh, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { Certificate } from "src/api/backend"; +import { + CertificateInUseFormatter, + DateFormatter, + DomainsFormatter, + EmptyData, + GravatarFormatter, + HasPermission, +} from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl, T } from "src/locale"; +import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals"; +import { CERTIFICATES, MANAGE } from "src/modules/Permissions"; + +interface Props { + data: Certificate[]; + isFiltered?: boolean; + isFetching?: boolean; + onDelete?: (id: number) => void; + onRenew?: (id: number) => void; + onDownload?: (id: number) => void; +} +export default function Table({ data, isFetching, onDelete, onRenew, onDownload, isFiltered }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "domainNames", + header: intl.formatMessage({ id: "column.name" }), + cell: (info: any) => { + const value = info.getValue(); + return ( + + ); + }, + }), + columnHelper.accessor((row: any) => row, { + id: "provider", + header: intl.formatMessage({ id: "column.provider" }), + cell: (info: any) => { + const r = info.getValue(); + if (r.provider === "letsencrypt") { + if (r.meta?.dnsChallenge && r.meta?.dnsProvider) { + return ( + <> + – {r.meta?.dnsProvider} + + ); + } + return ; + } + if (r.provider === "other") { + return ; + } + return ; + }, + }), + columnHelper.accessor((row: any) => row.expiresOn, { + id: "expiresOn", + header: intl.formatMessage({ id: "column.expires" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row, { + id: "proxyHosts", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + const r = info.getValue(); + return ( + + ); + }, + }), + columnHelper.display({ + id: "id", + cell: (info: any) => { + return ( + + +
    + + + + { + e.preventDefault(); + onRenew?.(info.row.original.id); + }} + > + + + + + { + e.preventDefault(); + onDownload?.(info.row.original.id); + }} + > + + + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper, onDelete, onRenew, onDownload], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + const customAddBtn = ( +
    + + + ); + + return ( + + } + /> + ); +} diff --git a/frontend/src/pages/Certificates/TableWrapper.tsx b/frontend/src/pages/Certificates/TableWrapper.tsx new file mode 100644 index 00000000..14dfc417 --- /dev/null +++ b/frontend/src/pages/Certificates/TableWrapper.tsx @@ -0,0 +1,161 @@ +import { IconHelp, IconSearch } from "@tabler/icons-react"; +import { useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { deleteCertificate, downloadCertificate } from "src/api/backend"; +import { Button, HasPermission, LoadingPage } from "src/components"; +import { useCertificates } from "src/hooks"; +import { T } from "src/locale"; +import { + showCustomCertificateModal, + showDeleteConfirmModal, + showDNSCertificateModal, + showHelpModal, + showHTTPCertificateModal, + showRenewCertificateModal, +} from "src/modals"; +import { CERTIFICATES, MANAGE } from "src/modules/Permissions"; +import { showError, showObjectSuccess } from "src/notifications"; +import Table from "./Table"; + +export default function TableWrapper() { + const [search, setSearch] = useState(""); + const { isFetching, isLoading, isError, error, data } = useCertificates([ + "owner", + "dead_hosts", + "proxy_hosts", + "redirection_hosts", + "streams", + ]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + const handleDelete = async (id: number) => { + await deleteCertificate(id); + showObjectSuccess("certificate", "deleted"); + }; + + const handleDownload = async (id: number) => { + try { + await downloadCertificate(id); + } catch (err: any) { + showError(err.message); + } + }; + + let filtered = null; + if (search && data) { + filtered = data?.filter( + (item) => + item.domainNames.some((domain: string) => domain.toLowerCase().includes(search)) || + item.niceName.toLowerCase().includes(search), + ); + } else if (search !== "") { + // this can happen if someone deletes the last item while searching + setSearch(""); + } + + return ( +
    +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + {data?.length ? ( +
    + + + + setSearch(e.target.value.toLowerCase().trim())} + /> +
    + ) : null} + + + {data?.length ? ( + +
    +
    +
    +
    + showDeleteConfirmModal({ + title: , + onConfirm: () => handleDelete(id), + invalidations: [["certificates"], ["certificate", id]], + children: , + }) + } + /> + + + ); +} diff --git a/frontend/src/pages/Certificates/index.tsx b/frontend/src/pages/Certificates/index.tsx new file mode 100644 index 00000000..130fb5a9 --- /dev/null +++ b/frontend/src/pages/Certificates/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { CERTIFICATES, VIEW } from "src/modules/Permissions"; +import TableWrapper from "./TableWrapper"; + +const Certificates = () => { + return ( + + + + ); +}; + +export default Certificates; diff --git a/frontend/src/pages/Dashboard/index.tsx b/frontend/src/pages/Dashboard/index.tsx new file mode 100644 index 00000000..5cb64867 --- /dev/null +++ b/frontend/src/pages/Dashboard/index.tsx @@ -0,0 +1,132 @@ +import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc } from "@tabler/icons-react"; +import { useNavigate } from "react-router-dom"; +import { HasPermission } from "src/components"; +import { useHostReport } from "src/hooks"; +import { T } from "src/locale"; +import { DEAD_HOSTS, PROXY_HOSTS, REDIRECTION_HOSTS, STREAMS, VIEW } from "src/modules/Permissions"; + +const Dashboard = () => { + const { data: hostReport } = useHostReport(); + const navigate = useNavigate(); + + return ( + + ); +}; + +export default Dashboard; diff --git a/frontend/src/pages/Login/index.module.css b/frontend/src/pages/Login/index.module.css new file mode 100644 index 00000000..16f8477c --- /dev/null +++ b/frontend/src/pages/Login/index.module.css @@ -0,0 +1,10 @@ +.logo { + width: 200px; +} + +.helperBtns { + position: absolute; + top: 10px; + right: 10px; + z-index: 1000; +} diff --git a/frontend/src/pages/Login/index.tsx b/frontend/src/pages/Login/index.tsx new file mode 100644 index 00000000..0d555380 --- /dev/null +++ b/frontend/src/pages/Login/index.tsx @@ -0,0 +1,128 @@ +import cn from "classnames"; +import { Field, Form, Formik } from "formik"; +import { useEffect, useRef, useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { Button, LocalePicker, Page, ThemeSwitcher } from "src/components"; +import { useAuthState } from "src/context"; +import { useHealth } from "src/hooks"; +import { intl, T } from "src/locale"; +import { validateEmail, validateString } from "src/modules/Validations"; +import styles from "./index.module.css"; + +export default function Login() { + const emailRef = useRef(null); + const [formErr, setFormErr] = useState(""); + const { login } = useAuthState(); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + setFormErr(""); + try { + await login(values.email, values.password); + } catch (err) { + if (err instanceof Error) { + setFormErr(err.message); + } + } + setSubmitting(false); + }; + + useEffect(() => { + // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. + emailRef.current.focus(); + }, []); + + const health = useHealth(); + + const getVersion = () => { + if (!health.data) { + return ""; + } + const v = health.data.version; + return `v${v.major}.${v.minor}.${v.revision}`; + }; + + return ( + +
    + + +
    +
    +
    + Nginx Proxy Manager +
    +
    +
    +

    + +

    + {formErr !== "" && {formErr}} + + {({ isSubmitting }) => ( +
    +
    + + {({ field, form }: any) => ( + + )} + +
    +
    + + {({ field, form }: any) => ( + <> + + + )} + +
    +
    + +
    + + )} +
    +
    +
    +
    {getVersion()}
    +
    +
    + ); +} diff --git a/frontend/src/pages/Nginx/DeadHosts/Table.tsx b/frontend/src/pages/Nginx/DeadHosts/Table.tsx new file mode 100644 index 00000000..78699535 --- /dev/null +++ b/frontend/src/pages/Nginx/DeadHosts/Table.tsx @@ -0,0 +1,158 @@ +import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { DeadHost } from "src/api/backend"; +import { + CertificateFormatter, + DomainsFormatter, + EmptyData, + GravatarFormatter, + HasPermission, + TrueFalseFormatter, +} from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl, T } from "src/locale"; +import { DEAD_HOSTS, MANAGE } from "src/modules/Permissions"; + +interface Props { + data: DeadHost[]; + isFiltered?: boolean; + isFetching?: boolean; + onEdit?: (id: number) => void; + onDelete?: (id: number) => void; + onDisableToggle?: (id: number, enabled: boolean) => void; + onNew?: () => void; +} +export default function Table({ data, isFetching, onEdit, onDelete, onDisableToggle, onNew, isFiltered }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "domainNames", + header: intl.formatMessage({ id: "column.source" }), + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + }), + columnHelper.accessor((row: any) => row.certificate, { + id: "certificate", + header: intl.formatMessage({ id: "column.ssl" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row.enabled, { + id: "enabled", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", + cell: (info: any) => { + return ( + + +
    + + + + { + e.preventDefault(); + onEdit?.(info.row.original.id); + }} + > + + + + + { + e.preventDefault(); + onDisableToggle?.(info.row.original.id, !info.row.original.enabled); + }} + > + + + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper, onDelete, onEdit, onDisableToggle], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return ( + + } + /> + ); +} diff --git a/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx new file mode 100644 index 00000000..07dd1318 --- /dev/null +++ b/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx @@ -0,0 +1,110 @@ +import { IconHelp, IconSearch } from "@tabler/icons-react"; +import { useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { deleteDeadHost, toggleDeadHost } from "src/api/backend"; +import { Button, HasPermission, LoadingPage } from "src/components"; +import { useDeadHosts } from "src/hooks"; +import { T } from "src/locale"; +import { showDeadHostModal, showDeleteConfirmModal, showHelpModal } from "src/modals"; +import { DEAD_HOSTS, MANAGE } from "src/modules/Permissions"; +import { showObjectSuccess } from "src/notifications"; +import Table from "./Table"; + +export default function TableWrapper() { + const queryClient = useQueryClient(); + const [search, setSearch] = useState(""); + const { isFetching, isLoading, isError, error, data } = useDeadHosts(["owner", "certificate"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + const handleDelete = async (id: number) => { + await deleteDeadHost(id); + showObjectSuccess("dead-host", "deleted"); + }; + + const handleDisableToggle = async (id: number, enabled: boolean) => { + await toggleDeadHost(id, enabled); + queryClient.invalidateQueries({ queryKey: ["dead-hosts"] }); + queryClient.invalidateQueries({ queryKey: ["dead-host", id] }); + showObjectSuccess("dead-host", enabled ? "enabled" : "disabled"); + }; + + let filtered = null; + if (search && data) { + filtered = data?.filter((item) => { + return item.domainNames.some((domain: string) => domain.toLowerCase().includes(search)); + }); + } else if (search !== "") { + // this can happen if someone deletes the last item while searching + setSearch(""); + } + + return ( +
    +
    +
    +
    +
    +
    +

    + +

    +
    + +
    +
    + {data?.length ? ( +
    + + + + setSearch(e.target.value.toLowerCase().trim())} + /> +
    + ) : null} + + + {data?.length ? ( + + ) : null} + +
    +
    +
    +
    +
    showDeadHostModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: , + onConfirm: () => handleDelete(id), + invalidations: [["dead-hosts"], ["dead-host", id]], + children: , + }) + } + onDisableToggle={handleDisableToggle} + onNew={() => showDeadHostModal("new")} + /> + + + ); +} diff --git a/frontend/src/pages/Nginx/DeadHosts/index.tsx b/frontend/src/pages/Nginx/DeadHosts/index.tsx new file mode 100644 index 00000000..0464b7a6 --- /dev/null +++ b/frontend/src/pages/Nginx/DeadHosts/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { DEAD_HOSTS, VIEW } from "src/modules/Permissions"; +import TableWrapper from "./TableWrapper"; + +const DeadHosts = () => { + return ( + + + + ); +}; + +export default DeadHosts; diff --git a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx new file mode 100644 index 00000000..cfbd29f6 --- /dev/null +++ b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx @@ -0,0 +1,174 @@ +import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { ProxyHost } from "src/api/backend"; +import { + AccessListFormatter, + CertificateFormatter, + DomainsFormatter, + EmptyData, + GravatarFormatter, + HasPermission, + TrueFalseFormatter, +} from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl, T } from "src/locale"; +import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions"; + +interface Props { + data: ProxyHost[]; + isFiltered?: boolean; + isFetching?: boolean; + onEdit?: (id: number) => void; + onDelete?: (id: number) => void; + onDisableToggle?: (id: number, enabled: boolean) => void; + onNew?: () => void; +} +export default function Table({ data, isFetching, onEdit, onDelete, onDisableToggle, onNew, isFiltered }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "domainNames", + header: intl.formatMessage({ id: "column.source" }), + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + }), + columnHelper.accessor((row: any) => row, { + id: "forwardHost", + header: intl.formatMessage({ id: "column.destination" }), + cell: (info: any) => { + const value = info.getValue(); + return `${value.forwardHost}:${value.forwardPort}`; + }, + }), + columnHelper.accessor((row: any) => row.certificate, { + id: "certificate", + header: intl.formatMessage({ id: "column.ssl" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row.accessList, { + id: "accessList", + header: intl.formatMessage({ id: "column.access" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row.enabled, { + id: "enabled", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", + cell: (info: any) => { + return ( + + +
    + + + + { + e.preventDefault(); + onEdit?.(info.row.original.id); + }} + > + + + + + { + e.preventDefault(); + onDisableToggle?.(info.row.original.id, !info.row.original.enabled); + }} + > + + + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper, onEdit, onDisableToggle, onDelete], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return ( + + } + /> + ); +} diff --git a/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx new file mode 100644 index 00000000..68af43e1 --- /dev/null +++ b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx @@ -0,0 +1,116 @@ +import { IconHelp, IconSearch } from "@tabler/icons-react"; +import { useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { deleteProxyHost, toggleProxyHost } from "src/api/backend"; +import { Button, HasPermission, LoadingPage } from "src/components"; +import { useProxyHosts } from "src/hooks"; +import { T } from "src/locale"; +import { showDeleteConfirmModal, showHelpModal, showProxyHostModal } from "src/modals"; +import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions"; +import { showObjectSuccess } from "src/notifications"; +import Table from "./Table"; + +export default function TableWrapper() { + const queryClient = useQueryClient(); + const [search, setSearch] = useState(""); + const { isFetching, isLoading, isError, error, data } = useProxyHosts(["owner", "access_list", "certificate"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + const handleDelete = async (id: number) => { + await deleteProxyHost(id); + showObjectSuccess("proxy-host", "deleted"); + }; + + const handleDisableToggle = async (id: number, enabled: boolean) => { + await toggleProxyHost(id, enabled); + queryClient.invalidateQueries({ queryKey: ["proxy-hosts"] }); + queryClient.invalidateQueries({ queryKey: ["proxy-host", id] }); + showObjectSuccess("proxy-host", enabled ? "enabled" : "disabled"); + }; + + let filtered = null; + if (search && data) { + filtered = data?.filter( + (item) => + item.domainNames.some((domain: string) => domain.toLowerCase().includes(search)) || + item.forwardHost.toLowerCase().includes(search) || + `${item.forwardPort}`.includes(search), + ); + } else if (search !== "") { + // this can happen if someone deletes the last item while searching + setSearch(""); + } + + return ( +
    +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + {data?.length ? ( +
    + + + + setSearch(e.target.value.toLowerCase().trim())} + /> +
    + ) : null} + + + {data?.length ? ( + + ) : null} + +
    +
    +
    +
    +
    showProxyHostModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: , + onConfirm: () => handleDelete(id), + invalidations: [["proxy-hosts"], ["proxy-host", id]], + children: , + }) + } + onDisableToggle={handleDisableToggle} + onNew={() => showProxyHostModal("new")} + /> + + + ); +} diff --git a/frontend/src/pages/Nginx/ProxyHosts/index.tsx b/frontend/src/pages/Nginx/ProxyHosts/index.tsx new file mode 100644 index 00000000..d20658e1 --- /dev/null +++ b/frontend/src/pages/Nginx/ProxyHosts/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { PROXY_HOSTS, VIEW } from "src/modules/Permissions"; +import TableWrapper from "./TableWrapper"; + +const ProxyHosts = () => { + return ( + + + + ); +}; + +export default ProxyHosts; diff --git a/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx b/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx new file mode 100644 index 00000000..ece00bd6 --- /dev/null +++ b/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx @@ -0,0 +1,179 @@ +import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { RedirectionHost } from "src/api/backend"; +import { + CertificateFormatter, + DomainsFormatter, + EmptyData, + GravatarFormatter, + HasPermission, + TrueFalseFormatter, +} from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl, T } from "src/locale"; +import { MANAGE, REDIRECTION_HOSTS } from "src/modules/Permissions"; + +interface Props { + data: RedirectionHost[]; + isFiltered?: boolean; + isFetching?: boolean; + onEdit?: (id: number) => void; + onDelete?: (id: number) => void; + onDisableToggle?: (id: number, enabled: boolean) => void; + onNew?: () => void; +} +export default function Table({ data, isFetching, onEdit, onDelete, onDisableToggle, onNew, isFiltered }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "domainNames", + header: intl.formatMessage({ id: "column.source" }), + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + }), + columnHelper.accessor((row: any) => row.forwardHttpCode, { + id: "forwardHttpCode", + header: intl.formatMessage({ id: "column.http-code" }), + cell: (info: any) => { + return info.getValue(); + }, + }), + columnHelper.accessor((row: any) => row.forwardScheme, { + id: "forwardScheme", + header: intl.formatMessage({ id: "column.scheme" }), + cell: (info: any) => { + return info.getValue().toUpperCase(); + }, + }), + columnHelper.accessor((row: any) => row.forwardDomainName, { + id: "forwardDomainName", + header: intl.formatMessage({ id: "column.destination" }), + cell: (info: any) => { + return info.getValue(); + }, + }), + columnHelper.accessor((row: any) => row.certificate, { + id: "certificate", + header: intl.formatMessage({ id: "column.ssl" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row.enabled, { + id: "enabled", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", + cell: (info: any) => { + return ( + + +
    + + + + { + e.preventDefault(); + onEdit?.(info.row.original.id); + }} + > + + + + + { + e.preventDefault(); + onDisableToggle?.(info.row.original.id, !info.row.original.enabled); + }} + > + + + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper, onEdit, onDisableToggle, onDelete], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return ( + + } + /> + ); +} diff --git a/frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx new file mode 100644 index 00000000..382b7336 --- /dev/null +++ b/frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx @@ -0,0 +1,116 @@ +import { IconHelp, IconSearch } from "@tabler/icons-react"; +import { useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { deleteRedirectionHost, toggleRedirectionHost } from "src/api/backend"; +import { Button, HasPermission, LoadingPage } from "src/components"; +import { useRedirectionHosts } from "src/hooks"; +import { T } from "src/locale"; +import { showDeleteConfirmModal, showHelpModal, showRedirectionHostModal } from "src/modals"; +import { MANAGE, REDIRECTION_HOSTS } from "src/modules/Permissions"; +import { showObjectSuccess } from "src/notifications"; +import Table from "./Table"; + +export default function TableWrapper() { + const queryClient = useQueryClient(); + const [search, setSearch] = useState(""); + const { isFetching, isLoading, isError, error, data } = useRedirectionHosts(["owner", "certificate"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + const handleDelete = async (id: number) => { + await deleteRedirectionHost(id); + showObjectSuccess("redirection-host", "deleted"); + }; + + const handleDisableToggle = async (id: number, enabled: boolean) => { + await toggleRedirectionHost(id, enabled); + queryClient.invalidateQueries({ queryKey: ["redirection-hosts"] }); + queryClient.invalidateQueries({ queryKey: ["redirection-host", id] }); + showObjectSuccess("redirection-host", enabled ? "enabled" : "disabled"); + }; + + let filtered = null; + if (search && data) { + filtered = data?.filter((item) => { + return ( + item.domainNames.some((domain: string) => domain.toLowerCase().includes(search)) || + item.forwardDomainName.toLowerCase().includes(search) + ); + }); + } else if (search !== "") { + // this can happen if someone deletes the last item while searching + setSearch(""); + } + + return ( +
    +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + {data?.length ? ( +
    + + + + setSearch(e.target.value.toLowerCase().trim())} + /> +
    + ) : null} + + + {data?.length ? ( + + ) : null} + +
    +
    +
    +
    +
    showRedirectionHostModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: , + onConfirm: () => handleDelete(id), + invalidations: [["redirection-hosts"], ["redirection-host", id]], + children: , + }) + } + onDisableToggle={handleDisableToggle} + onNew={() => showRedirectionHostModal("new")} + /> + + + ); +} diff --git a/frontend/src/pages/Nginx/RedirectionHosts/index.tsx b/frontend/src/pages/Nginx/RedirectionHosts/index.tsx new file mode 100644 index 00000000..32bf2441 --- /dev/null +++ b/frontend/src/pages/Nginx/RedirectionHosts/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { REDIRECTION_HOSTS, VIEW } from "src/modules/Permissions"; +import TableWrapper from "./TableWrapper"; + +const RedirectionHosts = () => { + return ( + + + + ); +}; + +export default RedirectionHosts; diff --git a/frontend/src/pages/Nginx/Streams/Table.tsx b/frontend/src/pages/Nginx/Streams/Table.tsx new file mode 100644 index 00000000..b7246565 --- /dev/null +++ b/frontend/src/pages/Nginx/Streams/Table.tsx @@ -0,0 +1,187 @@ +import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { Stream } from "src/api/backend"; +import { + CertificateFormatter, + EmptyData, + GravatarFormatter, + HasPermission, + TrueFalseFormatter, + ValueWithDateFormatter, +} from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl, T } from "src/locale"; +import { MANAGE, STREAMS } from "src/modules/Permissions"; + +interface Props { + data: Stream[]; + isFiltered?: boolean; + isFetching?: boolean; + onEdit?: (id: number) => void; + onDelete?: (id: number) => void; + onDisableToggle?: (id: number, enabled: boolean) => void; + onNew?: () => void; +} +export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, onDisableToggle, onNew }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "incomingPort", + header: intl.formatMessage({ id: "column.incoming-port" }), + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + }), + columnHelper.accessor((row: any) => row, { + id: "forwardHttpCode", + header: intl.formatMessage({ id: "column.destination" }), + cell: (info: any) => { + const value = info.getValue(); + return `${value.forwardingHost}:${value.forwardingPort}`; + }, + }), + columnHelper.accessor((row: any) => row, { + id: "tcpForwarding", + header: intl.formatMessage({ id: "column.protocol" }), + cell: (info: any) => { + const value = info.getValue(); + return ( + <> + {value.tcpForwarding ? ( + + + + ) : null} + {value.udpForwarding ? ( + + + + ) : null} + + ); + }, + }), + columnHelper.accessor((row: any) => row.certificate, { + id: "certificate", + header: intl.formatMessage({ id: "column.ssl" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row.enabled, { + id: "enabled", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", + cell: (info: any) => { + return ( + + +
    + + + + { + e.preventDefault(); + onEdit?.(info.row.original.id); + }} + > + + + + + { + e.preventDefault(); + onDisableToggle?.(info.row.original.id, !info.row.original.enabled); + }} + > + + + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper, onEdit, onDisableToggle, onDelete], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return ( + + } + /> + ); +} diff --git a/frontend/src/pages/Nginx/Streams/TableWrapper.tsx b/frontend/src/pages/Nginx/Streams/TableWrapper.tsx new file mode 100644 index 00000000..ec9a8d40 --- /dev/null +++ b/frontend/src/pages/Nginx/Streams/TableWrapper.tsx @@ -0,0 +1,114 @@ +import { IconHelp, IconSearch } from "@tabler/icons-react"; +import { useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { deleteStream, toggleStream } from "src/api/backend"; +import { Button, HasPermission, LoadingPage } from "src/components"; +import { useStreams } from "src/hooks"; +import { T } from "src/locale"; +import { showDeleteConfirmModal, showHelpModal, showStreamModal } from "src/modals"; +import { MANAGE, STREAMS } from "src/modules/Permissions"; +import { showObjectSuccess } from "src/notifications"; +import Table from "./Table"; + +export default function TableWrapper() { + const queryClient = useQueryClient(); + const [search, setSearch] = useState(""); + const [_deleteId, _setDeleteIdd] = useState(0); + const { isFetching, isLoading, isError, error, data } = useStreams(["owner", "certificate"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + const handleDelete = async (id: number) => { + await deleteStream(id); + showObjectSuccess("stream", "deleted"); + }; + + const handleDisableToggle = async (id: number, enabled: boolean) => { + await toggleStream(id, enabled); + queryClient.invalidateQueries({ queryKey: ["streams"] }); + queryClient.invalidateQueries({ queryKey: ["stream", id] }); + showObjectSuccess("stream", enabled ? "enabled" : "disabled"); + }; + + let filtered = null; + if (search && data) { + filtered = data?.filter((item) => { + return ( + `${item.incomingPort}`.includes(search) || + `${item.forwardingPort}`.includes(search) || + item.forwardingHost.includes(search) + ); + }); + } else if (search !== "") { + // this can happen if someone deletes the last item while searching + setSearch(""); + } + + return ( +
    +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + {data?.length ? ( +
    + + + + setSearch(e.target.value.toLowerCase().trim())} + /> +
    + ) : null} + + + {data?.length ? ( + + ) : null} + +
    +
    +
    +
    +
    showStreamModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: , + onConfirm: () => handleDelete(id), + invalidations: [["streams"], ["stream", id]], + children: , + }) + } + onDisableToggle={handleDisableToggle} + onNew={() => showStreamModal("new")} + /> + + + ); +} diff --git a/frontend/src/pages/Nginx/Streams/index.tsx b/frontend/src/pages/Nginx/Streams/index.tsx new file mode 100644 index 00000000..31c4f636 --- /dev/null +++ b/frontend/src/pages/Nginx/Streams/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { STREAMS, VIEW } from "src/modules/Permissions"; +import TableWrapper from "./TableWrapper"; + +const Streams = () => { + return ( + + + + ); +}; + +export default Streams; diff --git a/frontend/src/pages/Settings/DefaultSite.tsx b/frontend/src/pages/Settings/DefaultSite.tsx new file mode 100644 index 00000000..1ee1cb5e --- /dev/null +++ b/frontend/src/pages/Settings/DefaultSite.tsx @@ -0,0 +1,269 @@ +import CodeEditor from "@uiw/react-textarea-code-editor"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import { Button, Loading } from "src/components"; +import { useSetSetting, useSetting } from "src/hooks"; +import { intl, T } from "src/locale"; +import { validateString } from "src/modules/Validations"; +import { showObjectSuccess } from "src/notifications"; + +export default function DefaultSite() { + const { data, isLoading, error } = useSetting("default-site"); + const { mutate: setSetting } = useSetSetting(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + const payload = { + id: "default-site", + value: values.value, + meta: { + redirect: values.redirect, + html: values.html, + }, + }; + + setSetting(payload, { + onError: (err: any) => setErrorMsg(), + onSuccess: () => { + showObjectSuccess("setting", "saved"); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }); + }; + + if (!isLoading && error) { + return ( +
    +
    + + {error.message} + +
    +
    + ); + } + + if (isLoading) { + return ( +
    +
    + +
    +
    + ); + } + + return ( + + {({ values }) => ( +
    +
    + setErrorMsg(null)} dismissible> + {errorMsg} + + + {({ field, form }: any) => ( +
    + +
    + + + + + +
    +
    + )} +
    + {values.value === "redirect" && ( + + {({ field, form }: any) => ( +
    + +
    + + {form.errors.redirect ? ( +
    + {form.errors.redirect && form.touched.redirect + ? form.errors.redirect + : null} +
    + ) : null} +
    +
    + )} +
    + )} + {values.value === "html" && ( + + {({ field, form }: any) => ( +
    + +
    + + {form.errors.html ? ( +
    + {form.errors.html && form.touched.html ? form.errors.html : null} +
    + ) : null} +
    +
    + )} +
    + )} +
    +
    +
    + +
    +
    + + )} +
    + ); +} diff --git a/frontend/src/pages/Settings/Layout.tsx b/frontend/src/pages/Settings/Layout.tsx new file mode 100644 index 00000000..a0a77db2 --- /dev/null +++ b/frontend/src/pages/Settings/Layout.tsx @@ -0,0 +1,40 @@ +import { T } from "src/locale"; +import DefaultSite from "./DefaultSite"; + +export default function Layout() { + // Taken from https://preview.tabler.io/settings.html + // Refer to that when updating this content + + return ( +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + +
    + +
    +
    +
    +
    + ); +} diff --git a/frontend/src/pages/Settings/index.tsx b/frontend/src/pages/Settings/index.tsx new file mode 100644 index 00000000..7904cbc0 --- /dev/null +++ b/frontend/src/pages/Settings/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { ADMIN, VIEW } from "src/modules/Permissions"; +import Layout from "./Layout"; + +const Settings = () => { + return ( + + + + ); +}; + +export default Settings; diff --git a/frontend/src/pages/Setup/index.module.css b/frontend/src/pages/Setup/index.module.css new file mode 100644 index 00000000..16f8477c --- /dev/null +++ b/frontend/src/pages/Setup/index.module.css @@ -0,0 +1,10 @@ +.logo { + width: 200px; +} + +.helperBtns { + position: absolute; + top: 10px; + right: 10px; + z-index: 1000; +} diff --git a/frontend/src/pages/Setup/index.tsx b/frontend/src/pages/Setup/index.tsx new file mode 100644 index 00000000..4becf5fa --- /dev/null +++ b/frontend/src/pages/Setup/index.tsx @@ -0,0 +1,196 @@ +import { useQueryClient } from "@tanstack/react-query"; +import cn from "classnames"; +import { Field, Form, Formik } from "formik"; +import { useState } from "react"; +import { Alert } from "react-bootstrap"; +import { createUser } from "src/api/backend"; +import { Button, LocalePicker, Page, ThemeSwitcher } from "src/components"; +import { useAuthState } from "src/context"; +import { intl, T } from "src/locale"; +import { validateEmail, validateString } from "src/modules/Validations"; +import styles from "./index.module.css"; + +interface Payload { + name: string; + email: string; + password: string; +} + +export default function Setup() { + const queryClient = useQueryClient(); + const { login } = useAuthState(); + const [errorMsg, setErrorMsg] = useState(null); + + const onSubmit = async (values: Payload, { setSubmitting }: any) => { + setErrorMsg(null); + + // Set a nickname, which is the first word of the name + const nickname = values.name.split(" ")[0]; + + const { password, ...payload } = { + ...values, + ...{ + nickname, + auth: { + type: "password", + secret: values.password, + }, + }, + }; + + try { + const user = await createUser(payload, true); + if (user?.id) { + try { + await login(user.email, password); + // Trigger a Health change + await queryClient.refetchQueries({ queryKey: ["health"] }); + // window.location.reload(); + } catch (err: any) { + setErrorMsg(err.message); + } + } else { + setErrorMsg("cannot_create_user"); + } + } catch (err: any) { + setErrorMsg(err.message); + } + setSubmitting(false); + }; + + return ( + +
    + + +
    +
    +
    + Nginx Proxy Manager +
    +
    + setErrorMsg(null)} dismissible> + {errorMsg} + + + {({ isSubmitting }) => ( +
    +
    +

    + +

    +

    + +

    +
    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.name ? ( +
    + {form.errors.name && form.touched.name + ? form.errors.name + : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.email ? ( +
    + {form.errors.email && form.touched.email + ? form.errors.email + : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.password ? ( +
    + {form.errors.password && form.touched.password + ? form.errors.password + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    + +
    + + )} +
    +
    +
    +
    + ); +} diff --git a/frontend/src/pages/Users/Table.tsx b/frontend/src/pages/Users/Table.tsx new file mode 100644 index 00000000..d07fc9bc --- /dev/null +++ b/frontend/src/pages/Users/Table.tsx @@ -0,0 +1,245 @@ +import { + IconDotsVertical, + IconEdit, + IconLock, + IconLogin2, + IconPower, + IconShield, + IconTrash, +} from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { User } from "src/api/backend"; +import { + EmailFormatter, + EmptyData, + GravatarFormatter, + RolesFormatter, + TrueFalseFormatter, + ValueWithDateFormatter, +} from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl, T } from "src/locale"; + +interface Props { + data: User[]; + isFiltered?: boolean; + isFetching?: boolean; + currentUserId?: number; + onEditUser?: (id: number) => void; + onEditPermissions?: (id: number) => void; + onSetPassword?: (id: number) => void; + onDeleteUser?: (id: number) => void; + onDisableToggle?: (id: number, enabled: boolean) => void; + onNewUser?: () => void; + onLoginAs?: (id: number) => void; +} +export default function Table({ + data, + isFiltered, + isFetching, + currentUserId, + onEditUser, + onEditPermissions, + onSetPassword, + onDeleteUser, + onDisableToggle, + onNewUser, + onLoginAs, +}: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row, { + id: "avatar", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "name", + header: intl.formatMessage({ id: "column.name" }), + cell: (info: any) => { + const value = info.getValue(); + // Hack to reuse domains formatter + return ( + + ); + }, + }), + columnHelper.accessor((row: any) => row.email, { + id: "email", + header: intl.formatMessage({ id: "column.email" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row.roles, { + id: "roles", + header: intl.formatMessage({ id: "column.roles" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row.isDisabled, { + id: "isDisabled", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", + cell: (info: any) => { + return ( + + +
    + + + + { + e.preventDefault(); + onEditUser?.(info.row.original.id); + }} + > + + + + {currentUserId !== info.row.original.id ? ( + <> + { + e.preventDefault(); + onEditPermissions?.(info.row.original.id); + }} + > + + + + { + e.preventDefault(); + onSetPassword?.(info.row.original.id); + }} + > + + + + { + e.preventDefault(); + onDisableToggle?.(info.row.original.id, info.row.original.isDisabled); + }} + > + + + + {info.row.original.isDisabled ? ( +
    + + +
    + ) : ( + { + e.preventDefault(); + onLoginAs?.(info.row.original.id); + }} + > + + + + )} + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [ + columnHelper, + currentUserId, + onEditUser, + onDisableToggle, + onDeleteUser, + onEditPermissions, + onSetPassword, + onLoginAs, + ], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return ( + + } + /> + ); +} diff --git a/frontend/src/pages/Users/TableWrapper.tsx b/frontend/src/pages/Users/TableWrapper.tsx new file mode 100644 index 00000000..461ed6ab --- /dev/null +++ b/frontend/src/pages/Users/TableWrapper.tsx @@ -0,0 +1,123 @@ +import { IconSearch } from "@tabler/icons-react"; +import { useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { deleteUser, toggleUser } from "src/api/backend"; +import { Button, LoadingPage } from "src/components"; +import { useAuthState } from "src/context"; +import { useUser, useUsers } from "src/hooks"; +import { T } from "src/locale"; +import { showDeleteConfirmModal, showPermissionsModal, showSetPasswordModal, showUserModal } from "src/modals"; +import { showError, showObjectSuccess } from "src/notifications"; +import Table from "./Table"; + +export default function TableWrapper() { + const queryClient = useQueryClient(); + const { loginAs } = useAuthState(); + const [search, setSearch] = useState(""); + const { isFetching, isLoading, isError, error, data } = useUsers(["permissions"]); + const { data: currentUser } = useUser("me"); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + const handleLoginAs = async (id: number) => { + try { + await loginAs(id); + } catch (err) { + if (err instanceof Error) { + showError(err.message); + } + } + }; + + const handleDelete = async (id: number) => { + await deleteUser(id); + showObjectSuccess("user", "deleted"); + }; + + const handleDisableToggle = async (id: number, enabled: boolean) => { + await toggleUser(id, enabled); + queryClient.invalidateQueries({ queryKey: ["users"] }); + queryClient.invalidateQueries({ queryKey: ["user", id] }); + showObjectSuccess("user", enabled ? "enabled" : "disabled"); + }; + + let filtered = null; + if (search && data) { + filtered = data?.filter((item) => { + return ( + item.name.toLowerCase().includes(search) || + item.nickname.toLowerCase().includes(search) || + item.email.toLowerCase().includes(search) + ); + }); + } else if (search !== "") { + // this can happen if someone deletes the last item while searching + setSearch(""); + } + + return ( +
    +
    +
    +
    +
    +
    +

    + +

    +
    + {data?.length ? ( +
    +
    +
    + + + + setSearch(e.target.value.toLowerCase().trim())} + /> +
    + + +
    +
    + ) : null} +
    +
    +
    showUserModal(id)} + onEditPermissions={(id: number) => showPermissionsModal(id)} + onSetPassword={(id: number) => showSetPasswordModal(id)} + onDeleteUser={(id: number) => + showDeleteConfirmModal({ + title: , + onConfirm: () => handleDelete(id), + invalidations: [["users"], ["user", id]], + children: , + }) + } + onDisableToggle={handleDisableToggle} + onNewUser={() => showUserModal("new")} + onLoginAs={handleLoginAs} + /> + + + ); +} diff --git a/frontend/src/pages/Users/index.tsx b/frontend/src/pages/Users/index.tsx new file mode 100644 index 00000000..53b43664 --- /dev/null +++ b/frontend/src/pages/Users/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { ADMIN, VIEW } from "src/modules/Permissions"; +import TableWrapper from "./TableWrapper"; + +const Users = () => { + return ( + + + + ); +}; + +export default Users; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..fb86d45b --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,43 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "skipLibCheck": true, + "baseUrl": ".", + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "src/*": [ + "./src/*" + ], + "test/*": [ + "./test/*" + ] + } + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 00000000..eca66688 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 00000000..10165d07 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,51 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; +import checker from "vite-plugin-checker"; +import tsconfigPaths from "vite-tsconfig-paths"; +import "vitest/config"; +import { execFile } from "node:child_process"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + { + name: "trigger-script-on-reload", + configureServer(server) { + server.watcher.on("change", (file) => { + if (file.includes("locale/src")) { + console.log(`File changed: ${file}, running locale-compile script...`); + execFile("yarn", ["locale-compile"], (error, stdout, _stderr) => { + if (error) { + throw error; + } + console.log(stdout); + execFile("yarn", ["locale-sort"], (error, stdout, _stderr) => { + if (error) { + throw error; + } + console.log(stdout); + }); + }); + } + }); + }, + }, + react(), + checker({ + // e.g. use TypeScript check + typescript: true, + }), + tsconfigPaths(), + ], + server: { + host: true, + port: 5173, + strictPort: true, + allowedHosts: true, + }, + test: { + environment: "happy-dom", + setupFiles: ["./vitest-setup.js"], + }, + assetsInclude: ["**/*.md", "**/*.png", "**/*.svg"], +}); diff --git a/frontend/vitest-setup.js b/frontend/vitest-setup.js new file mode 100644 index 00000000..f149f27a --- /dev/null +++ b/frontend/vitest-setup.js @@ -0,0 +1 @@ +import "@testing-library/jest-dom/vitest"; diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js deleted file mode 100644 index 05350a47..00000000 --- a/frontend/webpack.config.js +++ /dev/null @@ -1,144 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const Visualizer = require('webpack-visualizer-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const PACKAGE = require('./package.json'); - -module.exports = { - entry: { - main: './js/index.js', - login: './js/login.js' - }, - output: { - path: path.resolve(__dirname, 'dist'), - filename: `js/[name].bundle.js?v=${PACKAGE.version}`, - chunkFilename: `js/[name].bundle.[id].js?v=${PACKAGE.version}`, - publicPath: '/' - }, - resolve: { - alias: { - 'tabler-core': 'tabler-ui/dist/assets/js/core', - 'bootstrap': 'tabler-ui/dist/assets/js/vendors/bootstrap.bundle.min', - 'sparkline': 'tabler-ui/dist/assets/js/vendors/jquery.sparkline.min', - 'selectize': 'tabler-ui/dist/assets/js/vendors/selectize.min', - 'tablesorter': 'tabler-ui/dist/assets/js/vendors/jquery.tablesorter.min', - 'vector-map': 'tabler-ui/dist/assets/js/vendors/jquery-jvectormap-2.0.3.min', - 'vector-map-de': 'tabler-ui/dist/assets/js/vendors/jquery-jvectormap-de-merc', - 'vector-map-world': 'tabler-ui/dist/assets/js/vendors/jquery-jvectormap-world-mill', - 'circle-progress': 'tabler-ui/dist/assets/js/vendors/circle-progress.min', - 'c3': 'tabler-ui/dist/assets/js/vendors/chart.bundle.min' - } - }, - module: { - rules: [ - // Shims for tabler-ui - { - test: /assets\/js\/core/, - loader: 'imports-loader?bootstrap' - }, - { - test: /jquery-jvectormap-de-merc/, - loader: 'imports-loader?vector-map' - }, - { - test: /jquery-jvectormap-world-mill/, - loader: 'imports-loader?vector-map' - }, - - // other: - { - type: 'javascript/auto', // <= Set the module.type explicitly - test: /\bmessages\.json$/, - loader: 'messageformat-loader', - options: { - biDiSupport: false, - disablePluralKeyChecks: false, - formatters: null, - intlSupport: false, - locale: ['en'], - strictNumberSign: false - } - }, - { - test: /\.js$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader' - } - }, - { - test: /\.ejs$/, - loader: 'ejs-loader' - }, - { - test: /\.scss$/, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - 'sass-loader' - ] - }, - { - test: /.*tabler.*\.(jpe?g|gif|png|svg|eot|woff|ttf)$/, - use: [ - { - loader: 'file-loader', - options: { - outputPath: 'assets/tabler-ui/' - } - } - ] - }, - { - test: /source-sans-pro.*\.(woff(2)?)(\?v=\d+\.\d+\.\d+)?$/, - use: [ - { - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'assets/' - } - } - ] - } - ] - }, - plugins: [ - new webpack.ProvidePlugin({ - $: 'jquery', - jQuery: 'jquery', - _: 'underscore' - }), - new HtmlWebpackPlugin({ - template: '!!ejs-webpack-loader!html/index.ejs', - filename: 'index.html', - inject: false, - templateParameters: { - version: PACKAGE.version - } - }), - new HtmlWebpackPlugin({ - template: '!!ejs-webpack-loader!html/login.ejs', - filename: 'login.html', - inject: false, - templateParameters: { - version: PACKAGE.version - } - }), - new MiniCssExtractPlugin({ - filename: 'css/[name].css', - chunkFilename: 'css/[id].css' - }), - new Visualizer({ - filename: '../webpack_stats.html' - }), - new CopyWebpackPlugin([{ - from: 'app-images', - to: 'images', - toType: 'dir', - context: '/app/frontend' - }]) - ] -}; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a7eca950..bf95adea 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2,3660 +2,1618 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" - integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== +"@adobe/css-tools@^4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.4.tgz#2856c55443d3d461693f32d2b96fb6ea92e1ffa9" + integrity sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg== + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== dependencies: - "@babel/highlight" "^7.10.4" - -"@babel/code-frame@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== - dependencies: - "@babel/highlight" "^7.22.13" - chalk "^2.4.2" - -"@babel/core@^7.9.0": - version "7.11.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.1.tgz#2c55b604e73a40dc21b0e52650b11c65cf276643" - integrity sha512-XqF7F6FWQdKGGWAzGELL+aCO1p+lRY5Tj5/tbT3St1G8NaH70jhhDIKknIZaDans0OQBG5wRAldROLHSt44BgQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.11.0" - "@babel/helper-module-transforms" "^7.11.0" - "@babel/helpers" "^7.10.4" - "@babel/parser" "^7.11.1" - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.11.0" - "@babel/types" "^7.11.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.19" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/generator@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.0.tgz#4b90c78d8c12825024568cbe83ee6c9af193585c" - integrity sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ== - dependencies: - "@babel/types" "^7.11.0" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" - integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== - dependencies: - "@babel/types" "^7.23.0" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-member-expression-to-functions@^7.10.4": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" - integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q== - dependencies: - "@babel/types" "^7.11.0" - -"@babel/helper-module-imports@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" - integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-module-transforms@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" - integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg== - dependencies: - "@babel/helper-module-imports" "^7.10.4" - "@babel/helper-replace-supers" "^7.10.4" - "@babel/helper-simple-access" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/template" "^7.10.4" - "@babel/types" "^7.11.0" - lodash "^4.17.19" - -"@babel/helper-optimise-call-expression@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" - integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-replace-supers@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" - integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.10.4" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/helper-simple-access@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" - integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== - dependencies: - "@babel/template" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/helper-split-export-declaration@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" - integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== - dependencies: - "@babel/types" "^7.11.0" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - -"@babel/helper-validator-identifier@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" - integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helpers@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" - integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== - dependencies: - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/highlight@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" - integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== - dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.27.1" js-tokens "^4.0.0" + picocolors "^1.1.1" -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== +"@babel/compat-data@^7.27.2": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" + integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== + +"@babel/core@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" + integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.10.4", "@babel/parser@^7.11.1": - version "7.11.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.2.tgz#0882ab8a455df3065ea2dcb4c753b2460a24bead" - integrity sha512-Vuj/+7vLo6l1Vi7uuO+1ngCDNeVmNbTngcJFKCR/oEtz8tKz0CJxZEGmPt9KcIloZhOZ3Zit6xbpXT2MDlS9Vw== - -"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== - -"@babel/template@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" - integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/parser" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/template@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/traverse@^7.10.4", "@babel/traverse@^7.11.0": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" - integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.0" - "@babel/types" "^7.23.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.4" + "@babel/types" "^7.28.4" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" debug "^4.1.0" - globals "^11.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" -"@babel/types@^7.10.4", "@babel/types@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" - integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== +"@babel/generator@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - lodash "^4.17.19" - to-fast-properties "^2.0.0" + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" -"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" - integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" -"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" - integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== +"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@babel/helper-plugin-utils@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" + integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== + dependencies: + "@babel/types" "^7.28.2" + +"@babel/parser@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + +"@babel/plugin-transform-react-jsx-self@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz#af678d8506acf52c577cac73ff7fe6615c85fc92" + integrity sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-source@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz#dcfe2c24094bb757bf73960374e7c55e434f19f0" + integrity sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/runtime@^7.12.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" + integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== + +"@babel/runtime@^7.12.5", "@babel/runtime@^7.24.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.3.tgz#75c5034b55ba868121668be5d5bb31cc64e6e61a" + integrity sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA== + +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.3.tgz#6911a10795d2cce43ec6a28cffc440cca2593434" + integrity sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.3" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.2" + debug "^4.3.1" + +"@babel/traverse@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.28.2": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" + integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@babel/types@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@biomejs/biome@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.3.2.tgz#aeeb5f12c39571a18f36a919be63ba7dbc7b290a" + integrity sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg== + optionalDependencies: + "@biomejs/cli-darwin-arm64" "2.3.2" + "@biomejs/cli-darwin-x64" "2.3.2" + "@biomejs/cli-linux-arm64" "2.3.2" + "@biomejs/cli-linux-arm64-musl" "2.3.2" + "@biomejs/cli-linux-x64" "2.3.2" + "@biomejs/cli-linux-x64-musl" "2.3.2" + "@biomejs/cli-win32-arm64" "2.3.2" + "@biomejs/cli-win32-x64" "2.3.2" + +"@biomejs/cli-darwin-arm64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.2.tgz#93f866161abe32e702987ccbddf492c1aabe016f" + integrity sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew== + +"@biomejs/cli-darwin-x64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.2.tgz#9c3dffdac12e4f4d8db7680ca20f58ace1f38c23" + integrity sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA== + +"@biomejs/cli-linux-arm64-musl@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.2.tgz#a0424d2fe355cc43c375b3fbf3e42d39b7221d0e" + integrity sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw== + +"@biomejs/cli-linux-arm64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.2.tgz#f85717c04d420ede20523d173a1fc10df60d4d37" + integrity sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw== + +"@biomejs/cli-linux-x64-musl@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.2.tgz#d3e114c744c32d2c50a77c13476bd941819c92d8" + integrity sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA== + +"@biomejs/cli-linux-x64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.2.tgz#f66ce85d2d757d45e6edecce04753a805bd816f0" + integrity sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA== + +"@biomejs/cli-win32-arm64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.2.tgz#b46f8b47a3d97e766cc5ad5eb67d90eeb230b2cb" + integrity sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg== + +"@biomejs/cli-win32-x64@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.2.tgz#a14f5e220dd496705278315ee3e5e028dd657344" + integrity sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ== + +"@emotion/babel-plugin@^11.13.5": + version "11.13.5" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz#eab8d65dbded74e0ecfd28dc218e75607c4e7bc0" + integrity sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/serialize" "^1.3.3" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@^11.14.0", "@emotion/cache@^11.4.0": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.14.0.tgz#ee44b26986eeb93c8be82bb92f1f7a9b21b2ed76" + integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + stylis "4.2.0" + +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== + +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + +"@emotion/react@^11.8.1": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.14.0.tgz#cfaae35ebc67dd9ef4ea2e9acc6cd29e157dd05d" + integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.13.5" + "@emotion/cache" "^11.14.0" + "@emotion/serialize" "^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.3.tgz#d291531005f17d704d0463a032fe679f376509e8" + integrity sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA== + dependencies: + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/unitless" "^0.10.0" + "@emotion/utils" "^1.4.2" + csstype "^3.0.2" + +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== + +"@emotion/unitless@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745" + integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== + +"@emotion/use-insertion-effect-with-fallbacks@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz#8a8cb77b590e09affb960f4ff1e9a89e532738bf" + integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== + +"@emotion/utils@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.2.tgz#6df6c45881fcb1c412d6688a311a98b7f59c1b52" + integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== + +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== + +"@esbuild/aix-ppc64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz#bef96351f16520055c947aba28802eede3c9e9a9" + integrity sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA== + +"@esbuild/android-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz#d2e70be7d51a529425422091e0dcb90374c1546c" + integrity sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg== + +"@esbuild/android-arm@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.9.tgz#d2a753fe2a4c73b79437d0ba1480e2d760097419" + integrity sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ== + +"@esbuild/android-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.9.tgz#5278836e3c7ae75761626962f902a0d55352e683" + integrity sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw== + +"@esbuild/darwin-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz#f1513eaf9ec8fa15dcaf4c341b0f005d3e8b47ae" + integrity sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg== + +"@esbuild/darwin-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz#e27dbc3b507b3a1cea3b9280a04b8b6b725f82be" + integrity sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ== + +"@esbuild/freebsd-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz#364e3e5b7a1fd45d92be08c6cc5d890ca75908ca" + integrity sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q== + +"@esbuild/freebsd-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz#7c869b45faeb3df668e19ace07335a0711ec56ab" + integrity sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg== + +"@esbuild/linux-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz#48d42861758c940b61abea43ba9a29b186d6cb8b" + integrity sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw== + +"@esbuild/linux-arm@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz#6ce4b9cabf148274101701d112b89dc67cc52f37" + integrity sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw== + +"@esbuild/linux-ia32@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz#207e54899b79cac9c26c323fc1caa32e3143f1c4" + integrity sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A== + +"@esbuild/linux-loong64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz#0ba48a127159a8f6abb5827f21198b999ffd1fc0" + integrity sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ== + +"@esbuild/linux-mips64el@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz#a4d4cc693d185f66a6afde94f772b38ce5d64eb5" + integrity sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA== + +"@esbuild/linux-ppc64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz#0f5805c1c6d6435a1dafdc043cb07a19050357db" + integrity sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w== + +"@esbuild/linux-riscv64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz#6776edece0f8fca79f3386398b5183ff2a827547" + integrity sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg== + +"@esbuild/linux-s390x@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz#3f6f29ef036938447c2218d309dc875225861830" + integrity sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA== + +"@esbuild/linux-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz#831fe0b0e1a80a8b8391224ea2377d5520e1527f" + integrity sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg== + +"@esbuild/netbsd-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz#06f99d7eebe035fbbe43de01c9d7e98d2a0aa548" + integrity sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q== + +"@esbuild/netbsd-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz#db99858e6bed6e73911f92a88e4edd3a8c429a52" + integrity sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g== + +"@esbuild/openbsd-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz#afb886c867e36f9d86bb21e878e1185f5d5a0935" + integrity sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ== + +"@esbuild/openbsd-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz#30855c9f8381fac6a0ef5b5f31ac6e7108a66ecf" + integrity sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA== + +"@esbuild/openharmony-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz#2f2144af31e67adc2a8e3705c20c2bd97bd88314" + integrity sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg== + +"@esbuild/sunos-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz#69b99a9b5bd226c9eb9c6a73f990fddd497d732e" + integrity sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw== + +"@esbuild/win32-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz#d789330a712af916c88325f4ffe465f885719c6b" + integrity sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ== + +"@esbuild/win32-ia32@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz#52fc735406bd49688253e74e4e837ac2ba0789e3" + integrity sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww== + +"@esbuild/win32-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz#585624dc829cfb6e7c0aa6c3ca7d7e6daa87e34f" + integrity sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ== + +"@floating-ui/core@^1.7.3": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.3.tgz#462d722f001e23e46d86fd2bd0d21b7693ccb8b7" + integrity sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w== + dependencies: + "@floating-ui/utils" "^0.2.10" + +"@floating-ui/dom@^1.0.1": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.4.tgz#ee667549998745c9c3e3e84683b909c31d6c9a77" + integrity sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA== + dependencies: + "@floating-ui/core" "^1.7.3" + "@floating-ui/utils" "^0.2.10" + +"@floating-ui/utils@^0.2.10": + version "0.2.10" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c" + integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== + +"@formatjs/cli@^6.7.4": + version "6.7.4" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.7.4.tgz#21fdab4e215763034682abf148b64b4f8d9d4a5c" + integrity sha512-k6uqdeZDAjDd7iKKQ8yFYizFpbi5Y9H9NkV+hoIhmxaMSGvWRnRusQJaIQ+2rI14MH6knW6fx7tnO15C+ijDiw== + +"@formatjs/ecma402-abstract@2.3.6": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz#d6ca9d3579054fe1e1a0a0b5e872e0d64922e4e1" + integrity sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw== + dependencies: + "@formatjs/fast-memoize" "2.2.7" + "@formatjs/intl-localematcher" "0.6.2" + decimal.js "^10.4.3" + tslib "^2.8.0" + +"@formatjs/fast-memoize@2.2.7": + version "2.2.7" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz#707f9ddaeb522a32f6715bb7950b0831f4cc7b15" + integrity sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ== + dependencies: + tslib "^2.8.0" + +"@formatjs/icu-messageformat-parser@2.11.4": + version "2.11.4" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz#63bd2cd82d08ae2bef55adeeb86486df68826f32" + integrity sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw== + dependencies: + "@formatjs/ecma402-abstract" "2.3.6" + "@formatjs/icu-skeleton-parser" "1.8.16" + tslib "^2.8.0" + +"@formatjs/icu-skeleton-parser@1.8.16": + version "1.8.16" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz#13f81f6845c7cf6599623006aacaf7d6b4ad2970" + integrity sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ== + dependencies: + "@formatjs/ecma402-abstract" "2.3.6" + tslib "^2.8.0" + +"@formatjs/intl-localematcher@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz#e9ebe0b4082d7d48e5b2d753579fb7ece4eaefea" + integrity sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA== + dependencies: + tslib "^2.8.0" + +"@formatjs/intl@3.1.8": + version "3.1.8" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-3.1.8.tgz#17f4a5721f32cd077ab04949be558f9f767c39f5" + integrity sha512-LWXgwI5zTMatvR8w8kCNh/priDTOF/ZssokMBHJ7ZWXFoYLVOYo0EJERD9Eajv+xsfQO1QkuAt77KWQ1OI4mOQ== + dependencies: + "@formatjs/ecma402-abstract" "2.3.6" + "@formatjs/fast-memoize" "2.2.7" + "@formatjs/icu-messageformat-parser" "2.11.4" + intl-messageformat "10.7.18" + tslib "^2.8.0" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.19" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" - integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.30" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99" + integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@nodelib/fs.scandir@2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" - integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== +"@parcel/watcher-android-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" + integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA== + +"@parcel/watcher-darwin-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67" + integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw== + +"@parcel/watcher-darwin-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8" + integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg== + +"@parcel/watcher-freebsd-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b" + integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ== + +"@parcel/watcher-linux-arm-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1" + integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA== + +"@parcel/watcher-linux-arm-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e" + integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q== + +"@parcel/watcher-linux-arm64-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30" + integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w== + +"@parcel/watcher-linux-arm64-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2" + integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg== + +"@parcel/watcher-linux-x64-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e" + integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A== + +"@parcel/watcher-linux-x64-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee" + integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== + +"@parcel/watcher-win32-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243" + integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw== + +"@parcel/watcher-win32-ia32@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6" + integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ== + +"@parcel/watcher-win32-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947" + integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA== + +"@parcel/watcher@^2.4.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200" + integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== dependencies: - "@nodelib/fs.stat" "2.0.3" - run-parallel "^1.1.9" + detect-libc "^1.0.3" + is-glob "^4.0.3" + micromatch "^4.0.5" + node-addon-api "^7.0.0" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.5.1" + "@parcel/watcher-darwin-arm64" "2.5.1" + "@parcel/watcher-darwin-x64" "2.5.1" + "@parcel/watcher-freebsd-x64" "2.5.1" + "@parcel/watcher-linux-arm-glibc" "2.5.1" + "@parcel/watcher-linux-arm-musl" "2.5.1" + "@parcel/watcher-linux-arm64-glibc" "2.5.1" + "@parcel/watcher-linux-arm64-musl" "2.5.1" + "@parcel/watcher-linux-x64-glibc" "2.5.1" + "@parcel/watcher-linux-x64-musl" "2.5.1" + "@parcel/watcher-win32-arm64" "2.5.1" + "@parcel/watcher-win32-ia32" "2.5.1" + "@parcel/watcher-win32-x64" "2.5.1" -"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" - integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== -"@nodelib/fs.walk@^1.2.3": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" - integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== +"@react-aria/ssr@^3.5.0": + version "3.9.10" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.10.tgz#7fdc09e811944ce0df1d7e713de1449abd7435e6" + integrity sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ== dependencies: - "@nodelib/fs.scandir" "2.1.3" - fastq "^1.6.0" + "@swc/helpers" "^0.5.0" -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" - integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== +"@restart/hooks@^0.4.9": + version "0.4.16" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.16.tgz#95ae8ac1cc7e2bd4fed5e39800ff85604c6d59fb" + integrity sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w== dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" + dequal "^2.0.3" -"@npmcli/fs@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" - integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== +"@restart/hooks@^0.5.0": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.5.1.tgz#6776b3859e33aea72b23b81fc47021edf17fd247" + integrity sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q== dependencies: - "@gar/promisify" "^1.1.3" - semver "^7.3.5" + dequal "^2.0.3" -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== +"@restart/ui@^1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@restart/ui/-/ui-1.9.4.tgz#9d61f56f2647f5ab8a33d87b278b9ce183511a26" + integrity sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA== dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" + "@babel/runtime" "^7.26.0" + "@popperjs/core" "^2.11.8" + "@react-aria/ssr" "^3.5.0" + "@restart/hooks" "^0.5.0" + "@types/warning" "^3.0.3" + dequal "^2.0.3" + dom-helpers "^5.2.0" + uncontrollable "^8.0.4" + warning "^4.0.3" -"@npmcli/move-file@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" - integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== +"@rolldown/pluginutils@1.0.0-beta.43": + version "1.0.0-beta.43" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz#fa8249860113711ad3c8053bc79cb07c79b77f62" + integrity sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ== + +"@rollup/rollup-android-arm-eabi@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.0.tgz#939c1be9625d428d8513e4ab60d406fe8db23718" + integrity sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ== + +"@rollup/rollup-android-arm64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.0.tgz#b74005775903f7a8f4e363d2840c1dcef3776ff3" + integrity sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw== + +"@rollup/rollup-darwin-arm64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz#8c04603cdcf1ec0cd6b27152b3827e49295f2962" + integrity sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg== + +"@rollup/rollup-darwin-x64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz#19ec976f1cc663def2692cd7ffb32981f2b0b733" + integrity sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw== + +"@rollup/rollup-freebsd-arm64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.0.tgz#a96b4ad8346229f6fcbd9d57f1c53040b037c2da" + integrity sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ== + +"@rollup/rollup-freebsd-x64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.0.tgz#fa565a282bc57967ee6668607b181678bdd74e4a" + integrity sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA== + +"@rollup/rollup-linux-arm-gnueabihf@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.0.tgz#dfc88f7295e1f98d77f25296be787e8a5d6ced75" + integrity sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w== + +"@rollup/rollup-linux-arm-musleabihf@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.0.tgz#32cd70c87455ca031f0361090cf17da5a2ef66d5" + integrity sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg== + +"@rollup/rollup-linux-arm64-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.0.tgz#0e7e1fe7241e3384f6c6b4ccdbcfa8ad8c78b869" + integrity sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g== + +"@rollup/rollup-linux-arm64-musl@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.0.tgz#5d421f2f3e4a84786c4dfd9ce97e595c9b59e7f4" + integrity sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ== + +"@rollup/rollup-linux-loongarch64-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.0.tgz#a0fb5c7d0e88319e18acfd9436f19ee39354b027" + integrity sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ== + +"@rollup/rollup-linux-ppc64-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.0.tgz#a65b598af12f25210c3295da551a6e3616ea488d" + integrity sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg== + +"@rollup/rollup-linux-riscv64-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.0.tgz#10ba776214ae2857c5bf4389690dabb2fbaf7d98" + integrity sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA== + +"@rollup/rollup-linux-riscv64-musl@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.0.tgz#c2a46cbaa329d5f21e5808f5a66bb9c78cf68aac" + integrity sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ== + +"@rollup/rollup-linux-s390x-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.0.tgz#a07447be069d64462e30c66611be20c4513963ed" + integrity sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ== + +"@rollup/rollup-linux-x64-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz#8887c58bd51242754ae9c56947d6e883332dcc74" + integrity sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA== + +"@rollup/rollup-linux-x64-musl@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.0.tgz#6403fda72a2b3b9fbbeeff93d14f1c45ef9775f3" + integrity sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw== + +"@rollup/rollup-openharmony-arm64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.0.tgz#52809afccaff47e731b965a0c16e5686be819d5f" + integrity sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q== + +"@rollup/rollup-win32-arm64-msvc@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.0.tgz#23fe00ddbb40b27a3889bc1e99e6310d97353ad5" + integrity sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg== + +"@rollup/rollup-win32-ia32-msvc@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.0.tgz#520b588076b593413d919912d69dfd5728a1f305" + integrity sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw== + +"@rollup/rollup-win32-x64-msvc@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz#d81efe6a12060c7feddf9805e2a94c3ab0679f48" + integrity sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg== + +"@standard-schema/spec@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c" + integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== + +"@swc/helpers@^0.5.0": + version "0.5.17" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971" + integrity sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A== dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" + tslib "^2.8.0" -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== +"@tabler/core@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@tabler/core/-/core-1.4.0.tgz#9e011289d92cf6120655793cef6576fd67eb07f3" + integrity sha512-5BigzOlbOH9N0Is4u0rYNRCiwtnUXWO57K9zwuscygcicAa8UV9MGaS4zTgQsZEtZ9tsNANhN/YD8gCBGKYCiw== dependencies: - defer-to-connect "^1.0.1" + "@popperjs/core" "^2.11.8" + bootstrap "5.3.7" -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tabler/icons-react@^3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.35.0.tgz#27f295f36b42f8dc2e7841dd651c8905d0097818" + integrity sha512-XG7t2DYf3DyHT5jxFNp5xyLVbL4hMJYJhiSdHADzAjLRYfL7AnjlRfiHDHeXxkb2N103rEIvTsBRazxXtAUz2g== + dependencies: + "@tabler/icons" "3.35.0" -"@tootallnate/once@2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" - integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@tabler/icons@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.35.0.tgz#6f35247e41baba2a1b0f4dff048bb1335d6c1075" + integrity sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ== -"@types/anymatch@*": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" - integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== +"@tanstack/query-core@5.90.6": + version "5.90.6" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.90.6.tgz#e4627c442a4701adb350270dae5f6b72b2498eae" + integrity sha512-AnZSLF26R8uX+tqb/ivdrwbVdGemdEDm1Q19qM6pry6eOZ6bEYiY7mWhzXT1YDIPTNEVcZ5kYP9nWjoxDLiIVw== -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@tanstack/query-devtools@5.90.1": + version "5.90.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.90.1.tgz#c57a739a5293f4960a4a6b6fb4b7e4a56e6bd932" + integrity sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ== -"@types/html-minifier-terser@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880" - integrity sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA== +"@tanstack/react-query-devtools@^5.90.2": + version "5.90.2" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.90.2.tgz#248d6ae89ace7dc2da816fa95cdc2b4f63c9e4d2" + integrity sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ== + dependencies: + "@tanstack/query-devtools" "5.90.1" -"@types/json-schema@^7.0.4": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== +"@tanstack/react-query@^5.90.6": + version "5.90.6" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.90.6.tgz#50f46ca81679c3fda185ea7d2c7787af78ed0364" + integrity sha512-gB1sljYjcobZKxjPbKSa31FUTyr+ROaBdoH+wSSs9Dk+yDCmMs+TkTV3PybRRVLC7ax7q0erJ9LvRWnMktnRAw== + dependencies: + "@tanstack/query-core" "5.90.6" -"@types/json-schema@^7.0.8": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@tanstack/react-table@^8.21.3": + version "8.21.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.21.3.tgz#2c38c747a5731c1a07174fda764b9c2b1fb5e91b" + integrity sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww== + dependencies: + "@tanstack/table-core" "8.21.3" -"@types/minimist@^1.2.0": +"@tanstack/table-core@8.21.3": + version "8.21.3" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.21.3.tgz#2977727d8fc8dfa079112d9f4d4c019110f1732c" + integrity sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg== + +"@testing-library/dom@^10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.1.tgz#d444f8a889e9a46e9a3b4f3b88e0fcb3efb6cf95" + integrity sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.3.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + picocolors "1.1.1" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@^6.9.1": + version "6.9.1" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz#7613a04e146dd2976d24ddf019730d57a89d56c2" + integrity sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + picocolors "^1.1.1" + redent "^3.0.0" + +"@testing-library/react@^16.3.0": + version "16.3.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.0.tgz#3a85bb9bdebf180cd76dba16454e242564d598a6" + integrity sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw== + dependencies: + "@babel/runtime" "^7.12.5" + +"@types/aria-query@^5.0.1": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== + +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/chai@^5.2.2": + version "5.2.2" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.2.tgz#6f14cea18180ffc4416bc0fd12be05fdd73bdd6b" + integrity sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg== + dependencies: + "@types/deep-eql" "*" + +"@types/country-flag-icons@^1.2.2": version "1.2.2" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" - integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== + resolved "https://registry.yarnpkg.com/@types/country-flag-icons/-/country-flag-icons-1.2.2.tgz#8f51089cab857f0f700feabd38b3960d006d64f2" + integrity sha512-CefEn/J336TBDp7NX8JqzlDtCBOsm8M3r1Li0gEOt0HOMHF1XemNyrx9lSHjsafcb1yYWybU0N8ZAXuyCaND0w== -"@types/node@*": - version "14.0.27" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" - integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g== - -"@types/normalize-package-data@^2.4.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" - integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== - -"@types/source-list-map@*": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" - integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== - -"@types/tapable@*", "@types/tapable@^1.0.5": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" - integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA== - -"@types/uglify-js@*": - version "3.9.3" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.3.tgz#d94ed608e295bc5424c9600e6b8565407b6b4b6b" - integrity sha512-KswB5C7Kwduwjj04Ykz+AjvPcfgv/37Za24O2EDzYNbwyzOo8+ydtvzUfZ5UMguiVu29Gx44l1A6VsPPcmYu9w== +"@types/debug@^4.0.0": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== dependencies: - source-map "^0.6.1" + "@types/ms" "*" -"@types/webpack-sources@*": - version "1.4.2" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-1.4.2.tgz#5d3d4dea04008a779a90135ff96fb5c0c9e6292c" - integrity sha512-77T++JyKow4BQB/m9O96n9d/UUHWLQHlcqXb9Vsf4F1+wKNrrlWNFPDLKNT92RJnCSL6CieTc+NDXtCVZswdTw== +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.7.3" + "@types/estree" "*" -"@types/webpack@^4.41.8": - version "4.41.21" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.21.tgz#cc685b332c33f153bb2f5fc1fa3ac8adeb592dee" - integrity sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA== +"@types/estree@*", "@types/estree@1.0.8", "@types/estree@^1.0.0": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/hast@^2.0.0": + version "2.3.10" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.10.tgz#5c9d9e0b304bbb8879b857225c5ebab2d81d7643" + integrity sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw== dependencies: - "@types/anymatch" "*" - "@types/node" "*" - "@types/tapable" "*" - "@types/uglify-js" "*" - "@types/webpack-sources" "*" - source-map "^0.6.0" + "@types/unist" "^2" -"@webassemblyjs/ast@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" - integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" + "@types/unist" "*" -"@webassemblyjs/floating-point-hex-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" - integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== - -"@webassemblyjs/helper-api-error@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" - integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== - -"@webassemblyjs/helper-buffer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" - integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== - -"@webassemblyjs/helper-code-frame@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" - integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.7" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz#306e3a3a73828522efa1341159da4846e7573a6c" + integrity sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g== dependencies: - "@webassemblyjs/wast-printer" "1.9.0" + hoist-non-react-statics "^3.3.0" -"@webassemblyjs/helper-fsm@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" - integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== +"@types/humps@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/humps/-/humps-2.0.6.tgz#a358688fe092e40b5f50261e0a55e2fa6d68cabe" + integrity sha512-Fagm1/a/1J9gDKzGdtlPmmTN5eSw/aaTzHtj740oSfo+MODsSY2WglxMmhTdOglC8nxqUhGGQ+5HfVtBvxo3Kg== -"@webassemblyjs/helper-module-context@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" - integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== +"@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== dependencies: - "@webassemblyjs/ast" "1.9.0" + "@types/unist" "*" -"@webassemblyjs/helper-wasm-bytecode@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" - integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== -"@webassemblyjs/helper-wasm-section@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" - integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== +"@types/node@^20.0.0": + version "20.19.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.11.tgz#728cab53092bd5f143beed7fbba7ba99de3c16c4" + integrity sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" + undici-types "~6.21.0" -"@webassemblyjs/ieee754@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" - integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + +"@types/prismjs@^1.0.0": + version "1.26.5" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.5.tgz#72499abbb4c4ec9982446509d2f14fb8483869d6" + integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ== + +"@types/prop-types@^15.7.12": + version "15.7.15" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" + integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== + +"@types/react-dom@^19.2.2": + version "19.2.2" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.2.tgz#a4cc874797b7ddc9cb180ef0d5dc23f596fc2332" + integrity sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw== + +"@types/react-table@^7.7.20": + version "7.7.20" + resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.20.tgz#2f68e70ca7a703ad8011a8da55c38482f0eb4314" + integrity sha512-ahMp4pmjVlnExxNwxyaDrFgmKxSbPwU23sGQw2gJK4EhCvnvmib2s/O/+y1dfV57dXOwpr2plfyBol+vEHbi2w== dependencies: - "@xtuc/ieee754" "^1.2.0" + "@types/react" "*" -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" - integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== +"@types/react-transition-group@^4.4.0", "@types/react-transition-group@^4.4.6": + version "4.4.12" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" + integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== + +"@types/react@*", "@types/react@16 || 17 || 18 || 19", "@types/react@>=16.9.11": + version "19.1.12" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.12.tgz#7bfaa76aabbb0b4fe0493c21a3a7a93d33e8937b" + integrity sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w== dependencies: - "@xtuc/long" "4.2.2" + csstype "^3.0.2" -"@webassemblyjs/utf8@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" - integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== - -"@webassemblyjs/wasm-edit@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" - integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== +"@types/react@^19.2.2": + version "19.2.2" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.2.tgz#ba123a75d4c2a51158697160a4ea2ff70aa6bf36" + integrity sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/helper-wasm-section" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-opt" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - "@webassemblyjs/wast-printer" "1.9.0" + csstype "^3.0.2" -"@webassemblyjs/wasm-gen@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" - integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/unist@^2", "@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + +"@types/warning@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.3.tgz#d1884c8cc4a426d1ac117ca2611bf333834c6798" + integrity sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q== + +"@types/whatwg-mimetype@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz#e5e06dcd3e92d4e622ef0129637707d66c28d6a4" + integrity sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA== + +"@uiw/react-textarea-code-editor@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@uiw/react-textarea-code-editor/-/react-textarea-code-editor-3.1.1.tgz#8ca1b706a3081a51c68bc0df91c9c3cdadd9944e" + integrity sha512-AERRbp/d85vWR+UPgsB5hEgerNXuyszdmhWl2fV2H2jN63jgOobwEnjIpb76Vwy8SaGa/AdehaoJX2XZgNXtJA== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" + "@babel/runtime" "^7.18.6" + rehype "~13.0.0" + rehype-prism-plus "2.0.0" -"@webassemblyjs/wasm-opt@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" - integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== +"@ungap/structured-clone@^1.0.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@vitejs/plugin-react@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz#1f37671a227571437d6e324b824256dac157570e" + integrity sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" + "@babel/core" "^7.28.4" + "@babel/plugin-transform-react-jsx-self" "^7.27.1" + "@babel/plugin-transform-react-jsx-source" "^7.27.1" + "@rolldown/pluginutils" "1.0.0-beta.43" + "@types/babel__core" "^7.20.5" + react-refresh "^0.18.0" -"@webassemblyjs/wasm-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" - integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== +"@vitest/expect@4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.0.6.tgz#33df10e2f9728b7338c2a2331c75814d1f840ab7" + integrity sha512-5j8UUlBVhOjhj4lR2Nt9sEV8b4WtbcYh8vnfhTNA2Kn5+smtevzjNq+xlBuVhnFGXiyPPNzGrOVvmyHWkS5QGg== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" + "@standard-schema/spec" "^1.0.0" + "@types/chai" "^5.2.2" + "@vitest/spy" "4.0.6" + "@vitest/utils" "4.0.6" + chai "^6.0.1" + tinyrainbow "^3.0.3" -"@webassemblyjs/wast-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" - integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== +"@vitest/mocker@4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.0.6.tgz#3e27579d4444ca113685fc040961ce4b415ba5d2" + integrity sha512-3COEIew5HqdzBFEYN9+u0dT3i/NCwppLnO1HkjGfAP1Vs3vti1Hxm/MvcbC4DAn3Szo1M7M3otiAaT83jvqIjA== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/floating-point-hex-parser" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-code-frame" "1.9.0" - "@webassemblyjs/helper-fsm" "1.9.0" - "@xtuc/long" "4.2.2" + "@vitest/spy" "4.0.6" + estree-walker "^3.0.3" + magic-string "^0.30.19" -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" - integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== +"@vitest/pretty-format@4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.0.6.tgz#af838540d3cd6b29c5d434fbdd36eb2543b040a3" + integrity sha512-4vptgNkLIA1W1Nn5X4x8rLJBzPiJwnPc+awKtfBE5hNMVsoAl/JCCPPzNrbf+L4NKgklsis5Yp2gYa+XAS442g== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" + tinyrainbow "^3.0.3" -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -acorn-jsx@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" - integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== - -acorn-node@^1.2.0: - version "1.8.2" - resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" - integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== +"@vitest/runner@4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.0.6.tgz#5a938015cfb202b96cbff4688400f1cd4899b40a" + integrity sha512-trPk5qpd7Jj+AiLZbV/e+KiiaGXZ8ECsRxtnPnCrJr9OW2mLB72Cb824IXgxVz/mVU3Aj4VebY+tDTPn++j1Og== dependencies: - acorn "^7.0.0" - acorn-walk "^7.0.0" - xtend "^4.0.2" + "@vitest/utils" "4.0.6" + pathe "^2.0.3" -acorn-walk@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn@^5.2.1: - version "5.7.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" - integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== - -acorn@^6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" - integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== - -acorn@^7.0.0, acorn@^7.1.1: - version "7.4.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" - integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== - -agent-base@6, agent-base@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== +"@vitest/snapshot@4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.0.6.tgz#5cf47e396123cc379944632e908e74fb78d58f13" + integrity sha512-PaYLt7n2YzuvxhulDDu6c9EosiRuIE+FI2ECKs6yvHyhoga+2TBWI8dwBjs+IeuQaMtZTfioa9tj3uZb7nev1g== dependencies: - debug "4" + "@vitest/pretty-format" "4.0.6" + magic-string "^0.30.19" + pathe "^2.0.3" -agentkeepalive@^4.1.3, agentkeepalive@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" - integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== +"@vitest/spy@4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.0.6.tgz#3860eb53cfe333c5eefe8b510eb7d71da7f4bd70" + integrity sha512-g9jTUYPV1LtRPRCQfhbMintW7BTQz1n6WXYQYRQ25qkyffA4bjVXjkROokZnv7t07OqfaFKw1lPzqKGk1hmNuQ== + +"@vitest/utils@4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.0.6.tgz#e8ce743a4a5adcd7228823249b643bc894c0955d" + integrity sha512-bG43VS3iYKrMIZXBo+y8Pti0O7uNju3KvNn6DrQWhQQKcLavMB+0NZfO1/QBAEbq0MaQ3QjNsnnXlGQvsh0Z6A== dependencies: - humanize-ms "^1.2.1" - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2: - version "6.12.3" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" - integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - integrity sha1-DNkKVhCT810KmSVsIrcGlDP60Rc= - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= - -ansi-align@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" - integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== - dependencies: - string-width "^3.0.0" - -ansi-colors@^3.0.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" - integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== - -ansi-escapes@^4.2.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== - dependencies: - type-fest "^0.11.0" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + "@vitest/pretty-format" "4.0.6" + tinyrainbow "^3.0.3" ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== +aria-query@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== dependencies: - color-convert "^1.9.0" + dequal "^2.0.3" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== - dependencies: - "@types/color-name" "^1.1.1" - color-convert "^2.0.1" +aria-query@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -"aproba@^1.0.3 || ^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" - integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== - -aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" - integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== - dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: +babel-plugin-macros@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= - dependencies: - array-uniq "^1.0.1" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= - -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -asn1.js@^5.2.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" - integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - safer-buffer "^2.1.0" - -assert@^1.1.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -ast-types@0.9.6: - version "0.9.6" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" - integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk= - -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== - -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -async-foreach@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" - integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= - -async@~0.2.6: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E= - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -babel-code-frame@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-core@^6.26.0, babel-core@^6.26.3: - version "6.26.3" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" - integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.1" - debug "^2.6.9" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.8" - slash "^1.0.0" - source-map "^0.5.7" - -babel-generator@^6.26.0: - version "6.26.1" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" - integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== - dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.7" - trim-right "^1.0.1" - -babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" - integrity sha1-zORReto1b0IgvK6KAsKzRvmlZmQ= - dependencies: - babel-helper-explode-assignable-expression "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-call-delegate@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" - integrity sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340= - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-define-map@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" - integrity sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8= - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-explode-assignable-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" - integrity sha1-8luCz33BBDPFX3BZLVdGQArCLKo= - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" - integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk= - dependencies: - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-get-function-arity@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" - integrity sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-hoist-variables@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" - integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-optimise-call-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" - integrity sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-regex@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" - integrity sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI= - dependencies: - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-remap-async-to-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" - integrity sha1-XsWBgnrXI/7N04HxySg5BnbkVRs= - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-replace-supers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" - integrity sha1-v22/5Dk40XNpohPKiov3S2qQqxo= - dependencies: - babel-helper-optimise-call-expression "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helpers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" - integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-loader@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" - integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw== - dependencies: - find-cache-dir "^2.1.0" - loader-utils "^1.4.0" - mkdirp "^0.5.3" - pify "^4.0.1" - schema-utils "^2.6.5" - -babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-check-es2015-constants@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" - integrity sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-syntax-async-functions@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" - integrity sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU= - -babel-plugin-syntax-exponentiation-operator@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" - integrity sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4= - -babel-plugin-syntax-trailing-function-commas@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" - integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM= - -babel-plugin-transform-async-to-generator@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" - integrity sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E= - dependencies: - babel-helper-remap-async-to-generator "^6.24.1" - babel-plugin-syntax-async-functions "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-arrow-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" - integrity sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" - integrity sha1-u8UbSflk1wy42OC5ToICRs46YUE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoping@^6.23.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" - integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8= - dependencies: - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-plugin-transform-es2015-classes@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" - integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs= - dependencies: - babel-helper-define-map "^6.24.1" - babel-helper-function-name "^6.24.1" - babel-helper-optimise-call-expression "^6.24.1" - babel-helper-replace-supers "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-computed-properties@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" - integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM= - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-destructuring@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" - integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-duplicate-keys@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" - integrity sha1-c+s9MQypaePvnskcU3QabxV2Qj4= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-for-of@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" - integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-function-name@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" - integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos= - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" - integrity sha1-T1SgLWzWbPkVKAAZox0xklN3yi4= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" - integrity sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ= - dependencies: - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: - version "6.26.2" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" - integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-types "^6.26.0" - -babel-plugin-transform-es2015-modules-systemjs@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" - integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM= - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-umd@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" - integrity sha1-rJl+YoXNGO1hdq22B9YCNErThGg= - dependencies: - babel-plugin-transform-es2015-modules-amd "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-object-super@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" - integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40= - dependencies: - babel-helper-replace-supers "^6.24.1" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-parameters@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" - integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys= - dependencies: - babel-helper-call-delegate "^6.24.1" - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-shorthand-properties@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" - integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-spread@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" - integrity sha1-1taKmfia7cRTbIGlQujdnxdG+NE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-sticky-regex@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" - integrity sha1-AMHNsaynERLN8M9hJsLta0V8zbw= - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-template-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" - integrity sha1-qEs0UPfp+PH2g51taH2oS7EjbY0= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-typeof-symbol@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" - integrity sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-unicode-regex@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" - integrity sha1-04sS9C6nMj9yk4fxinxa4frrNek= - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - regexpu-core "^2.0.0" - -babel-plugin-transform-exponentiation-operator@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" - integrity sha1-KrDJx/MJj6SJB3cruBP+QejeOg4= - dependencies: - babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" - babel-plugin-syntax-exponentiation-operator "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-regenerator@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" - integrity sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8= - dependencies: - regenerator-transform "^0.10.0" - -babel-plugin-transform-strict-mode@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" - integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-preset-env@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" - integrity sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg== - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-to-generator "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.23.0" - babel-plugin-transform-es2015-classes "^6.23.0" - babel-plugin-transform-es2015-computed-properties "^6.22.0" - babel-plugin-transform-es2015-destructuring "^6.23.0" - babel-plugin-transform-es2015-duplicate-keys "^6.22.0" - babel-plugin-transform-es2015-for-of "^6.23.0" - babel-plugin-transform-es2015-function-name "^6.22.0" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.22.0" - babel-plugin-transform-es2015-modules-commonjs "^6.23.0" - babel-plugin-transform-es2015-modules-systemjs "^6.23.0" - babel-plugin-transform-es2015-modules-umd "^6.23.0" - babel-plugin-transform-es2015-object-super "^6.22.0" - babel-plugin-transform-es2015-parameters "^6.23.0" - babel-plugin-transform-es2015-shorthand-properties "^6.22.0" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.22.0" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.23.0" - babel-plugin-transform-es2015-unicode-regex "^6.22.0" - babel-plugin-transform-exponentiation-operator "^6.22.0" - babel-plugin-transform-regenerator "^6.22.0" - browserslist "^3.2.6" - invariant "^2.2.2" - semver "^5.3.0" - -babel-register@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" - integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= - dependencies: - babel-core "^6.26.0" - babel-runtime "^6.26.0" - core-js "^2.5.0" - home-or-tmp "^2.0.0" - lodash "^4.17.4" - mkdirp "^0.5.1" - source-map-support "^0.4.15" - -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -babel-template@^6.24.1, babel-template@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.24.1, babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" - -babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== - -"backbone.marionette@^4.0.0, 4.0.0-beta.1": - version "4.0.0-beta.1" - resolved "https://registry.yarnpkg.com/backbone.marionette/-/backbone.marionette-4.0.0-beta.1.tgz#793528fc5c60620200e2440e6dcd04ba16c3aab3" - integrity sha1-eTUo/FxgYgIA4kQObc0EuhbDqrM= - dependencies: - backbone.radio "^2.0.0" - -backbone.marionette@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/backbone.marionette/-/backbone.marionette-4.1.2.tgz#55de74363219f6d5c343dab5bff6aeb20fc44419" - integrity sha512-T8wWxZZnuYjylONTnWZsGsgXtdx2ZrE38pZWJI9LmPqzYK5j0T8uduapFO7OEpsW5rtdbBgwof30xhzAkbb5eQ== - dependencies: - backbone.radio "^2.0.0" - -backbone.radio@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/backbone.radio/-/backbone.radio-2.0.0.tgz#bbe8672b373e313f99f36d2fbcf583fe77d04f42" - integrity sha1-u+hnKzc+MT+Z820vvPWD/nfQT0I= - -backbone@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" - integrity sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ== - dependencies: - underscore ">=1.8.3" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base62@^1.1.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428" - integrity sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA== - -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -big.js@^3.1.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" - integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - -binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -bluebird@^3.5.5: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bn.js@^5.0.0, bn.js@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" - integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== - -boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= - -bootstrap@^4.0.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.1.tgz#f7322c7dd3e6376d430efc0c3f57e4d8005eb5b2" - integrity sha512-bxUooHBSbvefnIZfjD0LE8nfdPKrtiFy2sgrxQwUZ0UpFzpjVbVMUxaGIoo9XWT4B2LG1HX6UQg0UMOakT0prQ== - -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^3.0.1, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-rsa@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" - integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== - dependencies: - bn.js "^5.0.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e" - integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg== - dependencies: - bn.js "^5.2.1" - browserify-rsa "^4.1.0" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.5.4" - inherits "^2.0.4" - parse-asn1 "^5.1.6" - readable-stream "^3.6.2" - safe-buffer "^5.2.1" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -browserslist@^3.2.6: - version "3.2.8" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" - integrity sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ== - dependencies: - caniuse-lite "^1.0.30000844" - electron-to-chromium "^1.3.47" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= - -buffer@^4.3.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= - -cacache@^12.0.2, cacache@^12.0.3: - version "12.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" - integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cacache@^15.2.0: - version "15.3.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" - integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - -cacache@^16.1.0: - version "16.1.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" - integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== - dependencies: - "@npmcli/fs" "^2.1.0" - "@npmcli/move-file" "^2.0.0" - chownr "^2.0.0" - fs-minipass "^2.1.0" - glob "^8.0.1" - infer-owner "^1.0.4" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - mkdirp "^1.0.4" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^9.0.0" - tar "^6.1.11" - unique-filename "^2.0.0" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - -call-bound@^1.0.3, call-bound@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bootstrap@5.3.7: + version "5.3.7" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.7.tgz#8640065036124d961d885d80b5945745e1154d90" + integrity sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw== + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.25.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.4.tgz#ebdd0e1d1cf3911834bab3a6cd7b917d9babf5af" + integrity sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg== + dependencies: + caniuse-lite "^1.0.30001737" + electron-to-chromium "^1.5.211" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@3.0.x: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" - integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= - dependencies: - no-case "^2.2.0" - upper-case "^1.1.1" +caniuse-lite@^1.0.30001737: + version "1.0.30001739" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz#b34ce2d56bfc22f4352b2af0144102d623a124f4" + integrity sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA== -camel-case@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" - integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== - dependencies: - pascal-case "^3.1.1" - tslib "^1.10.0" - -camelcase-keys@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" - integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== - dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.1" - -camelcase@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= - -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -caniuse-lite@^1.0.30000844: - version "1.0.30001111" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001111.tgz#dd0ce822c70eb6c7c068e4a55c22e19ec1501298" - integrity sha512-xnDje2wchd/8mlJu8sXvWxOGvMgv+uT3iZ3bkIAynKOzToCssWCmkz/ZIkQBs/2pUB4uwnJKVORWQ31UkbVjOg== - -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - integrity sha1-qg0yYptu6XIgBBHL1EYckHvCt60= - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.0.0, chalk@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -chokidar@^3.2.2, chokidar@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" - integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.4.0" - optionalDependencies: - fsevents "~2.1.2" - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -clean-css@4.2.x, clean-css@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" - integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== - dependencies: - source-map "~0.6.0" - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-boxes@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" - integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -cliui@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" - integrity sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE= - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: +ccount@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +chai@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.0.tgz#181bca6a219cddb99c3eeefb82483800ffa550ce" + integrity sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA== -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== - -commander@2.17.x: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== - -commander@^2.20.0, commander@^2.5.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - -commander@~2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== - -common-prefix@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/common-prefix/-/common-prefix-1.1.0.tgz#e3a5ea7fafaefc7eb84e760523e1afb985f90f00" - integrity sha1-46Xqf6+u/H64TnYFI+GvuYX5DwA= - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= - -commoner@^0.10.1: - version "0.10.8" - resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" - integrity sha1-NPw2cs0kOT6LtH5wyqApOBH08sU= - dependencies: - commander "^2.5.0" - detective "^4.3.1" - glob "^5.0.15" - graceful-fs "^4.1.2" - iconv-lite "^0.4.5" - mkdirp "^0.5.0" - private "^0.1.6" - q "^1.1.2" - recast "^0.11.17" - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -console-browserify@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" - integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== - -console-control-strings@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= - -convert-source-map@^1.5.1, convert-source-map@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== - dependencies: - safe-buffer "~5.1.1" - -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -copy-webpack-plugin@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz#5481a03dea1123d88a988c6ff8b78247214f0b88" - integrity sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg== - dependencies: - cacache "^12.0.3" - find-cache-dir "^2.1.0" - glob-parent "^3.1.0" - globby "^7.1.1" - is-glob "^4.0.1" - loader-utils "^1.2.3" - minimatch "^3.0.4" - normalize-path "^3.0.0" - p-limit "^2.2.1" - schema-utils "^1.0.0" - serialize-javascript "^2.1.2" - webpack-log "^2.0.0" - -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= - -core-js@^2.4.0, core-js@^2.5.0: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -create-ecdh@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" - integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== - dependencies: - bn.js "^4.1.0" - elliptic "^6.5.3" - -create-hash@^1.1.0, create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hash@~1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" - integrity sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - ripemd160 "^2.0.0" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -css-loader@^3.5.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" - integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== - dependencies: - camelcase "^5.3.1" - cssesc "^3.0.0" - icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.32" - postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.2.0" - postcss-modules-values "^3.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^2.7.0" - semver "^6.3.0" - -css-select@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" - -css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== - -cssesc@^3.0.0: +character-entities-legacy@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== -cyclist@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" - integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= - -d3@^3.5.6: - version "3.5.17" - resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" - integrity sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g= - -debug@4, debug@^4.3.3: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -debug@^4.0.1, debug@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -decamelize-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= - dependencies: - decamelize "^1.1.0" - map-obj "^1.0.0" - -decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decode-uri-component@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: +character-entities@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -des.js@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" - integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= - -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= - dependencies: - repeating "^2.0.0" - -detective@^4.3.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" - integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig== - dependencies: - acorn "^5.2.1" - defined "^1.0.0" - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -dir-glob@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" - integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== - dependencies: - path-type "^3.0.0" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dom-converter@^0.2: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" - -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - -domelementtype@1, domelementtype@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: +character-reference-invalid@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== +chokidar@^4.0.0, chokidar@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== dependencies: - domelementtype "1" + readdirp "^4.0.1" -domutils@1.5.1: +classnames@^2.3.2, classnames@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" + integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== + +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +country-flag-icons@^1.5.21: + version "1.5.21" + resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.5.21.tgz#f7502b144c523f5a6ad8d29de780c90a5f9a131e" + integrity sha512-0KmU4oeiyAM+F+atzK99ghQDQJKxEY3tiDhnRraVFL4o65rZgrmrx7xKi0b+hxcVpcEpuUbu+KCC6TKTZQTDcA== + +css.escape@^1.5.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +date-fns@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" + integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== + +debug@^4.0.0, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: - dom-serializer "0" - domelementtype "1" + ms "^2.1.3" -domutils@^1.5.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== dependencies: - dom-serializer "0" - domelementtype "1" + ms "^2.1.3" -dot-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" - integrity sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA== +decimal.js@^10.4.3: + version "10.6.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" + integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== + +decode-named-character-reference@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz#25c32ae6dd5e21889549d40f676030e9514cc0ed" + integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q== dependencies: - no-case "^3.0.3" - tslib "^1.10.0" + character-entities "^2.0.0" -dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== - dependencies: - is-obj "^2.0.0" +decode-uri-component@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5" + integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ== -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -ejs-include-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ejs-include-regex/-/ejs-include-regex-1.0.0.tgz#e2f71575cbfd551ac800b2474c1f62a38e70093a" - integrity sha1-4vcVdcv9VRrIALJHTB9io45wCTo= - -ejs-lint@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ejs-lint/-/ejs-lint-1.1.0.tgz#f3fd7b164fde618bcefb8e5ece8f9ac206739ce0" - integrity sha512-SnOxzUtJug5C92wtFTZ+Zyb+eoqOlN+WyqDk6HDSXgY5FseDgm8MhZ/r70nkzrh0AMPpifzebep3gGILzAlQNg== - dependencies: - chalk "^4.0.0" - ejs "3.0.1" - ejs-include-regex "^1.0.0" - globby "^11.0.0" - read-input "^0.3.1" - rewire "^5.0.0" - syntax-error "^1.1.6" - yargs "^15.0.0" - -ejs-loader@^0.3.6: - version "0.3.7" - resolved "https://registry.yarnpkg.com/ejs-loader/-/ejs-loader-0.3.7.tgz#82d3cd0a3d3f64d519332b95f9b8a7897c9fcaf4" - integrity sha512-K1HBDWXQZkcIAnP5h65kWsD7o7NABvHswOH49rVHX7POGaTM2kYQfkFZVn4ZQeiRnzqbtf07LxSitOVRdR98GA== - dependencies: - loader-utils "^0.2.7" - lodash "^4.17.15" - -ejs-webpack-loader@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ejs-webpack-loader/-/ejs-webpack-loader-2.2.2.tgz#0536acdd79ba4cdbefb4248fcbe7441e264955d7" - integrity sha512-fuZ5djtVnvoMv4xlyQs3sh9JfIh167iPg7Q1ABFdQIbPHqRgeRWQCvodGybQhiRRCUIeqH9HPtfB8hJimPSPbA== - dependencies: - ejs "^2.0.0" - html-minifier "^3" - loader-utils "^0.2.7" - merge "^1.2.0" - uglify-js "~2.6.1" - -ejs@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.0.1.tgz#30c8f6ee9948502cc32e85c37a3f8b39b5a614a5" - integrity sha512-cuIMtJwxvzumSAkqaaoGY/L6Fc/t6YvoP9/VIaK0V/CyqKLEQ8sqODmYfy/cjXEdZ9+OOL8TecbJu+1RsofGDw== - -ejs@^2.0.0: - version "2.7.4" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" - integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== - -electron-to-chromium@^1.3.47: - version "1.3.522" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.522.tgz#4a6485ad187ffd31913bba0747d0e36405f405d6" - integrity sha512-67V62Z4CFOiAtox+o+tosGfVk0QX4DJgH609tjT8QymbJZVAI/jWnAthnr8c5hnRNziIRwkc9EMQYejiVz3/9Q== - -elliptic@^6.5.3, elliptic@^6.5.4: - version "6.6.1" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" - integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -encoding@^0.1.12, encoding@^0.1.13: - version "0.1.13" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" - integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== - dependencies: - iconv-lite "^0.6.2" - -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@^4.1.1, enhanced-resolve@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" - integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -entities@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== - -entities@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== - -env-paths@^2.2.0: +deepmerge@^2.1.1: version "2.2.1" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== -envify@^3.0.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/envify/-/envify-3.4.1.tgz#d7122329e8df1688ba771b12501917c9ce5cbce8" - integrity sha1-1xIjKejfFoi6dxsSUBkXyc5cvOg= - dependencies: - jstransform "^11.0.3" - through "~2.3.4" - -err-code@^2.0.2: +dequal@^2.0.0, dequal@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" - integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== dependencies: - prr "~1.0.1" + dequal "^2.0.0" + +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + +dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +electron-to-chromium@^1.5.211: + version "1.5.213" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz#f434187f227fb7e67bfcf8243b959cf3ce14013e" + integrity sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q== + +entities@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: - version "1.17.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" +es-module-lexer@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== -es-define-property@^1.0.0, es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== +esbuild@^0.25.0: + version "0.25.9" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.9.tgz#15ab8e39ae6cdc64c24ff8a2c0aef5b3fd9fa976" + integrity sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.25.9" + "@esbuild/android-arm" "0.25.9" + "@esbuild/android-arm64" "0.25.9" + "@esbuild/android-x64" "0.25.9" + "@esbuild/darwin-arm64" "0.25.9" + "@esbuild/darwin-x64" "0.25.9" + "@esbuild/freebsd-arm64" "0.25.9" + "@esbuild/freebsd-x64" "0.25.9" + "@esbuild/linux-arm" "0.25.9" + "@esbuild/linux-arm64" "0.25.9" + "@esbuild/linux-ia32" "0.25.9" + "@esbuild/linux-loong64" "0.25.9" + "@esbuild/linux-mips64el" "0.25.9" + "@esbuild/linux-ppc64" "0.25.9" + "@esbuild/linux-riscv64" "0.25.9" + "@esbuild/linux-s390x" "0.25.9" + "@esbuild/linux-x64" "0.25.9" + "@esbuild/netbsd-arm64" "0.25.9" + "@esbuild/netbsd-x64" "0.25.9" + "@esbuild/openbsd-arm64" "0.25.9" + "@esbuild/openbsd-x64" "0.25.9" + "@esbuild/openharmony-arm64" "0.25.9" + "@esbuild/sunos-x64" "0.25.9" + "@esbuild/win32-arm64" "0.25.9" + "@esbuild/win32-ia32" "0.25.9" + "@esbuild/win32-x64" "0.25.9" -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-scope@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" - integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-utils@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint@^6.8.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" - integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== - dependencies: - "@babel/code-frame" "^7.0.0" - ajv "^6.10.0" - chalk "^2.1.0" - cross-spawn "^6.0.5" - debug "^4.0.1" - doctrine "^3.0.0" - eslint-scope "^5.0.0" - eslint-utils "^1.4.3" - eslint-visitor-keys "^1.1.0" - espree "^6.1.2" - esquery "^1.0.1" - esutils "^2.0.2" - file-entry-cache "^5.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - inquirer "^7.0.0" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.14" - minimatch "^3.0.4" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.3" - progress "^2.0.0" - regexpp "^2.0.1" - semver "^6.1.2" - strip-ansi "^5.2.0" - strip-json-comments "^3.0.1" - table "^5.2.3" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -espree@^6.1.2: - version "6.2.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" - integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== - dependencies: - acorn "^7.1.1" - acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.1.0" - -esprima-fb@^15001.1.0-dev-harmony-fb: - version "15001.1.0-dev-harmony-fb" - resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" - integrity sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE= - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esprima@~3.1.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - -esquery@^1.0.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== - dependencies: - estraverse "^4.1.0" - -estraverse@^4.1.0, estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -events@^3.0.0: +escalade@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" - integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" + "@types/estree" "^1.0.0" -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" +expect-type@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.2.tgz#c030a329fb61184126c8447585bc75a7ec6fbff3" + integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA== -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= - dependencies: - homedir-polyfill "^1.0.1" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: +extend@^3.0.0: version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" +ez-modal-react@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/ez-modal-react/-/ez-modal-react-1.0.5.tgz#38d36c5e31f54f6b7cb7afa0cc79a8d1190c2805" + integrity sha512-/A8yLK54tpmWCMkW8Pwqc2xxspmimGOOw/m+1Y+tNtUIheuDHhLynHP1Q0utciJEGDAK849aQcd+6DrJ88hggQ== -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1: - version "3.2.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" - integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" - merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" +fdir@^6.4.4, fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fastq@^1.6.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" - integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== - dependencies: - reusify "^1.0.4" - -fbjs@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.6.1.tgz#9636b7705f5ba9684d44b72f78321254afc860f7" - integrity sha1-lja3cF9bqWhNRLcveDISVK/IYPc= - dependencies: - core-js "^1.0.0" - loose-envify "^1.0.0" - promise "^7.0.3" - ua-parser-js "^0.7.9" - whatwg-fetch "^0.9.0" - -figgy-pudding@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" - integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== - -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== - dependencies: - flat-cache "^2.0.1" - -file-loader@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f" - integrity sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ== - dependencies: - loader-utils "^2.0.0" - schema-utils "^2.6.5" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" -find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== +filter-obj@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed" + integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng== + +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +formik@^2.4.6: + version "2.4.6" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.6.tgz#4da75ca80f1a827ab35b08fd98d5a76e928c9686" + integrity sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g== dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" + "@types/hoist-non-react-statics" "^3.3.1" + deepmerge "^2.1.1" + hoist-non-react-statics "^3.3.0" + lodash "^4.17.21" + lodash-es "^4.17.21" + react-fast-compare "^2.0.1" + tiny-warning "^1.0.2" + tslib "^2.0.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== - dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== - -flush-write-stream@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - -for-each@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" - integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== - dependencies: - is-callable "^1.2.7" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-minipass@^2.0.0, fs-minipass@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - -gauge@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" - integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== - dependencies: - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.3" - console-control-strings "^1.1.0" - has-unicode "^2.0.1" - signal-exit "^3.0.7" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.5" - -gaze@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" - integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== - dependencies: - globule "^1.0.0" - -gensync@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" - integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== - -get-caller-file@^2.0.1, get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.4, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - -glob@^5.0.15: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -global-dirs@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" - integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== - dependencies: - ini "^1.3.5" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== - dependencies: - type-fest "^0.8.1" - -globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== - -globby@^11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" - integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - -globby@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" - integrity sha1-+yzP+UAfhgCUXfral0QMypcrhoA= - dependencies: - array-union "^1.0.1" - dir-glob "^2.0.0" - glob "^7.1.2" - ignore "^3.3.5" - pify "^3.0.0" - slash "^1.0.0" - -globule@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.2.tgz#d8bdd9e9e4eef8f96e245999a5dee7eb5d8529c4" - integrity sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA== - dependencies: - glob "~7.1.1" - lodash "~4.17.10" - minimatch "~3.0.2" - -gopd@^1.0.1, gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== - -graceful-fs@^4.2.6: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -hard-rejection@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" - integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has-symbols@^1.0.3, has-symbols@^1.1.0: +generate-password-browser@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + resolved "https://registry.yarnpkg.com/generate-password-browser/-/generate-password-browser-1.1.0.tgz#ec1661d0f3ce0b36e2ffdf6099578725a43d12e9" + integrity sha512-qsQve0rVbCqGqAfKgZwjxKUfI1d1nyd22dz+kE8gn1iw1LxGkR+Slsl79XXfm2wxuK27IkopTs5KXcOEQnhg0w== dependencies: - has-symbols "^1.0.3" + buffer "^6.0.3" + randombytes "^2.0.5" -has-unicode@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + +happy-dom@^20.0.10: + version "20.0.10" + resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-20.0.10.tgz#01fb5f09426420994e47e966598e272102678dd2" + integrity sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g== dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash-base@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" - integrity sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw== - dependencies: - inherits "^2.0.1" - -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" + "@types/node" "^20.0.0" + "@types/whatwg-mimetype" "^3.0.2" + whatwg-mimetype "^3.0.0" hasown@^2.0.2: version "2.0.2" @@ -3664,828 +1622,296 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" -he@1.2.x, he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= +hast-util-from-html@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz#485c74785358beb80c4ba6346299311ac4c49c82" + integrity sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw== dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" + "@types/hast" "^3.0.0" + devlop "^1.1.0" + hast-util-from-parse5 "^8.0.0" + parse5 "^7.0.0" + vfile "^6.0.0" + vfile-message "^4.0.0" -home-or-tmp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" - integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= +hast-util-from-parse5@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e" + integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg== dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.1" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + hastscript "^9.0.0" + property-information "^7.0.0" + vfile "^6.0.0" + vfile-location "^5.0.0" + web-namespaces "^2.0.0" -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== +hast-util-parse-selector@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz#25ab00ae9e75cbc62cf7a901f68a247eade659e2" + integrity sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA== dependencies: - parse-passwd "^1.0.0" + "@types/hast" "^2.0.0" -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -hosted-git-info@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" - integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== dependencies: - lru-cache "^6.0.0" + "@types/hast" "^3.0.0" -html-minifier-terser@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" - integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== +hast-util-to-html@^9.0.0: + version "9.0.5" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005" + integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw== dependencies: - camel-case "^4.1.1" - clean-css "^4.2.3" - commander "^4.1.1" - he "^1.2.0" - param-case "^3.0.3" - relateurl "^0.2.7" - terser "^4.6.3" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-whitespace "^3.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + stringify-entities "^4.0.0" + zwitch "^2.0.4" -html-minifier@^3: - version "3.5.21" - resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c" - integrity sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA== +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.6" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" + integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== dependencies: - camel-case "3.0.x" - clean-css "4.2.x" - commander "2.17.x" - he "1.2.x" - param-case "2.1.x" - relateurl "0.2.x" - uglify-js "3.4.x" + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + style-to-js "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" -html-webpack-plugin@^4.0.4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.3.0.tgz#53bf8f6d696c4637d5b656d3d9863d89ce8174fd" - integrity sha512-C0fzKN8yQoVLTelcJxZfJCE+aAvQiY2VUf3UuKrR4a9k5UMWYOtpDLsaXwATbcVCnI05hUS7L9ULQHWLZhyi3w== +hast-util-to-string@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz#a4f15e682849326dd211c97129c94b0c3e76527c" + integrity sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A== dependencies: - "@types/html-minifier-terser" "^5.0.0" - "@types/tapable" "^1.0.5" - "@types/webpack" "^4.41.8" - html-minifier-terser "^5.0.1" - loader-utils "^1.2.3" - lodash "^4.17.15" - pretty-error "^2.1.1" - tapable "^1.1.3" - util.promisify "1.0.0" + "@types/hast" "^3.0.0" -htmlparser2@^3.3.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" + "@types/hast" "^3.0.0" -http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== - -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== +hastscript@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-7.2.0.tgz#0eafb7afb153d047077fa2a833dc9b7ec604d10b" + integrity sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw== dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" + "@types/hast" "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^3.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" -http-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" - integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== +hastscript@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff" + integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w== dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= - -https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: - agent-base "6" - debug "4" + react-is "^16.7.0" -humanize-ms@^1.2.1: +html-url-attributes@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" + integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== + +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== + +humps@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa" + integrity sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g== + +ieee754@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -iconv-lite@^0.4.24, iconv-lite@^0.4.5: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" +immutable@^5.0.2: + version "5.1.3" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.3.tgz#e6486694c8b76c37c063cca92399fa64098634d4" + integrity sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg== -iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -icss-utils@^4.0.0, icss-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" - integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== - dependencies: - postcss "^7.0.14" - -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= - -ignore-by-default@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" - integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= - -ignore@^3.3.5: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" - integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== - -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -ignore@^5.1.4: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== - -import-fresh@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= - -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - -imports-loader@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-0.8.0.tgz#030ea51b8ca05977c40a3abfd9b4088fe0be9a69" - integrity sha512-kXWL7Scp8KQ4552ZcdVTeaQCZSLW+e6nJfp3cwUMB673T7Hr98Xjx5JK+ql7ADlJUvj1JS5O01RLbKoutN5QDQ== - dependencies: - loader-utils "^1.0.2" - source-map "^0.6.1" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - indent-string@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= +inline-style-parser@0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22" + integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q== -infer-owner@^1.0.3, infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= +intl-messageformat@10.7.18: + version "10.7.18" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.7.18.tgz#51a6f387afbca9b0f881b2ec081566db8c540b0d" + integrity sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g== dependencies: - once "^1.3.0" - wrappy "1" + "@formatjs/ecma402-abstract" "2.3.6" + "@formatjs/fast-memoize" "2.2.7" + "@formatjs/icu-messageformat-parser" "2.11.4" + tslib "^2.8.0" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -inquirer@^7.0.0: - version "7.3.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" - integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.19" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.6.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -interpret@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -invariant@^2.2.2: +invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== dependencies: loose-envify "^1.0.0" -ip@^2.0.0: +is-alphabetical@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" - integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: - binary-extensions "^1.0.0" + hasown "^2.0.2" -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.4, is-callable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" - integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== - -is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-core-module@^2.5.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz#3c0ef7d31b4acfc574f80c58409d568a836848e3" - integrity sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ== - dependencies: - has "^1.0.3" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-finite@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" - integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" -is-installed-globally@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== - dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" - -is-lambda@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" - integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== - -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" - integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== - -is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== - dependencies: - has-symbols "^1.0.1" - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -is-typed-array@^1.1.14: - version "1.1.15" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" - integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== - dependencies: - which-typed-array "^1.1.16" - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-windows@^1.0.1, is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= - -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -jquery-mask-plugin@^1.14.16: - version "1.14.16" - resolved "https://registry.yarnpkg.com/jquery-mask-plugin/-/jquery-mask-plugin-1.14.16.tgz#9ebb55947d984da5aade45315b2fe6b113e28aae" - integrity sha512-reywdHlYEkPbzWjTpcc1fk9XQ3PLvO5dzEAVqy8zI7NTF22tB1HbeU3iboZTLdkBEPaWAqeI2HtEjsGQ4roZKw== - -jquery-serializejson@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/jquery-serializejson/-/jquery-serializejson-2.9.0.tgz#03e3764e3a4b42c1c5aae9f93d7f19320c5f35a6" - integrity sha512-xR7rjl0tRKIVioV5lOkOSv7K8BHMvGzYzC7Ech1iAYuZiYf0ksEpLC8OqjA5VApXf/qn/49O9hTmW70+/EA0vA== - -jquery@^3.5.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" - integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== - -js-base64@^2.4.9: - version "2.6.4" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" - integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - -js-yaml@^3.13.1: - version "3.14.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" - integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" - integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - -json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -json5@^0.5.0, json5@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - -json5@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== - dependencies: - minimist "^1.2.5" - -jstransform@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223" - integrity sha1-CaeJk+CuTU70SH9hVakfYZDLQiM= - dependencies: - base62 "^1.1.0" - commoner "^0.10.1" - esprima-fb "^15001.1.0-dev-harmony-fb" - object-assign "^2.0.0" - source-map "^0.4.2" - -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -klona@^2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" - integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== - -latest-version@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= - -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -loader-runner@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" - integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== -loader-utils@^0.2.7: - version "0.2.17" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" - integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" - object-assign "^4.0.1" +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@~4.17.10: +lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" - integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== -loose-envify@^1.0.0: +loose-envify@^1.0.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: js-tokens "^3.0.0 || ^4.0.0" -lower-case@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" - integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= - -lower-case@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" - integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== - dependencies: - tslib "^1.10.0" - -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -4493,864 +1919,367 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + +magic-string@^0.30.19: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== dependencies: - yallist "^4.0.0" + "@jridgewell/sourcemap-codec" "^1.5.5" -lru-cache@^7.7.1: - version "7.18.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" - integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== - -make-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== +mdast-util-from-markdown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a" + integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== dependencies: - pify "^4.0.1" - semver "^5.6.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-fetch-happen@^10.0.4: - version "10.2.1" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" - integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== - dependencies: - agentkeepalive "^4.2.1" - cacache "^16.1.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-fetch "^2.0.3" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.3" - promise-retry "^2.0.1" - socks-proxy-agent "^7.0.0" - ssri "^9.0.0" - -make-fetch-happen@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" - integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== - dependencies: - agentkeepalive "^4.1.3" - cacache "^15.2.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^6.0.0" - minipass "^3.1.3" - minipass-collect "^1.0.2" - minipass-fetch "^1.3.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.2" - promise-retry "^2.0.1" - socks-proxy-agent "^6.0.0" - ssri "^8.0.0" - -make-plural@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-4.3.0.tgz#f23de08efdb0cac2e0c9ba9f315b0dff6b4c2735" - integrity sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA== - optionalDependencies: - minimist "^1.2.0" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - -map-obj@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" - integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -marionette.approuter@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/marionette.approuter/-/marionette.approuter-1.0.2.tgz#bd45b801762fea4ec5caa9505640413596cc432c" - integrity sha512-XjcKb1Y6KROCmdZxO/rtOdRhd3Hfrs+7zWjtfiuCFS3VZa2IQjNgKUuIGmaKDZte2AmKRRMaPvXMh22nKYFh8A== - -marionette.templatecache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/marionette.templatecache/-/marionette.templatecache-1.0.0.tgz#579b9a53b1b6428f8f0a0071cff2175a2d71e65b" - integrity sha1-V5uaU7G2Qo+PCgBxz/IXWi1x5ls= - dependencies: - backbone.marionette "^4.0.0, 4.0.0-beta.1" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" - integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -meow@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" - integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== - dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize "^1.2.0" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "4.1.0" - normalize-package-data "^3.0.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.18.0" - yargs-parser "^20.2.3" - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -merge@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" - integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== - -messageformat-convert@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/messageformat-convert/-/messageformat-convert-0.3.1.tgz#03b5453ee87e66da6eef1670ce0caf997ee64e51" - integrity sha512-fpNfsvFNj5VCAMN0Hpu9D4zhnGkEHL3cILJBAOydkzzdyrSoFlYIHAPRWRajOcHDgYXt+g0NIEzq0bfW3sd5Bw== - dependencies: - common-prefix "1.1.0" - make-plural "^4.3.0" - -messageformat-formatters@^2.0.1: +mdast-util-mdx-expression@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz#0492c1402a48775f751c9b17c0354e92be012b08" - integrity sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg== - -messageformat-loader@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/messageformat-loader/-/messageformat-loader-0.8.1.tgz#709a8f38e36257b19a9492dbfdbc743c03351fa0" - integrity sha512-hk721fJttjqoIfW6cMcLjFPsJ7C2bL9lj7Jy2btfWf7zsu6gWlLFeTKIUfi7S1v4U1gP4dkzHa1giYsAwD+aVA== + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== dependencies: - loader-utils "^1.2.3" - messageformat-convert "^0.3.1" - yaml "^1.6.0" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" -messageformat-parser@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/messageformat-parser/-/messageformat-parser-4.1.3.tgz#b824787f57fcda7d50769f5b63e8d4fda68f5b9e" - integrity sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg== - -messageformat@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/messageformat/-/messageformat-2.3.0.tgz#de263c49029d5eae65d7ee25e0754f57f425ad91" - integrity sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w== +mdast-util-mdx-jsx@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== dependencies: - make-plural "^4.3.0" - messageformat-formatters "^2.0.1" - messageformat-parser "^4.1.2" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== dependencies: - braces "^3.0.1" - picomatch "^2.0.5" + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== +mdast-util-to-hast@^13.0.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4" + integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" -mimic-fn@^2.1.0: +mdast-util-to-markdown@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" + integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + +micromark-core-commonmark@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4" + integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-space@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== + +micromark@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb" + integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromatch@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -mini-css-extract-plugin@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" - integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A== - dependencies: - loader-utils "^1.1.0" - normalize-url "1.9.1" - schema-utils "^1.0.0" - webpack-sources "^1.1.0" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - -"minimatch@2 || 3", minimatch@^3.0.4, minimatch@~3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimist-options@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" - integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== - dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - kind-of "^6.0.3" - -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-fetch@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" - integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== - dependencies: - minipass "^3.1.0" - minipass-sized "^1.0.3" - minizlib "^2.0.0" - optionalDependencies: - encoding "^0.1.12" - -minipass-fetch@^2.0.3: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" - integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== - dependencies: - minipass "^3.1.6" - minipass-sized "^1.0.3" - minizlib "^2.1.2" - optionalDependencies: - encoding "^0.1.13" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" - integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== - dependencies: - minipass "^3.0.0" - -minipass-sized@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" - integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== - dependencies: - minipass "^3.0.0" - -minipass@^3.0.0: - version "3.1.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732" - integrity sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw== - dependencies: - yallist "^4.0.0" - -minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3, minipass@^3.1.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== - -minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -moment@^2.29.4: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== - -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.2, ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.0.0: +ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== -nan@^2.12.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" - integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== -nan@^2.17.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" - integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== +npm-run-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-6.0.0.tgz#25cfdc4eae04976f3349c0b1afc089052c362537" + integrity sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA== dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" + path-key "^4.0.0" + unicorn-magic "^0.3.0" -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -negotiator@^0.6.2, negotiator@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -no-case@^2.2.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" - integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== - dependencies: - lower-case "^1.1.1" - -no-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" - integrity sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw== - dependencies: - lower-case "^2.0.1" - tslib "^1.10.0" - -node-gyp@^8.4.1: - version "8.4.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" - integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^9.1.0" - nopt "^5.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - -node-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-sass@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-9.0.0.tgz#c21cd17bd9379c2d09362b3baf2cbf089bce08ed" - integrity sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg== - dependencies: - async-foreach "^0.1.3" - chalk "^4.1.2" - cross-spawn "^7.0.3" - gaze "^1.0.0" - get-stdin "^4.0.1" - glob "^7.0.3" - lodash "^4.17.15" - make-fetch-happen "^10.0.4" - meow "^9.0.0" - nan "^2.17.0" - node-gyp "^8.4.1" - sass-graph "^4.0.1" - stdout-stream "^1.4.0" - "true-case-path" "^2.2.1" - -nodemon@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" - integrity sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ== - dependencies: - chokidar "^3.2.2" - debug "^3.2.6" - ignore-by-default "^1.0.1" - minimatch "^3.0.4" - pstree.remy "^1.1.7" - semver "^5.7.1" - supports-color "^5.5.0" - touch "^3.1.0" - undefsafe "^2.0.2" - update-notifier "^4.0.0" - -nopt@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" - integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== - dependencies: - abbrev "1" - -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= - dependencies: - abbrev "1" - -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" - integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== - dependencies: - hosted-git-info "^4.0.1" - is-core-module "^2.5.0" - semver "^7.3.4" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-url@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== - -npmlog@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" - integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== - dependencies: - are-we-there-yet "^3.0.0" - console-control-strings "^1.1.0" - gauge "^4.0.3" - set-blocking "^2.0.0" - -nth-check@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -numeral@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" - integrity sha1-StCAk21EPCVhrtnyGX7//iX05QY= - -object-assign@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" - integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo= - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-inspect@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.getownpropertydescriptors@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.1.tgz#5c8016847b0d67fcedb7eef254751cfcdc7e9418" - integrity sha512-ZpZpjcJeugQfWsfyQlshVoowIIQ1qBGSVll4rfDq6JJVO//fesjoX808hXWfBjY+ROZgpKDI5TRSRBSoJiZ8eg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - -p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - -pako@~1.0.5: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parallel-transform@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" - integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== - dependencies: - cyclist "^1.0.1" - inherits "^2.0.3" - readable-stream "^2.1.5" - -param-case@2.1.x: - version "2.1.1" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" - integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= - dependencies: - no-case "^2.2.0" - -param-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" - integrity sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA== - dependencies: - dot-case "^3.0.3" - tslib "^1.10.0" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== parent-module@^1.0.0: version "1.0.1" @@ -5359,28 +2288,18 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0: - version "5.1.5" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" - integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" - -parse-asn1@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" - integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== - dependencies: - asn1.js "^5.2.0" - browserify-aes "^1.0.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" + "@types/unist" "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" parse-json@^5.0.0: version "5.2.0" @@ -5392,454 +2311,269 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= +parse-numeric-range@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" + integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== -pascal-case@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" - integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== +parse5@^7.0.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== dependencies: - no-case "^3.0.3" - tslib "^1.10.0" + entities "^6.0.0" -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -path-exists@^4.0.0: +path-key@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.6: +path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pbkdf2@^3.0.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.3.tgz#8be674d591d65658113424592a95d1517318dd4b" - integrity sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA== - dependencies: - create-hash "~1.1.3" - create-hmac "^1.1.7" - ripemd160 "=2.0.1" - safe-buffer "^5.2.1" - sha.js "^2.4.11" - to-buffer "^1.2.0" - -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -possible-typed-array-names@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" - integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== - -postcss-modules-extract-imports@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" - integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== - dependencies: - postcss "^7.0.5" - -postcss-modules-local-by-default@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" - integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== - dependencies: - icss-utils "^4.1.1" - postcss "^7.0.32" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" - integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^6.0.0" - -postcss-modules-values@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" - integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== - dependencies: - icss-utils "^4.0.0" - postcss "^7.0.6" - -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" - integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -postcss@^7.0.14, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.36" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb" - integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -prepend-http@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - -pretty-error@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" - integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= - dependencies: - renderkid "^2.0.1" - utila "~0.4" - -private@^0.1.6, private@^0.1.8, private@~0.1.5: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -progress@^2.0.0: +pathe@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -promise-retry@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" - integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== - dependencies: - err-code "^2.0.2" - retry "^0.12.0" +picocolors@1.1.1, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -promise@^7.0.3: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -pstree.remy@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" - integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== - -public-encrypt@^4.0.0: +picomatch@^4.0.2, picomatch@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +postcss-simple-vars@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz#836b3097a54dcd13dbd3c36a5dbdd512fad2954c" + integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A== + +postcss@^8.5.6: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== +prop-types-extra@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" + integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" + react-is "^16.3.2" + warning "^4.0.0" -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= +property-information@^6.0.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" + integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== -punycode@^1.2.4: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= +property-information@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d" + integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -pupa@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" - integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== +query-string@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.3.1.tgz#d0c93e6c7fb7c17bdf04aa09e382114580ede270" + integrity sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw== dependencies: - escape-goat "^2.0.0" + decode-uri-component "^0.4.1" + filter-obj "^5.1.0" + split-on-first "^3.0.0" -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" + performance-now "^2.1.0" -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= - -quick-lru@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" - integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: +randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== +react-bootstrap@^2.10.10: + version "2.10.10" + resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-2.10.10.tgz#be0b0d951a69987152d75c0e6986c80425efdf21" + integrity sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ== dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" + "@babel/runtime" "^7.24.7" + "@restart/hooks" "^0.4.9" + "@restart/ui" "^1.9.4" + "@types/prop-types" "^15.7.12" + "@types/react-transition-group" "^4.4.6" + classnames "^2.3.2" + dom-helpers "^5.2.1" + invariant "^2.2.4" + prop-types "^15.8.1" + prop-types-extra "^1.1.0" + react-transition-group "^4.4.5" + uncontrollable "^7.2.1" + warning "^4.0.3" -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== +react-dom@^19.2.0: + version "19.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.0.tgz#00ed1e959c365e9a9d48f8918377465466ec3af8" + integrity sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ== dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" + scheduler "^0.27.0" -react-dom@^0.14.0: - version "0.14.9" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.14.9.tgz#05064a3dcf0fb1880a3b2bfc9d58c55d8d9f6293" - integrity sha1-BQZKPc8PsYgKOyv8nVjFXY2fYpM= +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== -react@^0.14.0: - version "0.14.9" - resolved "https://registry.yarnpkg.com/react/-/react-0.14.9.tgz#9110a6497c49d44ba1c0edd317aec29c2e0d91d1" - integrity sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE= +react-intl@^7.1.14: + version "7.1.14" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-7.1.14.tgz#5500f76b0bb35694a7bec418b1adf1533dad6752" + integrity sha512-VE/0Wi/lHJlBC7APQpCzLUdIt3GB5B0GZrRW8Q+ACbkHI4j+Wwgg9J1TniN6zmLHmPH5gxXcMy+fkSPfw5p1WQ== dependencies: - envify "^3.0.0" - fbjs "^0.6.1" + "@formatjs/ecma402-abstract" "2.3.6" + "@formatjs/icu-messageformat-parser" "2.11.4" + "@formatjs/intl" "3.1.8" + "@types/hoist-non-react-statics" "^3.3.1" + "@types/react" "16 || 17 || 18 || 19" + hoist-non-react-statics "^3.3.2" + intl-messageformat "10.7.18" + tslib "^2.8.0" -read-input@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/read-input/-/read-input-0.3.1.tgz#5b3169308013464ffda6ec92e58d2d3cea948df1" - integrity sha1-WzFpMIATRk/9puyS5Y0tPOqUjfE= +react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-markdown@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca" + integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ== dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + hast-util-to-jsx-runtime "^2.0.0" + html-url-attributes "^3.0.0" + mdast-util-to-hast "^13.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + unified "^11.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" +react-refresh@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.18.0.tgz#2dce97f4fe932a4d8142fa1630e475c1729c8062" + integrity sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw== -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== +react-router-dom@^7.9.5: + version "7.9.5" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.9.5.tgz#99a88cde83919bdfc84fbb3d6bf7c6fc18ca0758" + integrity sha512-mkEmq/K8tKN63Ae2M7Xgz3c9l9YNbY+NHH6NNeUmLA3kDkhKXRsNb/ZpxaEunvGo2/3YXdk5EJU3Hxp3ocaBPw== 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" + react-router "7.9.5" -readable-stream@^3.1.1, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== +react-router@7.9.5: + version "7.9.5" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.9.5.tgz#68722186b4c9f42be36e658d9fe5d62ac1e0808b" + integrity sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A== dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" + cookie "^1.0.1" + set-cookie-parser "^2.6.0" -readable-stream@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== +react-select@^5.10.2: + version "5.10.2" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.10.2.tgz#8dffc69dfd7d74684d9613e6eb27204e3b99e127" + integrity sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ== dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" + "@babel/runtime" "^7.12.0" + "@emotion/cache" "^11.4.0" + "@emotion/react" "^11.8.1" + "@floating-ui/dom" "^1.0.1" + "@types/react-transition-group" "^4.4.0" + memoize-one "^6.0.0" + prop-types "^15.6.0" + react-transition-group "^4.3.0" + use-isomorphic-layout-effect "^1.2.0" -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== +react-toastify@^11.0.5: + version "11.0.5" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-11.0.5.tgz#ce4c42d10eeb433988ab2264d3e445c4e9d13313" + integrity sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA== dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" + clsx "^2.1.1" -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== +react-transition-group@^4.3.0, react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== dependencies: - picomatch "^2.2.1" + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" -recast@^0.11.17: - version "0.11.23" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" - integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= - dependencies: - ast-types "0.9.6" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" +react@^19.2.0: + version "19.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-19.2.0.tgz#d33dd1721698f4376ae57a54098cb47fc75d93a5" + integrity sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ== + +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== redent@^3.0.0: version "3.0.0" @@ -5849,791 +2583,204 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -regenerate@^1.2.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" - integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regenerator-transform@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" - integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q== +refractor@^4.8.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-4.9.0.tgz#2e1c7af0157230cdd2f9086660912eadc5f68323" + integrity sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og== dependencies: - babel-runtime "^6.18.0" - babel-types "^6.19.0" - private "^0.1.6" + "@types/hast" "^2.0.0" + "@types/prismjs" "^1.0.0" + hastscript "^7.0.0" + parse-entities "^4.0.0" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== +rehype-parse@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-9.0.1.tgz#9993bda129acc64c417a9d3654a7be38b2a94c20" + integrity sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag== dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" + "@types/hast" "^3.0.0" + hast-util-from-html "^2.0.0" + unified "^11.0.0" -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== - -regexpu-core@^2.0.0: +rehype-prism-plus@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" - integrity sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA= + resolved "https://registry.yarnpkg.com/rehype-prism-plus/-/rehype-prism-plus-2.0.0.tgz#75b1e2d0dd7496125987a1732cb7d560de02a0fd" + integrity sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ== dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" + hast-util-to-string "^3.0.0" + parse-numeric-range "^1.3.0" + refractor "^4.8.0" + rehype-parse "^9.0.0" + unist-util-filter "^5.0.0" + unist-util-visit "^5.0.0" -registry-auth-token@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.0.tgz#1d37dffda72bbecd0f581e4715540213a65eb7da" - integrity sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w== +rehype-stringify@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-10.0.1.tgz#2ec1ebc56c6aba07905d3b4470bdf0f684f30b75" + integrity sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA== dependencies: - rc "^1.2.8" + "@types/hast" "^3.0.0" + hast-util-to-html "^9.0.0" + unified "^11.0.0" -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== +rehype@~13.0.0: + version "13.0.2" + resolved "https://registry.yarnpkg.com/rehype/-/rehype-13.0.2.tgz#ab0b3ac26573d7b265a0099feffad450e4cf1952" + integrity sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A== dependencies: - rc "^1.2.8" + "@types/hast" "^3.0.0" + rehype-parse "^9.0.0" + rehype-stringify "^10.0.0" + unified "^11.0.0" -regjsgen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" - integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= - -regjsparser@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" - integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== dependencies: - jsesc "~0.5.0" + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" -relateurl@0.2.x, relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -renderkid@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149" - integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA== +remark-rehype@^11.0.0: + version "11.1.2" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37" + integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== dependencies: - css-select "^1.1.0" - dom-converter "^0.2" - htmlparser2 "^3.3.0" - strip-ansi "^3.0.0" - utila "^0.4.0" - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.5.2, repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - dependencies: - resolve-from "^3.0.0" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.10.0, resolve@^1.3.2: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== +resolve@^1.19.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== dependencies: - path-parse "^1.0.6" + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= +rollup@^4.43.0: + version "4.50.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.50.0.tgz#6f237f598b7163ede33ce827af8534c929aaa186" + integrity sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw== dependencies: - lowercase-keys "^1.0.0" + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.50.0" + "@rollup/rollup-android-arm64" "4.50.0" + "@rollup/rollup-darwin-arm64" "4.50.0" + "@rollup/rollup-darwin-x64" "4.50.0" + "@rollup/rollup-freebsd-arm64" "4.50.0" + "@rollup/rollup-freebsd-x64" "4.50.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.50.0" + "@rollup/rollup-linux-arm-musleabihf" "4.50.0" + "@rollup/rollup-linux-arm64-gnu" "4.50.0" + "@rollup/rollup-linux-arm64-musl" "4.50.0" + "@rollup/rollup-linux-loongarch64-gnu" "4.50.0" + "@rollup/rollup-linux-ppc64-gnu" "4.50.0" + "@rollup/rollup-linux-riscv64-gnu" "4.50.0" + "@rollup/rollup-linux-riscv64-musl" "4.50.0" + "@rollup/rollup-linux-s390x-gnu" "4.50.0" + "@rollup/rollup-linux-x64-gnu" "4.50.0" + "@rollup/rollup-linux-x64-musl" "4.50.0" + "@rollup/rollup-openharmony-arm64" "4.50.0" + "@rollup/rollup-win32-arm64-msvc" "4.50.0" + "@rollup/rollup-win32-ia32-msvc" "4.50.0" + "@rollup/rollup-win32-x64-msvc" "4.50.0" + fsevents "~2.3.2" -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== +rooks@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/rooks/-/rooks-9.3.0.tgz#7a3eb44192b7d5b1321acdf15fafdee37987c348" + integrity sha512-ez9ReItW+a/GXsA92Lfh5KWTjhbzlp354KOlC5oh9RHVD5fs/GaWwBq72F2xkDXVblgCgFs0Nel1hxojtszhgw== dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" + fast-deep-equal "^3.1.3" + lodash.debounce "^4.0.8" + raf "^3.4.1" + use-sync-external-store "^1.4.0" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rewire@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/rewire/-/rewire-5.0.0.tgz#c4e6558206863758f6234d8f11321793ada2dbff" - integrity sha512-1zfitNyp9RH5UDyGGLe9/1N0bMlPQ0WrX0Tmg11kMHBpqwPJI4gfPpP7YngFyLbFmhXh19SToAG0sKKEFcOIJA== - dependencies: - eslint "^6.8.0" - -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" - integrity sha1-YTObci/mo1FWiSENJOFMlhSGE+8= - dependencies: - align-text "^0.1.1" - -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - -rimraf@^2.5.4, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -ripemd160@=2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" - integrity sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w== - dependencies: - hash-base "^2.0.0" - inherits "^2.0.1" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-parallel@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" - integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== - -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" - integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= - dependencies: - aproba "^1.1.1" - -rxjs@^6.6.0: - version "6.6.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2" - integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg== - dependencies: - tslib "^1.9.0" - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= +sass@^1.93.3: + version "1.93.3" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.93.3.tgz#3ff0aa5879dc910d32eae10c282a2847bd63e758" + integrity sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg== dependencies: - ret "~0.1.10" + chokidar "^4.0.0" + immutable "^5.0.2" + source-map-js ">=0.6.2 <2.0.0" + optionalDependencies: + "@parcel/watcher" "^2.4.1" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +scheduler@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" + integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== -sass-graph@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-4.0.1.tgz#2ff8ca477224d694055bf4093f414cf6cfad1d2e" - integrity sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA== - dependencies: - glob "^7.0.0" - lodash "^4.17.11" - scss-tokenizer "^0.4.3" - yargs "^17.2.1" - -sass-loader@^10.0.0: - version "10.5.2" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.5.2.tgz#1ca30534fff296417b853c7597ca3b0bbe8c37d0" - integrity sha512-vMUoSNOUKJILHpcNCCyD23X34gve1TS7Rjd9uXHeKqhvBG39x6XbswFDtpbTElj6XdMFezoWhkh5vtKudf2cgQ== - dependencies: - klona "^2.0.4" - loader-utils "^2.0.0" - neo-async "^2.6.2" - schema-utils "^3.0.0" - semver "^7.3.2" - -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - -schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - -schema-utils@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -scss-tokenizer@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz#1058400ee7d814d71049c29923d2b25e61dc026c" - integrity sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw== - dependencies: - js-base64 "^2.4.9" - source-map "^0.7.3" - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -serialize-javascript@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" - integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== - -serialize-javascript@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" - integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== - dependencies: - randombytes "^2.1.0" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-function-length@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= - -sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== - dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" - -smart-buffer@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -socks-proxy-agent@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" - integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== - dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" - -socks-proxy-agent@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" - integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== - dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" - -socks@^2.6.2: +set-cookie-parser@^2.6.0: version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== - dependencies: - ip "^2.0.0" - smart-buffer "^4.2.0" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943" + integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= - dependencies: - is-plain-obj "^1.0.0" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@^0.4.15: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== - dependencies: - source-map "^0.5.6" - -source-map-support@~0.5.12: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - integrity sha1-66T12pwNyZneaAMti092FzZSA2s= - dependencies: - amdefine ">=0.0.4" - -source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1: +source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -ssri@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" - integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== - dependencies: - figgy-pudding "^3.5.1" - -ssri@^8.0.0, ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - -ssri@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" - integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== - dependencies: - minipass "^3.1.1" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -stdout-stream@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" - integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== - dependencies: - readable-stream "^2.0.1" - -stream-browserify@^2.0.1: +space-separated-tokens@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + +split-on-first@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" + integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.9.0.tgz#1a6f7243b339dca4c9fd55e1c7504c77ef23e8f1" + integrity sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw== + +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-each@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" - integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" strip-indent@^3.0.0: version "3.0.0" @@ -6642,194 +2789,75 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@^3.0.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -style-loader@^1.1.3: - version "1.2.1" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a" - integrity sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg== +style-to-js@^1.0.0: + version "1.1.18" + resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.18.tgz#3e6c13bd4c4db079bd2c2c94571cce5c758bc2ff" + integrity sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg== dependencies: - loader-utils "^2.0.0" - schema-utils "^2.6.6" + style-to-object "1.0.11" -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^5.3.0, supports-color@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== +style-to-object@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.11.tgz#cf252c4051758b7acb18a5efb296f91fb79bb9c4" + integrity sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow== dependencies: - has-flag "^3.0.0" + inline-style-parser "0.2.4" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== -supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== - dependencies: - has-flag "^4.0.0" - -syntax-error@^1.1.6: - version "1.4.0" - resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" - integrity sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w== - dependencies: - acorn-node "^1.2.0" - -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== - dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" - -"tabler-ui@git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813": - version "0.0.31" - resolved "git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813" - dependencies: - bootstrap "^4.0.0" - -tapable@^1.0.0, tapable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - -tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: - version "6.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" - integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^5.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== - -terser-webpack-plugin@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz#2c63544347324baafa9a56baaddf1634c8abfc2f" - integrity sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^3.1.0" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser@^4.1.2, terser@^4.6.3: - version "4.8.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f" - integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through@^2.3.6, through@~2.3.4: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== - dependencies: - setimmediate "^1.0.4" - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= - -to-buffer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.1.tgz#2ce650cdb262e9112a18e65dc29dcb513c8155e0" - integrity sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ== - dependencies: - isarray "^2.0.5" - safe-buffer "^5.2.1" - typed-array-buffer "^1.0.3" - -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-readable-stream@^1.0.0: +supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= +tiny-invariant@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinyglobby@^0.2.14: + version "0.2.14" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" + integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" + fdir "^6.4.4" + picomatch "^4.0.2" + +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + +tinyrainbow@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-3.0.3.tgz#984a5b1c1b25854a9b6bccbe77964d0593d1ea42" + integrity sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q== + +tmp@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" + integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== to-regex-range@^5.0.1: version "5.0.1" @@ -6838,635 +2866,262 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -touch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" - integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== - dependencies: - nopt "~1.0.10" - -trim-newlines@^3.0.0: +trim-lines@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" - integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +trough@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" + integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== -"true-case-path@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" - integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== +tsconfck@^3.0.3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.6.tgz#da1f0b10d82237ac23422374b3fce1edb23c3ead" + integrity sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w== -tslib@^1.10.0, tslib@^1.9.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" - integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== +tslib@^2.0.0, tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= +typescript@5.9.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= +uncontrollable@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" + integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== dependencies: - prelude-ls "~1.1.2" + "@babel/runtime" "^7.6.3" + "@types/react" ">=16.9.11" + invariant "^2.2.4" + react-lifecycles-compat "^3.0.4" -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== +uncontrollable@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-8.0.4.tgz#a0a8307f638795162fafd0550f4a1efa0f8c5eb6" + integrity sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ== -type-fest@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" - integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== +unicorn-magic@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz#4efd45c85a69e0dd576d25532fbfa22aa5c8a104" + integrity sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA== -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -typed-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" - integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== +unified@^11.0.0: + version "11.0.5" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - is-typed-array "^1.1.14" + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== +unist-util-filter@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/unist-util-filter/-/unist-util-filter-5.0.1.tgz#f9f3a0bdee007e040964c274dda27bac663d0a39" + integrity sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw== dependencies: - is-typedarray "^1.0.0" + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -ua-parser-js@^0.7.9: - version "0.7.33" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" - integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== - -uglify-js@3.4.x: - version "3.4.10" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f" - integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw== +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== dependencies: - commander "~2.19.0" - source-map "~0.6.1" + "@types/unist" "^3.0.0" -uglify-js@~2.6.1: - version "2.6.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.6.4.tgz#65ea2fb3059c9394692f15fed87c2b36c16b9adf" - integrity sha1-ZeovswWck5RpLxX+2HwrNsFrmt8= +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.0" + "@types/unist" "^3.0.0" -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc= - -undefsafe@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" - integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== - dependencies: - debug "^2.2.0" - -underscore@>=1.8.3, underscore@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e" - integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - -unique-filename@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" - integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== - dependencies: - unique-slug "^3.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - -unique-slug@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" - integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== - dependencies: - imurmurhash "^0.1.4" - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== - -update-notifier@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" - integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== - dependencies: - boxen "^4.2.0" - chalk "^3.0.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.0.0" - pupa "^2.0.1" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -upper-case@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" - integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -util.promisify@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" - integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== - dependencies: - define-properties "^1.1.2" - object.getownpropertydescriptors "^2.0.3" - -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== - dependencies: - inherits "2.0.3" - -utila@^0.4.0, utila@~0.4: - version "0.4.0" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= - -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" - integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -vm-browserify@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" - integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== - -watchpack-chokidar2@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" - integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== - dependencies: - chokidar "^2.1.8" - -watchpack@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b" - integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg== - dependencies: - graceful-fs "^4.1.2" - neo-async "^2.5.0" - optionalDependencies: - chokidar "^3.4.1" - watchpack-chokidar2 "^2.0.0" - -webpack-cli@^3.3.11: - version "3.3.12" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" - integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== - dependencies: - chalk "^2.4.2" - cross-spawn "^6.0.5" - enhanced-resolve "^4.1.1" - findup-sync "^3.0.0" - global-modules "^2.0.0" - import-local "^2.0.0" - interpret "^1.4.0" - loader-utils "^1.4.0" - supports-color "^6.1.0" - v8-compile-cache "^2.1.1" - yargs "^13.3.2" - -webpack-log@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" - integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== - dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" - -webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack-visualizer-plugin@^0.1.11: - version "0.1.11" - resolved "https://registry.yarnpkg.com/webpack-visualizer-plugin/-/webpack-visualizer-plugin-0.1.11.tgz#b8770ad86b4f652612c68b1b782253faf9f8a34e" - integrity sha1-uHcK2GtPZSYSxosbeCJT+vn4o04= - dependencies: - d3 "^3.5.6" - mkdirp "^0.5.1" - react "^0.14.0" - react-dom "^0.14.0" - -webpack@^4.42.1: - version "4.44.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.1.tgz#17e69fff9f321b8f117d1fda714edfc0b939cc21" - integrity sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.4.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.3.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.7.4" - webpack-sources "^1.4.1" - -whatwg-fetch@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0" - integrity sha1-DjaExsuZlbQ+/J3wPkw2XZX9nMA= - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which-typed-array@^1.1.16: - version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" - integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.8" - call-bound "^1.0.4" - for-each "^0.3.5" - get-proto "^1.0.1" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - -which@^1.2.14, which@^1.2.9, which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1, which@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" - integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== - dependencies: - string-width "^1.0.2 || 2 || 3 || 4" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0= - -word-wrap@~1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" - integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== - -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= - -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== - dependencies: - errno "~0.1.7" - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - -xdg-basedir@^4.0.0: +unist-util-stringify-position@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" -xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +unist-util-visit-parents@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" -y18n@^4.0.0: +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +use-isomorphic-layout-effect@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz#2f11a525628f56424521c748feabc2ffcc962fce" + integrity sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA== + +use-sync-external-store@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" + integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== + +vfile-location@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3" + integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== + dependencies: + "@types/unist" "^3.0.0" + vfile "^6.0.0" + +vfile-message@^4.0.0: version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" + integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +vfile@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + +vite-plugin-checker@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.11.0.tgz#063ce180c3751b790a6472e19c5d1a352b215070" + integrity sha512-iUdO9Pl9UIBRPAragwi3as/BXXTtRu4G12L3CMrjx+WVTd9g/MsqNakreib9M/2YRVkhZYiTEwdH2j4Dm0w7lw== + dependencies: + "@babel/code-frame" "^7.27.1" + chokidar "^4.0.3" + npm-run-path "^6.0.0" + picocolors "^1.1.1" + picomatch "^4.0.3" + tiny-invariant "^1.3.3" + tinyglobby "^0.2.14" + vscode-uri "^3.1.0" + +vite-tsconfig-paths@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz#d9a71106a7ff2c1c840c6f1708042f76a9212ed4" + integrity sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w== + dependencies: + debug "^4.1.1" + globrex "^0.1.2" + tsconfck "^3.0.3" + +"vite@^6.0.0 || ^7.0.0", vite@^7.1.12: + version "7.1.12" + resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.12.tgz#8b29a3f61eba23bcb93fc9ec9af4a3a1e83eecdb" + integrity sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug== + dependencies: + esbuild "^0.25.0" + fdir "^6.5.0" + picomatch "^4.0.3" + postcss "^8.5.6" + rollup "^4.43.0" + tinyglobby "^0.2.15" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.0.6.tgz#a0cbc78192cce8726d06c471b8e5b5b9cc6beea5" + integrity sha512-gR7INfiVRwnEOkCk47faros/9McCZMp5LM+OMNWGLaDBSvJxIzwjgNFufkuePBNaesGRnLmNfW+ddbUJRZn0nQ== + dependencies: + "@vitest/expect" "4.0.6" + "@vitest/mocker" "4.0.6" + "@vitest/pretty-format" "4.0.6" + "@vitest/runner" "4.0.6" + "@vitest/snapshot" "4.0.6" + "@vitest/spy" "4.0.6" + "@vitest/utils" "4.0.6" + debug "^4.4.3" + es-module-lexer "^1.7.0" + expect-type "^1.2.2" + magic-string "^0.30.19" + pathe "^2.0.3" + picomatch "^4.0.3" + std-env "^3.9.0" + tinybench "^2.9.0" + tinyexec "^0.3.2" + tinyglobby "^0.2.15" + tinyrainbow "^3.0.3" + vite "^6.0.0 || ^7.0.0" + why-is-node-running "^2.3.0" + +vscode-uri@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c" + integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ== + +warning@^4.0.0, warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +web-namespaces@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" + integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yaml@^1.6.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== - -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^20.2.3: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^13.3.2: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yargs@^15.0.0: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" - -yargs@^17.2.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - integrity sha1-9+572FfdfB0tOMDnTvvWgdFDH9E= - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" +zwitch@^2.0.0, zwitch@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== diff --git a/scripts/.common.sh b/scripts/.common.sh index c89dbf4f..2b59e6ed 100644 --- a/scripts/.common.sh +++ b/scripts/.common.sh @@ -21,7 +21,7 @@ get_container_ip () { local container_name=$1 local container local ip - container=$(docker-compose ps --all -q "${container_name}" | tail -n1) + container=$(docker compose ps --all -q "${container_name}" | tail -n1) ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container") echo "$ip" } diff --git a/scripts/buildx b/scripts/buildx index 4da6c167..5ad77ed9 100755 --- a/scripts/buildx +++ b/scripts/buildx @@ -5,7 +5,6 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" echo -e "${BLUE}❯ ${CYAN}Building docker multiarch: ${YELLOW}${*}${RESET}" -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${DIR}/.." || exit 1 # determine commit if not already set diff --git a/scripts/ci/frontend-build b/scripts/ci/frontend-build index ef1e75de..64e26e50 100755 --- a/scripts/ci/frontend-build +++ b/scripts/ci/frontend-build @@ -15,9 +15,8 @@ if hash docker 2>/dev/null; then -e CI=true \ -e NODE_OPTIONS=--openssl-legacy-provider \ -v "$(pwd)/frontend:/app/frontend" \ - -v "$(pwd)/global:/app/global" \ -w /app/frontend "${DOCKER_IMAGE}" \ - sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend" + sh -c "yarn install && yarn lint && yarn build && chown -R $(id -u):$(id -g) /app/frontend" echo -e "${BLUE}❯ ${GREEN}Building Frontend Complete${RESET}" else diff --git a/scripts/ci/fulltest-cypress b/scripts/ci/fulltest-cypress index c7255ffb..54fa0528 100755 --- a/scripts/ci/fulltest-cypress +++ b/scripts/ci/fulltest-cypress @@ -27,20 +27,20 @@ YELLOW='\E[1;33m' export BLUE CYAN GREEN RESET YELLOW echo -e "${BLUE}❯ ${CYAN}Starting fullstack cypress testing ...${RESET}" -echo -e "${BLUE}❯ $(docker-compose config)${RESET}" +echo -e "${BLUE}❯ $(docker compose config)${RESET}" # $1: container_name get_container_ip () { local container_name=$1 local container local ip - container=$(docker-compose ps --all -q "${container_name}" | tail -n1) + container=$(docker compose ps --all -q "${container_name}" | tail -n1) ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container") echo "$ip" } # Bring up a stack, in steps so we can inject IPs everywhere -docker-compose up -d pdns pdns-db +docker compose up -d pdns pdns-db PDNS_IP=$(get_container_ip "pdns") echo -e "${BLUE}❯ ${YELLOW}PDNS IP is ${PDNS_IP}${RESET}" @@ -50,7 +50,7 @@ rm -rf "$LOCAL_DNSROUTER_CONFIG.tmp" # IMPORTANT: changes to dnsrouter-config.json will affect this line: jq --arg a "$PDNS_IP" '.servers[0].upstreams[1].upstream = $a' "$LOCAL_DNSROUTER_CONFIG" > "$LOCAL_DNSROUTER_CONFIG.tmp" -docker-compose up -d dnsrouter +docker compose up -d dnsrouter DNSROUTER_IP=$(get_container_ip "dnsrouter") echo -e "${BLUE}❯ ${YELLOW}DNS Router IP is ${DNSROUTER_IP}" @@ -65,26 +65,26 @@ rm -rf "${LOCAL_RESOLVE}" printf "nameserver %s\noptions ndots:0" "${DNSROUTER_IP}" > "${LOCAL_RESOLVE}" # bring up all remaining containers, except cypress! -docker-compose up -d --remove-orphans stepca squid -docker-compose pull db-mysql || true # ok to fail -docker-compose pull db-postgres || true # ok to fail -docker-compose pull authentik authentik-redis authentik-ldap || true # ok to fail -docker-compose up -d --remove-orphans --pull=never fullstack +docker compose up -d --remove-orphans stepca squid +docker compose pull db-mysql || true # ok to fail +docker compose pull db-postgres || true # ok to fail +docker compose pull authentik authentik-redis authentik-ldap || true # ok to fail +docker compose up -d --remove-orphans --pull=never fullstack # wait for main container to be healthy -bash "$DIR/../wait-healthy" "$(docker-compose ps --all -q fullstack)" 120 +bash "$DIR/../wait-healthy" "$(docker compose ps --all -q fullstack)" 120 # Run tests rm -rf "$DIR/../../test/results" -docker-compose up --build cypress +docker compose up --build cypress # Get results -docker cp -L "$(docker-compose ps --all -q cypress):/test/results" "$DIR/../../test/" -docker cp -L "$(docker-compose ps --all -q fullstack):/data/logs" "$DIR/../../test/results/" +docker cp -L "$(docker compose ps --all -q cypress):/test/results" "$DIR/../../test/" +docker cp -L "$(docker compose ps --all -q fullstack):/data/logs" "$DIR/../../test/results/" if [ "$2" = "cleanup" ]; then echo -e "${BLUE}❯ ${CYAN}Cleaning up containers ...${RESET}" - docker-compose down --remove-orphans --volumes -t 30 + docker compose down --remove-orphans --volumes -t 30 fi echo -e "${BLUE}❯ ${GREEN}Fullstack cypress testing complete${RESET}" diff --git a/scripts/ci/test-and-build b/scripts/ci/test-and-build index a09ac9be..a0de3414 100755 --- a/scripts/ci/test-and-build +++ b/scripts/ci/test-and-build @@ -10,10 +10,9 @@ docker pull "${TESTING_IMAGE}" echo -e "${BLUE}❯ ${CYAN}Testing backend ...${RESET}" docker run --rm \ -v "$(pwd)/backend:/app" \ - -v "$(pwd)/global:/app/global" \ -w /app \ "${TESTING_IMAGE}" \ - sh -c 'yarn install && yarn eslint . && rm -rf node_modules' + sh -c 'yarn install && yarn lint . && rm -rf node_modules' echo -e "${BLUE}❯ ${GREEN}Testing Complete${RESET}" # Build diff --git a/scripts/cypress-dev b/scripts/cypress-dev index a0c64ad9..f3749f8d 100755 --- a/scripts/cypress-dev +++ b/scripts/cypress-dev @@ -3,11 +3,11 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "$DIR/.common.sh" -# Ensure docker-compose exists -if hash docker-compose 2>/dev/null; then +# Ensure docker exists +if hash docker 2>/dev/null; then cd "${DIR}/.." rm -rf "$DIR/../test/results" - docker-compose up --build cypress + docker compose up --build cypress else - echo -e "${RED}❯ docker-compose command is not available${RESET}" + echo -e "${RED}❯ docker command is not available${RESET}" fi diff --git a/scripts/destroy-dev b/scripts/destroy-dev index 1309aad4..b3ead38d 100755 --- a/scripts/destroy-dev +++ b/scripts/destroy-dev @@ -3,12 +3,11 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "$DIR/.common.sh" -# Ensure docker-compose exists # Make sure docker exists -if hash docker-compose 2>/dev/null; then +if hash docker 2>/dev/null; then cd "${DIR}/.." echo -e "${BLUE}❯ ${CYAN}Destroying Dev Stack ...${RESET}" - docker-compose down --remove-orphans --volumes + docker compose down --remove-orphans --volumes else - echo -e "${RED}❯ docker-compose command is not available${RESET}" + echo -e "${RED}❯ docker command is not available${RESET}" fi diff --git a/scripts/docs-build b/scripts/docs-build index 3715588c..bc166f64 100755 --- a/scripts/docs-build +++ b/scripts/docs-build @@ -3,7 +3,7 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "$DIR/.common.sh" -# Ensure docker-compose exists +# Ensure docker exists if hash docker 2>/dev/null; then cd "${DIR}/.." echo -e "${BLUE}❯ ${CYAN}Building Docs ...${RESET}" diff --git a/scripts/start-dev b/scripts/start-dev index b972ab80..c561ac9a 100755 --- a/scripts/start-dev +++ b/scripts/start-dev @@ -3,14 +3,14 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "$DIR/.common.sh" -# Ensure docker-compose exists -if hash docker-compose 2>/dev/null; then +# Ensure docker exists +if hash docker 2>/dev/null; then cd "${DIR}/.." echo -e "${BLUE}❯ ${CYAN}Starting Dev Stack ...${RESET}" - echo -e "${BLUE}❯ $(docker-compose config)${RESET}" + echo -e "${BLUE}❯ $(docker compose config)${RESET}" # Bring up a stack, in steps so we can inject IPs everywhere - docker-compose up -d pdns pdns-db + docker compose up -d pdns pdns-db PDNS_IP=$(get_container_ip "pdns") echo -e "${BLUE}❯ ${YELLOW}PDNS IP is ${PDNS_IP}${RESET}" @@ -20,7 +20,7 @@ if hash docker-compose 2>/dev/null; then # IMPORTANT: changes to dnsrouter-config.json will affect this line: jq --arg a "$PDNS_IP" '.servers[0].upstreams[1].upstream = $a' "$LOCAL_DNSROUTER_CONFIG" > "$LOCAL_DNSROUTER_CONFIG.tmp" - docker-compose up -d dnsrouter + docker compose up -d dnsrouter DNSROUTER_IP=$(get_container_ip "dnsrouter") echo -e "${BLUE}❯ ${YELLOW}DNS Router IP is ${DNSROUTER_IP}" @@ -35,14 +35,14 @@ if hash docker-compose 2>/dev/null; then printf "nameserver %s\noptions ndots:0" "${DNSROUTER_IP}" > "${LOCAL_RESOLVE}" # bring up all remaining containers, except cypress! - docker-compose up -d --remove-orphans stepca squid - docker-compose pull db db-postgres authentik-redis authentik authentik-worker authentik-ldap - docker-compose build --pull --parallel fullstack - docker-compose up -d --remove-orphans fullstack - docker-compose up -d --remove-orphans swagger + docker compose up -d --remove-orphans stepca squid + docker compose pull db db-postgres authentik-redis authentik authentik-worker authentik-ldap + docker compose build --pull --parallel fullstack + docker compose up -d --remove-orphans fullstack + docker compose up -d --remove-orphans swagger # wait for main container to be healthy - bash "$DIR/wait-healthy" "$(docker-compose ps --all -q fullstack)" 120 + bash "$DIR/wait-healthy" "$(docker compose ps --all -q fullstack)" 120 echo "" echo -e "${CYAN}Admin UI: http://127.0.0.1:3081${RESET}" @@ -58,5 +58,5 @@ if hash docker-compose 2>/dev/null; then echo " docker logs -f npm2dev.core" fi else - echo -e "${RED}❯ docker-compose command is not available${RESET}" + echo -e "${RED}❯ docker command is not available${RESET}" fi diff --git a/scripts/stop-dev b/scripts/stop-dev index 3d27551f..283b45b6 100755 --- a/scripts/stop-dev +++ b/scripts/stop-dev @@ -3,12 +3,11 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "$DIR/.common.sh" -# Ensure docker-compose exists # Make sure docker exists -if hash docker-compose 2>/dev/null; then +if hash docker 2>/dev/null; then cd "${DIR}/.." echo -e "${BLUE}❯ ${CYAN}Stopping Dev Stack ...${RESET}" - docker-compose down --remove-orphans + docker compose down --remove-orphans else - echo -e "${RED}❯ docker-compose command is not available${RESET}" + echo -e "${RED}❯ docker command is not available${RESET}" fi diff --git a/test/cypress/config/dev.js b/test/cypress/config/dev.js new file mode 100644 index 00000000..e2f7b6f9 --- /dev/null +++ b/test/cypress/config/dev.js @@ -0,0 +1,22 @@ +const { defineConfig } = require('cypress'); + +module.exports = defineConfig({ + requestTimeout: 30000, + defaultCommandTimeout: 20000, + reporter: 'cypress-multi-reporters', + reporterOptions: { + configFile: 'multi-reporter.json' + }, + video: true, + videosFolder: 'results/videos', + screenshotsFolder: 'results/screenshots', + e2e: { + setupNodeEvents(on, config) { + return require("../plugins/index.js")(on, config); + }, + env: { + swaggerBase: '{{baseUrl}}/api/schema?ts=' + Date.now(), + }, + baseUrl: 'http://127.0.0.1:3081', + } +}); diff --git a/test/cypress/e2e/api/Certificates.cy.js b/test/cypress/e2e/api/Certificates.cy.js index 9f47edcb..00da38b2 100644 --- a/test/cypress/e2e/api/Certificates.cy.js +++ b/test/cypress/e2e/api/Certificates.cy.js @@ -5,6 +5,7 @@ describe('Certificates endpoints', () => { let certID; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); @@ -80,9 +81,7 @@ describe('Certificates endpoints', () => { data: { domain_names: ['test.com"||echo hello-world||\\\\n test.com"'], meta: { - dns_challenge: false, - letsencrypt_agree: true, - letsencrypt_email: 'admin@example.com', + dns_challenge: false, }, provider: 'letsencrypt', }, diff --git a/test/cypress/e2e/api/Dashboard.cy.js b/test/cypress/e2e/api/Dashboard.cy.js index 62cb40e4..8da332ab 100644 --- a/test/cypress/e2e/api/Dashboard.cy.js +++ b/test/cypress/e2e/api/Dashboard.cy.js @@ -4,6 +4,7 @@ describe('Dashboard endpoints', () => { let token; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); diff --git a/test/cypress/e2e/api/FullCertProvision.cy.js b/test/cypress/e2e/api/FullCertProvision.cy.js index 9c6a7d2d..2c7d9ffd 100644 --- a/test/cypress/e2e/api/FullCertProvision.cy.js +++ b/test/cypress/e2e/api/FullCertProvision.cy.js @@ -4,6 +4,7 @@ describe('Full Certificate Provisions', () => { let token; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); @@ -18,8 +19,6 @@ describe('Full Certificate Provisions', () => { 'website1.example.com' ], meta: { - letsencrypt_email: 'admin@example.com', - letsencrypt_agree: true, dns_challenge: false }, provider: 'letsencrypt' @@ -41,11 +40,9 @@ describe('Full Certificate Provisions', () => { 'website2.example.com' ], meta: { - letsencrypt_email: "admin@example.com", dns_challenge: true, dns_provider: 'powerdns', dns_provider_credentials: 'dns_powerdns_api_url = http://ns1.pdns:8081\r\ndns_powerdns_api_key = npm', - letsencrypt_agree: true, propagation_seconds: 5, }, provider: 'letsencrypt' diff --git a/test/cypress/e2e/api/Ldap.cy.js b/test/cypress/e2e/api/Ldap.cy.js index 715c793b..64e17730 100644 --- a/test/cypress/e2e/api/Ldap.cy.js +++ b/test/cypress/e2e/api/Ldap.cy.js @@ -5,6 +5,7 @@ describe('LDAP with Authentik', () => { if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') { before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { _token = tok; diff --git a/test/cypress/e2e/api/ProxyHosts.cy.js b/test/cypress/e2e/api/ProxyHosts.cy.js index 476945e7..5f437cf9 100644 --- a/test/cypress/e2e/api/ProxyHosts.cy.js +++ b/test/cypress/e2e/api/ProxyHosts.cy.js @@ -4,6 +4,7 @@ describe('Proxy Hosts endpoints', () => { let token; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); @@ -21,8 +22,7 @@ describe('Proxy Hosts endpoints', () => { access_list_id: '0', certificate_id: 0, meta: { - letsencrypt_agree: false, - dns_challenge: false + dns_challenge: false }, advanced_config: '', locations: [], diff --git a/test/cypress/e2e/api/Settings.cy.js b/test/cypress/e2e/api/Settings.cy.js index fcaa0628..a925b2d3 100644 --- a/test/cypress/e2e/api/Settings.cy.js +++ b/test/cypress/e2e/api/Settings.cy.js @@ -4,6 +4,7 @@ describe('Settings endpoints', () => { let token; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); diff --git a/test/cypress/e2e/api/Streams.cy.js b/test/cypress/e2e/api/Streams.cy.js index e38f9dba..5e5fc763 100644 --- a/test/cypress/e2e/api/Streams.cy.js +++ b/test/cypress/e2e/api/Streams.cy.js @@ -4,6 +4,7 @@ describe('Streams', () => { let token; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; // Set default site content @@ -42,11 +43,7 @@ describe('Streams', () => { forwarding_host: '127.0.0.1', forwarding_port: 80, certificate_id: 0, - meta: { - dns_provider_credentials: "", - letsencrypt_agree: false, - dns_challenge: true - }, + meta: {}, tcp_forwarding: true, udp_forwarding: false } @@ -74,11 +71,7 @@ describe('Streams', () => { forwarding_host: '127.0.0.1', forwarding_port: 80, certificate_id: 0, - meta: { - dns_provider_credentials: "", - letsencrypt_agree: false, - dns_challenge: true - }, + meta: {}, tcp_forwarding: false, udp_forwarding: true } @@ -101,11 +94,7 @@ describe('Streams', () => { forwarding_host: '127.0.0.1', forwarding_port: 80, certificate_id: 0, - meta: { - dns_provider_credentials: "", - letsencrypt_agree: false, - dns_challenge: true - }, + meta: {}, tcp_forwarding: true, udp_forwarding: true } @@ -162,11 +151,7 @@ describe('Streams', () => { forwarding_host: '127.0.0.1', forwarding_port: 80, certificate_id: certID, - meta: { - dns_provider_credentials: "", - letsencrypt_agree: false, - dns_challenge: true - }, + meta: {}, tcp_forwarding: true, udp_forwarding: false } diff --git a/test/cypress/e2e/api/SwaggerSchema.cy.js b/test/cypress/e2e/api/SwaggerSchema.cy.js new file mode 100644 index 00000000..63578e21 --- /dev/null +++ b/test/cypress/e2e/api/SwaggerSchema.cy.js @@ -0,0 +1,21 @@ +/// + +const SWAGGER_SCHEMA_FILENAME = 'results/swagger-schema.json'; + +describe('Swagger Schema Linting', () => { + it('Should be a completely valid schema', () => { + // Save the schema to a file and lint it + cy.request('/api/schema') + .then((response) => { + const fileContent = response.body; + cy.writeFile(SWAGGER_SCHEMA_FILENAME, fileContent); + }) + .then(() => { + cy.exec(`yarn swagger-lint '${SWAGGER_SCHEMA_FILENAME}'`) + .then((result) => { + cy.log("Swagger Vacuum Results:\n", result.stdout); + expect(result.code).to.eq(0); + }); + }); + }); +}); diff --git a/test/cypress/e2e/api/Users.cy.js b/test/cypress/e2e/api/Users.cy.js index 7307f87d..044dcdee 100644 --- a/test/cypress/e2e/api/Users.cy.js +++ b/test/cypress/e2e/api/Users.cy.js @@ -4,6 +4,7 @@ describe('Users endpoints', () => { let token; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 7d602ecb..621d0d77 100644 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -12,10 +12,10 @@ import 'cypress-wait-until'; Cypress.Commands.add('randomString', (length) => { - var result = ''; - var characters = 'ABCDEFGHIJK LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - var charactersLength = characters.length; - for (var i = 0; i < length; i++) { + let result = ''; + const characters = 'ABCDEFGHIJK LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; @@ -40,16 +40,81 @@ Cypress.Commands.add('validateSwaggerSchema', (method, code, path, data) => { }).should('equal', null); }); -Cypress.Commands.add('getToken', () => { - // login with existing user - cy.task('backendApiPost', { - path: '/api/tokens', - data: { - identity: 'admin@example.com', - secret: 'changeme' +Cypress.Commands.add('createInitialUser', (defaultUser) => { + let user = { + name: 'Cypress McGee', + nickname: 'Cypress', + email: 'cypress@example.com', + auth: { + type: 'password', + secret: 'changeme' + }, + }; + + if (typeof defaultUser === 'object' && defaultUser) { + user = Object.assign({}, user, defaultUser); + } + + return cy.task('backendApiPost', { + path: '/api/users', + data: user, + }).then((data) => { + // Check the swagger schema: + cy.validateSwaggerSchema('post', 201, '/users', data); + expect(data).to.have.property('id'); + expect(data.id).to.be.greaterThan(0); + cy.wrap(data); + }); +}); + +Cypress.Commands.add('getToken', (defaultUser, defaultAuth) => { + if (typeof defaultAuth === 'object' && defaultAuth) { + if (!defaultUser) { + defaultUser = {}; } - }).then(res => { - cy.wrap(res.token); + defaultUser.auth = defaultAuth; + } + + cy.task('backendApiGet', { + path: '/api/', + }).then((data) => { + // Check the swagger schema: + cy.validateSwaggerSchema('get', 200, '/', data); + + if (!data.setup) { + cy.log('Setup = false'); + // create a new user + cy.createInitialUser(defaultUser).then(() => { + return cy.getToken(defaultUser); + }); + } else { + let auth = { + identity: 'cypress@example.com', + secret: 'changeme', + }; + + if (typeof defaultUser === 'object' && defaultUser && typeof defaultUser.auth === 'object' && defaultUser.auth) { + auth = Object.assign({}, auth, defaultUser.auth); + } + + cy.log('Setup = true'); + // login with existing user + cy.task('backendApiPost', { + path: '/api/tokens', + data: auth, + }).then((res) => { + cy.wrap(res.token); + }); + } + }); +}); + +Cypress.Commands.add('resetUsers', () => { + cy.task('backendApiDelete', { + path: '/api/users' + }).then((data) => { + expect(data).to.be.equal(true); + cy.wrap(data); }); }); diff --git a/test/package.json b/test/package.json index a3a3cad6..bb0ad4b9 100644 --- a/test/package.json +++ b/test/package.json @@ -5,22 +5,25 @@ "main": "index.js", "dependencies": { "@jc21/cypress-swagger-validation": "^0.3.2", - "axios": "^1.7.9", + "@quobix/vacuum": "^0.19.4", + "axios": "^1.13.1", "cypress": "^14.0.1", "cypress-multi-reporters": "^2.0.5", "cypress-wait-until": "^3.0.2", - "eslint": "^9.19.0", + "eslint": "^9.39.0", "eslint-plugin-align-assignments": "^1.1.2", - "eslint-plugin-chai-friendly": "^1.0.1", - "eslint-plugin-cypress": "^4.1.0", - "form-data": "^4.0.1", + "eslint-plugin-chai-friendly": "^1.1.0", + "eslint-plugin-cypress": "^5.2.0", + "form-data": "^4.0.4", "lodash": "^4.17.21", - "mocha": "^11.1.0", + "mocha": "^11.7.4", "mocha-junit-reporter": "^2.2.1" }, "scripts": { "cypress": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress open --config-file=cypress/config/ci.js", - "cypress:headless": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress run --config-file=cypress/config/ci.js" + "cypress:headless": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress run --config-file=cypress/config/ci.js", + "cypress:dev": "cypress run --config-file=cypress/config/dev.js", + "swagger-lint": "vacuum lint -b -q -d -a -n=warn" }, "author": "", "license": "ISC" diff --git a/test/vacuum-rules.yaml b/test/vacuum-rules.yaml new file mode 100644 index 00000000..fc129b72 --- /dev/null +++ b/test/vacuum-rules.yaml @@ -0,0 +1,971 @@ +description: Recommended rules for a high quality specification. +documentationUrl: https://quobix.com/vacuum/rulesets/recommended +rules: + component-description: + category: + description: Documentation is really important, in OpenAPI, just about everything can and should have a description. This set of rules checks for absent descriptions, poor quality descriptions (copy/paste), or short descriptions. + id: descriptions + name: Descriptions + description: Component description check + formats: + - oas3 + - oas3_1 + - oas3_2 + given: $ + howToFix: Components are the inputs and outputs of a specification. A user needs to be able to understand each component and what id does. Descriptions are critical to understanding components. Add a description! + id: component-description + recommended: true + resolved: true + severity: warn + then: + function: oasComponentDescriptions + type: validation + duplicate-paths: + category: + description: Operations are the core of the contract, they define paths and HTTP methods. These rules check operations have been well constructed, looks for operationId, parameter, schema and return types in depth. + id: operations + name: Operations + description: Paths cannot be duplicated; only the last definition will be kept. + formats: + - oas3 + - oas3_1 + - oas3_2 + given: $ + howToFix: Duplicate path definitions found in your OpenAPI specification. In YAML, duplicate keys are allowed, but only the last occurrence is used. This means earlier path definitions are silently ignored, which can lead to missing API endpoints in your specification. + id: duplicate-paths + recommended: true + severity: error + then: + function: duplicatePaths + type: validation + duplicated-entry-in-enum: + category: + description: Schemas are how request bodies and response payloads are defined. They define the data going in and the data flowing out of an operation. These rules check for structural validity, checking types, checking required fields and validating correct use of structures. + id: schemas + name: Schemas + description: Enum values must not have duplicate entry + formats: + - oas3 + - oas3_1 + - oas3_2 + - oas2 + given: $ + howToFix: Enums need to be unique, you can't duplicate them in the same definition. Please remove the duplicate value. + id: duplicated-entry-in-enum + recommended: true + severity: error + then: + function: duplicatedEnum + type: validation + info-description: + category: + description: The info object contains licencing, contact, authorship details and more. Checks to confirm required details have been completed. + id: information + name: Contract Information + description: Info section is missing a description + formats: + - oas3 + - oas3_1 + - oas3_2 + - oas2 + given: $ + howToFix: The 'info' section is missing a description, surely you want people to know what this spec is all about, right? + id: info-description + recommended: true + resolved: true + severity: error + then: + function: infoDescription + type: validation + info-license-spdx: + category: + description: The info object contains licencing, contact, authorship details and more. Checks to confirm required details have been completed. + id: information + name: Contract Information + description: License section cannot contain both an identifier and a URL, they are mutually exclusive. + formats: + - oas3 + - oas3_1 + - oas3_2 + - oas2 + given: $ + howToFix: A license can contain either a URL or an SPDX identifier, but not both, They are mutually exclusive and cannot both be present. Choose one or the other + id: info-license-spdx + recommended: true + resolved: true + severity: error + then: + function: infoLicenseURLSPDX + type: validation + migrate-zally-ignore: + category: + description: Validation rules make sure that certain characters or patterns have not been used that may cause issues when rendering in different types of applications. + id: validation + name: Validation + description: x-zally-ignore keys should be migrated to x-lint-ignore for compatibility with vacuum + formats: + - oas3 + - oas3_1 + - oas3_2 + - oas2 + given: $ + howToFix: Migrate x-zally-ignore directives to vacuum's x-lint-ignore. Rename the key to x-lint-ignore and update the ignored rule id to the vacuum equivalent rule. + id: migrate-zally-ignore + recommended: true + resolved: true + severity: warn + then: + function: migrateZallyIgnore + type: validation + no-$ref-siblings: + category: + description: Schemas are how request bodies and response payloads are defined. They define the data going in and the data flowing out of an operation. These rules check for structural validity, checking types, checking required fields and validating correct use of structures. + id: schemas + name: Schemas + description: $ref values cannot be placed next to other properties (like a description) + formats: + - oas2 + - oas3 + given: $ + howToFix: $ref values must not be placed next to sibling nodes, There should only be a single node when using $ref. A common mistake is adding 'description' next to a $ref. This is wrong. remove all siblings! + id: no-$ref-siblings + recommended: true + severity: error + then: + function: refSiblings + type: validation + no-ambiguous-paths: + category: + description: Operations are the core of the contract, they define paths and HTTP methods. These rules check operations have been well constructed, looks for operationId, parameter, schema and return types in depth. + id: operations + name: Operations + description: Paths need to resolve unambiguously from one another + formats: + - oas3 + - oas3_1 + - oas3_2 + - oas2 + given: $ + howToFix: Paths must all resolve unambiguously, they can't be confused with one another (/{id}/ambiguous and /ambiguous/{id} are the same thing. Make sure every path and the variables used are unique and do conflict with one another. Check the ordering of variables and the naming of path segments. + id: no-ambiguous-paths + recommended: true + resolved: true + severity: error + then: + function: noAmbiguousPaths + type: validation + no-eval-in-markdown: + category: + description: Validation rules make sure that certain characters or patterns have not been used that may cause issues when rendering in different types of applications. + id: validation + name: Validation + description: Markdown descriptions must not have `eval()` statements' + formats: + - oas3 + - oas3_1 + - oas3_2 + - oas2 + given: $ + howToFix: Remove all references to 'eval()' in the description. These can be used by malicious actors to embed code in contracts that is then executed when read by a browser. + id: no-eval-in-markdown + recommended: true + resolved: true + severity: error + then: + function: noEvalDescription + functionOptions: + pattern: eval\( + type: validation + no-http-verbs-in-path: + category: + description: Operations are the core of the contract, they define paths and HTTP methods. These rules check operations have been well constructed, looks for operationId, parameter, schema and return types in depth. + id: operations + name: Operations + description: Path segments must not contain an HTTP verb + formats: + - oas3 + - oas3_1 + - oas3_2 + - oas2 + given: $ + howToFix: When HTTP verbs (get/post/put etc) are used in path segments, it muddies the semantics of REST and creates a confusing and inconsistent experience. It's highly recommended that verbs are not used in path segments. Replace those HTTP verbs with more meaningful nouns. + id: no-http-verbs-in-path + recommended: true + severity: warn + then: + function: noVerbsInPath + type: style + no-request-body: + category: + description: Operations are the core of the contract, they define paths and HTTP methods. These rules check operations have been well constructed, looks for operationId, parameter, schema and return types in depth. + id: operations + name: Operations + description: HTTP GET and DELETE should not accept request bodies + formats: + - oas3 + - oas3_1 + - oas3_2 + given: $ + howToFix: Remove 'requestBody' from HTTP GET and DELETE methods + id: no-request-body + recommended: true + severity: warn + then: + function: noRequestBody + type: style + no-script-tags-in-markdown: + category: + description: Validation rules make sure that certain characters or patterns have not been used that may cause issues when rendering in different types of applications. + id: validation + name: Validation + description: Markdown descriptions must not have `