diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..cfa8d4ab17 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,60 @@ +# Force LF line endings for files that must be Unix-format inside containers. +# Without this, a clone on Windows with default core.autocrlf=true silently +# converts these to CRLF, which breaks `#!/bin/bash` shebangs and s6 service +# scripts at container runtime. + +# Docker build context (Dockerfiles, scripts, runtime configs, init scripts) +docker/** text eol=lf +Dockerfile text eol=lf +Dockerfile.* text eol=lf + +# Backend shell helpers and CI scripts +scripts/** text eol=lf +*.sh text eol=lf + +# Deployment manifests +deployment/** text eol=lf + +# NGINX & service configs +*.conf text eol=lf +*.ini text eol=lf +*.yaml text eol=lf +*.yml text eol=lf + +# Backend / migrations / schema (no need for CRLF anywhere on Unix builds) +backend/** text eol=lf + +# Frontend sources can be LF too (Vite/Biome are LF-native) +frontend/** text eol=lf + +# Binary files — never touch +*.so binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.woff binary +*.woff2 binary +*.ttf binary +*.pdf binary +*.gz binary +*.zip binary +*.tar binary +*.tgz binary +*.tbz binary +*.bz2 binary +*.7z binary +*.rar binary +*.jar binary +*.exe binary +*.dll binary +*.dylib binary +*.a binary +*.o binary +*.class binary +*.bin binary +*.dat binary +*.db binary +*.sqlite binary +*.sqlite3 binary diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..f1b4ffb6fe --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,104 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/backend" + schedule: + interval: "weekly" + groups: + dev-patch-updates: + dependency-type: "development" + update-types: + - "patch" + dev-minor-updates: + dependency-type: "development" + update-types: + - "minor" + prod-patch-updates: + dependency-type: "production" + update-types: + - "patch" + prod-minor-updates: + dependency-type: "production" + update-types: + - "minor" + + - package-ecosystem: "npm" + directory: "/frontend" + schedule: + interval: "weekly" + groups: + dev-patch-updates: + dependency-type: "development" + update-types: + - "patch" + dev-minor-updates: + dependency-type: "development" + update-types: + - "minor" + prod-patch-updates: + dependency-type: "production" + update-types: + - "patch" + prod-minor-updates: + dependency-type: "production" + update-types: + - "minor" + + - package-ecosystem: "npm" + directory: "/docs" + schedule: + interval: "weekly" + groups: + dev-patch-updates: + dependency-type: "development" + update-types: + - "patch" + dev-minor-updates: + dependency-type: "development" + update-types: + - "minor" + prod-patch-updates: + dependency-type: "production" + update-types: + - "patch" + prod-minor-updates: + dependency-type: "production" + update-types: + - "minor" + + - package-ecosystem: "npm" + directory: "/test" + schedule: + interval: "weekly" + groups: + dev-patch-updates: + dependency-type: "development" + update-types: + - "patch" + dev-minor-updates: + dependency-type: "development" + update-types: + - "minor" + prod-patch-updates: + dependency-type: "production" + update-types: + - "patch" + prod-minor-updates: + dependency-type: "production" + update-types: + - "minor" + + - package-ecosystem: "docker" + directory: "/docker" + schedule: + interval: "weekly" + groups: + updates: + update-types: + - "patch" + - "minor" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f859b12784..3a2ae9e8f9 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: stale-issue-label: 'stale' stale-pr-label: 'stale' diff --git a/.gitignore b/.gitignore index fbb8167e7a..49b7e2ac22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .idea +.qodo ._* .vscode certbot-help.txt @@ -7,3 +8,4 @@ test/node_modules */node_modules docker/dev/dnsrouter-config.json.tmp docker/dev/resolv.conf +/.port-assets/ diff --git a/.version b/.version index ccc99d0214..1fee4b29e3 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.12.3 +2.14.0-appsec.1 diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 66ed7cb6a8..0000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,280 +0,0 @@ -import groovy.transform.Field - -@Field -def shOutput = "" -def buildxPushTags = "" - -pipeline { - agent { - label 'docker-multiarch' - } - options { - buildDiscarder(logRotator(numToKeepStr: '5')) - disableConcurrentBuilds() - ansiColor('xterm') - } - environment { - IMAGE = 'nginx-proxy-manager' - BUILD_VERSION = getVersion() - MAJOR_VERSION = '2' - BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('\\\\', '-').replaceAll('/', '-').replaceAll('\\.', '-')}" - BUILDX_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}" - COMPOSE_INTERACTIVE_NO_CLI = 1 - } - stages { - stage('Environment') { - parallel { - stage('Master') { - when { - branch 'master' - } - steps { - script { - buildxPushTags = "-t docker.io/jc21/${IMAGE}:${BUILD_VERSION} -t docker.io/jc21/${IMAGE}:${MAJOR_VERSION} -t docker.io/jc21/${IMAGE}:latest" - } - } - } - stage('Other') { - when { - not { - branch 'master' - } - } - steps { - script { - // Defaults to the Branch name, which is applies to all branches AND pr's - buildxPushTags = "-t docker.io/nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}" - } - } - } - stage('Versions') { - steps { - sh 'cat frontend/package.json | jq --arg BUILD_VERSION "${BUILD_VERSION}" \'.version = $BUILD_VERSION\' | sponge frontend/package.json' - sh 'echo -e "\\E[1;36mFrontend Version is:\\E[1;33m $(cat frontend/package.json | jq -r .version)\\E[0m"' - sh 'cat backend/package.json | jq --arg BUILD_VERSION "${BUILD_VERSION}" \'.version = $BUILD_VERSION\' | sponge backend/package.json' - sh 'echo -e "\\E[1;36mBackend Version is:\\E[1;33m $(cat backend/package.json | jq -r .version)\\E[0m"' - sh 'sed -i -E "s/(version-)[0-9]+\\.[0-9]+\\.[0-9]+(-green)/\\1${BUILD_VERSION}\\2/" README.md' - } - } - stage('Docker Login') { - steps { - withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) { - sh 'docker login -u "${duser}" -p "${dpass}"' - } - } - } - } - } - stage('Builds') { - parallel { - stage('Project') { - steps { - script { - // Frontend and Backend - def shStatusCode = sh(label: 'Checking and Building', returnStatus: true, script: ''' - set -e - ./scripts/ci/frontend-build > ${WORKSPACE}/tmp-sh-build 2>&1 - ./scripts/ci/test-and-build > ${WORKSPACE}/tmp-sh-build 2>&1 - ''') - shOutput = readFile "${env.WORKSPACE}/tmp-sh-build" - if (shStatusCode != 0) { - error "${shOutput}" - } - } - } - post { - always { - sh 'rm -f ${WORKSPACE}/tmp-sh-build' - } - failure { - npmGithubPrComment("CI Error:\n\n```\n${shOutput}\n```", true) - } - } - } - stage('Docs') { - steps { - dir(path: 'docs') { - sh 'yarn install' - sh 'yarn build' - } - } - } - } - } - stage('Test Sqlite') { - environment { - COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_sqlite" - COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.sqlite.yml' - } - when { - not { - equals expected: 'UNSTABLE', actual: currentBuild.result - } - } - steps { - sh 'rm -rf ./test/results/junit/*' - sh './scripts/ci/fulltest-cypress' - } - post { - 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' - junit 'test/results/junit/*' - sh 'docker-compose down --remove-orphans --volumes -t 30 || true' - } - unstable { - dir(path: 'test/results') { - archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') - } - } - } - } - stage('Test Mysql') { - environment { - COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_mysql" - COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.mysql.yml' - } - when { - not { - equals expected: 'UNSTABLE', actual: currentBuild.result - } - } - steps { - sh 'rm -rf ./test/results/junit/*' - sh './scripts/ci/fulltest-cypress' - } - post { - 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' - junit 'test/results/junit/*' - sh 'docker-compose down --remove-orphans --volumes -t 30 || true' - } - unstable { - dir(path: 'test/results') { - archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') - } - } - } - } - stage('Test Postgres') { - environment { - COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_postgres" - COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.postgres.yml' - } - when { - not { - equals expected: 'UNSTABLE', actual: currentBuild.result - } - } - steps { - sh 'rm -rf ./test/results/junit/*' - sh './scripts/ci/fulltest-cypress' - } - post { - 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' - - junit 'test/results/junit/*' - sh 'docker-compose down --remove-orphans --volumes -t 30 || true' - } - unstable { - dir(path: 'test/results') { - archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') - } - } - } - } - stage('MultiArch Build') { - when { - not { - equals expected: 'UNSTABLE', actual: currentBuild.result - } - } - steps { - sh "./scripts/buildx --push ${buildxPushTags}" - } - } - stage('Docs / Comment') { - parallel { - stage('Docs Job') { - when { - allOf { - branch pattern: "^(develop|master)\$", comparator: "REGEXP" - not { - equals expected: 'UNSTABLE', actual: currentBuild.result - } - } - } - steps { - build wait: false, job: 'nginx-proxy-manager-docs', parameters: [string(name: 'docs_branch', value: "$BRANCH_NAME")] - } - } - stage('PR Comment') { - when { - allOf { - changeRequest() - not { - equals expected: 'UNSTABLE', actual: currentBuild.result - } - } - } - steps { - script { - npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on -[DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev) -as `nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}` - -**Note:** ensure you backup your NPM instance before testing this image! Especially if there are database changes -**Note:** this is a different docker image namespace than the official image -""", true) - } - } - } - } - } - } - post { - always { - sh 'echo Reverting ownership' - sh 'docker run --rm -v "$(pwd):/data" jc21/ci-tools chown -R "$(id -u):$(id -g)" /data' - printResult(true) - } - failure { - archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true) - } - unstable { - archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true) - } - } -} - -def getVersion() { - ver = sh(script: 'cat .version', returnStdout: true) - return ver.trim() -} - -def getCommit() { - ver = sh(script: 'git log -n 1 --format=%h', returnStdout: true) - return ver.trim() -} diff --git a/backend/.eslintrc.json b/backend/.eslintrc.json deleted file mode 100644 index 6d6172a482..0000000000 --- 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 fefbcfa6d1..0000000000 --- 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 59f7def205..3039bbb8ea 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 0000000000..5a8a97c571 --- /dev/null +++ b/backend/biome.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.14/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 83e6c8c50d..3c4564626b 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 81% rename from global/certbot-dns-plugins.json rename to backend/certbot/dns-plugins.json index 2191c21385..8f592f66db 100644 --- a/global/certbot-dns-plugins.json +++ b/backend/certbot/dns-plugins.json @@ -10,9 +10,9 @@ "active24": { "name": "Active24", "package_name": "certbot-dns-active24", - "version": "~=1.5.1", + "version": "~=2.0.0", "dependencies": "", - "credentials": "dns_active24_token=\"TOKEN\"", + "credentials": "dns_active24_api_key = \ndns_active24_secret = ", "full_plugin_name": "dns-active24" }, "aliyun": { @@ -23,14 +23,30 @@ "credentials": "dns_aliyun_access_key = 12345678\ndns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef", "full_plugin_name": "dns-aliyun" }, + "arvan": { + "name": "ArvanCloud", + "package_name": "certbot-dns-arvan", + "version": ">=0.1.0", + "dependencies": "", + "credentials": "dns_arvan_key = Apikey xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "full_plugin_name": "dns-arvan" + }, "azure": { "name": "Azure", "package_name": "certbot-dns-azure", - "version": "~=1.2.0", - "dependencies": "", + "version": "~=2.6.1", + "dependencies": "azure-mgmt-dns==8.2.0", "credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2", "full_plugin_name": "dns-azure" }, + "baidu": { + "name": "baidu", + "package_name": "certbot-dns-baidu", + "version": "~=0.1.1", + "dependencies": "", + "credentials": "dns_baidu_access_key = 12345678\ndns_baidu_secret_key = 1234567890abcdef1234567890abcdef", + "full_plugin_name": "dns-baidu" + }, "beget": { "name":"Beget", "package_name": "certbot-beget-plugin", @@ -47,18 +63,26 @@ "credentials": "# Bunny API token used by Certbot (see https://dash.bunny.net/account/settings)\ndns_bunny_api_key = xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", "full_plugin_name": "dns-bunny" }, + "cdmon": { + "name": "cdmon", + "package_name": "certbot-dns-cdmon", + "version": "~=0.4.1", + "dependencies": "", + "credentials": "dns_cdmon_api_key=your-cdmon-api-token\ndns_cdmon_domain=your_domain_is_optional", + "full_plugin_name": "dns-cdmon" + }, "cloudflare": { "name": "Cloudflare", "package_name": "certbot-dns-cloudflare", "version": "=={{certbot-version}}", - "dependencies": "cloudflare==2.19.* acme=={{certbot-version}}", + "dependencies": "acme=={{certbot-version}}", "credentials": "# Cloudflare API token\ndns_cloudflare_api_token=0123456789abcdef0123456789abcdef01234567", "full_plugin_name": "dns-cloudflare" }, "cloudns": { "name": "ClouDNS", "package_name": "certbot-dns-cloudns", - "version": "~=0.6.0", + "version": "~=0.7.0", "dependencies": "", "credentials": "# Target user ID (see https://www.cloudns.net/api-settings/)\n\tdns_cloudns_auth_id=1234\n\t# Alternatively, one of the following two options can be set:\n\t# dns_cloudns_sub_auth_id=1234\n\t# dns_cloudns_sub_auth_user=foobar\n\n\t# API password\n\tdns_cloudns_auth_password=password1", "full_plugin_name": "dns-cloudns" @@ -90,11 +114,19 @@ "cpanel": { "name": "cPanel", "package_name": "certbot-dns-cpanel", - "version": "~=0.2.2", + "version": "~=0.4.0", "dependencies": "", - "credentials": "cpanel_url = https://cpanel.example.com:2083\ncpanel_username = user\ncpanel_password = hunter2", + "credentials": "cpanel_url = https://cpanel.example.com:2083\ncpanel_username = your_username\ncpanel_password = your_password\ncpanel_token = your_api_token", "full_plugin_name": "cpanel" }, + "ddnss": { + "name": "DDNSS", + "package_name": "certbot-dns-ddnss", + "version": "~=1.1.0", + "dependencies": "", + "credentials": "dns_ddnss_token = YOUR_DDNSS_API_TOKEN", + "full_plugin_name": "dns-ddnss" + }, "desec": { "name": "deSEC", "package_name": "certbot-dns-desec", @@ -164,7 +196,7 @@ "package_name": "certbot-dns-domainoffensive", "version": "~=2.0.0", "dependencies": "", - "credentials": "dns_do_api_token = YOUR_DO_DE_AUTH_TOKEN", + "credentials": "dns_domainoffensive_api_token = YOUR_DO_DE_AUTH_TOKEN", "full_plugin_name": "dns-domainoffensive" }, "domeneshop": { @@ -199,6 +231,14 @@ "credentials": "dns_eurodns_applicationId = myuser\ndns_eurodns_apiKey = mysecretpassword\ndns_eurodns_endpoint = https://rest-api.eurodns.com/user-api-gateway/proxy", "full_plugin_name": "dns-eurodns" }, + "firstdomains": { + "name": "First Domains", + "package_name": "certbot-dns-firstdomains", + "version": ">=1.0", + "dependencies": "", + "credentials": "dns_firstdomains_username = myremoteuser\ndns_firstdomains_password = verysecureremoteuserpassword", + "full_plugin_name": "dns-firstdomains" + }, "freedns": { "name": "FreeDNS", "package_name": "certbot-dns-freedns", @@ -209,8 +249,8 @@ }, "gandi": { "name": "Gandi Live DNS", - "package_name": "certbot_plugin_gandi", - "version": "~=1.5.0", + "package_name": "certbot-dns-gandi", + "version": "~=1.6.1", "dependencies": "", "credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN", "full_plugin_name": "dns-gandi" @@ -223,6 +263,14 @@ "credentials": "dns_gcore_apitoken = 0123456789abcdef0123456789abcdef01234567", "full_plugin_name": "dns-gcore" }, + "glesys": { + "name": "Glesys", + "package_name": "certbot-dns-glesys", + "version": "~=2.1.0", + "dependencies": "", + "credentials": "dns_glesys_user = CL00000\ndns_glesys_password = apikeyvalue", + "full_plugin_name": "dns-glesys" + }, "godaddy": { "name": "GoDaddy", "package_name": "certbot-dns-godaddy", @@ -255,6 +303,14 @@ "credentials": "dns_he_user = Me\ndns_he_pass = my HE password", "full_plugin_name": "dns-he" }, + "he-ddns": { + "name": "Hurricane Electric - DDNS", + "package_name": "certbot-dns-he-ddns", + "version": "~=0.1.0", + "dependencies": "", + "credentials": "dns_he_ddns_password = verysecurepassword", + "full_plugin_name": "dns-he-ddns" + }, "hetzner": { "name": "Hetzner", "package_name": "certbot-dns-hetzner", @@ -263,6 +319,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", @@ -327,10 +391,18 @@ "credentials": "dns_joker_username = \ndns_joker_password = \ndns_joker_domain = ", "full_plugin_name": "dns-joker" }, + "kas": { + "name": "All-Inkl", + "package_name": "certbot-dns-kas", + "version": "~=0.1.1", + "dependencies": "kasserver", + "credentials": "dns_kas_user = your_kas_user\ndns_kas_password = your_kas_password", + "full_plugin_name": "dns-kas" + }, "leaseweb": { "name": "LeaseWeb", "package_name": "certbot-dns-leaseweb", - "version": "~=1.0.1", + "version": "~=1.0.3", "dependencies": "", "credentials": "dns_leaseweb_api_token = 01234556789", "full_plugin_name": "dns-leaseweb" @@ -359,6 +431,14 @@ "credentials": "dns_luadns_email = user@example.com\ndns_luadns_token = 0123456789abcdef0123456789abcdef", "full_plugin_name": "dns-luadns" }, + "mchost24": { + "name": "MC-HOST24", + "package_name": "certbot-dns-mchost24", + "version": "", + "dependencies": "", + "credentials": "# Obtain API token using https://github.com/JoeJoeTV/mchost24-api-python\ndns_mchost24_api_token=", + "full_plugin_name": "dns-mchost24" + }, "mijnhost": { "name": "mijn.host", "package_name": "certbot-dns-mijn-host", @@ -383,6 +463,14 @@ "credentials": "dns_netcup_customer_id = 123456\ndns_netcup_api_key = 0123456789abcdef0123456789abcdef01234567\ndns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123", "full_plugin_name": "dns-netcup" }, + "nicru": { + "name": "nic.ru", + "package_name": "certbot-dns-nicru", + "version": "~=1.0.3", + "dependencies": "", + "credentials": "dns_nicru_client_id = application-id\ndns_nicru_client_secret = application-token\ndns_nicru_username = 0001110/NIC-D\ndns_nicru_password = password\ndns_nicru_scope = .+:.+/zones/example.com(/.+)?\ndns_nicru_service = DNS_SERVICE_NAME\ndns_nicru_zone = example.com", + "full_plugin_name": "dns-nicru" + }, "njalla": { "name": "Njalla", "package_name": "certbot-dns-njalla", @@ -426,7 +514,7 @@ "porkbun": { "name": "Porkbun", "package_name": "certbot-dns-porkbun", - "version": "~=0.9", + "version": "~=0.11.0", "dependencies": "", "credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret", "full_plugin_name": "dns-porkbun" @@ -471,14 +559,38 @@ "credentials": "[default]\naws_access_key_id=AKIAIOSFODNN7EXAMPLE\naws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "full_plugin_name": "dns-route53" }, + "simply": { + "name": "Simply", + "package_name": "certbot-dns-simply", + "version": "~=0.1.2", + "dependencies": "", + "credentials": "dns_simply_account_name = UExxxxxx\ndns_simply_api_key = DsHJdsjh2812872sahj", + "full_plugin_name": "dns-simply" + }, + "spaceship": { + "name": "Spaceship", + "package_name": "certbot-dns-spaceship", + "version": "~=1.0.4", + "dependencies": "", + "credentials": "[spaceship]\napi_key=your_api_key\napi_secret=your_api_secret", + "full_plugin_name": "dns-spaceship" + }, "strato": { "name": "Strato", "package_name": "certbot-dns-strato", - "version": "~=0.2.1", + "version": "~=0.2.2", "dependencies": "", "credentials": "dns_strato_username = user\ndns_strato_password = pass\n# uncomment if youre using two factor authentication:\n# dns_strato_totp_devicename = 2fa_device\n# dns_strato_totp_secret = 2fa_secret\n#\n# uncomment if domain name contains special characters\n# insert domain display name as seen on your account page here\n# dns_strato_domain_display_name = my-punicode-url.de\n#\n# if youre not using strato.de or another special endpoint you can customise it below\n# you will probably only need to adjust the host, but you can also change the complete endpoint url\n# dns_strato_custom_api_scheme = https\n# dns_strato_custom_api_host = www.strato.de\n# dns_strato_custom_api_port = 443\n# dns_strato_custom_api_path = \"/apps/CustomerService\"", "full_plugin_name": "dns-strato" }, + "selectelv2": { + "name": "Selectel api v2", + "package_name": "certbot-dns-selectel-api-v2", + "version": "~=0.3.0", + "dependencies": "", + "credentials": "dns_selectel_api_v2_account_id = your_account_id\ndns_selectel_api_v2_project_name = your_project\ndns_selectel_api_v2_username = your_username\ndns_selectel_api_v2_password = your_password", + "full_plugin_name": "dns-selectel-api-v2" + }, "timeweb": { "name": "Timeweb Cloud", "package_name": "certbot-dns-timeweb", diff --git a/backend/config/sqlite-test-db.json b/backend/config/sqlite-test-db.json index ad54886517..b0e8707557 100644 --- a/backend/config/sqlite-test-db.json +++ b/backend/config/sqlite-test-db.json @@ -2,7 +2,7 @@ "database": { "engine": "knex-native", "knex": { - "client": "sqlite3", + "client": "better-sqlite3", "connection": { "filename": "/app/config/mydb.sqlite" }, diff --git a/backend/db.js b/backend/db.js index 1a8b1634e7..bf540f8a22 100644 --- a/backend/db.js +++ b/backend/db.js @@ -1,27 +1,42 @@ -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/'); -} +let instance = null; + +const generateDbConfig = () => { + if (!configHas("database")) { + throw new Error( + "Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/", + ); + } + + const cfg = configGet("database"); -function generateDbConfig() { - const cfg = config.get('database'); - if (cfg.engine === 'knex-native') { + 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", + }, }; +}; + +const getInstance = () => { + if (!instance) { + instance = knex(generateDbConfig()); + } + return instance; } -module.exports = require('knex')(generateDbConfig()); +export default getInstance; diff --git a/backend/index.js b/backend/index.js index 5513782519..0028566727 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,37 +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"; -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'); +const IP_RANGES_FETCH_ENABLED = process.env.IP_RANGES_FETCH_ENABLED !== "false"; - return migrate.latest() +async function appStart() { + return migrateUp() .then(setup) - .then(schema.getCompiledSchema) - .then(internalIpRanges.fetch) + .then(getCompiledSchema) + .then(() => { + 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); }); } @@ -39,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/2fa.js b/backend/internal/2fa.js new file mode 100644 index 0000000000..43307e02c3 --- /dev/null +++ b/backend/internal/2fa.js @@ -0,0 +1,305 @@ +import crypto from "node:crypto"; +import bcrypt from "bcrypt"; +import { createGuardrails, generateSecret, generateURI, verify } from "otplib"; +import errs from "../lib/error.js"; +import authModel from "../models/auth.js"; +import internalUser from "./user.js"; + +const APP_NAME = "Nginx Proxy Manager"; +const BACKUP_CODE_COUNT = 8; + +/** + * Generate backup codes + * @returns {Promise<{plain: string[], hashed: string[]}>} + */ +const generateBackupCodes = async () => { + const plain = []; + const hashed = []; + + for (let i = 0; i < BACKUP_CODE_COUNT; i++) { + const code = crypto.randomBytes(4).toString("hex").toUpperCase(); + plain.push(code); + const hash = await bcrypt.hash(code, 10); + hashed.push(hash); + } + + return { plain, hashed }; +}; + +const internal2fa = { + /** + * Check if user has 2FA enabled + * @param {number} userId + * @returns {Promise} + */ + isEnabled: async (userId) => { + const auth = await internal2fa.getUserPasswordAuth(userId); + return auth?.meta?.totp_enabled === true; + }, + + /** + * Get 2FA status for user + * @param {Access} access + * @param {number} userId + * @returns {Promise<{enabled: boolean, backup_codes_remaining: number}>} + */ + getStatus: async (access, userId) => { + await access.can("users:password", userId); + await internalUser.get(access, { id: userId }); + const auth = await internal2fa.getUserPasswordAuth(userId); + const enabled = auth?.meta?.totp_enabled === true; + let backup_codes_remaining = 0; + + if (enabled) { + const backupCodes = auth.meta.backup_codes || []; + backup_codes_remaining = backupCodes.length; + } + + return { + enabled, + backup_codes_remaining, + }; + }, + + /** + * Start 2FA setup - store pending secret + * + * @param {Access} access + * @param {number} userId + * @returns {Promise<{secret: string, otpauth_url: string}>} + */ + startSetup: async (access, userId) => { + await access.can("users:password", userId); + const user = await internalUser.get(access, { id: userId }); + const secret = generateSecret(); + const otpauth_url = generateURI({ + issuer: APP_NAME, + label: user.email, + secret: secret, + }); + const auth = await internal2fa.getUserPasswordAuth(userId); + + // ensure user isn't already setup for 2fa + const enabled = auth?.meta?.totp_enabled === true; + if (enabled) { + throw new errs.ValidationError("2FA is already enabled"); + } + + const meta = auth.meta || {}; + meta.totp_pending_secret = secret; + + await authModel + .query() + .where("id", auth.id) + .andWhere("user_id", userId) + .andWhere("type", "password") + .patch({ meta }); + + return { secret, otpauth_url }; + }, + + /** + * Enable 2FA after verifying code + * + * @param {Access} access + * @param {number} userId + * @param {string} code + * @returns {Promise<{backup_codes: string[]}>} + */ + enable: async (access, userId, code) => { + await access.can("users:password", userId); + await internalUser.get(access, { id: userId }); + const auth = await internal2fa.getUserPasswordAuth(userId); + const secret = auth?.meta?.totp_pending_secret || false; + + if (!secret) { + throw new errs.ValidationError("No pending 2FA setup found"); + } + + const result = await verify({ token: code, secret }); + if (!result.valid) { + throw new errs.ValidationError("Invalid verification code"); + } + + const { plain, hashed } = await generateBackupCodes(); + + const meta = { + ...auth.meta, + totp_secret: secret, + totp_enabled: true, + totp_enabled_at: new Date().toISOString(), + backup_codes: hashed, + }; + delete meta.totp_pending_secret; + + await authModel + .query() + .where("id", auth.id) + .andWhere("user_id", userId) + .andWhere("type", "password") + .patch({ meta }); + + return { backup_codes: plain }; + }, + + /** + * Disable 2FA + * + * @param {Access} access + * @param {number} userId + * @param {string} code + * @returns {Promise} + */ + disable: async (access, userId, code) => { + await access.can("users:password", userId); + await internalUser.get(access, { id: userId }); + const auth = await internal2fa.getUserPasswordAuth(userId); + + const enabled = auth?.meta?.totp_enabled === true; + if (!enabled) { + throw new errs.ValidationError("2FA is not enabled"); + } + + const result = await verify({ + token: code, + secret: auth.meta.totp_secret, + guardrails: createGuardrails({ + MIN_SECRET_BYTES: 10, + }), + }); + + if (!result.valid) { + throw new errs.AuthError("Invalid verification code"); + } + + const meta = { ...auth.meta }; + delete meta.totp_secret; + delete meta.totp_enabled; + delete meta.totp_enabled_at; + delete meta.backup_codes; + + await authModel + .query() + .where("id", auth.id) + .andWhere("user_id", userId) + .andWhere("type", "password") + .patch({ meta }); + }, + + /** + * Verify 2FA code for login + * + * @param {number} userId + * @param {string} token + * @returns {Promise} + */ + verifyForLogin: async (userId, token) => { + const auth = await internal2fa.getUserPasswordAuth(userId); + const secret = auth?.meta?.totp_secret || false; + + if (!secret) { + return false; + } + + // Try TOTP code first, if it's 6 chars. it will throw errors if it's not 6 chars + // and the backup codes are 8 chars. + if (token.length === 6) { + const result = await verify({ + token, + secret, + // These guardrails lower the minimum length requirement for secrets. + // In v12 of otplib the default minimum length is 10 and in v13 it is 16. + // Since there are 2fa secrets in the wild generated with v12 we need to allow shorter secrets + // so people won't be locked out when upgrading. + guardrails: createGuardrails({ + MIN_SECRET_BYTES: 10, + }), + }); + + if (result.valid) { + return true; + } + } + + // Try backup codes + const backupCodes = auth?.meta?.backup_codes || []; + for (let i = 0; i < backupCodes.length; i++) { + const match = await bcrypt.compare(token.toUpperCase(), backupCodes[i]); + if (match) { + // Remove used backup code + const updatedCodes = [...backupCodes]; + updatedCodes.splice(i, 1); + const meta = { ...auth.meta, backup_codes: updatedCodes }; + await authModel + .query() + .where("id", auth.id) + .andWhere("user_id", userId) + .andWhere("type", "password") + .patch({ meta }); + return true; + } + } + + return false; + }, + + /** + * Regenerate backup codes + * + * @param {Access} access + * @param {number} userId + * @param {string} token + * @returns {Promise<{backup_codes: string[]}>} + */ + regenerateBackupCodes: async (access, userId, token) => { + await access.can("users:password", userId); + await internalUser.get(access, { id: userId }); + const auth = await internal2fa.getUserPasswordAuth(userId); + const enabled = auth?.meta?.totp_enabled === true; + const secret = auth?.meta?.totp_secret || false; + + if (!enabled) { + throw new errs.ValidationError("2FA is not enabled"); + } + if (!secret) { + throw new errs.ValidationError("No 2FA secret found"); + } + + const result = await verify({ + token, + secret, + }); + + if (!result.valid) { + throw new errs.ValidationError("Invalid verification code"); + } + + const { plain, hashed } = await generateBackupCodes(); + + const meta = { ...auth.meta, backup_codes: hashed }; + await authModel + .query() + .where("id", auth.id) + .andWhere("user_id", userId) + .andWhere("type", "password") + .patch({ meta }); + + return { backup_codes: plain }; + }, + + getUserPasswordAuth: async (userId) => { + const auth = await authModel + .query() + .where("user_id", userId) + .andWhere("type", "password") + .first(); + + if (!auth) { + throw new errs.ItemNotFoundError("Auth not found"); + } + + return auth; + }, +}; + +export default internal2fa; diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index f6043e18b7..60a7105dbe 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'); - -function omissions () { - return ['is_deleted']; -} +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"; + +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; - - let promises = []; - - // Now add the items - data.items.map((item) => { - promises.push(accessListAuthModel - .query() - .insert({ - access_list_id: row.id, - username: item.username, - password: item.password - }) - ); - }); - - // 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 - }) - ); - }); - } + .then(utils.omitRow(omissions())); + + data.id = row.id; + + 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; + }); + + // Clients + data.clients?.map((client) => { + promises.push( + accessListClientModel.query().insert({ + access_list_id: row.id, + address: client.address, + directive: client.directive, + }), + ); + return true; + }); + + await Promise.all(promises); + + // 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); + } - 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); - - 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); - }); - }); + // 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 = []; - - 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); - } - }); + 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}`, + ); + } - 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); - } - }); + // 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); } - }) - .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 - }) - ); - } - }); + return true; + }); + + const query = accessListAuthModel.query().delete().where("access_list_id", data.id); + + if (itemsToKeep.length) { + query.andWhere("username", "NOT IN", itemsToKeep); + } - let query = accessListClientModel - .query() - .delete() - .where('access_list_id', data.id); - - return query - .then(() => { - // Add new items - if (promises.length) { - return Promise.all(promises); - } - }); + 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, + }), + ); } - }) - .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); - }); + 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 (access_data.permission_visibility !== 'all') { - query.andWhere('access_list.owner_user_id', access.token.getUserId(1)); - } + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); + } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } + let row = await query.then(utils.omitRow(omissions())); - 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"], + }); + + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } - // 1. update row to be deleted - // 2. update any proxy hosts that were using it (ignoring permissions) - // 3. reconfigure those hosts - // 4. audit log - - // 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 - - // 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; - }); - - return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); - }) - .then(() => { - return internalNginx.reload(); - }); - } - }) - .then(() => { - // delete the htpasswd file - let htpasswd_file = internalAccessList.getFilename(row); - - 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(() => { + // 1. update row to be deleted + // 2. update any proxy hosts that were using it (ignoring permissions) + // 3. reconfigure those hosts + // 4. audit log + + // 1. update row to be deleted + await accessListModel + .query() + .where("id", row.id) + .patch({ + is_deleted: 1, + }); + + // 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 }); + + // 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)); - } + 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) + .groupBy("access_list.id") + .allowGraph("[owner,items,clients]") + .orderBy("access_list.name", "ASC"); - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('name', 'like', '%' + search_query + '%'); - }); - } + if (accessData.permission_visibility !== "all") { + query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); + } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } + // Query is used for searching + if (typeof searchQuery === "string") { + query.where(function () { + this.where("name", "like", `%${searchQuery}%`); + }); + } - return query.then(utils.omitRows(omissions())); - }) - .then((rows) => { - if (rows) { - rows.map(function (row, idx) { - if (typeof row.items !== 'undefined' && row.items) { - rows[idx] = internalAccessList.maskItems(row); - } - }); - } + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } - return rows; + 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 (typeof val.password !== 'undefined' && val.password) { - repeat_for = val.password.length - 1; - first_char = val.password.charAt(0); + if (list && typeof list.items !== "undefined") { + list.items.map((val, idx) => { + let repeatFor = 8; + let firstChar = "*"; + + 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); - - 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); - }); + // 2. create empty access file + fs.writeFileSync(htpasswdFile, '', {encoding: 'utf8'}); + + // 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 60bdd2efa9..02700dc5da 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]'); - - // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { - query.where(function () { - this.where(castJsonIfNeed('meta'), 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query; + getAll: async (access, expand, searchQuery) => { + await access.can("auditlog:list"); + + const query = auditLogModel + .query() + .orderBy("created_on", "DESC") + .orderBy("id", "DESC") + .limit(100) + .allowGraph("[user]"); + + // 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); - } - - 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 || {} - })); - } + 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) { + 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 f2e845a24b..d54e941d2e 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -1,44 +1,46 @@ -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'); - - -const letsencryptStaging = config.useLetsencryptStaging(); -const letsencryptServer = config.useLetsencryptServer(); -const letsencryptConfig = '/etc/letsencrypt.ini'; -const certbotCommand = 'certbot'; - -function omissions() { - return ['is_deleted', 'owner.is_deleted']; -} +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 { ProxyAgent } from "proxy-agent"; +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 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 +51,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 +76,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 +99,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 +114,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(", "); + } + + // this command really should clean up and delete the cert if it can't fully succeed + const certificate = await certificateModel.query().insertAndFetch(data); - if (data.provider === 'letsencrypt') { - data.nice_name = data.domain_names.join(', '); + 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 +244,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 }); + + 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}`, + ); + } - 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); - - // 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 +287,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 (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 data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } + 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]); + } - 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; - }); + return internalCertificate.cleanExpansions(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 +338,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 +393,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); - - 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; - }); + if (!row || !row.id) { + throw new error.ItemNotFoundError(data.id); + } + + 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 +427,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'); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } + getAll: async (access, expand, searchQuery) => { + const accessData = await access.can("certificates:list"); - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('nice_name', 'like', '%' + search_query + '%'); - }); - } + const query = certificateModel + .query() + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,proxy_hosts,redirection_hosts,dead_hosts,streams]") + .orderBy("nice_name", "ASC"); - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - 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 +509,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 +552,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 = {}; + // Put file contents into an object + const files = {}; + _.map(data.files, (file, name) => { + if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { + files[name] = file.data.toString(); + } + }); - _.each(files, (file) => { - data = _.assign({}, data, file); - }); + // 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 data; - }); + return Promise.all(promises).then((files) => { + let data = {}; + _.each(files, (file) => { + data = _.assign({}, data, file); }); + return data; + }); }, /** @@ -637,73 +594,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(); - } - }); - - // 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); - }); - }); + _.map(data.files, (file, name) => { + if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { + row.meta[name] = file.data.toString(); + } + }); + + 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); + 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 +657,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) => { + const filepath = await tempWrite(certificate); + try { + 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 = {}; - - 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'); - }) - - .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 lines = result.split('\n'); - lines.map(function (str) { - const regex = /^(\S+)=(.*)$/gim; - const match = regex.exec(str.trim()); - - if (match && typeof match[2] !== 'undefined') { - const date = 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; - } - } - }); + getCertificateInfoFromFile: async (certificateFile, throw_expired) => { + const certData = {}; - if (!validFrom || !validTo) { - throw new error.ValidationError('Could not determine dates from certificate: ' + result); - } + 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]; + } - if (throw_expired && validTo < parseInt(moment().format('X'), 10)) { - throw new error.ValidationError('Certificate has expired'); - } + 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 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; - certData['dates'] = { - from: validFrom, - to: validTo - }; + const lines = result3.split("\n"); + lines.map((str) => { + const regex = /^(\S+)=(.*)$/gim; + const match = regex.exec(str.trim()); - return certData; - }).catch((err) => { - throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err); + 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; + } + } + 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 +752,129 @@ 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(', ')); - - 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 ' : ''); - - logger.info('Command:', cmd); - - return utils.exec(cmd) - .then((result) => { - logger.success(result); - return result; - }); + requestLetsEncryptSsl: async (certificate, email) => { + logger.info( + `Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); + + 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(","), + ]; + + // Add key-type parameter if specified + if (certificate.meta?.key_type) { + args.push("--key-type", certificate.meta.key_type); + } + + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); + args.push(...adds.args); + + 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'; - - 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 ' : ''); - - // Prepend the path to the credentials file as an environment variable - if (certificate.meta.dns_provider === 'route53') { - mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd; + const hasConfigArg = certificate.meta.dns_provider !== "route53"; + + 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, + ]; + + 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'; + // Add key-type parameter if specified + if (certificate.meta?.key_type) { + args.push("--key-type", certificate.meta.key_type); } - logger.info('Command:', mainCmd); + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); + args.push(...adds.args); + + 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 +884,170 @@ 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; - - 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'); - } + renew: async (access, data) => { + await access.can("certificates:update", data); + const certificate = await internalCertificate.get(access, data); + + 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(', ')); - - 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 ' : ''); - - logger.info('Command:', cmd); - - return utils.exec(cmd) - .then((result) => { - logger.info(result); - return result; - }); + renewLetsEncryptSsl: async (certificate) => { + logger.info( + `Renewing LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); + + 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", + ]; + + // Add key-type parameter if specified + if (certificate.meta?.key_type) { + args.push("--key-type", certificate.meta.key_type); + } + + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); + args.push(...adds.args); + + 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(', ')}`); - - 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 ' : ''); - - // 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; + logger.info( + `Renewing LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); + + 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", + ]; + + // Add key-type parameter if specified + if (certificate.meta?.key_type) { + args.push("--key-type", certificate.meta.key_type); } - logger.info('Command:', mainCmd); + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); + args.push(...adds.args); - return utils.exec(mainCmd) - .then(async (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 - * @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(', ')); - - 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 ' : ''); - - // Don't fail command if file does not exist - const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`; - - logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd); - - 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; - } - }); + revokeLetsEncryptSsl: async (certificate, throwErrors) => { + logger.info( + `Revoking LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); + + const args = [ + "revoke", + "--config", + letsencryptConfig, + "--work-dir", + certbotWorkDir, + "--logs-dir", + certbotLogsDir, + "--cert-path", + `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, + "--delete-after-revoke", + ]; + + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); + args.push(...adds.args); + + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); + + 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 +1055,209 @@ 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'); + /** + * + * @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" }); - 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'); + const results = {}; + for (const domain of payload.domains) { + results[domain] = await internalCertificate.performTestForDomain(domain); } - // 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) - } - }; + // Remove the test challenge file + fs.unlinkSync(testChallengeFile); + + return results; + }, + + performTestForDomain: async (domain) => { + logger.info(`Testing http challenge for ${domain}`); + const agent = new ProxyAgent(); + 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), + }, + agent, + }; + + 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; + }); - 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}`); - } + 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', function (e) { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); - 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'; - } 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}`; - } + 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}`; + }, - const results = {}; + getAdditionalCertbotArgs: (certificate_id, dns_provider) => { + const args = []; + if (useLetsencryptServer() !== null) { + args.push("--server", useLetsencryptServer()); + } + if (useLetsencryptStaging() && useLetsencryptServer() === null) { + args.push("--staging"); + } - for (const domain of domains){ - results[domain] = await performTestForDomain(domain); + // 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}`; } - // Remove the test challenge file - fs.unlinkSync(testChallengeFile); + if (dns_provider === "duckdns") { + args.push("--dns-duckdns-no-txt-restore"); + } - return results; - } + 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 6bbdf61be6..21b120124f 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'); - -function omissions () { - return ['is_deleted']; -} +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"; + +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 = []; - - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); - }); - - 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); - - // Fix for db field not having a default value - // for this optional field. - if (typeof data.advanced_config === 'undefined') { - data.advanced_config = ''; - } + await access.can("dead_hosts:create", data); - 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; + // Get a list of the domain names and check each of them against existing records + const domainNameCheckPromises = []; + + data.domain_names.map((domain_name) => { + domainNameCheckPromises.push(internalHost.isHostnameTaken(domain_name)); + return true; + }); + + 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`); } - }) - .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 = []; - - 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); - } + await access.can("dead_hosts:update", 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; + // 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`); } - }) - .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()); - }); - }); + 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(); - 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 (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)); + } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } + 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; - - 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; + row.enabled = 1; + + 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; - - 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; + row.enabled = 0; + + 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'); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } + 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"); - // 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 (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } + // Query is used for searching + if (typeof searchQuery === "string" && searchQuery.length > 0) { + query.where(function () { + this.where(castJsonIfNeed("domain_names"), "like", `%${searchQuery}%`); + }); + } - return query.then(utils.omitRows(omissions())); - }) - .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { - return internalHost.cleanAllRowsCertificateMeta(rows); - } + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } - return rows; - }); + 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 52c6d2bdab..748716262a 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; - - const combined_data = _.assign({}, existing_data, data); + cleanSslHstsData: (data, existingData) => { + const combinedData = _.assign({}, existingData || {}, 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) - ]; - - return Promise.all(promises) - .then((promises_results) => { - let response_object = { - total_count: 0, - dead_hosts: [], - proxy_hosts: [], - redirection_hosts: [] - }; - - 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; - } - - 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; - } - - 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; - }); + getHostsWithDomains: async (domainNames) => { + const responseObject = { + 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; + + const redirRes = await redirectionHostModel.query().where("is_deleted", 0); + responseObject.redirection_hosts = internalHost._getHostsWithDomains(redirRes, domainNames); + responseObject.total_count += responseObject.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; + + 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; - - if (promises_results[0]) { - // Proxy Hosts - if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[0], ignore_type === 'proxy' && ignore_id ? ignore_id : 0)) { - is_taken = true; - } + 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[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 d34ee5a1b9..6aa2b88a98 100644 --- a/backend/internal/ip_ranges.js +++ b/backend/internal/ip_ranges.js @@ -1,45 +1,53 @@ -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'); - -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'; +import fs from "node:fs"; +import https from "node:https"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { ProxyAgent } from "proxy-agent"; +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 __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) => { + const agent = new ProxyAgent(); 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, { agent }, (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 +57,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 +88,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 +133,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-openappsec.js b/backend/internal/nginx-openappsec.js index 78e170592f..36ee758710 100755 --- a/backend/internal/nginx-openappsec.js +++ b/backend/internal/nginx-openappsec.js @@ -1,19 +1,20 @@ -const util = require('util'); -const execPromise = util.promisify(require('child_process').exec); -const { exec } = require('child_process'); -const _ = require('lodash'); -const fs = require('fs'); -const logger = require('../logger').nginx; -const config = require('../lib/config'); -const yaml = require('js-yaml'); -const path = require('path'); -const constants = require('../lib/constants'); +import { exec } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import util from "node:util"; +import yaml from "js-yaml"; +import _ from "lodash"; +import { isDebugMode } from "../lib/config.js"; +import constants from "../lib/constants.js"; +import { nginx as logger } from "../logger.js"; + +const execPromise = util.promisify(exec); const internalNginxOpenappsec = { // module constants - CONFIG_TEMPLATE_FILE_NAME: 'local-policy-open-appsec-enabled-for-proxy-host.yaml', - CONFIG_TEMPLATE_DIR: '/app/templates', + CONFIG_TEMPLATE_FILE_NAME: "local-policy-open-appsec-enabled-for-proxy-host.yaml", + CONFIG_TEMPLATE_DIR: "/app/templates", // module variables config: null, @@ -28,32 +29,31 @@ const internalNginxOpenappsec = { * @returns {Promise} */ generateConfig: (access, row, d) => { - return access.can('settings:update', row.id) + return access.can("settings:update", row.id) .then(() => { - if (config.debug()) { - logger.info('Generating openappsec config:', JSON.stringify(data, null, 2)); + const data = row ? { ...row, ...d } : d; + if (isDebugMode()) { + logger.info("Generating openappsec config:", JSON.stringify(data, null, 2)); } - - const data = row ? { ...row, ...d } : d; - logger.debug('data', JSON.stringify(data)); + logger.debug("data", JSON.stringify(data)); - const openappsecMode = data.use_openappsec == false ? 'inactive' : data.openappsec_mode; + const openappsecMode = data.use_openappsec == false ? "inactive" : data.openappsec_mode; - const configTemplateFilePath = path.join(internalNginxOpenappsec.CONFIG_TEMPLATE_DIR, internalNginxOpenappsec.CONFIG_TEMPLATE_FILE_NAME) + const configTemplateFilePath = path.join(internalNginxOpenappsec.CONFIG_TEMPLATE_DIR, internalNginxOpenappsec.CONFIG_TEMPLATE_FILE_NAME); const configFilePath = path.join(constants.APPSEC_EXT_DIR, constants.APPSEC_CONFIG_FILE_NAME); - let openappsecConfig = yaml.load(fs.readFileSync(configFilePath, 'utf8')); - let openappsecConfigTemplate = yaml.load(fs.readFileSync(configTemplateFilePath, 'utf8')); + let openappsecConfig = yaml.load(fs.readFileSync(configFilePath, "utf8")); + let openappsecConfigTemplate = yaml.load(fs.readFileSync(configTemplateFilePath, "utf8")); internalNginxOpenappsec.config = openappsecConfig; internalNginxOpenappsec.configTemplate = openappsecConfigTemplate; - const specificRuleName = 'npm-managed-specific-rule-proxyhost-' + row.id; - const logTriggerName = 'npm-managed-log-trigger-proxyhost-' + row.id; - const practiceName = 'npm-managed-practice-proxyhost-' + row.id; - - _.remove(openappsecConfig.policies['specific-rules'], rule => rule.name === specificRuleName || rule.name.startsWith(`${specificRuleName}.`)); + const specificRuleName = "npm-managed-specific-rule-proxyhost-" + row.id; + const logTriggerName = "npm-managed-log-trigger-proxyhost-" + row.id; + const practiceName = "npm-managed-practice-proxyhost-" + row.id; + + _.remove(openappsecConfig.policies["specific-rules"], rule => rule.name === specificRuleName || rule.name.startsWith(`${specificRuleName}.`)); data.domain_names.forEach((domain, index) => { let ruleName = index > 0 ? `${specificRuleName}.${index}` : specificRuleName; @@ -64,25 +64,25 @@ const internalNginxOpenappsec = { mode: openappsecMode, practices: [practiceName] }; - internalNginxOpenappsec.updateNode('policies', 'specific-rules', specificRuleNode, openappsecMode); + internalNginxOpenappsec.updateNode("policies", "specific-rules", specificRuleNode, openappsecMode); }); - internalNginxOpenappsec.updateNode('', 'practices', { name: practiceName, 'web-attacks.override-mode': openappsecMode, 'web-attacks.minimum-confidence': data.minimum_confidence }, openappsecMode); - internalNginxOpenappsec.updateNode('', 'log-triggers', { name: logTriggerName }, openappsecMode); + internalNginxOpenappsec.updateNode("", "practices", { name: practiceName, "web-attacks.override-mode": openappsecMode, "web-attacks.minimum-confidence": data.minimum_confidence }, openappsecMode); + internalNginxOpenappsec.updateNode("", "log-triggers", { name: logTriggerName }, openappsecMode); - // remove all openappsec managed location config nodes for a proxy host. + // remove all openappsec managed location config nodes for a proxy host. let pattern = new RegExp(`^npm-managed.*-${row.id}-.*`); internalNginxOpenappsec.removeMatchingNodes(openappsecConfig, pattern); // for each data.location, create location config nodes data.locations.forEach((location, index) => { - let locationSpecificRuleName = 'npm-managed-specific-rule-proxyhost-' + row.id + '-' + index; - let locationLogTriggerName = 'npm-managed-log-trigger-proxyhost-' + row.id + '-' + index; - let locationPracticeName = 'npm-managed-practice-proxyhost-' + row.id + '-' + index; + let locationSpecificRuleName = "npm-managed-specific-rule-proxyhost-" + row.id + "-" + index; + let locationLogTriggerName = "npm-managed-log-trigger-proxyhost-" + row.id + "-" + index; + let locationPracticeName = "npm-managed-practice-proxyhost-" + row.id + "-" + index; - let locationOpenappsecMode = location.use_openappsec == false ? 'inactive' : location.openappsec_mode; + let locationOpenappsecMode = location.use_openappsec == false ? "inactive" : location.openappsec_mode; - _.remove(openappsecConfig.policies['specific-rules'], rule => rule.name === locationSpecificRuleName || rule.name.startsWith(`${locationSpecificRuleName}.`)); + _.remove(openappsecConfig.policies["specific-rules"], rule => rule.name === locationSpecificRuleName || rule.name.startsWith(`${locationSpecificRuleName}.`)); data.domain_names.forEach((domain, index) => { let locationUrl = domain + location.path; @@ -95,27 +95,26 @@ const internalNginxOpenappsec = { mode: locationOpenappsecMode, practices: [locationPracticeName] }; - internalNginxOpenappsec.updateNode('policies', 'specific-rules', domainSpecificRuleNode, locationOpenappsecMode, 'location', openappsecMode); + internalNginxOpenappsec.updateNode("policies", "specific-rules", domainSpecificRuleNode, locationOpenappsecMode, "location", openappsecMode); }); - internalNginxOpenappsec.updateNode('', 'practices', { name: locationPracticeName, 'web-attacks.override-mode': locationOpenappsecMode, 'web-attacks.minimum-confidence': location.minimum_confidence }, locationOpenappsecMode, 'location', openappsecMode); - internalNginxOpenappsec.updateNode('', 'log-triggers', { name: locationLogTriggerName }, locationOpenappsecMode, 'location', openappsecMode); + internalNginxOpenappsec.updateNode("", "practices", { name: locationPracticeName, "web-attacks.override-mode": locationOpenappsecMode, "web-attacks.minimum-confidence": location.minimum_confidence }, locationOpenappsecMode, "location", openappsecMode); + internalNginxOpenappsec.updateNode("", "log-triggers", { name: locationLogTriggerName }, locationOpenappsecMode, "location", openappsecMode); }); fs.writeFileSync(configFilePath, yaml.dump(openappsecConfig)); }, (err) => { - logger.error('Error generating openappsec config:', err); + logger.error("Error generating openappsec config:", err); return Promise.reject(err); }) .then(() => { // Return the notifyPolicyUpdate promise chain // notify openappsec to apply the policy return internalNginxOpenappsec.notifyPolicyUpdate().catch((errorMessage) => { - // console.error('Error:', errorMessage); const errorMessageForUI = `Error: Policy couldn’t be applied, open-appsec-agent container is not responding. Check if open-appec-agent container is running, then apply open-appsec Configuration - again by clicking here: + again by clicking here:
Settings -> open-appsec Advanced -> Save Settings`; return Promise.reject(new Error(errorMessageForUI)); @@ -123,21 +122,21 @@ const internalNginxOpenappsec = { }); }, - /** + /** * Remove all openappsec managed config nodes for a proxy host. - * + * * @param {Object} access * @param {Object} row * @returns {Promise} - * + * */ deleteConfig: (access, row) => { - return access.can('settings:update', row.id) + return access.can("settings:update", row.id) .then(() => { const configFilePath = path.join(constants.APPSEC_EXT_DIR, constants.APPSEC_CONFIG_FILE_NAME); - let openappsecConfig = yaml.load(fs.readFileSync(configFilePath, 'utf8')); + let openappsecConfig = yaml.load(fs.readFileSync(configFilePath, "utf8")); - // remove all openappsec managed location config nodes for a proxy host. + // remove all openappsec managed location config nodes for a proxy host. let pattern = new RegExp(`^npm-managed.*-${row.id}`); internalNginxOpenappsec.removeMatchingNodes(openappsecConfig, pattern); fs.writeFileSync(configFilePath, yaml.dump(openappsecConfig)); @@ -146,27 +145,26 @@ const internalNginxOpenappsec = { // Return the notifyPolicyUpdate promise chain // notify openappsec to apply the policy return internalNginxOpenappsec.notifyPolicyUpdate().catch((errorMessage) => { - console.error('---Error:', errorMessage); + logger.error("Error notifying open-appsec to apply the policy:", errorMessage); const errorMessageForUI = `Error: Policy couldn’t be applied, open-appsec-agent container is not responding. Check if open-appec-agent container is running, then apply open-appsec Configuration - again by clicking here: + again by clicking here:
Settings -> open-appsec Advanced -> Save Settings`; return Promise.reject(new Error(errorMessageForUI)); }); }) .catch((err) => { - logger.error('Error deleting openappsec config:', err); - // throw err; // Propagate the error to the caller + logger.error("Error deleting openappsec config:", err); }); }, - + /** * Update a node in the openappsec config. * - if the node does not exist, create it. * - if the node exists, update it. * - if openappsecMode is 'inactive', delete the node. - * + * * @param {String} parentNodePath - path to the parent node. e.g. 'policies'. * @param {String} nodeName - name of the node. e.g. 'specific-rules', 'practices', 'log-triggers'. * @param {Object} nodeItemProperties @@ -174,29 +172,29 @@ const internalNginxOpenappsec = { * @param {String} nodeType - 'host' or 'location' * @param {String} hostAppsecMode - to check if the host of a location is inactive. */ - updateNode: function (parentNodePath, nodeName, nodeItemProperties, openappsecMode, nodeType = 'host', hostAppsecMode = '') { + updateNode: function (parentNodePath, nodeName, nodeItemProperties, openappsecMode, nodeType = "host", hostAppsecMode = "") { // if no parent node path is specified, use the root of the config object. const parent = parentNodePath ? _.get(this.config, parentNodePath, this.config) : this.config; if (!parent) { - console.log('parent is not defined'); + logger.warn("parent is not defined"); return; } let nodeItems = _.find(parent[nodeName], { name: nodeItemProperties.name }); - if (openappsecMode == 'inactive' && nodeItems) { + if (openappsecMode == "inactive" && nodeItems) { _.remove(parent[nodeName], { name: nodeItemProperties.name }); } - if (openappsecMode !== 'inactive' || nodeType === 'location' && hostAppsecMode !== 'inactive') { + if (openappsecMode !== "inactive" || nodeType === "location" && hostAppsecMode !== "inactive") { if (!nodeItems) { // create the node from the template if it does not exist. let templateSearchPath = parentNodePath ? `${parentNodePath}.${nodeName}[0]` : `${nodeName}[0]`; nodeItems = _.cloneDeep(_.get(this.configTemplate, templateSearchPath)); // update the node with the nodeItemProperties. if the nodeType is 'location' and the openappsecMode is 'inactive', only update the name, host, and the (inactive) mode. - if (nodeType === 'location' && openappsecMode === 'inactive') { - nodeItemProperties = _.pick(nodeItemProperties, ['name', 'host', 'triggers', 'practices', 'mode', 'web-attacks.override-mode']); + if (nodeType === "location" && openappsecMode === "inactive") { + nodeItemProperties = _.pick(nodeItemProperties, ["name", "host", "triggers", "practices", "mode", "web-attacks.override-mode"]); } Object.keys(nodeItemProperties).forEach(key => { @@ -215,7 +213,7 @@ const internalNginxOpenappsec = { notifyPolicyUpdate: async function() { if (!constants.USE_NOTIFY_POLICY) { - console.log('USE_NOTIFY_POLICY is false'); + logger.info("USE_NOTIFY_POLICY is false"); return; } let ports = constants.PORTS; @@ -225,22 +223,18 @@ const internalNginxOpenappsec = { const data = `{"policy_path":"${constants.POLICY_PATH}"}`; const command = `curl -s -o /dev/null -w "%{http_code}" --data '${data}' ${constants.HOSTURL}:${port}/set-apply-policy`; let { stdout } = await execPromise(command); - if (stdout === '200') { - console.log(`Policy applied successfully on port ${port}`); + if (stdout === "200") { + logger.info(`Policy applied successfully on port ${port}`); return; } else { - console.log(`Policy Unexpected response code: ${stdout}`); + logger.warn(`Policy Unexpected response code: ${stdout}`); lastError = new Error(`Unexpected response code: ${stdout}`); } } catch (error) { - console.log(`Error notifying openappsec to apply the policy on port ${port}: ${error.message}`); + logger.warn(`Error notifying openappsec to apply the policy on port ${port}: ${error.message}`); lastError = error; } } - - // if (lastError) { - // throw lastError; - // } }, /** @@ -272,21 +266,21 @@ const internalNginxOpenappsec = { /** * Get the openappsec mode, use_openappsec and minimum_confidence for a proxy host. - * + * * @param {Object} openappsecConfig - openappsec config object * @param {Number} rowId - proxy host id * @returns {Object} { mode, use_openappsec, minimum_confidence } */ getOpenappsecFields: (openappsecConfig, rowId) => { - const specificRuleName = 'npm-managed-specific-rule-proxyhost-' + rowId; + const specificRuleName = "npm-managed-specific-rule-proxyhost-" + rowId; - const specificRule = _.find(openappsecConfig?.policies['specific-rules'], { name: specificRuleName }); - const mode = specificRule?.mode || 'inactive'; - const use_openappsec = mode !== 'inactive' && mode !== undefined; + const specificRule = _.find(openappsecConfig?.policies["specific-rules"], { name: specificRuleName }); + const mode = specificRule?.mode || "inactive"; + const use_openappsec = mode !== "inactive" && mode !== undefined; - const practiceName = 'npm-managed-practice-proxyhost-' + rowId; + const practiceName = "npm-managed-practice-proxyhost-" + rowId; const practice = _.find(openappsecConfig?.practices, { name: practiceName }); - const minimum_confidence = practice?.['web-attacks']['minimum-confidence'] || 'high'; + const minimum_confidence = practice?.["web-attacks"]["minimum-confidence"] || "high"; return { mode, use_openappsec, minimum_confidence }; }, @@ -305,14 +299,14 @@ const internalNginxOpenappsec = { * @param {String} filename */ deleteFile: (filename) => { - logger.debug('Deleting file: ' + filename); + logger.debug("Deleting file: " + filename); try { fs.unlinkSync(filename); } catch (err) { - logger.debug('Could not delete file:', JSON.stringify(err, null, 2)); + logger.debug("Could not delete file:", JSON.stringify(err, null, 2)); } } }; -module.exports = internalNginxOpenappsec; +export default internalNginxOpenappsec; diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 5f802c0048..fe84607f96 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 internalNginx = { +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]); - - if (locationCopy.forward_host.indexOf('/') > -1) { - const splitted = locationCopy.forward_host.split('/'); + 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("/"); 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,31 @@ 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); } } + // For redirection hosts, if the scheme is not http or https, set it to $scheme + if (nice_host_type === "redirection_host" && ['http', 'https'].indexOf(host.forward_scheme.toLowerCase()) === -1) { + host.forward_scheme = "$scheme"; + } + 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 +245,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 +254,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 +270,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 +289,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 +306,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 +323,7 @@ const internalNginx = { * @returns String */ getFileFriendlyHostType: (host_type) => { - return host_type.replace(new RegExp('-', 'g'), '_'); + return host_type.replace(/-/g, "_"); }, /** @@ -340,8 +333,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 +347,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 +368,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 +386,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 +406,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 +419,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/openappsec-log.js b/backend/internal/openappsec-log.js index dfa770a57c..71214ef2d3 100755 --- a/backend/internal/openappsec-log.js +++ b/backend/internal/openappsec-log.js @@ -1,14 +1,15 @@ -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const error = require('../lib/error'); -const { APPSEC_LOG_DIR } = require('../lib/constants'); +import fs from "node:fs"; +import path from "node:path"; +import readline from "node:readline"; +import constants from "../lib/constants.js"; + +const { APPSEC_LOG_DIR } = constants; const internalOpenappsecLog = { countTotalLines: async function (directoryPath) { const files = await fs.promises.readdir(directoryPath); - const logFiles = files.filter(file => path.extname(file).startsWith('.log')); + const logFiles = files.filter(file => path.extname(file).startsWith(".log")); let totalLineCount = 0; @@ -19,7 +20,7 @@ const internalOpenappsecLog = { const readStream = fs.createReadStream(filePath); const rl = readline.createInterface({ input: readStream }); const firstLine = await new Promise(resolve => { - rl.on('line', line => { + rl.on("line", line => { rl.close(); resolve(line); }); @@ -33,8 +34,8 @@ const internalOpenappsecLog = { } // If the first line is a data line, read the rest of the file - const content = await fs.promises.readFile(filePath, 'utf8'); - const lines = content.split('\n'); + const content = await fs.promises.readFile(filePath, "utf8"); + const lines = content.split("\n"); totalLineCount += lines.length; } @@ -42,8 +43,8 @@ const internalOpenappsecLog = { }, processFile: async function (filePath) { - const content = await fs.promises.readFile(filePath, 'utf8'); - const lines = content.split('\n'); + const content = await fs.promises.readFile(filePath, "utf8"); + const lines = content.split("\n"); const dataLines = []; for (const line of lines) { @@ -84,17 +85,17 @@ const internalOpenappsecLog = { getAll: function (access, expand, search_query) { - return access.can('auditlog:list') + return access.can("auditlog:list") .then(async () => { const directoryPath = APPSEC_LOG_DIR; const files = await fs.promises.readdir(directoryPath); - const logFiles = files.filter(file => path.extname(file).startsWith('.log')); + const logFiles = files.filter(file => path.extname(file).startsWith(".log")); // Sort the logFiles array logFiles.sort((a, b) => { const baseA = path.basename(a, path.extname(a)); const baseB = path.basename(b, path.extname(b)); - return baseA.localeCompare(baseB, undefined, { numeric: true, sensitivity: 'base' }); + return baseA.localeCompare(baseB, undefined, { numeric: true, sensitivity: "base" }); }); const wrappedObjects = []; @@ -109,19 +110,19 @@ const internalOpenappsecLog = { }, getPage: function (access, expand, search_query, page, perPage) { - return access.can('auditlog:list') + return access.can("auditlog:list") .then(async () => { const directoryPath = APPSEC_LOG_DIR; let totalDataLines = await this.countTotalLines(directoryPath); const files = await fs.promises.readdir(directoryPath); - const logFiles = files.filter(file => path.extname(file).startsWith('.log')); + const logFiles = files.filter(file => path.extname(file).startsWith(".log")); // Sort the logFiles array logFiles.sort((a, b) => { const baseA = path.basename(a, path.extname(a)); const baseB = path.basename(b, path.extname(b)); - return baseA.localeCompare(baseB, undefined, { numeric: true, sensitivity: 'base' }); + return baseA.localeCompare(baseB, undefined, { numeric: true, sensitivity: "base" }); }); const wrappedObjects = []; @@ -149,4 +150,4 @@ const internalOpenappsecLog = { }, }; -module.exports = internalOpenappsecLog; +export default internalOpenappsecLog; diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 5699c9b2ab..6e375fd862 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -1,105 +1,105 @@ -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 internalNginxOpenappsec= require('./nginx-openappsec'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); -const fs = require('fs'); -const path = require('path'); -const yaml = require('js-yaml'); -const {castJsonIfNeed} = require('../lib/helpers'); - -function omissions () { - return ['is_deleted', 'owner.is_deleted']; -} +import fs from "node:fs"; +import yaml from "js-yaml"; +import _ from "lodash"; +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import utils from "../lib/utils.js"; +import { nginx as logger } from "../logger.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"; +import internalNginxOpenappsec from "./nginx-openappsec.js"; + +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); - let db_data = _.assign({}, data); - // Remove the openappsec fields from data. they are not in the database. - delete db_data.use_openappsec; - delete db_data.openappsec_mode; - delete db_data.minimum_confidence; - // 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(db_data) - .then(utils.omitRow(omissions())); + // Build the SQL payload from the normalized thisData so it picks up + // cleanSslHstsData / advanced_config defaults, then strip the + // openappsec-only fields which are not columns on proxy_host. + const db_data = _.assign({}, thisData); + delete db_data.use_openappsec; + delete db_data.openappsec_mode; + delete db_data.minimum_confidence; + + return proxyHostModel.query().insertAndFetch(db_data).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 => { return internalNginxOpenappsec.generateConfig(access, row, data) @@ -107,21 +107,21 @@ const internalProxyHost = { return row; }) .catch((err) => { - console.log("Error generating openappsec config: " + err + err.stack); - // throw new error.ConfigurationError(err.message); + logger.error("Error generating openappsec config: " + err + (err && err.stack ? err.stack : "")); }); }) .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; }); @@ -135,56 +135,62 @@ 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 => { return internalNginxOpenappsec.generateConfig(access, row, data) @@ -192,58 +198,63 @@ const internalProxyHost = { return row; }) .catch((err) => { - console.log("Error generating openappsec config: " + err + err.stack); - // throw new error.ConfigurationError(err.message); + logger.error("Error generating openappsec config: " + err + (err && err.stack ? err.stack : "")); }); }) .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); - - // Remove the openappsec fields from data. they are not in the database - delete data.use_openappsec; - delete data.openappsec_mode; - delete data.minimum_confidence; + thisData = _.assign( + {}, + { + domain_names: row.domain_names, + }, + data, + ); + + thisData = internalHost.cleanSslHstsData(thisData, row); + + // Remove the openappsec fields from the SQL payload; they are + // not columns on proxy_host. (Deleting from `data` here was a + // bug — `_.assign` above already copied them onto `thisData`.) + delete thisData.use_openappsec; + delete thisData.openappsec_mode; + delete thisData.minimum_confidence; 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()); + }); }); }); }, @@ -257,55 +268,50 @@ 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; - }) - .then((row) => { // add openappsec fields to row try { const configFilePath = internalNginxOpenappsec.getConfigFilePath(access); - const openappsecConfig = yaml.load(fs.readFileSync(configFilePath, 'utf8')); - let result = internalNginxOpenappsec.getOpenappsecFields(openappsecConfig, row.id); - row.use_openappsec = result.use_openappsec; - row.openappsec_mode = result.mode; - row.minimum_confidence = result.minimum_confidence; - } - catch (e) { - console.log("Error reading openappsec config file: " + e); + const openappsecConfig = yaml.load(fs.readFileSync(configFilePath, "utf8")); + const result = internalNginxOpenappsec.getOpenappsecFields(openappsecConfig, thisRow.id); + thisRow.use_openappsec = result.use_openappsec; + thisRow.openappsec_mode = result.mode; + thisRow.minimum_confidence = result.minimum_confidence; + } catch (e) { + logger.error(`Error reading openappsec config file: ${e}`); } - return row; + return thisRow; }); }, @@ -317,43 +323,42 @@ 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 openappsec config return internalNginxOpenappsec.deleteConfig(access, row) - .catch((err) => { - throw new error.ConfigurationError(err.message); - }); - + .catch((err) => { + throw new errs.ConfigurationError(err.message); + }); }) .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()), }); }); }) @@ -370,39 +375,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()), }); }); }) @@ -419,39 +426,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()), }); }); }) @@ -468,55 +476,50 @@ 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'); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } + 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"); - // 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 (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } + // Query is used for searching + if (typeof searchQuery === "string" && searchQuery.length > 0) { + query.where(function () { + this.where(castJsonIfNeed("domain_names"), "like", `%${searchQuery}%`); + }); + } - return query.then(utils.omitRows(omissions())); - }) - .then((rows) => { - // add openappsec fields to rows - try { - const configFilePath = internalNginxOpenappsec.getConfigFilePath(access); - const openappsecConfig = yaml.load(fs.readFileSync(configFilePath, 'utf8')); - rows.map(function (row, idx) { - let result = internalNginxOpenappsec.getOpenappsecFields(openappsecConfig, row.id); - rows[idx].use_openappsec = result.use_openappsec; - rows[idx].openappsec_mode = result.mode; - rows[idx].minimum_confidence = result.minimum_confidence; - }); - } - catch (e) { - console.log("Error reading openappsec config file: " + e); - } - - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { - return internalHost.cleanAllRowsCertificateMeta(rows); - } + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } - return rows; + const rows = await query.then(utils.omitRows(omissions())); + + // add openappsec fields to rows + try { + const configFilePath = internalNginxOpenappsec.getConfigFilePath(access); + const openappsecConfig = yaml.load(fs.readFileSync(configFilePath, "utf8")); + rows.forEach((row, idx) => { + const result = internalNginxOpenappsec.getOpenappsecFields(openappsecConfig, row.id); + rows[idx].use_openappsec = result.use_openappsec; + rows[idx].openappsec_mode = result.mode; + rows[idx].minimum_confidence = result.minimum_confidence; }); + } catch (e) { + logger.error(`Error reading openappsec config file: ${e}`); + } + + if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { + return internalHost.cleanAllRowsCertificateMeta(rows); + } + return rows; }, /** @@ -527,20 +530,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 6a81b8662c..159ffd8b5e 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'); - -function omissions () { - return ['is_deleted']; -} +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"; + +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 = []; - - 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)); + const domain_name_check_promises = []; + + 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/remote-version.js b/backend/internal/remote-version.js new file mode 100644 index 0000000000..dd9c927797 --- /dev/null +++ b/backend/internal/remote-version.js @@ -0,0 +1,84 @@ +import https from "node:https"; +import { ProxyAgent } from "proxy-agent"; +import { debug, remoteVersion as logger } from "../logger.js"; +import pjson from "../package.json" with { type: "json" }; + +const VERSION_URL = "https://api.github.com/repos/NginxProxyManager/nginx-proxy-manager/releases/latest"; + +const internalRemoteVersion = { + cache_timeout: 1000 * 60 * 15, // 15 minutes + last_result: null, + last_fetch_time: null, + + /** + * Fetch the latest version info, using a cached result if within the cache timeout period. + * @return {Promise<{current: string, latest: string, update_available: boolean}>} Version info + */ + get: async () => { + if ( + !internalRemoteVersion.last_result || + !internalRemoteVersion.last_fetch_time || + Date.now() - internalRemoteVersion.last_fetch_time > internalRemoteVersion.cache_timeout + ) { + const raw = await internalRemoteVersion.fetchUrl(VERSION_URL); + const data = JSON.parse(raw); + internalRemoteVersion.last_result = data; + internalRemoteVersion.last_fetch_time = Date.now(); + } else { + debug(logger, "Using cached remote version result"); + } + + const latestVersion = internalRemoteVersion.last_result.tag_name; + const version = pjson.version.split("-").shift().split("."); + const currentVersion = `v${version[0]}.${version[1]}.${version[2]}`; + return { + current: currentVersion, + latest: latestVersion, + update_available: internalRemoteVersion.compareVersions(currentVersion, latestVersion), + }; + }, + + fetchUrl: (url) => { + const agent = new ProxyAgent(); + const headers = { + "User-Agent": `NginxProxyManager v${pjson.version}`, + }; + + return new Promise((resolve, reject) => { + logger.info(`Fetching ${url}`); + return https + .get(url, { agent, headers }, (res) => { + res.setEncoding("utf8"); + let raw_data = ""; + res.on("data", (chunk) => { + raw_data += chunk; + }); + res.on("end", () => { + resolve(raw_data); + }); + }) + .on("error", (err) => { + reject(err); + }); + }); + }, + + compareVersions: (current, latest) => { + const cleanCurrent = current.replace(/^v/, ""); + const cleanLatest = latest.replace(/^v/, ""); + + const currentParts = cleanCurrent.split(".").map(Number); + const latestParts = cleanLatest.split(".").map(Number); + + for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) { + const curr = currentParts[i] || 0; + const lat = latestParts[i] || 0; + + if (lat > curr) return true; + if (lat < curr) return false; + } + return false; + }, +}; + +export default internalRemoteVersion; diff --git a/backend/internal/report.js b/backend/internal/report.js index 4dde659bda..59f13fe695 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.permission_visibility), + internalRedirectionHost.getCount(userId, access_data.permission_visibility), + internalStream.getCount(userId, access_data.permission_visibility), + internalDeadHost.getCount(userId, access_data.permission_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-openappsec.js b/backend/internal/setting-openappsec.js index e009bc9fc4..e12cf2304c 100755 --- a/backend/internal/setting-openappsec.js +++ b/backend/internal/setting-openappsec.js @@ -1,8 +1,8 @@ -const fs = require('fs'); -const error = require('../lib/error'); -const path = require('path'); - -const constants = require('../lib/constants'); +import fs from "node:fs"; +import path from "node:path"; +import constants from "../lib/constants.js"; +import errs from "../lib/error.js"; +import { nginx as logger } from "../logger.js"; const internalOpenappsecSetting = { configFilePath: path.join(constants.APPSEC_EXT_DIR, constants.APPSEC_CONFIG_FILE_NAME), @@ -12,19 +12,19 @@ const internalOpenappsecSetting = { * @return {Promise} */ getLocalPolicy: (access) => { - return access.can('settings:list') + return access.can("settings:list") .then(() => { try { - const filePath = internalOpenappsecSetting.configFilePath + const filePath = internalOpenappsecSetting.configFilePath; if (!fs.existsSync(filePath)) { return; } - const fileContent = fs.readFileSync(filePath, 'utf8'); + const fileContent = fs.readFileSync(filePath, "utf8"); const jsonStr = JSON.stringify(fileContent); return jsonStr; } catch (err) { - console.error(err); + logger.error(err); } }); }, @@ -35,18 +35,18 @@ const internalOpenappsecSetting = { * @return {Promise} */ updateLocalPolicy: (access, data) => { - return access.can('settings:list') + return access.can("settings:list") .then(() => { - const filePath = internalOpenappsecSetting.configFilePath + const filePath = internalOpenappsecSetting.configFilePath; const yamlStr = data.local_policy; - fs.writeFileSync(filePath, yamlStr, {encoding: 'utf8'}); + fs.writeFileSync(filePath, yamlStr, { encoding: "utf8" }); return true; }) .catch((err) => { - console.error(err); - throw new error.ConfigurationError(err.message); + logger.error(err); + throw new errs.ConfigurationError(err.message); }); } }; -module.exports = internalOpenappsecSetting; +export default internalOpenappsecSetting; diff --git a/backend/internal/setting.js b/backend/internal/setting.js index d4ac67d8a4..f8fc711454 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 4d49bc3aad..805b6652a1 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'); - -function omissions () { - return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted']; -} +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"; + +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]') - .orderByRaw('CAST(incoming_port AS INTEGER) 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 0e6dec5e37..126283e2dd 100644 --- a/backend/internal/token.js +++ b/backend/internal/token.js @@ -1,14 +1,17 @@ -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'); - -const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password'; - -module.exports = { - +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"; +import twoFactor from "./2fa.js"; + +const ERROR_MESSAGE_INVALID_AUTH = "Invalid email or password"; +const ERROR_MESSAGE_INVALID_AUTH_I18N = "error.invalid-auth"; +const ERROR_MESSAGE_INVALID_2FA = "Invalid verification code"; +const ERROR_MESSAGE_INVALID_2FA_I18N = "error.invalid-2fa"; + +export default { /** * @param {Object} data * @param {String} data.identity @@ -18,70 +21,85 @@ 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) { - - 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); - } - - // 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); - } - - 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); - } + .where("email", data.identity.toLowerCase().trim()) + .andWhere("is_deleted", 0) + .andWhere("is_disabled", 0) + .first(); + + if (!user) { + throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH); + } + + const auth = await authModel + .query() + .where("user_id", "=", user.id) + .where("type", "=", "password") + .first(); + + 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}`); + } + + // Check if 2FA is enabled + const has2FA = await twoFactor.isEnabled(user.id); + if (has2FA) { + // Return challenge token instead of full token + const challengeToken = await Token.create({ + iss: issuer || "api", + attrs: { + id: user.id, + }, + scope: ["2fa-challenge"], + expiresIn: "5m", }); + + return { + requires_2fa: true, + challenge_token: challengeToken.token, + }; + } + + // 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 +109,129 @@ 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"); + }, + + /** + * Verify 2FA code and return full token + * @param {string} challengeToken + * @param {string} code + * @param {string} [expiry] + * @returns {Promise} + */ + verify2FA: async (challengeToken, code, expiry) => { + const Token = TokenModel(); + const tokenExpiry = expiry || "1d"; + + // Verify challenge token + let tokenData; + try { + tokenData = await Token.load(challengeToken); + } catch { + throw new errs.AuthError("Invalid or expired challenge token"); + } + + // Check scope + if (!tokenData.scope || tokenData.scope[0] !== "2fa-challenge") { + throw new errs.AuthError("Invalid challenge token"); + } + + const userId = tokenData.attrs?.id; + if (!userId) { + throw new errs.AuthError("Invalid challenge token"); + } + + // Verify 2FA code + const valid = await twoFactor.verifyForLogin(userId, code); + if (!valid) { + throw new errs.AuthError( + ERROR_MESSAGE_INVALID_2FA, + ERROR_MESSAGE_INVALID_2FA_I18N, + ); + } + + // Create full token + const expiryDate = parseDatePeriod(tokenExpiry); + if (expiryDate === null) { + throw new errs.AuthError(`Invalid expiry time: ${tokenExpiry}`); + } + + const signed = await Token.create({ + iss: "api", + attrs: { + id: userId, + }, + scope: ["user"], + expiresIn: tokenExpiry, + }); + + return { + token: signed.token, + expires: expiryDate.toISOString(), + }; }, /** * @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 742ab65d33..d13931d54a 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'); - -function omissions () { - return ['is_deleted']; -} +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"; + +const omissions = () => { + return ["is_deleted", "permissions.id", "permissions.user_id", "permissions.created_on", "permissions.modified_on"]; +}; -const internalUser = { +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'}); - - 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; + await access.can("users:create", data); + data.avatar = gravatar.url(data.email, { default: "mm" }); - 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) => { - - // 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; - }); - } + 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(); + + 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 data.id === 'undefined' || !data.id) { - data.id = access.token.getUserId(0); + if (typeof thisData.id === "undefined" || !thisData.id) { + thisData.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'); - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('name', 'like', '%' + search_query + '%') - .orWhere('email', 'like', '%' + search_query + '%'); - }); - } + 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(', ') + ']'); - } + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } - return query.then(utils.omitRows(omissions())); - }); + 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 0e658a6561..1ecaf250c2 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'); - -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 = {}; +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"; + +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; - - if (typeof object_cache[object_type] === 'undefined') { - switch (object_type) { - - // USERS - should only return yourself - case 'users': - resolve(token_user_id ? [token_user_id] : []); - break; - - // Proxy Hosts - case 'proxy_hosts': - query = proxyHostModel - .query() - .select('id') - .andWhere('is_deleted', 0); - - if (permissions.visibility === 'user') { - query.andWhere('owner_user_id', token_user_id); - } - - 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; + this.loadObjects = async (objectType) => { + let objects = null; + + if (Token.hasScope("user")) { + if (typeof tokenData.attrs.id === "undefined" || !tokenData.attrs.id) { + throw new errs.AuthError("User Token supplied without a User ID"); + } + + const tokenUserId = tokenData.attrs.id ? tokenData.attrs.id : 0; + + if (typeof objectCache[objectType] !== "undefined") { + objects = objectCache[objectType]; + } else { + switch (objectType) { + // USERS - should only return yourself + case "users": + objects = tokenUserId ? [tokenUserId] : []; + break; + + // Proxy Hosts + case "proxy_hosts": { + const query = proxyHostModel + .query() + .select("id") + .andWhere("is_deleted", 0); + + if (permissions.visibility === "user") { + query.andWhere("owner_user_id", tokenUserId); + } + + 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); } - } else { - resolve(object_cache[object_type]); + 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 new errs.PermissionError("Permission Denied", err); + } + }, }; -}; +} diff --git a/backend/lib/certbot.js b/backend/lib/certbot.js index eb1966dc78..3a2dd072ed 100644 --- a/backend/lib/certbot.js +++ b/backend/lib/certbot.js @@ -1,78 +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 function (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); + } - /** - * Installs a cerbot plugin given the key for the object from - * ../global/certbot-dns-plugins.json - * - * @param {string} pluginKey - * @returns {Object} - */ - installPlugin: async function (pluginKey) { - if (typeof dnsPlugins[pluginKey] === 'undefined') { - // throw Error(`Certbot plugin ${pluginKey} not found`); - throw new error.ItemNotFoundError(pluginKey); - } + 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; + }); +}; - const plugin = dnsPlugins[pluginKey]; - logger.start(`Installing ${pluginKey}...`); +/** + * @param {array} pluginKeys + */ +const installPlugins = async (pluginKeys) => { + let hasErrors = false; - plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); - plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); + return new Promise((resolve, reject) => { + if (pluginKeys.length === 0) { + resolve(); + return; + } - const cmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir ' + plugin.dependencies + ' ' + plugin.package_name + plugin.version + ' ' + ' && deactivate'; - return utils.exec(cmd) - .then((result) => { - logger.complete(`Installed ${pluginKey}`); - return result; + batchflow(pluginKeys) + .sequential() + .each((_i, pluginKey, next) => { + installPlugin(pluginKey) + .then(() => { + next(); + }) + .catch((err) => { + hasErrors = true; + next(err); + }); + }) + .error((err) => { + logger.error(err.message); }) - .catch((err) => { - throw err; + .end(() => { + if (hasErrors) { + reject( + new errs.CommandError("Some plugins failed to install. Please check the logs above", 1), + ); + } else { + resolve(); + } }); - }, + }); }; -module.exports = certbot; +export { installPlugins, installPlugin }; diff --git a/backend/lib/config.js b/backend/lib/config.js index 23184f3e83..cb01a0d0d0 100644 --- a/backend/lib/config.js +++ b/backend/lib/config.js @@ -1,48 +1,62 @@ -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'; const postgresEngine = 'pg'; -const sqliteClientName = 'sqlite3'; +const sqliteClientName = 'better-sqlite3'; 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; + + // Migrate those who have "mysql" engine to "mysql2" + if (configData.database.engine === "mysql") { + configData.database.engine = mysqlEngine; + } + + 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 +68,34 @@ 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 +103,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]; - } - return instance; - }, - - /** - * Is this a sqlite configuration? - * - * @returns {boolean} - */ - isSqlite: function () { - instance === null && configure(); - return instance.database.knex && instance.database.knex.client === sqliteClientName; - }, - - /** - * 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; +/** + * + * @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 null; + }); + + return has; +}; + +/** + * 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/constants.js b/backend/lib/constants.js index 7b96186545..b69627c159 100755 --- a/backend/lib/constants.js +++ b/backend/lib/constants.js @@ -1,9 +1,9 @@ -module.exports = { - APPSEC_CONFIG_FILE_NAME: 'local_policy.yaml', - APPSEC_EXT_DIR: '/ext/appsec', - APPSEC_LOG_DIR: '/ext/appsec-logs', +export default { + APPSEC_CONFIG_FILE_NAME: "local_policy.yaml", + APPSEC_EXT_DIR: "/ext/appsec", + APPSEC_LOG_DIR: "/ext/appsec-logs", USE_NOTIFY_POLICY: true, PORTS: [7777, 7778], - HOSTURL: 'http://127.0.0.1', - POLICY_PATH: '/etc/cp/conf/local_policy.yaml', -}; \ No newline at end of file + HOSTURL: "http://127.0.0.1", + POLICY_PATH: "/etc/cp/conf/local_policy.yaml", +}; diff --git a/backend/lib/error.js b/backend/lib/error.js index 413d6a7d9f..d7dbf0c965 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 6d5b8b5fba..6fbf3baf6d 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 17edccec04..90fe241e46 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 44aa36934a..ce907b6de9 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 24ffa58d05..188df2779d 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 4a37a40692..9c29ba2721 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 ad3df3c278..853a7a55a5 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]); - } +/** + * 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; - }, + return null; +}; - convertIntFieldsToBool: function (obj, fields) { - fields.forEach(function (field) { - if (typeof obj[field] !== 'undefined') { - obj[field] = obj[field] === 1; - } - }); - return obj; - }, +const convertIntFieldsToBool = (obj, fields) => { + fields.forEach((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; - }, +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} - */ - castJsonIfNeed: function (colName) { - return isPostgres() ? ref(colName).castText() : colName; - } +/** + * 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 f75f77ef41..0b8e284023 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 bcdb3341c9..af7ad3c952 100644 --- a/backend/lib/utils.js +++ b/backend/lib/utils.js @@ -1,106 +1,110 @@ -const _ = require('lodash'); -const exec = require('child_process').exec; -const execFile = require('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 function(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 }); - } - }); +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 }); + } + }); - child.on('error', (e) => { - reject(new error.CommandError(stderr, 1, e)); - }); + child.on("error", (e) => { + reject(new errs.CommandError(stderr, 1, e)); }); - return stdout; - }, + }); + return stdout; +}; - /** - * @param {String} cmd - * @param {Array} args - * @returns {Promise} - */ - execFile: function (cmd, args) { - // logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : '')); +/** + * @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) => { - execFile(cmd, args, function (err, stdout, /*stderr*/) { - if (err && typeof err === 'object') { - reject(err); - } else { - resolve(stdout.trim()); - } - }); + 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) => { /** - * Used in objection query builder - * - * @param {Array} omissions - * @returns {Function} + * @param {Object} row + * @returns {Object} */ - omitRow: function (omissions) { - /** - * @param {Object} row - * @returns {Object} - */ - return (row) => { - return _.omit(row, omissions); - }; - }, + return (row) => { + return _.omit(row, omissions); + }; +}; +/** + * Used in objection query builder + * + * @param {Array} omissions + * @returns {Function} + */ +const omitRows = (omissions) => { /** - * Used in objection query builder - * - * @param {Array} omissions - * @returns {Function} + * @param {Array} rows + * @returns {Object} */ - omitRows: function (omissions) { - /** - * @param {Array} rows - * @returns {Object} - */ - return (rows) => { - rows.forEach((row, idx) => { - rows[idx] = _.omit(row, omissions); - }); - return rows; - }; - }, + 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/`, + }); /** - * @returns {Object} Liquid render engine + * nginxAccessRule expects the object given to have 2 properties: + * + * directive string + * address string */ - getRenderEngine: function () { - const renderEngine = new Liquid({ - root: __dirname + '/../templates/' - }); + renderEngine.registerFilter("nginxAccessRule", (v) => { + if (typeof v.directive !== "undefined" && typeof v.address !== "undefined" && v.directive && v.address) { + return `${v.directive} ${v.address};`; + } + return ""; + }); - /** - * 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; - } + return renderEngine; }; + +export default { exec, execFile, omitRow, omitRows, getRenderEngine }; diff --git a/backend/lib/validator/api.js b/backend/lib/validator/api.js index fb31e64c6f..6c738d5097 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; - } - - if (typeof payload === 'undefined') { - reject(new error.ValidationError('Payload is undefined')); - return; - } - - 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); - } - }); -} - -module.exports = apiValidator; +const apiValidator = async (schema, payload /*, description*/) => { + if (!schema) { + throw new errs.ValidationError("Schema is undefined"); + } + + // 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) { + 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 c6d240967e..5f2586fd67 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 0ebb07c58a..2b60dbff7b 100644 --- a/backend/logger.js +++ b/backend/logger.js @@ -1,14 +1,26 @@ -const {Signale} = require('signale'); - -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'}) +import signale from "signale"; +import { isDebugMode } from "./lib/config.js"; + +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 remoteVersion = new signale.Signale({ scope: "Remote Version", ...opts }); + +const debug = (logger, ...args) => { + if (isDebugMode()) { + logger.debug(...args); + } +}; + +export { debug, global, migrate, express, access, nginx, ssl, certbot, importer, setup, ipRanges, remoteVersion }; diff --git a/backend/migrate.js b/backend/migrate.js index 263c870202..4c99cab66a 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 a112e8261c..d3c55d9c7f 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 0605485029..cce80d3768 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 05c277396e..fe11edc515 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 9f6b433678..cfa94a996a 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 22ae619ed6..ba3bc562af 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 2780c4df22..28fcc7b5d5 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 4bcfd51adf..c4f7797728 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 648b162a0b..1253130e8d 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 7dc9c1928f..a6cbe2c99a 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 3511e35b35..e682504840 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*/) { - - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] 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(function () { - logger.info('[' + migrate_name + '] 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(() => { + logger.info(`[${migrateName}] 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 ee0f0906fa..6bdaedb6a7 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 a7767eb199..dc57e2a7f3 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*/) { - - 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.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 0dad487681..b3f18aeb16 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*/) { - - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] 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 b9bea0b925..cf84298d60 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*/) { - - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] 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 a894ca5e6e..b4afabd4c9 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 4c50941ffa..c2805152fd 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 5f47b18ec7..0fbba1109c 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/migrations/20251111090000_redirect_auto_scheme.js b/backend/migrations/20251111090000_redirect_auto_scheme.js new file mode 100644 index 0000000000..9f5f9d0642 --- /dev/null +++ b/backend/migrations/20251111090000_redirect_auto_scheme.js @@ -0,0 +1,50 @@ +import { migrate as logger } from "../logger.js"; + +const migrateName = "redirect_auto_scheme"; + +/** + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @returns {Promise} + */ +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); + + return knex.schema + .table("redirection_host", async (table) => { + // change the column default from $scheme to auto + await table.string("forward_scheme").notNull().defaultTo("auto").alter(); + await knex('redirection_host') + .where('forward_scheme', '$scheme') + .update({ forward_scheme: 'auto' }); + }) + .then(() => { + logger.info(`[${migrateName}] redirection_host Table altered`); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); + + return knex.schema + .table("redirection_host", async (table) => { + await table.string("forward_scheme").notNull().defaultTo("$scheme").alter(); + await knex('redirection_host') + .where('forward_scheme', 'auto') + .update({ forward_scheme: '$scheme' }); + }) + .then(() => { + logger.info(`[${migrateName}] redirection_host Table altered`); + }); +}; + +export { up, down }; diff --git a/backend/migrations/20260131163528_trust_forwarded_proto.js b/backend/migrations/20260131163528_trust_forwarded_proto.js new file mode 100644 index 0000000000..546cbca674 --- /dev/null +++ b/backend/migrations/20260131163528_trust_forwarded_proto.js @@ -0,0 +1,43 @@ +import { migrate as logger } from "../logger.js"; + +const migrateName = "trust_forwarded_proto"; + +/** + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @returns {Promise} + */ +const up = function (knex) { + logger.info(`[${migrateName}] Migrating Up...`); + + return knex.schema + .alterTable('proxy_host', (table) => { + table.tinyint('trust_forwarded_proto').notNullable().defaultTo(0); + }) + .then(() => { + logger.info(`[${migrateName}] proxy_host Table altered`); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const down = function (knex) { + logger.info(`[${migrateName}] Migrating Down...`); + + return knex.schema + .alterTable('proxy_host', (table) => { + table.dropColumn('trust_forwarded_proto'); + }) + .then(() => { + logger.info(`[${migrateName}] proxy_host Table altered`); + }); +}; + +export { up, down }; \ No newline at end of file diff --git a/backend/migrations/20260513120000_open_appsec.js b/backend/migrations/20260513120000_open_appsec.js new file mode 100644 index 0000000000..4de1deb5f7 --- /dev/null +++ b/backend/migrations/20260513120000_open_appsec.js @@ -0,0 +1,47 @@ +import { migrate as logger } from "../logger.js"; + +const migrateName = "open_appsec"; + +/** + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @returns {Promise} + */ +const up = function (knex) { + logger.info(`[${migrateName}] Migrating Up...`); + + return knex.schema + .alterTable('proxy_host', (table) => { + table.boolean('use_openappsec').notNull().defaultTo(false); + table.string('minimum_confidence').notNull().defaultTo('high'); + table.string('openappsec_mode').notNull().defaultTo('detect-learn'); + }) + .then(() => { + logger.info(`[${migrateName}] proxy_host Table altered`); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const down = function (knex) { + logger.info(`[${migrateName}] Migrating Down...`); + + return knex.schema + .alterTable('proxy_host', (table) => { + table.dropColumn('use_openappsec'); + table.dropColumn('minimum_confidence'); + table.dropColumn('openappsec_mode'); + }) + .then(() => { + logger.info(`[${migrateName}] proxy_host Table altered`); + }); +}; + +export { up, down }; diff --git a/backend/models/access_list.js b/backend/models/access_list.js index 959df05f3d..427d447d62 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); +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: (qb) => { + qb.where("proxy_host.is_deleted", 0); }, - modify: function (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 3895539c21..75bf435294 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); +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 bffc00231a..91165fe13d 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); +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 45a4b46023..6e2d398f58 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); +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 469e96bf4f..e8af582d54 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'); - -Model.knex(db); - -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; - }); +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"]; + +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 d4ea21ad52..ad6e0a65fe 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); +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: (qb) => { + qb.where("redirection_host.is_deleted", 0); }, - modify: function (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 483da3b6be..0acf7ca76a 100644 --- a/backend/models/dead_host.js +++ b/backend/models/dead_host.js @@ -1,95 +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); +Model.knex(db()); -const boolFields = [ - 'is_deleted', - 'enabled', -]; +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: (qb) => { + qb.where("certificate.is_deleted", 0); }, - modify: function (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 dec70c3de6..293dcc7254 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); +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 07aa5dd3c8..e8f447c8e7 100644 --- a/backend/models/proxy_host.js +++ b/backend/models/proxy_host.js @@ -1,114 +1,115 @@ // 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); +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", + "trust_forwarded_proto", ]; 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 801627916d..46c7301754 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); +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 75aa900761..56f7dc5aac 100644 --- a/backend/models/setting.js +++ b/backend/models/setting.js @@ -1,10 +1,10 @@ // 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); +Model.knex(db()); class Setting extends Model { $beforeInsert () { @@ -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 dbec2dcb73..5f61945a65 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); +Model.knex(db()); -const boolFields = [ - 'enabled', - 'is_deleted', - '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: (qb) => { + qb.where("certificate.is_deleted", 0); }, - modify: function (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 7cf11e0357..4edab5bbee 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 78fd3dd67d..68a314466a 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); +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 bb87d5dc4c..d878471780 100644 --- a/backend/models/user_permission.js +++ b/backend/models/user_permission.js @@ -1,11 +1,11 @@ // 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); +Model.knex(db()); class UserPermission extends Model { $beforeInsert () { @@ -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 3d6d134205..90223a21c2 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 0e1561a930..b29114cd90 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,50 +1,53 @@ { "name": "nginx-proxy-manager", - "version": "2.12.3", + "version": "2.14.0-appsec.1", "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", - "archiver": "^5.3.0", + "@apidevtools/json-schema-ref-parser": "^14.1.1", + "ajv": "^8.18.0", + "archiver": "^7.0.1", "batchflow": "^0.4.0", - "bcrypt": "^5.0.0", - "body-parser": "^1.20.3", + "bcrypt": "^6.0.0", + "better-sqlite3": "^12.6.2", + "body-parser": "^2.2.2", "compression": "^1.7.4", - "express": "^4.20.0", - "express-fileupload": "^1.1.9", - "gravatar": "^1.8.0", + "express": "^5.2.1", + "express-fileupload": "^1.5.2", + "gravatar": "^1.8.2", "js-yaml": "^4.1.0", - "json-schema-ref-parser": "^8.0.0", - "jsonwebtoken": "^9.0.0", - "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", - "objection": "3.0.1", + "jsonwebtoken": "^9.0.3", + "knex": "3.1.0", + "liquidjs": "10.24.0", + "lodash": "^4.17.23", + "moment": "^2.30.1", + "mysql2": "^3.17.1", + "node-rsa": "^1.1.1", + "objection": "3.1.5", + "otplib": "^13.3.0", "path": "^0.12.7", - "pg": "^8.13.1", + "pg": "^8.18.0", + "proxy-agent": "^6.5.0", "signale": "1.4.0", - "sqlite3": "5.1.6", + "sqlite3": "^5.1.7", "temp-write": "^4.0.0" }, + "devDependencies": { + "@apidevtools/swagger-parser": "^12.1.0", + "@biomejs/biome": "^2.3.14", + "chalk": "5.6.2", + "nodemon": "^3.1.11" + }, "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 c68c7b35be..c40b1628d8 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); + } + }); + +/** + * 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); + } }); -module.exports = router; +export default router; diff --git a/backend/routes/main.js b/backend/routes/main.js index d5e8a05fe4..b2deebe3a9 100644 --- a/backend/routes/main.js +++ b/backend/routes/main.js @@ -1,53 +1,72 @@ -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 openappsecLogRoutes from "./openappsec-log.js"; +import openappsecSettingsRoutes from "./openappsec-settings.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"; +import versionRoutes from "./version.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('/openappsec-log', require('./openappsec-log')); -router.use('/reports', require('./reports')); -router.use('/settings', require('./settings')); -router.use('/openappsec-settings', require('./openappsec-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("/openappsec-log", openappsecLogRoutes); +router.use("/reports", reportsRoutes); +router.use("/settings", settingsRoutes); +router.use("/openappsec-settings", openappsecSettingsRoutes); +router.use("/version", versionRoutes); +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 3837512775..9dfcf7ec31 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", + }, + }, + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, }, - 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); + ); + 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", + }, + }, + }, + { + list_id: req.params.list_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, }, - 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); + ); + 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 bf47c03fcc..99f429b446 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,29 +126,68 @@ 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')); + .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); + } + }); + +/** + * Validate Certs before saving + * + * /api/nginx/certificates/validate + */ +router + .route("/validate") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * POST /api/nginx/certificates/validate + * + * Validate certificates + */ + .post(async (req, res, next) => { + if (!req.files) { + res.status(400).send({ error: "No files were uploaded" }); return; } - internalCertificate.testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains)) - .then((result) => { - res.status(200) - .send(result); - }) - .catch(next); + 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); + } }); /** @@ -107,7 +196,7 @@ router * /api/nginx/certificates/123 */ router - .route('/:certificate_id') + .route("/:certificate_id") .options((_, res) => { res.sendStatus(204); }) @@ -118,33 +207,38 @@ router * * Retrieve a specific certificate */ - .get((req, res, next) => { - validator({ - required: ['certificate_id'], - additionalProperties: false, - properties: { - certificate_id: { - $ref: 'common#/properties/id' + .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", + }, + }, }, - 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); + { + 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); + } }) /** @@ -152,13 +246,16 @@ router * * 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); + .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); + } }); /** @@ -167,7 +264,7 @@ router * /api/nginx/certificates/123/upload */ router - .route('/:certificate_id/upload') + .route("/:certificate_id/upload") .options((_, res) => { res.sendStatus(204); }) @@ -178,20 +275,21 @@ router * * Upload certificates */ - .post((req, res, next) => { + .post(async (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); + 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); } }); @@ -201,7 +299,7 @@ router * /api/nginx/certificates/123/renew */ router - .route('/:certificate_id/renew') + .route("/:certificate_id/renew") .options((_, res) => { res.sendStatus(204); }) @@ -212,16 +310,17 @@ router * * Renew certificate */ - .post((req, res, next) => { + .post(async (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); + 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); + } }); /** @@ -230,8 +329,8 @@ router * /api/nginx/certificates/123/download */ router - .route('/:certificate_id/download') - .options((req, res) => { + .route("/:certificate_id/download") + .options((_req, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -241,48 +340,16 @@ router * * 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); - }); - -/** - * Validate Certs before saving - * - * /api/nginx/certificates/validate - */ -router - .route('/validate') - .options((_, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/certificates/validate - * - * Validate certificates - */ - .post((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); + .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); } }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/dead_hosts.js b/backend/routes/nginx/dead_hosts.js index 83b3776534..31f7043635 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 3be4582a87..7045a195cc 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", + }, + }, + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, }, - 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); + ); + 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", + }, + }, + }, + { + host_id: req.params.host_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, }, - 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); + ); + 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 a46feb8416..9b5b5b374d 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", + }, + }, + }, + { + host_id: req.params.host_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, }, - 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); + ); + 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 c033f2ef19..dec2e1a13f 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", + }, + }, + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, }, - 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); + ); + 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", + }, + }, + }, + { + stream_id: req.params.stream_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, }, - 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); + ); + 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/openappsec-log.js b/backend/routes/openappsec-log.js index 08e7c38a3a..a54b1a9fdb 100755 --- a/backend/routes/openappsec-log.js +++ b/backend/routes/openappsec-log.js @@ -1,19 +1,19 @@ -const express = require('express'); -const jwtdecode = require('../lib/express/jwt-decode'); -const internalOpenappsecLog = require('../internal/openappsec-log'); +import express from "express"; +import internalOpenappsecLog from "../internal/openappsec-log.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/openappsec-log */ router - .route('/') - .options((req, res) => { + .route("/") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -26,10 +26,9 @@ router .get((req, res, next) => { return internalOpenappsecLog.getAll(res.locals.access) .then((policy) => { - res.status(200) - .send(policy); + res.status(200).send(policy); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/openappsec-settings.js b/backend/routes/openappsec-settings.js index ef63c13042..b8a7ab5a16 100755 --- a/backend/routes/openappsec-settings.js +++ b/backend/routes/openappsec-settings.js @@ -1,33 +1,32 @@ -const express = require('express'); -const jwtdecode = require('../lib/express/jwt-decode'); -const internalOpenappsecSetting = require('../internal/setting-openappsec'); +import express from "express"; +import internalOpenappsecSetting from "../internal/setting-openappsec.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/openappsec-settings */ router - .route('/') - .options((req, res) => { + .route("/") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) /** * GET /api/openappsec-settings - * + * * Retrieve the open-appsec local policy. */ .get((req, res, next) => { return internalOpenappsecSetting.getLocalPolicy(res.locals.access) .then((policy) => { - res.status(200) - .send(policy); + res.status(200).send(policy); }) .catch(next); }) @@ -40,10 +39,9 @@ router .put((req, res, next) => { return internalOpenappsecSetting.updateLocalPolicy(res.locals.access, req.body) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/reports.js b/backend/routes/reports.js index 98c6cf86d2..df9962ab11 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 fc3e48b6cc..86bc18660d 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; - } + 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); + 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); + } }); -module.exports = router; +export default router; diff --git a/backend/routes/settings.js b/backend/routes/settings.js index dac4c3d1a7..ca4af656b3 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 72d01d41d8..8a6a1bc0fb 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,36 @@ 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; +router + .route("/2fa") + .options((_, res) => { + res.sendStatus(204); + }) + + /** + * POST /tokens/2fa + * + * Verify 2FA code and get full token + */ + .post(async (req, res, next) => { + try { + const { challenge_token, code } = await apiValidator(getValidationSchema("/tokens/2fa", "post"), req.body); + const result = await internalToken.verify2FA(challenge_token, code); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +export default router; diff --git a/backend/routes/users.js b/backend/routes/users.js index f8ce366c9d..7c3da8c1d1 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -1,22 +1,28 @@ -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'); - -let router = express.Router({ +import express from "express"; +import internal2FA from "../internal/2fa.js"; +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"; + +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 +33,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 +72,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 +140,7 @@ router * /api/users/123 */ router - .route('/:user_id') + .route("/:user_id") .options((_, res) => { res.sendStatus(204); }) @@ -91,37 +152,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 +196,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 +216,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 +234,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 +246,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(201) - .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 +267,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 +279,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(201) - .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 +303,7 @@ router * /api/users/123/login */ router - .route('/:user_id/login') + .route("/:user_id/login") .options((_, res) => { res.sendStatus(204); }) @@ -235,13 +314,142 @@ 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(201) - .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); + } + }); + +/** + * User 2FA status + * + * /api/users/123/2fa + */ +router + .route("/:user_id/2fa") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + .all(userIdFromMe) + + /** + * POST /api/users/123/2fa + * + * Start 2FA setup, returns QR code URL + */ + .post(async (req, res, next) => { + try { + const result = await internal2FA.startSetup(res.locals.access, req.params.user_id); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }) + + /** + * GET /api/users/123/2fa + * + * Get 2FA status for a user + */ + .get(async (req, res, next) => { + try { + const status = await internal2FA.getStatus(res.locals.access, req.params.user_id); + res.status(200).send(status); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }) + + /** + * DELETE /api/users/123/2fa?code=XXXXXX + * + * Disable 2FA for a user + */ + .delete(async (req, res, next) => { + try { + const code = typeof req.query.code === "string" ? req.query.code : null; + if (!code) { + throw new errs.ValidationError("Missing required parameter: code"); + } + await internal2FA.disable(res.locals.access, req.params.user_id, code); + res.status(200).send(true); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +/** + * User 2FA enable + * + * /api/users/123/2fa/enable + */ +router + .route("/:user_id/2fa/enable") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + .all(userIdFromMe) + + /** + * POST /api/users/123/2fa/enable + * + * Verify code and enable 2FA + */ + .post(async (req, res, next) => { + try { + const { code } = await apiValidator( + getValidationSchema("/users/{userID}/2fa/enable", "post"), + req.body, + ); + const result = await internal2FA.enable(res.locals.access, req.params.user_id, code); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +/** + * User 2FA backup codes + * + * /api/users/123/2fa/backup-codes + */ +router + .route("/:user_id/2fa/backup-codes") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + .all(userIdFromMe) + + /** + * POST /api/users/123/2fa/backup-codes + * + * Regenerate backup codes + */ + .post(async (req, res, next) => { + try { + const { code } = await apiValidator( + getValidationSchema("/users/{userID}/2fa/backup-codes", "post"), + req.body, + ); + const result = await internal2FA.regenerateBackupCodes(res.locals.access, req.params.user_id, code); + 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/version.js b/backend/routes/version.js new file mode 100644 index 0000000000..266e56fff7 --- /dev/null +++ b/backend/routes/version.js @@ -0,0 +1,40 @@ +import express from "express"; +import internalRemoteVersion from "../internal/remote-version.js"; +import { debug, express as logger } from "../logger.js"; + +const router = express.Router({ + caseSensitive: true, + strict: true, + mergeParams: true, +}); + +/** + * /api/version/check + */ +router + .route("/check") + .options((_, res) => { + res.sendStatus(204); + }) + + /** + * GET /api/version/check + * + * Check for available updates + */ + .get(async (req, res, _next) => { + try { + const data = await internalRemoteVersion.get(); + res.status(200).send(data); + } catch (error) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${error}`); + // Send 200 even though there's an error to avoid triggering update checks repeatedly + res.status(200).send({ + current: null, + latest: null, + update_available: false, + }); + } + }); + +export default router; diff --git a/backend/schema/common.json b/backend/schema/common.json index 83de0143cb..00b06e005f 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 cd0218d725..d80eb06d8f 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 + "minLength": 1, + "example": "My Access List" }, - "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$" - } - ] + "meta": { + "type": "object", + "example": {} }, "satisfy_any": { - "type": "boolean" + "type": "boolean", + "example": true }, "pass_auth": { - "type": "boolean" + "type": "boolean", + "example": false }, - "meta": { - "type": "object" + "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 0000000000..7436852859 --- /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 3e5e8594b8..307cac8227 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 b75dcf61d4..80cd92befe 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,27 @@ "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 + }, + "key_type": { + "type": "string", + "enum": ["rsa", "ecdsa"], + "default": "rsa" } + }, + "example": { + "dns_challenge": false } } } diff --git a/backend/schema/components/check-version-object.json b/backend/schema/components/check-version-object.json new file mode 100644 index 0000000000..ef2ffac418 --- /dev/null +++ b/backend/schema/components/check-version-object.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "description": "Check Version object", + "additionalProperties": false, + "required": ["current", "latest", "update_available"], + "properties": { + "current": { + "type": ["string", "null"], + "description": "Current version string", + "example": "v2.10.1" + }, + "latest": { + "type": ["string", "null"], + "description": "Latest version string", + "example": "v2.13.4" + }, + "update_available": { + "type": "boolean", + "description": "Whether there's an update available", + "example": true + } + } +} diff --git a/backend/schema/components/dead-host-object.json b/backend/schema/components/dead-host-object.json index 792c2f81ed..b876ebf0a8 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 0000000000..c240db1863 --- /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 c2540cf1fd..6f350a0889 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 8d223417be..592ead2ca4 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 b852a014d0..cae9d26c02 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 bb13bdde25..51fb214444 100644 --- a/backend/schema/components/proxy-host-object.json +++ b/backend/schema/components/proxy-host-object.json @@ -23,9 +23,8 @@ "locations", "hsts_enabled", "hsts_subdomains", - "certificate" + "trust_forwarded_proto" ], - "additionalProperties": false, "properties": { "id": { "$ref": "../common.json#/properties/id" @@ -45,12 +44,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" @@ -68,22 +69,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" @@ -128,7 +135,15 @@ "type": "string" } } - } + }, + "example": [ + { + "path": "/app", + "forward_scheme": "http", + "forward_host": "example.com", + "forward_port": 80 + } + ] }, "hsts_enabled": { "$ref": "../common.json#/properties/hsts_enabled" @@ -136,15 +151,22 @@ "hsts_subdomains": { "$ref": "../common.json#/properties/hsts_subdomains" }, + "trust_forwarded_proto":{ + "type": "boolean", + "description": "Trust the forwarded headers", + "example": false + }, "certificate": { "oneOf": [ { - "type": "null" + "type": "null", + "example": null }, { "$ref": "./certificate-object.json" } - ] + ], + "example": null }, "owner": { "$ref": "./user-object.json" @@ -152,12 +174,14 @@ "access_list": { "oneOf": [ { - "type": "null" + "type": "null", + "example": null }, { "$ref": "./access-list-object.json" } - ] + ], + "example": null }, "use_openappsec": { "type": "boolean" @@ -168,6 +192,5 @@ "openappsec_mode": { "type": "string" } - } } diff --git a/backend/schema/components/redirection-host-object.json b/backend/schema/components/redirection-host-object.json index e7a495fd39..58169720b6 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 82407be3f8..4ae57bd796 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-list.json b/backend/schema/components/stream-list.json index 39789b4a73..b6e8b6d448 100644 --- a/backend/schema/components/stream-list.json +++ b/backend/schema/components/stream-list.json @@ -1,7 +1,7 @@ { "type": "array", - "description": "Proxy Hosts list", + "description": "Streams list", "items": { - "$ref": "./proxy-host-object.json" + "$ref": "./stream-object.json" } } diff --git a/backend/schema/components/stream-object.json b/backend/schema/components/stream-object.json index 848c30e6ef..602073ceca 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/token-challenge.json b/backend/schema/components/token-challenge.json new file mode 100644 index 0000000000..9d79cd92e9 --- /dev/null +++ b/backend/schema/components/token-challenge.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "description": "Token object", + "required": ["requires_2fa", "challenge_token"], + "additionalProperties": false, + "properties": { + "requires_2fa": { + "description": "Whether this token request requires two-factor authentication", + "example": true, + "type": "boolean" + }, + "challenge_token": { + "description": "Challenge Token used in subsequent 2FA verification", + "example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4", + "type": "string" + } + } +} diff --git a/backend/schema/components/user-object.json b/backend/schema/components/user-object.json index 180e8f1976..7acd0a4290 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 87b75f2570..0478486b0d 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"; -let compiledSchema = null; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); -module.exports = { +let compiledSchema = null; - /** - * 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; - }, +/** + * 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 - */ - 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; +/** + * 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 bc43e29ddc..62c09ce169 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 0000000000..38ff1c762b --- /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 8c3a4e025f..9f6ba2a984 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 a8b9adc699..ada40f5bf7 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 073585c8bd..182a883a11 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 e67023f89d..9705826f20 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 7f887dad65..61e8044013 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 4c5a4edd29..38b7003a1e 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 0d40bcb8c4..a99f619d69 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 4b858cae75..7c18d019dd 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 22317b337f..46afbf8753 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 ef4d20e5b1..4466d38d30 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 f38b8102a4..2b1ba3e610 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 0000000000..3efb1a0696 --- /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 2f4b556afe..5884c97299 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 5a3306c22f..15406c8b3d 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 2b9a8dd3b0..4d738d0187 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 21eb325ef2..9fa2bd126d 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 8a11a3f66d..feb04ff227 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 f3aa81a5b0..eed0ebc016 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 2cdcecf4b4..2a4d08aebd 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 ca3ce9faec..512c2a0af0 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 47e2f8b125..a3c24edc34 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 f9505ed48e..e07217c45c 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 c8bbb6932c..1ec38720c8 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 a07204fa98..1398b2255a 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,9 +56,10 @@ "http2_support": false, "forward_scheme": "http", "enabled": true, - "locations": null, + "locations": [], "hsts_enabled": false, "hsts_subdomains": false, + "trust_forwarded_proto": false, "use_openappsec": false, "minimum_confidence": "high", "openappsec_mode": "detect-learn" diff --git a/backend/schema/paths/nginx/proxy-hosts/hostID/delete.json b/backend/schema/paths/nginx/proxy-hosts/hostID/delete.json index 991ef0e9e3..da67944d57 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 54ff8a6632..14c2689c0d 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 9f052de052..fe504b781d 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 df1af9b84f..d3efc3c126 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,21 +30,23 @@ "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, "caching_enabled": false, "block_exploits": false, - "use_openappsec": false, - "minimum_confidence": "high", - "openappsec_mode": "detect-learn", + "use_openappsec": false, + "minimum_confidence": "high", + "openappsec_mode": "detect-learn", "advanced_config": "", "meta": { "nginx_online": true, @@ -51,9 +56,23 @@ "http2_support": false, "forward_scheme": "http", "enabled": true, - "locations": null, + "locations": [], "hsts_enabled": false, - "hsts_subdomains": false + "hsts_subdomains": false, + "trust_forwarded_proto": 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 ea734c42f1..8804f11837 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 @@ -53,6 +56,9 @@ "hsts_subdomains": { "$ref": "../../../../components/proxy-host-object.json#/properties/hsts_subdomains" }, + "trust_forwarded_proto": { + "$ref": "../../../../components/proxy-host-object.json#/properties/trust_forwarded_proto" + }, "http2_support": { "$ref": "../../../../components/proxy-host-object.json#/properties/http2_support" }, @@ -80,15 +86,15 @@ "locations": { "$ref": "../../../../components/proxy-host-object.json#/properties/locations" }, - "openappsec_mode": { - "$ref": "../../../../components/proxy-host-object.json#/properties/openappsec_mode" - }, - "minimum_confidence": { - "$ref": "../../../../components/proxy-host-object.json#/properties/minimum_confidence" - }, - "use_openappsec": { - "$ref": "../../../../components/proxy-host-object.json#/properties/use_openappsec" - } + "openappsec_mode": { + "$ref": "../../../../components/proxy-host-object.json#/properties/openappsec_mode" + }, + "minimum_confidence": { + "$ref": "../../../../components/proxy-host-object.json#/properties/minimum_confidence" + }, + "use_openappsec": { + "$ref": "../../../../components/proxy-host-object.json#/properties/use_openappsec" + } } } } @@ -102,13 +108,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, @@ -123,22 +131,25 @@ "http2_support": false, "forward_scheme": "http", "enabled": true, + "locations": [], "hsts_enabled": false, "hsts_subdomains": false, - "use_openappsec": false, - "minimum_confidence": "high", - "openappsec_mode": "detect-learn", + "trust_forwarded_proto": false, + "use_openappsec": false, + "minimum_confidence": "high", + "openappsec_mode": "detect-learn", "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 1a548247ea..b90674ac6b 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" @@ -41,6 +48,9 @@ "hsts_subdomains": { "$ref": "../../../components/proxy-host-object.json#/properties/hsts_subdomains" }, + "trust_forwarded_proto": { + "$ref": "../../../components/proxy-host-object.json#/properties/trust_forwarded_proto" + }, "http2_support": { "$ref": "../../../components/proxy-host-object.json#/properties/http2_support" }, @@ -67,18 +77,25 @@ }, "locations": { "$ref": "../../../components/proxy-host-object.json#/properties/locations" - }, - "openappsec_mode": { - "$ref": "../../../components/proxy-host-object.json#/properties/openappsec_mode" - }, - "minimum_confidence": { - "$ref": "../../../components/proxy-host-object.json#/properties/minimum_confidence" - }, - "use_openappsec": { - "$ref": "../../../components/proxy-host-object.json#/properties/use_openappsec" - } - + }, + "openappsec_mode": { + "$ref": "../../../components/proxy-host-object.json#/properties/openappsec_mode" + }, + "minimum_confidence": { + "$ref": "../../../components/proxy-host-object.json#/properties/minimum_confidence" + }, + "use_openappsec": { + "$ref": "../../../components/proxy-host-object.json#/properties/use_openappsec" + } } + }, + "example": { + "domain_names": [ + "test.example.com" + ], + "forward_scheme": "http", + "forward_host": "127.0.0.1", + "forward_port": 8080 } } } @@ -91,13 +108,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, @@ -109,23 +128,26 @@ "http2_support": false, "forward_scheme": "http", "enabled": true, + "locations": [], "hsts_enabled": false, "hsts_subdomains": false, + "trust_forwarded_proto": false, "certificate": null, "use_openappsec": false, "minimum_confidence": "high", "openappsec_mode": "detect-learn", "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 0b35e0fc4a..dfeb60497b 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 7330f36239..0b3232e810 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 8433220d5f..f44af11fd2 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 bef53436b7..07bf44dd3b 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 d780f874b0..577b5144a9 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 fd97cbfa8f..454a38805e 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 5bfde2c388..6aa533010e 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 17969ee4ef..6dda8e346b 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 d26996b692..0c986de8fa 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 3a96852582..585cd82d19 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 d1c1b1c84a..61de851682 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 dc914f5f27..d27ce5265e 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 801af13a7a..22fae8872b 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 14adb16315..21ae71ef7a 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 a40ddc723c..682a97b882 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 d435b00429..c9e601eab9 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 5d148d8af4..a99f7f2206 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 405b976d28..929f3e5f37 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 4ca6242939..050ad44125 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/2fa/post.json b/backend/schema/paths/tokens/2fa/post.json new file mode 100644 index 0000000000..c37af8d3a2 --- /dev/null +++ b/backend/schema/paths/tokens/2fa/post.json @@ -0,0 +1,55 @@ +{ + "operationId": "loginWith2FA", + "summary": "Verify 2FA code and get full token", + "tags": ["tokens"], + "requestBody": { + "description": "2fa Challenge Payload", + "required": true, + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "challenge_token": { + "minLength": 1, + "type": "string", + "example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" + }, + "code": { + "minLength": 6, + "maxLength": 8, + "type": "string", + "example": "012345" + } + }, + "required": ["challenge_token", "code"], + "type": "object" + }, + "example": { + "challenge_token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4", + "code": "012345" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "expires": "2025-02-04T20:40:46.340Z", + "token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" + } + } + }, + "schema": { + "$ref": "../../../components/token-object.json" + } + } + }, + "description": "200 response" + } + } +} diff --git a/backend/schema/paths/tokens/get.json b/backend/schema/paths/tokens/get.json index ef842eafe4..4e6ae2449c 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 99703ff0de..470be30314 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,15 +44,20 @@ "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" } } }, "schema": { - "$ref": "../../components/token-object.json" + "oneOf": [ + { + "$ref": "../../components/token-object.json" + }, + { + "$ref": "../../components/token-challenge.json" + } + ] } } }, diff --git a/backend/schema/paths/users/get.json b/backend/schema/paths/users/get.json index 3741530114..79c31ba808 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 c0213fe05b..49025e3250 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/2fa/backup-codes/post.json b/backend/schema/paths/users/userID/2fa/backup-codes/post.json new file mode 100644 index 0000000000..00cb9d5f10 --- /dev/null +++ b/backend/schema/paths/users/userID/2fa/backup-codes/post.json @@ -0,0 +1,92 @@ +{ + "operationId": "regenUser2faCodes", + "summary": "Regenerate 2FA backup codes", + "tags": ["users"], + "parameters": [ + { + "in": "path", + "name": "userID", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "description": "User ID", + "example": 2 + } + ], + "requestBody": { + "description": "Verification Payload", + "required": true, + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "code": { + "minLength": 6, + "maxLength": 8, + "type": "string", + "example": "123456" + } + }, + "required": ["code"], + "type": "object" + }, + "example": { + "code": "123456" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "backup_codes": [ + "6CD7CB06", + "495302F3", + "D8037852", + "A6FFC956", + "BC1A1851", + "A05E644F", + "A406D2E8", + "0AE3C522" + ] + } + } + }, + "schema": { + "type": "object", + "required": ["backup_codes"], + "additionalProperties": false, + "properties": { + "backup_codes": { + "description": "Backup codes", + "example": [ + "6CD7CB06", + "495302F3", + "D8037852", + "A6FFC956", + "BC1A1851", + "A05E644F", + "A406D2E8", + "0AE3C522" + ], + "type": "array", + "items": { + "type": "string", + "example": "6CD7CB06" + } + } + } + } + } + }, + "description": "200 response" + } + } +} diff --git a/backend/schema/paths/users/userID/2fa/delete.json b/backend/schema/paths/users/userID/2fa/delete.json new file mode 100644 index 0000000000..bff292d829 --- /dev/null +++ b/backend/schema/paths/users/userID/2fa/delete.json @@ -0,0 +1,48 @@ +{ + "operationId": "disableUser2fa", + "summary": "Disable 2fa for user", + "tags": ["users"], + "parameters": [ + { + "in": "path", + "name": "userID", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "description": "User ID", + "example": 2 + }, + { + "in": "query", + "name": "code", + "schema": { + "type": "string", + "minLength": 6, + "maxLength": 6, + "example": "012345" + }, + "required": true, + "description": "2fa Code", + "example": "012345" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "default": { + "value": true + } + }, + "schema": { + "type": "boolean" + } + } + }, + "description": "200 response" + } + } +} diff --git a/backend/schema/paths/users/userID/2fa/enable/post.json b/backend/schema/paths/users/userID/2fa/enable/post.json new file mode 100644 index 0000000000..74c9854085 --- /dev/null +++ b/backend/schema/paths/users/userID/2fa/enable/post.json @@ -0,0 +1,92 @@ +{ + "operationId": "enableUser2fa", + "summary": "Verify code and enable 2FA", + "tags": ["users"], + "parameters": [ + { + "in": "path", + "name": "userID", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "description": "User ID", + "example": 2 + } + ], + "requestBody": { + "description": "Verification Payload", + "required": true, + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "code": { + "minLength": 6, + "maxLength": 8, + "type": "string", + "example": "123456" + } + }, + "required": ["code"], + "type": "object" + }, + "example": { + "code": "123456" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "backup_codes": [ + "6CD7CB06", + "495302F3", + "D8037852", + "A6FFC956", + "BC1A1851", + "A05E644F", + "A406D2E8", + "0AE3C522" + ] + } + } + }, + "schema": { + "type": "object", + "required": ["backup_codes"], + "additionalProperties": false, + "properties": { + "backup_codes": { + "description": "Backup codes", + "example": [ + "6CD7CB06", + "495302F3", + "D8037852", + "A6FFC956", + "BC1A1851", + "A05E644F", + "A406D2E8", + "0AE3C522" + ], + "type": "array", + "items": { + "type": "string", + "example": "6CD7CB06" + } + } + } + } + } + }, + "description": "200 response" + } + } +} diff --git a/backend/schema/paths/users/userID/2fa/get.json b/backend/schema/paths/users/userID/2fa/get.json new file mode 100644 index 0000000000..78ce1886a8 --- /dev/null +++ b/backend/schema/paths/users/userID/2fa/get.json @@ -0,0 +1,57 @@ +{ + "operationId": "getUser2faStatus", + "summary": "Get user 2fa Status", + "tags": ["users"], + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "userID", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "description": "User ID", + "example": 2 + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "enabled": false, + "backup_codes_remaining": 0 + } + } + }, + "schema": { + "type": "object", + "additionalProperties": false, + "required": ["enabled", "backup_codes_remaining"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Is 2FA enabled for this user", + "example": true + }, + "backup_codes_remaining": { + "type": "integer", + "description": "Number of remaining backup codes for this user", + "example": 5 + } + } + } + } + } + } + } +} diff --git a/backend/schema/paths/users/userID/2fa/post.json b/backend/schema/paths/users/userID/2fa/post.json new file mode 100644 index 0000000000..cef5d196e1 --- /dev/null +++ b/backend/schema/paths/users/userID/2fa/post.json @@ -0,0 +1,52 @@ +{ + "operationId": "setupUser2fa", + "summary": "Start 2FA setup, returns QR code URL", + "tags": ["users"], + "parameters": [ + { + "in": "path", + "name": "userID", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "description": "User ID", + "example": 2 + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "secret": "JZYCEBIEEJYUGPQM", + "otpauth_url": "otpauth://totp/Nginx%20Proxy%20Manager:jc%40jc21.com?secret=JZYCEBIEEJYUGPQM&period=30&digits=6&algorithm=SHA1&issuer=Nginx%20Proxy%20Manager" + } + } + }, + "schema": { + "type": "object", + "required": ["secret", "otpauth_url"], + "additionalProperties": false, + "properties": { + "secret": { + "description": "TOTP Secret", + "example": "JZYCEBIEEJYUGPQM", + "type": "string" + }, + "otpauth_url": { + "description": "OTP Auth URL for QR Code generation", + "example": "otpauth://totp/Nginx%20Proxy%20Manager:jc%40jc21.com?secret=JZYCEBIEEJYUGPQM&period=30&digits=6&algorithm=SHA1&issuer=Nginx%20Proxy%20Manager", + "type": "string" + } + } + } + } + }, + "description": "200 response" + } + } +} diff --git a/backend/schema/paths/users/userID/auth/put.json b/backend/schema/paths/users/userID/auth/put.json index a72f5617c4..3dba45b5a1 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 7d4f36151d..767edfe89d 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 cb8ac61b46..2cf5587d9f 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 6148d182b4..46001d2ec3 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 2dcd2aed71..764e7466cc 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 60a6cd132d..eabaa3a0ea 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/paths/version/check/get.json b/backend/schema/paths/version/check/get.json new file mode 100644 index 0000000000..cbc576a7f5 --- /dev/null +++ b/backend/schema/paths/version/check/get.json @@ -0,0 +1,26 @@ +{ + "operationId": "checkVersion", + "summary": "Returns any new version data from github", + "tags": ["public"], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "current": "v2.12.0", + "latest": "v2.13.4", + "update_available": true + } + } + }, + "schema": { + "$ref": "../../../components/check-version-object.json" + } + } + } + } + } +} diff --git a/backend/schema/swagger.json b/backend/schema/swagger.json index 5a0142bff7..4222f19ddd 100644 --- a/backend/schema/swagger.json +++ b/backend/schema/swagger.json @@ -2,13 +2,69 @@ "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": [ { "url": "http://127.0.0.1:81/api" } ], + "components": { + "securitySchemes": { + "$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": { @@ -20,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" @@ -47,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}": { @@ -227,6 +293,16 @@ "$ref": "./paths/tokens/post.json" } }, + "/tokens/2fa": { + "post": { + "$ref": "./paths/tokens/2fa/post.json" + } + }, + "/version/check": { + "get": { + "$ref": "./paths/version/check/get.json" + } + }, "/users": { "get": { "$ref": "./paths/users/get.json" @@ -246,6 +322,27 @@ "$ref": "./paths/users/userID/delete.json" } }, + "/users/{userID}/2fa": { + "post": { + "$ref": "./paths/users/userID/2fa/post.json" + }, + "get": { + "$ref": "./paths/users/userID/2fa/get.json" + }, + "delete": { + "$ref": "./paths/users/userID/2fa/delete.json" + } + }, + "/users/{userID}/2fa/enable": { + "post": { + "$ref": "./paths/users/userID/2fa/enable/post.json" + } + }, + "/users/{userID}/2fa/backup-codes": { + "post": { + "$ref": "./paths/users/userID/2fa/backup-codes/post.json" + } + }, "/users/{userID}/auth": { "put": { "$ref": "./paths/users/userID/auth/put.json" diff --git a/backend/scripts/install-certbot-plugins b/backend/scripts/install-certbot-plugins index bf99541059..6acb022605 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 6b9b8e78a2..84f42793ea 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'; - - logger.info('Creating a new user: ' + email + ' with password: ' + password); - - const data = { - is_deleted: 0, - email: email, - name: 'Administrator', - nickname: 'Admin', - avatar: '', - roles: ['admin'], - }; - - 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 setupDefaultUser = async () => { + const initialAdminEmail = process.env.INITIAL_ADMIN_EMAIL; + const initialAdminPassword = process.env.INITIAL_ADMIN_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. + + if (!initialAdminEmail || !initialAdminPassword) { + return Promise.resolve(); + } + + 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: initialAdminEmail, + 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: initialAdminPassword, + 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 = []; - - 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); - } - - // 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)); - } - }); - - return certbot.installPlugins(plugins) - .then(() => { - if (promises.length) { - return Promise.all(promises) - .then(() => { - logger.info('Added Certbot plugins ' + plugins.join(', ')); - }); - } - }); + .where("is_deleted", 0) + .andWhere("provider", "letsencrypt"); + + if (certificates?.length) { + const plugins = []; + const promises = []; + + 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); + } + + // 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/templates/_access.conf b/backend/templates/_access.conf index f592637757..4f38854513 100644 --- a/backend/templates/_access.conf +++ b/backend/templates/_access.conf @@ -4,7 +4,7 @@ auth_basic "Authorization required"; auth_basic_user_file /data/access/{{ access_list_id }}; - {% if access_list.pass_auth == 0 or access_list.pass_auth == true %} + {% if access_list.pass_auth == 0 or access_list.pass_auth == false %} proxy_set_header Authorization ""; {% endif %} diff --git a/backend/templates/_forced_ssl.conf b/backend/templates/_forced_ssl.conf index 7fade20ca9..886e866ee4 100644 --- a/backend/templates/_forced_ssl.conf +++ b/backend/templates/_forced_ssl.conf @@ -1,6 +1,11 @@ {% if certificate and certificate_id > 0 -%} {% if ssl_forced == 1 or ssl_forced == true %} # Force SSL + {% if trust_forwarded_proto == true %} + set $trust_forwarded_proto "T"; + {% else %} + set $trust_forwarded_proto "F"; + {% endif %} include conf.d/include/force-ssl.conf; {% endif %} {% endif %} \ No newline at end of file diff --git a/backend/templates/local-policy-open-appsec-enabled-for-proxy-host.yaml b/backend/templates/local-policy-open-appsec-enabled-for-proxy-host.yaml index 6b560b3f85..a003b23764 100755 --- a/backend/templates/local-policy-open-appsec-enabled-for-proxy-host.yaml +++ b/backend/templates/local-policy-open-appsec-enabled-for-proxy-host.yaml @@ -1,122 +1,122 @@ -# This example is for NPM Proxy Host with open-appsec enabled, -# Enforcement Mode set to Prevent/Learn, hostname web.server.com/example - -policies: - default: - triggers: - - appsec-default-log-trigger - mode: inactive - practices: - - webapp-default-practice - custom-response: appsec-default-web-user-response - specific-rules: - - host: web.server.com/example - # as set in "Edit Proxy Host" in "Domain Names" field - # IMPORTANT LIMITATION: Currently open-appsec declarative with CRD version 1.0 only supports single host entry per specific rule - # This will be resolved with new CRDs 2.0 - name: npm-managed-specific-rule-proxyhost-1 - # This “name” key will be the actual reference to a specific Reverse Proxy object defined in NPM - triggers: - - npm-managed-log-trigger-proxyhost-1 - mode: prevent-learn - practices: - - npm-managed-practice-proxyhost-1 - -practices: - - name: webapp-default-practice - web-attacks: - max-body-size-kb: 1000000 - max-header-size-bytes: 102400 - max-object-depth: 40 - max-url-size-bytes: 32768 - minimum-confidence: high - override-mode: inactive - protections: - csrf-protection: inactive - error-disclosure: inactive - non-valid-http-methods: false - open-redirect: inactive - anti-bot: - injected-URIs: [] - validated-URIs: [] - override-mode: inactive - snort-signatures: - configmap: [] - override-mode: inactive - openapi-schema-validation: - configmap: [] - override-mode: inactive - - - name: npm-managed-practice-proxyhost-1 - web-attacks: - max-body-size-kb: 1000000 - max-header-size-bytes: 102400 - max-object-depth: 40 - max-url-size-bytes: 32768 - minimum-confidence: high - override-mode: inactive - protections: - csrf-protection: inactive - error-disclosure: inactive - non-valid-http-methods: false - open-redirect: inactive - anti-bot: - injected-URIs: [] - validated-URIs: [] - override-mode: inactive - snort-signatures: - configmap: [] - override-mode: inactive - openapi-schema-validation: - configmap: [] - override-mode: inactive - -log-triggers: - - name: appsec-default-log-trigger - access-control-logging: - allow-events: false - drop-events: true - additional-suspicious-events-logging: - enabled: true - minimum-severity: high - response-body: false - response-code: true - appsec-logging: - all-web-requests: false - detect-events: true - prevent-events: true - extended-logging: - http-headers: false - request-body: false - url-path: true - url-query: true - log-destination: - cloud: false - stdout: - format: json - - name: npm-managed-log-trigger-proxyhost-1 - access-control-logging: - allow-events: false - drop-events: true - additional-suspicious-events-logging: - enabled: true - minimum-severity: high - response-body: false - appsec-logging: - all-web-requests: false - detect-events: true - prevent-events: true - extended-logging: - http-headers: false - request-body: false - url-path: false - url-query: false - log-destination: - cloud: false - stdout: - format: json - -custom-responses: - - name: appsec-default-web-user-response - mode: response-code-only +# This example is for NPM Proxy Host with open-appsec enabled, +# Enforcement Mode set to Prevent/Learn, hostname web.server.com/example + +policies: + default: + triggers: + - appsec-default-log-trigger + mode: inactive + practices: + - webapp-default-practice + custom-response: appsec-default-web-user-response + specific-rules: + - host: web.server.com/example + # as set in "Edit Proxy Host" in "Domain Names" field + # IMPORTANT LIMITATION: Currently open-appsec declarative with CRD version 1.0 only supports single host entry per specific rule + # This will be resolved with new CRDs 2.0 + name: npm-managed-specific-rule-proxyhost-1 + # This “name” key will be the actual reference to a specific Reverse Proxy object defined in NPM + triggers: + - npm-managed-log-trigger-proxyhost-1 + mode: prevent-learn + practices: + - npm-managed-practice-proxyhost-1 + +practices: + - name: webapp-default-practice + web-attacks: + max-body-size-kb: 1000000 + max-header-size-bytes: 102400 + max-object-depth: 40 + max-url-size-bytes: 32768 + minimum-confidence: high + override-mode: inactive + protections: + csrf-protection: inactive + error-disclosure: inactive + non-valid-http-methods: false + open-redirect: inactive + anti-bot: + injected-URIs: [] + validated-URIs: [] + override-mode: inactive + snort-signatures: + configmap: [] + override-mode: inactive + openapi-schema-validation: + configmap: [] + override-mode: inactive + + - name: npm-managed-practice-proxyhost-1 + web-attacks: + max-body-size-kb: 1000000 + max-header-size-bytes: 102400 + max-object-depth: 40 + max-url-size-bytes: 32768 + minimum-confidence: high + override-mode: inactive + protections: + csrf-protection: inactive + error-disclosure: inactive + non-valid-http-methods: false + open-redirect: inactive + anti-bot: + injected-URIs: [] + validated-URIs: [] + override-mode: inactive + snort-signatures: + configmap: [] + override-mode: inactive + openapi-schema-validation: + configmap: [] + override-mode: inactive + +log-triggers: + - name: appsec-default-log-trigger + access-control-logging: + allow-events: false + drop-events: true + additional-suspicious-events-logging: + enabled: true + minimum-severity: high + response-body: false + response-code: true + appsec-logging: + all-web-requests: false + detect-events: true + prevent-events: true + extended-logging: + http-headers: false + request-body: false + url-path: true + url-query: true + log-destination: + cloud: false + stdout: + format: json + - name: npm-managed-log-trigger-proxyhost-1 + access-control-logging: + allow-events: false + drop-events: true + additional-suspicious-events-logging: + enabled: true + minimum-severity: high + response-body: false + appsec-logging: + all-web-requests: false + detect-events: true + prevent-events: true + extended-logging: + http-headers: false + request-body: false + url-path: false + url-query: false + log-destination: + cloud: false + stdout: + format: json + +custom-responses: + - name: appsec-default-web-user-response + mode: response-code-only http-response-code: 403 \ No newline at end of file diff --git a/backend/templates/openappsec.conf b/backend/templates/openappsec.conf index bc350cab1f..eef0a52b4f 100755 --- a/backend/templates/openappsec.conf +++ b/backend/templates/openappsec.conf @@ -1,16 +1,16 @@ -policies: - default: - triggers: - - appsec-default-log-trigger - mode: inactive - practices: - - webapp-default-practice - custom-response: appsec-default-web-user-response - specific-rules: - - host: {{ domain_names.first }} - triggers: - - appsec-default-log-trigger - mode: {{openappsec_mode}} - practices: - - webapp-default-practice +policies: + default: + triggers: + - appsec-default-log-trigger + mode: inactive + practices: + - webapp-default-practice + custom-response: appsec-default-web-user-response + specific-rules: + - host: {{ domain_names.first }} + triggers: + - appsec-default-log-trigger + mode: {{openappsec_mode}} + practices: + - webapp-default-practice custom-response: appsec-default-web-user-response \ No newline at end of file diff --git a/backend/templates/stream.conf b/backend/templates/stream.conf index 7333aaee13..3a10387b27 100644 --- a/backend/templates/stream.conf +++ b/backend/templates/stream.conf @@ -12,6 +12,9 @@ server { proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; + access_log /data/logs/stream-{{ id }}_access.log stream; + error_log /data/logs/stream-{{ id }}_error.log warn; + # Custom include /data/nginx/custom/server_stream[.]conf; include /data/nginx/custom/server_stream_tcp[.]conf; @@ -25,9 +28,12 @@ server { proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; + access_log /data/logs/stream-{{ id }}_access.log stream; + error_log /data/logs/stream-{{ id }}_error.log warn; + # Custom include /data/nginx/custom/server_stream[.]conf; include /data/nginx/custom/server_stream_udp[.]conf; } {% endif %} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/backend/validate-schema.js b/backend/validate-schema.js old mode 100644 new mode 100755 index 71a05c8184..b1870665fc --- 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 cea8210bc5..6c146b6eb4 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2,21 +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@14.0.1": + version "14.0.1" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.0.1.tgz#3bc445ed2eddf72bc2f9eb2e295c696bdc5be725" + integrity sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw== 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== +"@apidevtools/json-schema-ref-parser@^14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.1.1.tgz#0bae59809b7848c7a89dc6bb2f3bb980b7c290b1" + integrity sha512-uGF1YGOzzD50L7HLNWclXmsEhQflw8/zZHIz0/AzkJrKL5r9PceUipZxR/cp/8veTk4TVfdDJLyIwXLjaP5ePg== dependencies: - "@jsdevtools/ono" "^7.1.3" "@types/json-schema" "^7.0.15" js-yaml "^4.1.0" @@ -30,115 +28,93 @@ resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267" 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== +"@apidevtools/swagger-parser@^12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-12.1.0.tgz#ef73e5f9e32c2becef6d95b90fb4481b0fec8fe4" + integrity sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng== dependencies: - "@apidevtools/json-schema-ref-parser" "9.0.6" + "@apidevtools/json-schema-ref-parser" "14.0.1" "@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" - -"@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" - -"@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== - -"@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" + call-me-maybe "^1.0.2" -"@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/biome@^2.3.14": + version "2.3.14" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.3.14.tgz#b879cd5e0495334d4db7c49d6f3cc95b67d2909c" + integrity sha512-QMT6QviX0WqXJCaiqVMiBUCr5WRQ1iFSjvOLoTk6auKukJMvnMzWucXpwZB0e8F00/1/BsS9DzcKgWH+CLqVuA== + optionalDependencies: + "@biomejs/cli-darwin-arm64" "2.3.14" + "@biomejs/cli-darwin-x64" "2.3.14" + "@biomejs/cli-linux-arm64" "2.3.14" + "@biomejs/cli-linux-arm64-musl" "2.3.14" + "@biomejs/cli-linux-x64" "2.3.14" + "@biomejs/cli-linux-x64-musl" "2.3.14" + "@biomejs/cli-win32-arm64" "2.3.14" + "@biomejs/cli-win32-x64" "2.3.14" + +"@biomejs/cli-darwin-arm64@2.3.14": + version "2.3.14" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.14.tgz#da942618e1dc2d19322bc11d5dacfe7d7616a502" + integrity sha512-UJGPpvWJMkLxSRtpCAKfKh41Q4JJXisvxZL8ChN1eNW3m/WlPFJ6EFDCE7YfUb4XS8ZFi3C1dFpxUJ0Ety5n+A== + +"@biomejs/cli-darwin-x64@2.3.14": + version "2.3.14" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.14.tgz#3ad06cce8ef6d2b935582011bd0cc3ca98a9554d" + integrity sha512-PNkLNQG6RLo8lG7QoWe/hhnMxJIt1tEimoXpGQjwS/dkdNiKBLPv4RpeQl8o3s1OKI3ZOR5XPiYtmbGGHAOnLA== + +"@biomejs/cli-linux-arm64-musl@2.3.14": + version "2.3.14" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.14.tgz#e92681273dc59ac57b75b72f1b64a67543e50f8c" + integrity sha512-LInRbXhYujtL3sH2TMCH/UBwJZsoGwfQjBrMfl84CD4hL/41C/EU5mldqf1yoFpsI0iPWuU83U+nB2TUUypWeg== + +"@biomejs/cli-linux-arm64@2.3.14": + version "2.3.14" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.14.tgz#bab43ee0a88ba15a6d59ec648a4b415d68d6eeb7" + integrity sha512-KT67FKfzIw6DNnUNdYlBg+eU24Go3n75GWK6NwU4+yJmDYFe9i/MjiI+U/iEzKvo0g7G7MZqoyrhIYuND2w8QQ== + +"@biomejs/cli-linux-x64-musl@2.3.14": + version "2.3.14" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.14.tgz#ee90f7110dafdedf4644e0a27ac242975dcd88d3" + integrity sha512-KQU7EkbBBuHPW3/rAcoiVmhlPtDSGOGRPv9js7qJVpYTzjQmVR+C9Rfcz+ti8YCH+zT1J52tuBybtP4IodjxZQ== + +"@biomejs/cli-linux-x64@2.3.14": + version "2.3.14" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.14.tgz#d152e61c6dc847836ebc741fb70fe305414aa7fe" + integrity sha512-ZsZzQsl9U+wxFrGGS4f6UxREUlgHwmEfu1IrXlgNFrNnd5Th6lIJr8KmSzu/+meSa9f4rzFrbEW9LBBA6ScoMA== + +"@biomejs/cli-win32-arm64@2.3.14": + version "2.3.14" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.14.tgz#2c59e84f3d172bada2a1df94d6cf7e511c244a4e" + integrity sha512-+IKYkj/pUBbnRf1G1+RlyA3LWiDgra1xpS7H2g4BuOzzRbRB+hmlw0yFsLprHhbbt7jUzbzAbAjK/Pn0FDnh1A== + +"@biomejs/cli-win32-x64@2.3.14": + version "2.3.14" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.14.tgz#44405162f255fe153a5ff99379510c058bf7a1e8" + integrity sha512-oizCjdyQ3WJEswpb3Chdngeat56rIdSYK12JI3iI11Mt5T5EXcZ7WLuowzEaFPNJ3zmOQFliMN8QY1Pi+qsfdQ== "@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== - dependencies: - detect-libc "^2.0.0" - https-proxy-agent "^5.0.0" - make-dir "^3.1.0" - node-fetch "^2.6.7" - nopt "^5.0.0" - npmlog "^5.0.1" - rimraf "^3.0.2" - 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== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== 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== + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" -"@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" +"@noble/hashes@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.0.1.tgz#fc1a928061d1232b0a52bb754393c37a5216c89e" + integrity sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== "@npmcli/fs@^1.0.0": version "1.1.1" @@ -156,27 +132,70 @@ 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== +"@otplib/core@13.3.0": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@otplib/core/-/core-13.3.0.tgz#7f037af6cc5907c2cff9cf6092952088a0ce5a6e" + integrity sha512-pnQDOuCmFVeF/XnboJq9TOJgLoo2idNPJKMymOF8vGqJJ+ReKRYM9bUGjNPRWC0tHjMwu1TXbnzyBp494JgRag== -"@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== +"@otplib/hotp@13.3.0": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@otplib/hotp/-/hotp-13.3.0.tgz#135e45c6350ae60bbc1170a1801a6100e49794b8" + integrity sha512-XJMZGz2bg4QJwK7ulvl1GUI2VMn/flaIk/E/BTKAejHsX2kUtPF1bRhlZ2+elq8uU5Fs9Z9FHcQK2CPZNQbbUQ== + dependencies: + "@otplib/core" "13.3.0" + "@otplib/uri" "13.3.0" + +"@otplib/plugin-base32-scure@13.3.0": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.3.0.tgz#3362fc5dd568e9cecac913fd0fa169148c469142" + integrity sha512-/jYbL5S6GB0Ie3XGEWtLIr9s5ZICl/BfmNL7+8/W7usZaUU4GiyLd2S+JGsNCslPyqNekSudD864nDAvRI0s8w== + dependencies: + "@otplib/core" "13.3.0" + "@scure/base" "^2.0.0" + +"@otplib/plugin-crypto-noble@13.3.0": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.3.0.tgz#edfd6c8c54730cfdfc52c6a3fcd797e204fdd0e2" + integrity sha512-wmV+jBVncepgwv99G7Plrdzd0tHfbpXk2U+OD7MO7DzpDqOYEgOPi+IIneksJSTL8QvWdfi+uQEuhnER4fKouA== + dependencies: + "@noble/hashes" "^2.0.1" + "@otplib/core" "13.3.0" + +"@otplib/totp@13.3.0": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@otplib/totp/-/totp-13.3.0.tgz#1d1f61cfd1acd773e1051e6b918227ae34545808" + integrity sha512-XfjGNoN8d9S3Ove2j7AwkVV7+QDFsV7Lm7YwSiezNaHffkWtJ60aJYpmf+01dARdPST71U2ptueMsRJso4sq4A== + dependencies: + "@otplib/core" "13.3.0" + "@otplib/hotp" "13.3.0" + "@otplib/uri" "13.3.0" + +"@otplib/uri@13.3.0": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@otplib/uri/-/uri-13.3.0.tgz#7474d6dc0fdf8ab2d0cf2b79e2001446c4e7b6b3" + integrity sha512-3oh6nBXy+cm3UX9cxEAGZiDrfxHU2gfelYFV+XNCx+8dq39VaQVymwlU2yjPZiMAi/3agaUeEftf2RwM5F+Cyg== dependencies: - defer-to-connect "^1.0.1" + "@otplib/core" "13.3.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@scure/base@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-2.0.0.tgz#ba6371fddf92c2727e88ad6ab485db6e624f9a98" + integrity sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w== "@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== +"@tootallnate/quickjs-emscripten@^0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c" + integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== "@types/json-schema@^7.0.15": version "7.0.15" @@ -188,23 +207,20 @@ 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: - 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== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== dependencies: - 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== + event-target-shim "^5.0.0" -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== +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" agent-base@6, agent-base@^6.0.2: version "6.0.2" @@ -213,13 +229,16 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" +agent-base@^7.1.0, agent-base@^7.1.2: + version "7.1.4" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" + integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== + 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,68 +254,33 @@ 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== +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== 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.0.0" -ajv@^8.17.1, ajv@^8.6.3: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== +ajv@^8.0.0, ajv@^8.17.1, ajv@^8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc" + integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== dependencies: fast-deep-equal "^3.1.3" fast-uri "^3.0.1" 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" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" + integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -304,68 +288,56 @@ ansi-styles@^3.2.1: dependencies: 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== +ansi-styles@^4.0.0: + 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== +ansi-styles@^6.1.0: + version "6.2.3" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + +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== - -archiver-utils@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" - integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.1.0.tgz#75500a190313d95c64e871e7e4284c6ac219f0b1" + integrity sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew== + +archiver-utils@^5.0.0, archiver-utils@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-5.0.2.tgz#63bc719d951803efc72cf961a56ef810760dd14d" + integrity sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA== dependencies: - glob "^7.1.4" + glob "^10.0.0" graceful-fs "^4.2.0" + is-stream "^2.0.1" 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" + lodash "^4.17.15" normalize-path "^3.0.0" - readable-stream "^2.0.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== - dependencies: - archiver-utils "^2.1.0" - async "^3.2.0" - buffer-crc32 "^0.2.1" - readable-stream "^3.6.0" - readdir-glob "^1.0.0" - tar-stream "^2.2.0" - zip-stream "^4.1.0" + readable-stream "^4.0.0" -are-we-there-yet@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" - integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== +archiver@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-7.0.1.tgz#c9d91c350362040b8927379c7aa69c0655122f61" + integrity sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ== dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" + archiver-utils "^5.0.2" + async "^3.2.4" + buffer-crc32 "^1.0.0" + readable-stream "^4.0.0" + readdir-glob "^1.1.2" + tar-stream "^3.0.0" + zip-stream "^6.0.1" are-we-there-yet@^3.0.0: version "3.0.1" @@ -375,75 +347,92 @@ 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" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -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= - 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== +ast-types@^0.13.4: + version "0.13.4" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" + integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== + dependencies: + tslib "^2.0.1" + +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: +aws-ssl-profiles@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641" integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g== +b4a@^1.6.4: + version "1.7.3" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.7.3.tgz#24cf7ccda28f5465b66aec2bac69e32809bf112f" + integrity sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q== + 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== + +bare-events@^2.7.0: + version "2.8.2" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.8.2.tgz#7b3e10bd8e1fc80daf38bb516921678f566ab89f" + integrity sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ== 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== +basic-ftp@^5.0.2: + version "5.0.5" + resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.0.5.tgz#14a474f5fffecca1f4f406f1c26b18f800225ac0" + integrity sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg== + 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== +bcrypt@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-6.0.0.tgz#86643fddde9bcd0ad91400b063003fa4b0312835" + integrity sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg== dependencies: - node-addon-api "^3.0.0" - node-pre-gyp "0.15.0" + node-addon-api "^8.3.0" + node-gyp-build "^4.8.4" + +better-sqlite3@^12.6.2: + version "12.6.2" + resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-12.6.2.tgz#770649f28a62e543a360f3dfa1afe4cc944b1937" + integrity sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA== + dependencies: + bindings "^1.5.0" + prebuild-install "^7.1.1" 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,50 +444,40 @@ 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" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - 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" +body-parser@^2.2.1, body-parser@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.2.tgz#1a32cdb966beaf68de50a9dfbe5b58f83cb8890c" + integrity sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA== + dependencies: + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.3" + http-errors "^2.0.0" + iconv-lite "^0.7.0" + on-finished "^2.4.1" + qs "^6.14.1" + raw-body "^3.0.1" + type-is "^2.0.1" 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== + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== dependencies: 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" @@ -506,15 +485,15 @@ braces@~3.0.2: dependencies: fill-range "^7.1.1" -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= +buffer-crc32@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405" + integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w== -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,19 +503,22 @@ 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== +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: - dicer "0.3.0" + base64-js "^1.3.1" + ieee754 "^1.2.1" -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= +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: + streamsearch "^1.1.0" -bytes@3.1.2: +bytes@3.1.2, bytes@^3.1.2, bytes@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== @@ -565,52 +547,36 @@ 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== - 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== +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-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: - 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" +chalk@5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" + integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== chalk@^2.3.2: version "2.4.2" @@ -621,30 +587,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== - 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== +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: - 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 +612,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 +626,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,14 +643,14 @@ 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" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-support@^1.1.2, color-support@^1.1.3: +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== @@ -725,26 +661,22 @@ 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@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-6.0.2.tgz#26d31251a66b9d6ba23a84064ecd3a6a71d2609e" + integrity sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg== dependencies: - buffer-crc32 "^0.2.13" - crc32-stream "^4.0.2" + crc-32 "^1.2.0" + crc32-stream "^6.0.0" + is-stream "^2.0.1" normalize-path "^3.0.0" - readable-stream "^3.6.0" + readable-stream "^4.0.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,84 +684,67 @@ 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= - -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" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.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" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" +content-disposition@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.1.tgz#a8b7bbeb2904befdfb6787e5c0c086959f605f9b" + integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q== -content-type@~1.0.4, content-type@~1.0.5: +content-type@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -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= +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== -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.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== 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== +crc32-stream@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-6.0.0.tgz#8529a3868f8b27abb915f6c3617c0fadedbf9430" + integrity sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g== dependencies: crc-32 "^1.2.0" - readable-stream "^3.4.0" + readable-stream "^4.0.0" -cross-spawn@^7.0.2: +cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -838,128 +753,96 @@ cross-spawn@^7.0.2: 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== +data-uri-to-buffer@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz#8a58bb67384b261a38ef18bea1810cb01badd28b" + integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== 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, debug@^4.3.3, debug@^4.3.4, debug@^4.4.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: + 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== - 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== +degenerator@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-5.0.1.tgz#9403bf297c6dad9a1ece409b37db27954f91f2f5" + integrity sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ== dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" + ast-types "^0.13.4" + escodegen "^2.1.0" + esprima "^4.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, 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== -destroy@1.2.0: - version "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== - -dicer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" - integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== - 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" + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8" + integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== -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== +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: - is-obj "^2.0.0" + 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= +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== ecdsa-sig-formatter@1.0.11: version "1.0.11" @@ -971,29 +854,24 @@ 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" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -encodeurl@~2.0.0: +encodeurl@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== @@ -1006,9 +884,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,145 +907,59 @@ 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: +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== + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -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== +escodegen@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== dependencies: - esrecurse "^4.3.0" + esprima "^4.0.1" 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" + optionalDependencies: + source-map "~0.6.1" 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: +esprima@^4.0.1: 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" @@ -1178,100 +970,100 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: +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== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -exit-on-epipe@~1.0.1: +events-universal@^1.0.0: 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== - -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== - dependencies: - busboy "^0.3.1" - -express@^4.20.0: - version "4.20.0" - resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48" - integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw== - 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-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" - 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" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" + resolved "https://registry.yarnpkg.com/events-universal/-/events-universal-1.0.1.tgz#b56a84fd611b6610e0a2d0f09f80fdf931e2dfe6" + integrity sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw== + dependencies: + bare-events "^2.7.0" + +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +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.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 "^1.6.0" -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +express@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.1" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + depd "^2.0.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + +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-fifo@^1.2.0, fast-fifo@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== 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,23 +1072,22 @@ 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@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099" + integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" 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,49 +1099,29 @@ 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== +foreground-child@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== 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== + cross-spawn "^7.0.6" + signal-exit "^4.0.1" forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== 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,38 +1132,18 @@ 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= - -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -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" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -gauge@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" - integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== - dependencies: - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.2" - console-control-strings "^1.0.0" - has-unicode "^2.0.1" - object-assign "^4.1.1" - signal-exit "^3.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.2" - gauge@^4.0.3: version "4.0.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" @@ -1407,20 +1158,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 +1170,99 @@ 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" + dunder-proto "^1.0.1" + es-object-atoms "^1.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== +get-uri@^6.0.1: + version "6.0.5" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.5.tgz#714892aa4a871db671abc5395e5e9447bc306a16" + integrity sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg== dependencies: - pump "^3.0.0" + basic-ftp "^5.0.2" + data-uri-to-buffer "^6.0.2" + debug "^4.3.4" 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@^10.0.0: + version "10.5.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== 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" + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" -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== +glob@^7.1.3, glob@^7.1.4: + 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" -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.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -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,69 +1272,40 @@ 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= - -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-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== + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== -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-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-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= - -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" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -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" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== +http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" http-proxy-agent@^4.0.1: version "4.0.1" @@ -1650,6 +1316,14 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" +http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + 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" @@ -1658,6 +1332,14 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +https-proxy-agent@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" + humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -1665,21 +1347,21 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -iconv-lite@0.4.24, iconv-lite@^0.4.4: - 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" -ieee754@^1.1.13: +iconv-lite@^0.7.0, iconv-lite@^0.7.2, iconv-lite@~0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e" + integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -1687,45 +1369,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,12 +1389,12 @@ 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" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1753,9 +1402,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 +1414,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 +1427,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,210 +1436,131 @@ 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== - 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== +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: - 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-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== 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== +is-stream@^2.0.0, is-stream@^2.0.1: + 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== + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -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== +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== dependencies: - argparse "^1.0.7" - esprima "^4.0.0" + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== 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.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz#6cd57ab01e9b0ac07cb847d53d3c9b6ee31f7ae2" + integrity sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g== dependencies: - jws "^3.2.2" - lodash "^4.17.21" + jws "^4.0.1" + 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== +jwa@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804" + integrity sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg== 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" -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== +jws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.1.tgz#07edc1be8fac20e677b283ece261498bd38f0690" + integrity sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA== dependencies: - jwa "^1.4.1" + jwa "^2.0.1" safe-buffer "^5.0.1" -keyv@^3.0.0: +knex@3.1.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" - integrity sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg== + resolved "https://registry.yarnpkg.com/knex/-/knex-3.1.0.tgz#b6ddd5b5ad26a6315234a5b09ec38dc4a370bd8c" + integrity sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw== dependencies: colorette "2.0.19" - commander "^9.1.0" + commander "^10.0.0" debug "4.3.4" escalade "^3.1.1" esm "^3.2.25" @@ -1998,45 +1568,30 @@ knex@2.4.2: getopts "2.3.0" interpret "^2.2.0" lodash "^4.17.21" - pg-connection-string "2.5.0" + pg-connection-string "2.6.2" rechoir "^0.8.0" resolve-from "^5.0.0" 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" - integrity sha512-6yUDD8i6QRgVppB8dD73Z672lNa2pxHMsMNEZvbVQyj937wMk/kbKfhSnN9Sess/k8eRdgKeQHsTGIiCGkWrGw== +liquidjs@10.24.0: + version "10.24.0" + resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.24.0.tgz#1aa832189b48b4102049dfa7a8eea40281fa9389" + integrity sha512-TAUNAdgwaAXjjcUFuYVJm9kOVH7zc0mTKxsG9t9Lu4qdWjB2BEblyVIYpjWcmJLMGgiYqnGNJjpNMHx0gp/46A== dependencies: commander "^10.0.0" 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 +1601,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,62 +1613,55 @@ 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.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.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= +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.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= +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.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= +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.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= +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@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +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== -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== +lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.23: + version "4.17.23" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" + integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== -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== +long@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== -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@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== lru-cache@^6.0.0: version "6.0.0" @@ -2127,12 +1675,12 @@ 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.1.0, lru.min@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.3.tgz#c8c3d001dfb4cbe5b8d1f4bea207d4a320e5d76f" + integrity sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q== -make-dir@^3.0.0, make-dir@^3.1.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== @@ -2161,70 +1709,60 @@ make-fetch-happen@^9.1.0: socks-proxy-agent "^6.0.0" ssri "^8.0.0" -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= - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= +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== -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== +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== -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== +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== -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", mime-db@^1.54.0: + 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: - 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== +mime-types@^3.0.0, mime-types@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "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== + mime-db "^1.54.0" -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" +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.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" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + 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 +1806,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 +1813,15 @@ 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== +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -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 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" @@ -2303,95 +1831,99 @@ 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.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.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.17.1.tgz#781292f60df3806949c0a3161a51ad3e4e4689a1" + integrity sha512-UzIzdVwPXPoZm+FaJ4lNsRt28HtUwt68gpLH7NP1oSjd91M5Qn1XJzbIsSRMRc5CV3pvktLNshmbaFfMYqPBhQ== dependencies: - aws-ssl-profiles "^1.1.1" + aws-ssl-profiles "^1.1.2" denque "^2.1.0" generate-function "^2.3.1" - iconv-lite "^0.6.3" - long "^5.2.1" - lru-cache "^8.0.0" - named-placeholders "^1.1.3" + iconv-lite "^0.7.2" + long "^5.3.2" + lru.min "^1.1.3" + named-placeholders "^1.1.6" seq-queue "^0.0.5" - sqlstring "^2.3.2" + sql-escaper "^1.3.2" -named-placeholders@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351" - integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w== +named-placeholders@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.6.tgz#c50c6920b43f258f59c16add1e56654f5cc02bb5" + integrity sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w== dependencies: - lru-cache "^7.14.1" + lru.min "^1.1.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= - -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" +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== -negotiator@0.6.3, negotiator@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +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@^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@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== -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== +netmask@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== -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== +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: - whatwg-url "^5.0.0" + semver "^7.3.5" + +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-addon-api@^8.3.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.5.0.tgz#c91b2d7682fa457d2e1c388150f0dff9aafb8f3f" + integrity sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A== + +node-gyp-build@^4.8.4: + version "4.8.4" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" + integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== node-gyp@8.x: version "8.4.1" @@ -2409,52 +1941,28 @@ 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== dependencies: 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== +nodemon@^3.1.11: + version "3.1.11" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.11.tgz#04a54d1e794fbec9d8f6ffd8bf1ba9ea93a756ed" + integrity sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g== dependencies: - chokidar "^3.2.2" - debug "^3.2.6" + chokidar "^3.5.2" + debug "^4" ignore-by-default "^1.0.1" - minimatch "^3.0.4" - pstree.remy "^1.1.7" - semver "^5.7.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" 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,64 +1971,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" - integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== - dependencies: - are-we-there-yet "^2.0.0" - console-control-strings "^1.1.0" - gauge "^3.0.0" - set-blocking "^2.0.0" - npmlog@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" @@ -2528,85 +1983,53 @@ npmlog@^6.0.0: dependencies: are-we-there-yet "^3.0.0" console-control-strings "^1.1.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: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + gauge "^4.0.3" + set-blocking "^2.0.0" -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" - resolved "https://registry.yarnpkg.com/objection/-/objection-3.0.1.tgz#f67dc698187d10524e5d1b5d37a54e5bba49a42a" - integrity sha512-rqNnyQE+C55UHjdpTOJEKQHJGZ/BGtBBtgxdUpKG4DQXRUmqxfmgS/MhPWxB9Pw0mLSVLEltr6soD4c0Sddy0Q== +objection@3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/objection/-/objection-3.1.5.tgz#53c32f6b6cba2958bc28cf723de96c2676da8286" + integrity sha512-Hx/ipAwXSuRBbOMWFKtRsAN0yITafqXtWB4OT4Z9wED7ty1h7bOnBdhLtcNus23GwLJqcMsRWdodL2p5GwlnfQ== dependencies: - ajv "^8.6.2" + ajv "^8.17.1" + ajv-formats "^2.1.1" db-errors "^0.2.3" -on-finished@2.4.1: +on-finished@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== 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== +otplib@^13.3.0: + version "13.3.0" + resolved "https://registry.yarnpkg.com/otplib/-/otplib-13.3.0.tgz#2ead040ab29d1a829d1d7c510b059a3e4c76b2b0" + integrity sha512-VYMKyyDG8yt2q+z58sz54/EIyTh7+tyMrjeemR44iVh5+dkKtIs57irTqxjH+IkAL1uMmG1JIFhG5CxTpqdU5g== 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== + "@otplib/core" "13.3.0" + "@otplib/hotp" "13.3.0" + "@otplib/plugin-base32-scure" "13.3.0" + "@otplib/plugin-crypto-noble" "13.3.0" + "@otplib/totp" "13.3.0" + "@otplib/uri" "13.3.0" p-limit@^1.1.0: version "1.3.0" @@ -2622,17 +2045,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 +2059,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,39 +2069,49 @@ 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: +pac-proxy-agent@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz#9cfaf33ff25da36f6147a20844230ec92c06e5df" + integrity sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA== + dependencies: + "@tootallnate/quickjs-emscripten" "^0.23.0" + agent-base "^7.1.2" + debug "^4.3.4" + get-uri "^6.0.1" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.6" + pac-resolver "^7.0.1" + socks-proxy-agent "^8.0.5" + +pac-resolver@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.1.tgz#54675558ea368b64d210fd9c92a640b5f3b8abb6" + integrity sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg== + dependencies: + degenerator "^5.0.0" + netmask "^2.0.2" + +package-json-from-dist@^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" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== 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" -parseurl@~1.3.3: +parseurl@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -2700,7 +2119,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,7 +2129,7 @@ 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= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.1.0: version "3.1.1" @@ -2722,50 +2141,58 @@ path-parse@^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-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-to-regexp@^8.0.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" + integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== 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.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz#386035d4bfcf1a7045b026f8b21acf5353f14d65" + integrity sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ== -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.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475" + integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA== -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.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.11.0.tgz#5dca53ff595df33ba9db812e181b19909866d10b" + integrity sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ== 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.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.11.0.tgz#adf9a6651a30c839f565a3cc400110949c473d69" + integrity sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w== -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.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.11.0.tgz#2502908893edaa1e8c0feeba262dd7b40b317b53" + integrity sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g== -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 +2203,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.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.18.0.tgz#e9ee214206f5d9231240f1b82f22d2fa9de5cb75" + integrity sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ== 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.11.0" + pg-pool "^3.11.0" + pg-protocol "^1.11.0" + pg-types "2.2.0" + pgpass "1.0.5" optionalDependencies: - pg-cloudflare "^1.1.1" + pg-cloudflare "^1.3.0" -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 +2224,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,35 +2263,33 @@ 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" 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.1: +process@^0.11.1, process@^0.11.10: 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" @@ -2879,7 +2304,7 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -proxy-addr@~2.0.7: +proxy-addr@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== @@ -2887,71 +2312,66 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -pstree.remy@^1.1.7: +proxy-agent@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.5.0.tgz#9e49acba8e4ee234aacb539f89ed9c23d02f232d" + integrity sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A== + dependencies: + agent-base "^7.1.2" + debug "^4.3.4" + http-proxy-agent "^7.0.1" + https-proxy-agent "^7.0.6" + lru-cache "^7.14.1" + pac-proxy-agent "^7.1.0" + proxy-from-env "^1.1.0" + socks-proxy-agent "^8.0.5" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +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== +qs@^6.14.0, qs@^6.14.1: + version "6.14.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c" + integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== 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" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" + side-channel "^1.1.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: +range-parser@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== +raw-body@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" + integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.7.0" + 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 +2381,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.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 +2395,36 @@ 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== +readable-stream@^4.0.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91" + integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg== dependencies: - minimatch "^3.0.4" + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.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== +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 "^5.1.0" + +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 +2435,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 +2450,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,128 +2476,80 @@ 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== +router@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== 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== + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" -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== -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: +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.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: - 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.3, semver@^7.5.4: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== -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" - -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - 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" +send@^1.1.0, send@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.1.tgz#9eab743b874f3550f40a26867bf286ad60d3f3ed" + integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ== + dependencies: + debug "^4.4.3" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.1" + mime-types "^3.0.2" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.2" seq-queue@^0.0.5: version "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@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.1.tgz#7f186a4a4e5f5b663ad7a4294ff1bf37cf0e98a9" + integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw== dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.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: +setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== @@ -3221,26 +2566,56 @@ shebang-regex@^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== +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: - 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" + +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.1.0: + 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.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== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + signale@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/signale/-/signale-1.4.0.tgz#c4be58302fb0262ac00fc3d886a7c113759042f1" @@ -3250,6 +2625,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@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" @@ -3264,40 +2660,50 @@ socks-proxy-agent@^6.0.0: debug "^4.3.3" 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== +socks-proxy-agent@^8.0.5: + version "8.0.5" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz#b9cdb4e7e998509d7659d689ce7697ac21645bee" + integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw== + dependencies: + agent-base "^7.1.2" + debug "^4.3.4" + socks "^2.8.3" + +socks@^2.6.2, socks@^2.8.3: + 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" +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== + split2@^4.1.0: version "4.2.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= +sql-escaper@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/sql-escaper/-/sql-escaper-1.3.2.tgz#b11c68c6541fc05afdf508d52e54af7219455fad" + integrity sha512-lp+ZDVfSjHt+qAK1jXBTIXBNYnbo7gnaAGwoYTH9bE89kNkXwcu6g0WjJGRsdTKVpY1z70u3Y0IgmnBOoRybHw== -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" -sqlstring@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c" - integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg== - ssri@^8.0.0, ssri@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" @@ -3305,34 +2711,26 @@ ssri@^8.0.0, ssri@^8.0.1: dependencies: minipass "^3.1.1" -statuses@2.0.1: - version "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= +statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== -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" +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.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== +streamx@^2.15.0: + version "2.23.0" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.23.0.tgz#7d0f3d00d4a6c5de5728aecd6422b4008d66fd0b" + integrity sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg== dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" + events-universal "^1.0.0" + fast-fifo "^1.3.2" + text-decoder "^1.1.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": 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,25 +2739,25 @@ 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== +"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== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" -string_decoder@^1.1.1: +string_decoder@^1.1.1, string_decoder@^1.3.0: 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== @@ -3373,55 +2771,36 @@ 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== +"strip-ansi-cjs@npm: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.0" + ansi-regex "^5.0.1" -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== dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" + integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== + dependencies: + ansi-regex "^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" @@ -3430,19 +2809,22 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: 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== - dependencies: - has-flag "^4.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "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: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -3453,27 +2835,23 @@ 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== +tar-stream@^3.0.0: + version "3.1.7" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" + integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== 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" + b4a "^1.6.4" + fast-fifo "^1.2.0" + streamx "^2.15.0" 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 +2864,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 +2877,18 @@ 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= +text-decoder@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65" + integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA== + dependencies: + b4a "^1.6.4" 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" @@ -3526,61 +2896,41 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.1: +toidentifier@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 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" - -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== - 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== + version "3.1.1" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== -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== +tslib@^2.0.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== +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: - media-typer "0.3.0" - mime-types "~2.1.24" + safe-buffer "^5.0.1" -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== +type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== dependencies: - is-typedarray "^1.0.0" + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.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 +2946,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: +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" @@ -3653,38 +2963,20 @@ util@^0.10.3: dependencies: inherits "2.0.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= - 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== -vary@~1.1.2: +vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== 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: version "2.0.2" @@ -3693,31 +2985,21 @@ which@^2.0.1, which@^2.0.2: 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: +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== +"wrap-ansi-cjs@npm: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: - 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== + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrap-ansi@^6.2.0: version "6.2.0" @@ -3728,25 +3010,19 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + 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 +3030,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 +3064,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== +zip-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-6.0.1.tgz#e141b930ed60ccaf5d7fa9c8260e0d1748a2bbfb" + integrity sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA== dependencies: - archiver-utils "^2.1.0" - compress-commons "^4.1.0" - readable-stream "^3.6.0" + archiver-utils "^5.0.0" + compress-commons "^6.0.2" + readable-stream "^4.0.0" diff --git a/docker/Dockerfile b/docker/Dockerfile index 3345d23bdb..e294b0ca5b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,6 @@ # This file assumes that the frontend has been built using ./scripts/frontend-build FROM nginxproxymanager/testca AS testca -FROM letsencrypt/pebble AS pebbleca FROM nginxproxymanager/nginx-full:certbot-node ARG TARGETPLATFORM @@ -39,7 +38,6 @@ EXPOSE 80 81 443 COPY backend /app COPY frontend/dist /app/frontend -COPY global /app/global WORKDIR /app RUN yarn install \ @@ -55,7 +53,6 @@ COPY docker/lib/libosrc_shmem_ipc.so /usr/lib/libosrc_shmem_ipc.so # add late to limit cache-busting by modifications COPY docker/rootfs / -COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem COPY --from=testca /home/step/certs/root_ca.crt /etc/ssl/certs/NginxProxyManager.crt # patch nginx.conf for open-appsec attachment diff --git a/docker/Dockerfile.central-mgmt b/docker/Dockerfile.central-mgmt index 3e3754f6ca..763d762175 100644 --- a/docker/Dockerfile.central-mgmt +++ b/docker/Dockerfile.central-mgmt @@ -1,4 +1,4 @@ -FROM jc21/nginx-proxy-manager:2.12.3 +FROM jc21/nginx-proxy-manager:2.14.0 # add open-appsec attachment RUN mkdir -p /usr/lib/nginx diff --git a/docker/ci.env b/docker/ci.env index 7128295dd0..11e2b2f8b1 100644 --- a/docker/ci.env +++ b/docker/ci.env @@ -1,6 +1,6 @@ AUTHENTIK_SECRET_KEY=gl8woZe8L6IIX8SC0c5Ocsj0xPkX5uJo5DVZCFl+L/QGbzuplfutYuua2ODNLEiDD3aFd9H2ylJmrke0 AUTHENTIK_REDIS__HOST=authentik-redis -AUTHENTIK_POSTGRESQL__HOST=db-postgres +AUTHENTIK_POSTGRESQL__HOST=pgdb.internal AUTHENTIK_POSTGRESQL__USER=authentik AUTHENTIK_POSTGRESQL__NAME=authentik AUTHENTIK_POSTGRESQL__PASSWORD=07EKS5NLI6Tpv68tbdvrxfvj diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index dcb1f1f9b9..f0f5ec60d8 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -1,5 +1,4 @@ FROM nginxproxymanager/testca AS testca -FROM letsencrypt/pebble AS pebbleca FROM nginxproxymanager/nginx-full:certbot-node LABEL maintainer="Jamie Curnow " @@ -15,7 +14,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/* @@ -33,7 +32,6 @@ RUN rm -f /etc/nginx/conf.d/production.conf \ && chmod 644 -R /root/.cache # Certs for testing purposes -COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem COPY --from=testca /home/step/certs/root_ca.crt /etc/ssl/certs/NginxProxyManager.crt EXPOSE 80 81 443 diff --git a/docker/dev/pebble-config.json b/docker/dev/pebble-config.json deleted file mode 100644 index ea93790553..0000000000 --- a/docker/dev/pebble-config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "pebble": { - "listenAddress": "0.0.0.0:443", - "managementListenAddress": "0.0.0.0:15000", - "certificate": "test/certs/localhost/cert.pem", - "privateKey": "test/certs/localhost/key.pem", - "httpPort": 80, - "tlsPort": 443, - "ocspResponderURL": "", - "externalAccountBindingRequired": false - } -} diff --git a/docker/docker-compose.ci.mysql.yml b/docker/docker-compose.ci.mysql.yml index 388cdb382e..108a1dca3d 100644 --- a/docker/docker-compose.ci.mysql.yml +++ b/docker/docker-compose.ci.mysql.yml @@ -18,6 +18,7 @@ services: MYSQL_DATABASE: 'npm' MYSQL_USER: 'npm' MYSQL_PASSWORD: 'npmpass' + MARIADB_AUTO_UPGRADE: '1' volumes: - mysql_vol:/var/lib/mysql networks: diff --git a/docker/docker-compose.ci.postgres.yml b/docker/docker-compose.ci.postgres.yml index c4468c68bd..b8c4244668 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: "pgdb.internal" + DB_POSTGRES_PORT: "5432" + DB_POSTGRES_USER: "npm" + DB_POSTGRES_PASSWORD: "npmpass" + DB_POSTGRES_NAME: "npm" depends_on: - db-postgres - authentik @@ -19,29 +18,33 @@ 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 networks: - - fulltest + fulltest: + aliases: + - pgdb.internal 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 timeout: 3s volumes: - redis_vol:/data + networks: + - fulltest authentik: image: ghcr.io/goauthentik/server:2024.10.1 @@ -52,6 +55,8 @@ services: depends_on: - authentik-redis - db-postgres + networks: + - fulltest authentik-worker: image: ghcr.io/goauthentik/server:2024.10.1 @@ -62,16 +67,20 @@ services: depends_on: - authentik-redis - db-postgres + networks: + - fulltest 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 + networks: + - fulltest volumes: psql_vol: diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index 280a05465d..1bf6dade2d 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -3,29 +3,34 @@ # This is a base compose file, it should be extended with a # docker-compose.ci.*.yml file services: - fullstack: image: "${IMAGE}:${BRANCH_LOWER}-ci-${BUILD_NUMBER}" environment: - DEBUG: 'true' + 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' - 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_ci:/data' - - 'npm_le_ci:/etc/letsencrypt' - - './dev/letsencrypt.ini:/etc/letsencrypt.ini:ro' - - './dev/resolv.conf:/etc/resolv.conf:ro' - - '/etc/localtime:/etc/localtime:ro' + - "npm_data_ci:/data" + - "npm_le_ci:/etc/letsencrypt" + - "./dev/letsencrypt.ini:/etc/letsencrypt.ini:ro" + - "./dev/resolv.conf:/etc/resolv.conf:ro" + - "/etc/localtime:/etc/localtime:ro" healthcheck: test: ["CMD", "/usr/bin/check-health"] interval: 10s timeout: 3s expose: - - '80-81/tcp' - - '443/tcp' - - '1500-1503/tcp' + - "80/tcp" + - "81/tcp" + - "443/tcp" + - "1500/tcp" + - "1501/tcp" + - "1502/tcp" + - "1503/tcp" networks: fulltest: aliases: @@ -36,8 +41,8 @@ services: stepca: image: jc21/testca 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: fulltest: aliases: @@ -46,18 +51,18 @@ services: pdns: image: pschiffe/pdns-mysql:4.8 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 @@ -74,14 +79,14 @@ services: pdns-db: image: mariadb 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_vol:/var/lib/mysql' - - '/etc/localtime:/etc/localtime:ro' - - './dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro' + - "pdns_mysql_vol:/var/lib/mysql" + - "/etc/localtime:/etc/localtime:ro" + - "./dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro" networks: - fulltest @@ -98,22 +103,22 @@ services: context: ../ dockerfile: test/cypress/Dockerfile environment: - HTTP_PROXY: 'squid:3128' - HTTPS_PROXY: 'squid:3128' + HTTP_PROXY: "squid:3128" + HTTPS_PROXY: "squid:3128" volumes: - - 'cypress_logs:/test/results' - - './dev/resolv.conf:/etc/resolv.conf:ro' - - '/etc/localtime:/etc/localtime:ro' - command: cypress run --browser chrome --config-file=cypress/config/ci.js + - "cypress_logs:/test/results" + - "./dev/resolv.conf:/etc/resolv.conf:ro" + - "/etc/localtime:/etc/localtime:ro" + command: cypress run --browser chrome --config-file=cypress/config/ci.mjs networks: - fulltest squid: image: ubuntu/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: - fulltest diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 21c688af98..c074147fa3 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,25 +32,25 @@ 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: "pgdb.internal" + 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 - ../open-appsec-agent-deployment/localconfig:/ext/appsec - ../open-appsec-agent-deployment/logs:/ext/appsec-logs - - './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 @@ -71,32 +71,36 @@ 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 + networks: + nginx_proxy_manager: + aliases: + - pgdb.internal stepca: 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: @@ -117,7 +121,7 @@ services: - 3082:80 environment: URL: "http://npm:81/api/schema" - PORT: '80' + PORT: "80" depends_on: - fullstack @@ -125,9 +129,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: @@ -137,18 +141,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 @@ -166,14 +170,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 @@ -184,25 +188,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' - command: cypress run --browser chrome --config-file=cypress/config/ci.js + - "../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.mjs 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 @@ -244,9 +248,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/default.conf b/docker/rootfs/etc/nginx/conf.d/default.conf index e4262e1dc8..b3f61ebcb8 100644 --- a/docker/rootfs/etc/nginx/conf.d/default.conf +++ b/docker/rootfs/etc/nginx/conf.d/default.conf @@ -8,8 +8,8 @@ server { set $port "80"; server_name localhost-nginx-proxy-manager; - access_log /data/logs/fallback_access.log standard; - error_log /data/logs/fallback_error.log warn; + access_log /data/logs/fallback_http_access.log standard; + error_log /data/logs/fallback_http_error.log warn; include conf.d/include/assets.conf; include conf.d/include/block-exploits.conf; include conf.d/include/letsencrypt-acme-challenge.conf; @@ -30,7 +30,7 @@ server { set $port "443"; server_name localhost; - access_log /data/logs/fallback_access.log standard; + access_log /data/logs/fallback_http_access.log standard; error_log /dev/null crit; include conf.d/include/ssl-ciphers.conf; ssl_reject_handshake on; diff --git a/docker/rootfs/etc/nginx/conf.d/dev.conf b/docker/rootfs/etc/nginx/conf.d/dev.conf index edbdec8aca..67efc0f8a9 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/nginx/conf.d/include/force-ssl.conf b/docker/rootfs/etc/nginx/conf.d/include/force-ssl.conf index aa52f335d8..8e58c64a38 100644 --- a/docker/rootfs/etc/nginx/conf.d/include/force-ssl.conf +++ b/docker/rootfs/etc/nginx/conf.d/include/force-ssl.conf @@ -5,6 +5,28 @@ if ($scheme = "http") { if ($request_uri = /.well-known/acme-challenge/test-challenge) { set $test "${test}T"; } + +# Check if the ssl staff has been handled +set $test_ssl_handled ""; +if ($trust_forwarded_proto = "") { + set $trust_forwarded_proto "F"; +} +if ($trust_forwarded_proto = "T") { + set $test_ssl_handled "${test_ssl_handled}T"; +} +if ($http_x_forwarded_proto = "https") { + set $test_ssl_handled "${test_ssl_handled}S"; +} +if ($http_x_forwarded_scheme = "https") { + set $test_ssl_handled "${test_ssl_handled}S"; +} +if ($test_ssl_handled = "TSS") { + set $test_ssl_handled "TS"; +} +if ($test_ssl_handled = "TS") { + set $test "${test}S"; +} + if ($test = H) { return 301 https://$host$request_uri; } diff --git a/docker/rootfs/etc/nginx/conf.d/include/log.conf b/docker/rootfs/etc/nginx/conf.d/include/log-proxy.conf similarity index 89% rename from docker/rootfs/etc/nginx/conf.d/include/log.conf rename to docker/rootfs/etc/nginx/conf.d/include/log-proxy.conf index 0d74029061..448b052985 100644 --- a/docker/rootfs/etc/nginx/conf.d/include/log.conf +++ b/docker/rootfs/etc/nginx/conf.d/include/log-proxy.conf @@ -1,4 +1,4 @@ log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"'; log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"'; -access_log /data/logs/fallback_access.log proxy; +access_log /data/logs/fallback_http_access.log proxy; diff --git a/docker/rootfs/etc/nginx/conf.d/include/log-stream.conf b/docker/rootfs/etc/nginx/conf.d/include/log-stream.conf new file mode 100644 index 0000000000..db3ad31f29 --- /dev/null +++ b/docker/rootfs/etc/nginx/conf.d/include/log-stream.conf @@ -0,0 +1,3 @@ +log_format stream '[$time_local] [Client $remote_addr:$remote_port] $protocol $status $bytes_sent $bytes_received $session_time [Sent-to $upstream_addr] [Sent $upstream_bytes_sent] [Received $upstream_bytes_received] [Time $upstream_connect_time] $ssl_protocol $ssl_cipher'; + +access_log /data/logs/fallback_stream_access.log stream; diff --git a/docker/rootfs/etc/nginx/conf.d/include/proxy.conf b/docker/rootfs/etc/nginx/conf.d/include/proxy.conf index d346c4ef3e..fe2c2f2132 100644 --- a/docker/rootfs/etc/nginx/conf.d/include/proxy.conf +++ b/docker/rootfs/etc/nginx/conf.d/include/proxy.conf @@ -1,7 +1,7 @@ add_header X-Served-By $host; proxy_set_header Host $host; -proxy_set_header X-Forwarded-Scheme $scheme; -proxy_set_header X-Forwarded-Proto $scheme; +proxy_set_header X-Forwarded-Scheme $x_forwarded_scheme; +proxy_set_header X-Forwarded-Proto $x_forwarded_proto; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_pass $forward_scheme://$server:$port$request_uri; diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf index 3a31e14ce9..892cf158f5 100644 --- a/docker/rootfs/etc/nginx/nginx.conf +++ b/docker/rootfs/etc/nginx/nginx.conf @@ -47,7 +47,7 @@ http { proxy_cache_path /var/lib/nginx/cache/private levels=1:2 keys_zone=private-cache:5m max_size=1024m; # Log format and fallback log file - include /etc/nginx/conf.d/include/log.conf; + include /etc/nginx/conf.d/include/log-proxy.conf; # Dynamically generated resolvers file include /etc/nginx/conf.d/include/resolvers.conf; @@ -57,6 +57,18 @@ http { default http; } + # Handle upstream X-Forwarded-Proto and X-Forwarded-Scheme header + map $http_x_forwarded_proto $x_forwarded_proto { + "http" "http"; + "https" "https"; + default $scheme; + } + map $http_x_forwarded_scheme $x_forwarded_scheme { + "http" "http"; + "https" "https"; + default $scheme; + } + # Real IP Determination # Local subnets: @@ -85,6 +97,9 @@ http { } stream { + # Log format and fallback log file + include /etc/nginx/conf.d/include/log-stream.conf; + # Files generated by NPM include /data/nginx/stream/*.conf; 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 4f2035551d..91ed3fa805 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/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh index 378cc9caa8..fa94651897 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh @@ -8,21 +8,53 @@ log_info 'Setting ownership ...' # root chown root /tmp/nginx -# npm user and group -chown -R "$PUID:$PGID" /data -chown -R "$PUID:$PGID" /etc/letsencrypt -chown -R "$PUID:$PGID" /run/nginx -chown -R "$PUID:$PGID" /tmp/nginx -chown -R "$PUID:$PGID" /var/cache/nginx -chown -R "$PUID:$PGID" /var/lib/logrotate -chown -R "$PUID:$PGID" /var/lib/nginx -chown -R "$PUID:$PGID" /var/log/nginx - -# Don't chown entire /etc/nginx folder as this causes crashes on some systems -chown -R "$PUID:$PGID" /etc/nginx/nginx -chown -R "$PUID:$PGID" /etc/nginx/nginx.conf -chown -R "$PUID:$PGID" /etc/nginx/conf.d - -# Prevents errors when installing python certbot plugins when non-root -chown "$PUID:$PGID" /opt/certbot /opt/certbot/bin -find /opt/certbot/lib/python*/site-packages -not -user "$PUID" -execdir chown "$PUID:$PGID" {} \+ +locations=( + "/data" + "/etc/letsencrypt" + "/run/nginx" + "/tmp/nginx" + "/var/cache/nginx" + "/var/lib/logrotate" + "/var/lib/nginx" + "/var/log/nginx" + "/etc/nginx/nginx" + "/etc/nginx/nginx.conf" + "/etc/nginx/conf.d" +) + +chownit() { + local dir="$1" + local recursive="${2:-true}" + + local have + have="$(stat -c '%u:%g' "$dir")" + echo "- $dir ... " + + if [ "$have" != "$PUID:$PGID" ]; then + if [ "$recursive" = 'true' ] && [ -d "$dir" ]; then + chown -R "$PUID:$PGID" "$dir" + else + chown "$PUID:$PGID" "$dir" + fi + echo " DONE" + else + echo " SKIPPED" + fi +} + +for loc in "${locations[@]}"; do + chownit "$loc" +done + +if [ "$(is_true "${SKIP_CERTBOT_OWNERSHIP:-}")" = '1' ]; then + log_info 'Skipping ownership change of certbot directories' +else + log_info 'Changing ownership of certbot directories, this may take some time ...' + chownit "/opt/certbot" false + chownit "/opt/certbot/bin" false + + # Handle all site-packages directories efficiently + find /opt/certbot/lib -type d -name "site-packages" | while read -r SITE_PACKAGES_DIR; do + chownit "$SITE_PACKAGES_DIR" + done +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh index 0cb9f1264c..e02f41ca1e 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh @@ -5,12 +5,9 @@ set -e log_info 'Dynamic resolvers ...' -DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]') - # Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]` # thanks @tfmm -if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; -then +if [ "$(is_true "$DISABLE_IPV6")" = '1' ]; then echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf else echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh index 0c4d261cea..2ae61ae55d 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh @@ -8,14 +8,11 @@ set -e log_info 'IPv6 ...' -# Lowercase -DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]') - process_folder () { FILES=$(find "$1" -type f -name "*.conf") SED_REGEX= - if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then + if [ "$(is_true "$DISABLE_IPV6")" = '1' ]; then # IPV6 is disabled echo "Disabling IPV6 in hosts in: $1" SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g' diff --git a/docker/rootfs/usr/bin/common.sh b/docker/rootfs/usr/bin/common.sh index 13cf06acd2..46529870a3 100644 --- a/docker/rootfs/usr/bin/common.sh +++ b/docker/rootfs/usr/bin/common.sh @@ -56,3 +56,13 @@ get_group_id () { getent group "$1" | cut -d: -f3 fi } + +# param $1: value +is_true () { + VAL=$(echo "${1:-}" | tr '[:upper:]' '[:lower:]') + if [ "$VAL" == 'true' ] || [ "$VAL" == 'on' ] || [ "$VAL" == '1' ] || [ "$VAL" == 'yes' ]; then + echo '1' + else + echo '0' + fi +} diff --git a/docker/scripts/install-s6 b/docker/scripts/install-s6 index 5f3b73ec59..5fcb4e09bd 100755 --- a/docker/scripts/install-s6 +++ b/docker/scripts/install-s6 @@ -8,7 +8,7 @@ BLUE='\E[1;34m' GREEN='\E[1;32m' RESET='\E[0m' -S6_OVERLAY_VERSION=3.2.0.2 +S6_OVERLAY_VERSION=3.2.1.0 TARGETPLATFORM=${1:-linux/amd64} # Determine the correct binary file for the architecture given @@ -17,10 +17,6 @@ case $TARGETPLATFORM in S6_ARCH=aarch64 ;; - linux/arm/v7) - S6_ARCH=armhf - ;; - *) S6_ARCH=x86_64 ;; diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index cd4852378c..2e3cda7d06 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 3e3dcba252..d4523250cf 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 373fd08bbc..e4a9594e0e 100644 --- a/docs/src/advanced-config/index.md +++ b/docs/src/advanced-config/index.md @@ -161,6 +161,14 @@ The easy fix is to add a Docker environment variable to the Nginx Proxy Manager DISABLE_IPV6: 'true' ``` +## Disabling IP Ranges Fetch + +By default, NPM fetches IP ranges from CloudFront and Cloudflare during application startup. In environments with limited internet access or to speed up container startup, this fetch can be disabled: + +```yml + environment: + IP_RANGES_FETCH_ENABLED: 'false' +``` ## Custom Nginx Configurations @@ -220,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 ea375f47b3..b36685627f 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 fcf176fa94..a9de63a932 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 058c6012e9..0000000000 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 82de4ad068..0000000000 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 222c79d292..0000000000 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 6ea4d58f7e..0000000000 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 0000000000..f585c8592c 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 0000000000..0ce7fd40e9 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 0000000000..22aedb3cb7 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 0000000000..363c70831c 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 0000000000..dd9eebbac9 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 0000000000..feb4fa46e1 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 0000000000..431c69d2e2 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 0000000000..33a19bc405 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 0000000000..e870ae5240 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 0000000000..34ed0b54b3 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 0000000000..bce75b12da 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 0000000000..adc518f347 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 0000000000..4b5522c76c 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 0000000000..199708dce8 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 fce2711afa..0000000000 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 0c36de3f69..0000000000 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 0000000000..c0a48d671d 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 0000000000..86574aa988 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 0000000000..38057e8cff 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 0000000000..e1a45d96c8 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 0000000000..e90a88443c 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 0000000000..79657aa7ca 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 0000000000..cca39f1bfa 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 0000000000..84ee9104e5 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 0000000000..0ed2426653 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 0000000000..7e9498083d 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 0000000000..19de897520 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 0000000000..2afa273df5 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 0000000000..eeeb1fcbfc 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 0000000000..50b10885f1 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 99b12e1969..0000000000 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 317d02cadf..0000000000 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 e003c0b754..0000000000 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 7485c0f6d6..0000000000 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 5b61b57a8c..0000000000 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 8bd2102856..2e15fef671 100644 --- a/docs/src/screenshots/index.md +++ b/docs/src/screenshots/index.md @@ -4,17 +4,44 @@ outline: deep # Screenshots +### Light 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 +
+::: + +### Dark 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
::: diff --git a/docs/src/setup/index.md b/docs/src/setup/index.md index 5e126754f0..49b9e61828 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 @@ -22,6 +23,8 @@ services: # - '21:21' # FTP 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 @@ -150,7 +169,11 @@ Custom Postgres schema is not supported, as such `public` will be used. The docker images support the following architectures: - amd64 - arm64 -- armv7 + +::: warning +`armv7` is no longer supported in version 2.14+. This is due to Nodejs dropping support for armhf. Please +use the `2.13.7` image tag if this applies to you. +::: The docker images are a manifest of all the architecture docker builds supported, so this means you don't have to worry about doing anything special and you can follow the common instructions above. @@ -173,21 +196,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/superpowers/plans/2026-05-13-npm-2.14-port-audit.md b/docs/superpowers/plans/2026-05-13-npm-2.14-port-audit.md new file mode 100644 index 0000000000..828a188a6c --- /dev/null +++ b/docs/superpowers/plans/2026-05-13-npm-2.14-port-audit.md @@ -0,0 +1,249 @@ +# NPM 2.14 Port — Audit Report + +Audit completed 2026-05-13 against branch `align-2.14`, release tag +`v2.14.0-appsec.1`. All static checks were re-run; runtime validation is +deferred to a Docker-capable environment (see Section E). + +--- + +## A. Phase-by-phase summary + +| Phase | Tag SHA | Files touched | Key deliverable | Review status | Concerns bled into next phase | +| ----- | ------------ | ------------- | --------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------ | +| 1 | `a0360ffd` | 34 | Merge upstream v2.14 base; resolve proxy-host conflicts; defer open-appsec routes | spec + code-quality | `backend/package.json#version` left at upstream `2.0.0` — addressed in Phase 7. | +| 2 | `4c33cece` | 9 | Convert open-appsec backend modules to ESM; re-enable routes; add migration | spec + code-quality | None. | +| 3 | `4c33cece` | 0 (review) | Spec/code-quality review pass — no code mutation; same SHA as Phase 2 | review-only | None. | +| 4 | `6e7d50fc` | 32 | Frontend chrome: site-menu entry, login branding, settings tab | spec + code-quality | Some modal labels stayed English (cosmetic). | +| 5 | `9eecd725` | 25 | open-appsec configuration tab in `ProxyHostModal` + per-location overrides | spec + code-quality | None. | +| 6 | `a7d64297` | 39 | Open-appsec log section in React (4 tabs + details modal); locale compile; All-tab filter restore | spec + code-quality | Empty-state strings on log tabs not i18n keyed. | +| 7 | (HEAD) | (this phase) | Version bump, README/Jenkinsfile/deployment audit, audit doc, release tag | self-review | Runtime validation deferred. | + +Phase 3 was an inspection pass that did not produce a separate commit; the +`v2.14.0-appsec-phase3` tag points at the same SHA as Phase 2 to anchor the +review checkpoint. + +--- + +## B. Full file inventory (`git diff v2.14.0..HEAD --name-only`, 91 files) + +### Root (3) + +- `.gitignore` +- `.version` +- `README.md` + +### Backend (17) + +**Modules / lib:** +- `backend/internal/nginx-openappsec.js` +- `backend/internal/openappsec-log.js` +- `backend/internal/proxy-host.js` +- `backend/internal/setting-openappsec.js` +- `backend/lib/constants.js` +- `backend/package.json` + +**Routes:** +- `backend/routes/main.js` +- `backend/routes/openappsec-log.js` +- `backend/routes/openappsec-settings.js` + +**Migrations:** +- `backend/migrations/20260513120000_open_appsec.js` + +**Schemas:** +- `backend/schema/components/proxy-host-object.json` +- `backend/schema/paths/nginx/proxy-hosts/get.json` +- `backend/schema/paths/nginx/proxy-hosts/hostID/get.json` +- `backend/schema/paths/nginx/proxy-hosts/hostID/put.json` +- `backend/schema/paths/nginx/proxy-hosts/post.json` + +**Templates:** +- `backend/templates/local-policy-open-appsec-enabled-for-proxy-host.yaml` +- `backend/templates/openappsec.conf` + +### Frontend — application (33) + +- `frontend/package.json` +- `frontend/public/images/open-appsec-logo.svg` +- `frontend/src/Router.tsx` + +**API layer:** +- `frontend/src/api/backend/getOpenAppsecLogs.ts` +- `frontend/src/api/backend/getOpenAppsecSettings.ts` +- `frontend/src/api/backend/updateOpenAppsecSettings.ts` +- `frontend/src/api/backend/index.ts` +- `frontend/src/api/backend/models.ts` + +**Hooks:** +- `frontend/src/hooks/useOpenAppsecLogs.ts` +- `frontend/src/hooks/useOpenAppsecSettings.ts` +- `frontend/src/hooks/index.ts` + +**Components:** +- `frontend/src/components/Form/LocationsFields.tsx` +- `frontend/src/components/Form/OpenAppsecFields.tsx` +- `frontend/src/components/Form/index.ts` +- `frontend/src/components/SiteMenu.tsx` +- `frontend/src/components/Table/Formatter/SeverityFormatter.tsx` +- `frontend/src/components/Table/Formatter/index.ts` + +**Modals:** +- `frontend/src/modals/OpenAppsecLogDetailsModal.tsx` +- `frontend/src/modals/ProxyHostModal.tsx` +- `frontend/src/modals/index.ts` + +**Pages — Login:** +- `frontend/src/pages/Login/index.tsx` +- `frontend/src/pages/Login/index.module.css` + +**Pages — OpenAppsec log section:** +- `frontend/src/pages/OpenAppsecLog/index.tsx` +- `frontend/src/pages/OpenAppsecLog/Layout.tsx` +- `frontend/src/pages/OpenAppsecLog/Table.tsx` +- `frontend/src/pages/OpenAppsecLog/TableWrapper.tsx` +- `frontend/src/pages/OpenAppsecLog/columns.tsx` +- `frontend/src/pages/OpenAppsecLog/All/index.tsx` +- `frontend/src/pages/OpenAppsecLog/Detailed/index.tsx` +- `frontend/src/pages/OpenAppsecLog/Important/index.tsx` +- `frontend/src/pages/OpenAppsecLog/Notifications/index.tsx` + +**Pages — Settings:** +- `frontend/src/pages/Settings/Layout.tsx` +- `frontend/src/pages/Settings/OpenAppsec.tsx` + +### Frontend — locales (20) + +`frontend/src/locale/src/{bg,cs,de,en,es,fr,ga,hu,id,it,ja,ko,nl,pl,pt,ru,sk,tr,vi,zh}.json` + +All 21 source locales validate as JSON; compiled output (`locale/lang/*.json`) +matches the source count. + +### Docker (7) + +- `docker/Dockerfile` +- `docker/Dockerfile.central-mgmt` +- `docker/docker-compose.dev.yml` +- `docker/lib/libngx_module.so` +- `docker/lib/libosrc_compression_utils.so` +- `docker/lib/libosrc_nginx_attachment_util.so` +- `docker/lib/libosrc_shmem_ipc.so` + +### Deployment (8) + +- `deployment/docker-compose.yaml` +- `deployment/local_policy.yaml` +- `deployment/managed-from-npm-ui/docker-compose.yaml` +- `deployment/managed-from-npm-ui/local_policy.yaml` +- `deployment/managed-from-open-appsec-ui/docker-compose.yaml` +- `deployment/playground/managed-from-npm-ui/docker-compose.yaml` +- `deployment/playground/managed-from-npm-ui/local_policy.yaml` +- `deployment/playground/managed-from-open-appsec-web-ui/docker-compose.yaml` + +All compose files reference `ghcr.io/openappsec/*:latest` images — no pinned +NPM version strings to bump. + +--- + +## C. Build pipeline state + +The fork has no `Jenkinsfile`; CI is driven externally or absent. The +end-to-end local build is: + +```bash +# 1. Frontend build +cd frontend +npm install # or: yarn install +npm run build # produces frontend/dist/ + +# 2. Docker image build +cd .. +docker build -f docker/Dockerfile -t open-appsec-npm:2.14.0-appsec.1 . + +# Optional: central-management image +docker build -f docker/Dockerfile.central-mgmt \ + -t open-appsec-npm-central:2.14.0-appsec.1 . + +# 3. Bring stack up +cd deployment +docker compose up -d +``` + +Phase 7 confirmed every static-analysis step succeeds locally: + +| Check | Result | +| ------------------------------------------------------- | -------- | +| `node --check` on all backend internals / routes / migrations / `lib/constants.js` | PASS (0 failures) | +| JSON schema validation (proxy-host components + paths) | PASS (0 failures) | +| `npx tsc --noEmit` on `frontend/` | PASS (0 errors) | +| `biome lint` on all Phase-touched frontend files (24) | PASS (0 fixes applied) | +| `vite build` | PASS (`built in 17.21s`; one unrelated chunk-size warning) | +| All 20 source locales — JSON parse | PASS (0 failures) | +| Compiled locales present (`locale/lang/*.json`) | 21 / 21 | + +--- + +## D. Open issues / minor concerns + +Items deliberately left for follow-up because they did not block release: + +1. **Empty-state i18n on log tabs (Phase 6 minor).** The four log tabs + (`All`, `Important`, `Detailed`, `Notifications`) render hard-coded English + empty-state strings rather than `t(...)` keys. Low-risk cosmetic gap. +2. **Modal field labels in `OpenAppsecFields.tsx` (Phase 4–5 minor).** Some + labels and helper text in the open-appsec config tab inside + `ProxyHostModal` remain English-only. +3. **Vite chunk size warning.** The main bundle (`index-CQiS7jVD.js`) is + ~2.3 MB / 716 KB gzip. Pre-existing; not introduced by this port. Worth + addressing eventually via `manualChunks`. +4. **`docker/lib/*.so` binaries committed.** These ABI-locked attachment + libraries live in the tree (necessary for the build but heavy). README's + "compiling against other NGINX versions" section documents how to rebuild. +5. **Phase 1 reviewer flagged `backend/package.json#version` (`2.0.0`).** + Resolved in Phase 7 — bumped to `2.14.0-appsec.1` to match `.version` and + match the convention of the prior fork release (`7f11a051` used + `"2.12.3"`). +6. **`frontend/.yarn/` and `frontend/.yarnrc.yml` are untracked** in the + worktree (left over from local tooling). Confirm `.gitignore` covers them + before any future commit. + +--- + +## E. Runtime smoke checklist for the user + +These must run on a Docker-capable host. They are the **first** validations +to perform after pulling `align-2.14`: + +- [ ] `docker build -f docker/Dockerfile -t open-appsec-npm:2.14.0-appsec.1 .` + completes successfully. +- [ ] `docker build -f docker/Dockerfile.central-mgmt + -t open-appsec-npm-central:2.14.0-appsec.1 .` completes successfully. +- [ ] `cd deployment && docker compose up -d` brings up `npm-attachment` and + `appsec-agent` healthy; admin UI reachable on `http://localhost:81`. +- [ ] Backend migrations run cleanly against a fresh SQLite DB — verify + `backend/migrations/20260513120000_open_appsec.js` applies without + error (check the migration entry appears in the `migrations` table). +- [ ] Cypress e2e suite (`./scripts/ci/fulltest-cypress`) passes against the + built image. +- [ ] **WAF activation curl test:** create a proxy host via the UI, enable + open-appsec on it, send a request matching a known attack signature + (e.g. `curl -i 'http:///?id=1%20OR%201%3D1--'`), confirm the + agent returns a block response. +- [ ] **Open-appsec log streaming:** confirm log entries appear in the four + tabs (All / Important / Detailed / Notifications) and the details modal + opens correctly for a blocked request. +- [ ] Smoke-test the open-appsec config tab inside `ProxyHostModal` — toggle + on/off, change settings, save, reload, confirm persistence. +- [ ] Smoke-test per-location overrides (`LocationsFields.tsx`) — set a + different policy per custom location, confirm nginx config writes + reflect the override. + +Until every box above is ticked, the build is code-complete but not +runtime-validated. + +--- + +## Conclusion + +All seven phases are tagged. Static analysis is clean. The release artifact is +`v2.14.0-appsec.1` on branch `align-2.14`. Merge to `master` is held until the +user authorises it. diff --git a/docs/superpowers/plans/2026-05-13-npm-2.14-port.md b/docs/superpowers/plans/2026-05-13-npm-2.14-port.md new file mode 100644 index 0000000000..034af5607f --- /dev/null +++ b/docs/superpowers/plans/2026-05-13-npm-2.14-port.md @@ -0,0 +1,56 @@ +# NPM 2.14 Port — Plan & Status + +This file accompanies the `align-2.14` worktree. The plan was executed in seven +phases; this record summarises status and is referenced by the audit doc +(`2026-05-13-npm-2.14-port-audit.md`). + +## Phase tags (chronological) + +| Phase | Tag | Short SHA | +| ----- | ---------------------------- | ---------- | +| 1 | `v2.14.0-appsec-phase1` | `a0360ffd` | +| 2 | `v2.14.0-appsec-phase2` | `4c33cece` | +| 3 | `v2.14.0-appsec-phase3` | `4c33cece` | +| 4 | `v2.14.0-appsec-phase4` | `6e7d50fc` | +| 5 | `v2.14.0-appsec-phase5` | `9eecd725` | +| 6 | `v2.14.0-appsec-phase6` | `a7d64297` | +| 7 | `v2.14.0-appsec-phase7` | (Phase 7 commit) | +| Final | `v2.14.0-appsec.1` | (same) | + +Phases 1 and 3 share commit `4c33cece` in the chain because Phase 3 was a +spec/code-review-only pass (no code mutation); the same SHA carries both labels. + +## Outcomes summary + +- Phases 1–6 ported the open-appsec integration onto the v2.14 base (backend + ESM modules, schemas, migrations, frontend chrome, settings page, proxy-host + modal config tab, location overrides, log section). +- Phase 7 (this phase) bumped versions, audited static surfaces, and produced + the audit doc. Runtime validation is deferred. + +## Phase 7 — Deferred to runtime validation + +The following items could not be exercised on the current Windows shell because +no reachable Docker daemon is available. They are the **first** validations the +user should run in a Docker-capable environment: + +- [ ] `docker build` of `docker/Dockerfile` (main NPM image) succeeds and + produces a runnable image. +- [ ] `docker build` of `docker/Dockerfile.central-mgmt` succeeds and produces a + runnable image. +- [ ] `cd deployment && docker compose up -d` brings the stack online with + `npm-attachment` + `appsec-agent` healthy. +- [ ] Backend migrations run cleanly against a fresh SQLite DB + (`backend/migrations/20260513120000_open_appsec.js` applies without + error). +- [ ] Cypress e2e suite (`./scripts/ci/fulltest-cypress`) passes against the + built image. +- [ ] WAF activation curl test (per plan Phase 7.4): enable open-appsec on a + proxy host, send a request matching a known attack pattern, verify the + agent blocks it. +- [ ] Open-appsec log streaming: confirm entries appear in the four log tabs + (All / Important / Detailed / Notifications) after a blocked request. + +Until those boxes are checked, the build is considered code-complete but not +runtime-validated. The audit document records the same list under "Runtime +smoke checklist for the user". diff --git a/docs/yarn.lock b/docs/yarn.lock index 2adc44fd47..823a55c633 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -2,202 +2,206 @@ # yarn lockfile v1 -"@algolia/autocomplete-core@1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz#1d56482a768c33aae0868c8533049e02e8961be7" - integrity sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw== - dependencies: - "@algolia/autocomplete-plugin-algolia-insights" "1.9.3" - "@algolia/autocomplete-shared" "1.9.3" - -"@algolia/autocomplete-plugin-algolia-insights@1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz#9b7f8641052c8ead6d66c1623d444cbe19dde587" - integrity sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg== - dependencies: - "@algolia/autocomplete-shared" "1.9.3" - -"@algolia/autocomplete-preset-algolia@1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz#64cca4a4304cfcad2cf730e83067e0c1b2f485da" - integrity sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA== - dependencies: - "@algolia/autocomplete-shared" "1.9.3" - -"@algolia/autocomplete-shared@1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz#2e22e830d36f0a9cf2c0ccd3c7f6d59435b77dfa" - integrity sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ== - -"@algolia/cache-browser-local-storage@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz#0cc26b96085e1115dac5fcb9d826651ba57faabc" - integrity sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg== - dependencies: - "@algolia/cache-common" "4.23.3" - -"@algolia/cache-common@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.23.3.tgz#3bec79092d512a96c9bfbdeec7cff4ad36367166" - integrity sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A== - -"@algolia/cache-in-memory@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz#3945f87cd21ffa2bec23890c85305b6b11192423" - integrity sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg== - dependencies: - "@algolia/cache-common" "4.23.3" - -"@algolia/client-account@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.23.3.tgz#8751bbf636e6741c95e7c778488dee3ee430ac6f" - integrity sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA== - dependencies: - "@algolia/client-common" "4.23.3" - "@algolia/client-search" "4.23.3" - "@algolia/transporter" "4.23.3" - -"@algolia/client-analytics@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.23.3.tgz#f88710885278fe6fb6964384af59004a5a6f161d" - integrity sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA== - dependencies: - "@algolia/client-common" "4.23.3" - "@algolia/client-search" "4.23.3" - "@algolia/requester-common" "4.23.3" - "@algolia/transporter" "4.23.3" - -"@algolia/client-common@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.23.3.tgz#891116aa0db75055a7ecc107649f7f0965774704" - integrity sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw== - dependencies: - "@algolia/requester-common" "4.23.3" - "@algolia/transporter" "4.23.3" - -"@algolia/client-personalization@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.23.3.tgz#35fa8e5699b0295fbc400a8eb211dc711e5909db" - integrity sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g== - dependencies: - "@algolia/client-common" "4.23.3" - "@algolia/requester-common" "4.23.3" - "@algolia/transporter" "4.23.3" - -"@algolia/client-search@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.23.3.tgz#a3486e6af13a231ec4ab43a915a1f318787b937f" - integrity sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw== - dependencies: - "@algolia/client-common" "4.23.3" - "@algolia/requester-common" "4.23.3" - "@algolia/transporter" "4.23.3" - -"@algolia/logger-common@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.23.3.tgz#35c6d833cbf41e853a4f36ba37c6e5864920bfe9" - integrity sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g== - -"@algolia/logger-console@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.23.3.tgz#30f916781826c4db5f51fcd9a8a264a06e136985" - integrity sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A== - dependencies: - "@algolia/logger-common" "4.23.3" - -"@algolia/recommend@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-4.23.3.tgz#53d4f194d22d9c72dc05f3f7514c5878f87c5890" - integrity sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w== - dependencies: - "@algolia/cache-browser-local-storage" "4.23.3" - "@algolia/cache-common" "4.23.3" - "@algolia/cache-in-memory" "4.23.3" - "@algolia/client-common" "4.23.3" - "@algolia/client-search" "4.23.3" - "@algolia/logger-common" "4.23.3" - "@algolia/logger-console" "4.23.3" - "@algolia/requester-browser-xhr" "4.23.3" - "@algolia/requester-common" "4.23.3" - "@algolia/requester-node-http" "4.23.3" - "@algolia/transporter" "4.23.3" - -"@algolia/requester-browser-xhr@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz#9e47e76f60d540acc8b27b4ebc7a80d1b41938b9" - integrity sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw== - dependencies: - "@algolia/requester-common" "4.23.3" - -"@algolia/requester-common@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.23.3.tgz#7dbae896e41adfaaf1d1fa5f317f83a99afb04b3" - integrity sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw== - -"@algolia/requester-node-http@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz#c9f94a5cb96a15f48cea338ab6ef16bbd0ff989f" - integrity sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA== - dependencies: - "@algolia/requester-common" "4.23.3" - -"@algolia/transporter@4.23.3": - version "4.23.3" - resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.23.3.tgz#545b045b67db3850ddf0bbecbc6c84ff1f3398b7" - integrity sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ== - dependencies: - "@algolia/cache-common" "4.23.3" - "@algolia/logger-common" "4.23.3" - "@algolia/requester-common" "4.23.3" - -"@babel/helper-string-parser@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54" - integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== - -"@babel/helper-validator-identifier@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" - integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== - -"@babel/parser@^7.25.3": - version "7.25.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2" - integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ== - dependencies: - "@babel/types" "^7.25.8" - -"@babel/types@^7.25.8": - version "7.25.8" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1" - integrity sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg== - dependencies: - "@babel/helper-string-parser" "^7.25.7" - "@babel/helper-validator-identifier" "^7.25.7" - to-fast-properties "^2.0.0" - -"@docsearch/css@3.6.2", "@docsearch/css@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.6.2.tgz#ccd9c83dbfeaf34efe4e3547ee596714ae7e5891" - integrity sha512-vKNZepO2j7MrYBTZIGXvlUOIR+v9KRf70FApRgovWrj3GTs1EITz/Xb0AOlm1xsQBp16clVZj1SY/qaOJbQtZw== - -"@docsearch/js@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@docsearch/js/-/js-3.6.2.tgz#000d7d255e8387e7c5b82c7b87d3060398e1605d" - integrity sha512-pS4YZF+VzUogYrkblCucQ0Oy2m8Wggk8Kk7lECmZM60hTbaydSIhJTTiCrmoxtBqV8wxORnOqcqqOfbmkkQEcA== - dependencies: - "@docsearch/react" "3.6.2" +"@algolia/abtesting@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@algolia/abtesting/-/abtesting-1.12.2.tgz#1cba5e3c654d02c6d435822a0a0070a5c435daa6" + integrity sha512-oWknd6wpfNrmRcH0vzed3UPX0i17o4kYLM5OMITyMVM2xLgaRbIafoxL0e8mcrNNb0iORCJA0evnNDKRYth5WQ== + dependencies: + "@algolia/client-common" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +"@algolia/autocomplete-core@1.17.7": + version "1.17.7" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz#2c410baa94a47c5c5f56ed712bb4a00ebe24088b" + integrity sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q== + dependencies: + "@algolia/autocomplete-plugin-algolia-insights" "1.17.7" + "@algolia/autocomplete-shared" "1.17.7" + +"@algolia/autocomplete-plugin-algolia-insights@1.17.7": + version "1.17.7" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz#7d2b105f84e7dd8f0370aa4c4ab3b704e6760d82" + integrity sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A== + dependencies: + "@algolia/autocomplete-shared" "1.17.7" + +"@algolia/autocomplete-preset-algolia@1.17.7": + version "1.17.7" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz#c9badc0d73d62db5bf565d839d94ec0034680ae9" + integrity sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA== + dependencies: + "@algolia/autocomplete-shared" "1.17.7" + +"@algolia/autocomplete-shared@1.17.7": + version "1.17.7" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz#105e84ad9d1a31d3fb86ba20dc890eefe1a313a0" + integrity sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg== + +"@algolia/client-abtesting@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.46.2.tgz#264a72f0e9d2fe0d0dc5c3d2d16bbb9cfe2ce9e8" + integrity sha512-oRSUHbylGIuxrlzdPA8FPJuwrLLRavOhAmFGgdAvMcX47XsyM+IOGa9tc7/K5SPvBqn4nhppOCEz7BrzOPWc4A== + dependencies: + "@algolia/client-common" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +"@algolia/client-analytics@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.46.2.tgz#3f00a237508aa0c46c9c02dea9c855e0a78e241f" + integrity sha512-EPBN2Oruw0maWOF4OgGPfioTvd+gmiNwx0HmD9IgmlS+l75DatcBkKOPNJN+0z3wBQWUO5oq602ATxIfmTQ8bA== + dependencies: + "@algolia/client-common" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +"@algolia/client-common@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.46.2.tgz#7f282fd8f721b0d96958445df2170f4c7dce6aac" + integrity sha512-Hj8gswSJNKZ0oyd0wWissqyasm+wTz1oIsv5ZmLarzOZAp3vFEda8bpDQ8PUhO+DfkbiLyVnAxsPe4cGzWtqkg== + +"@algolia/client-insights@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.46.2.tgz#194b7b529ee8a4ffd5d70037745082996c3b9aa0" + integrity sha512-6dBZko2jt8FmQcHCbmNLB0kCV079Mx/DJcySTL3wirgDBUH7xhY1pOuUTLMiGkqM5D8moVZTvTdRKZUJRkrwBA== + dependencies: + "@algolia/client-common" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +"@algolia/client-personalization@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.46.2.tgz#d604da7f0a3df1b3e2a9fe338d368e48fb781f8e" + integrity sha512-1waE2Uqh/PHNeDXGn/PM/WrmYOBiUGSVxAWqiJIj73jqPqvfzZgzdakHscIVaDl6Cp+j5dwjsZ5LCgaUr6DtmA== + dependencies: + "@algolia/client-common" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +"@algolia/client-query-suggestions@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.46.2.tgz#f13bc5897bfbdc19509d430a26e9bbe2402e00c9" + integrity sha512-EgOzTZkyDcNL6DV0V/24+oBJ+hKo0wNgyrOX/mePBM9bc9huHxIY2352sXmoZ648JXXY2x//V1kropF/Spx83w== + dependencies: + "@algolia/client-common" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +"@algolia/client-search@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.46.2.tgz#771367916aaa3fb7a19d5944f8375504b0568ba6" + integrity sha512-ZsOJqu4HOG5BlvIFnMU0YKjQ9ZI6r3C31dg2jk5kMWPSdhJpYL9xa5hEe7aieE+707dXeMI4ej3diy6mXdZpgA== + dependencies: + "@algolia/client-common" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +"@algolia/ingestion@1.46.2": + version "1.46.2" + resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.46.2.tgz#2a5d8a592d9f864dfb438722506382af56f8554f" + integrity sha512-1Uw2OslTWiOFDtt83y0bGiErJYy5MizadV0nHnOoHFWMoDqWW0kQoMFI65pXqRSkVvit5zjXSLik2xMiyQJDWQ== + dependencies: + "@algolia/client-common" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +"@algolia/monitoring@1.46.2": + version "1.46.2" + resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.46.2.tgz#bd199368a49cb799cf12cfe76c49de6dd3021148" + integrity sha512-xk9f+DPtNcddWN6E7n1hyNNsATBCHIqAvVGG2EAGHJc4AFYL18uM/kMTiOKXE/LKDPyy1JhIerrh9oYb7RBrgw== + dependencies: + "@algolia/client-common" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +"@algolia/recommend@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.46.2.tgz#e74bade1254046ed9be8ccd37f2a116ab9799508" + integrity sha512-NApbTPj9LxGzNw4dYnZmj2BoXiAc8NmbbH6qBNzQgXklGklt/xldTvu+FACN6ltFsTzoNU6j2mWNlHQTKGC5+Q== + dependencies: + "@algolia/client-common" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +"@algolia/requester-browser-xhr@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.46.2.tgz#7662480143405e815e1eed99136b4b2acd838ee7" + integrity sha512-ekotpCwpSp033DIIrsTpYlGUCF6momkgupRV/FA3m62SreTSZUKjgK6VTNyG7TtYfq9YFm/pnh65bATP/ZWJEg== + dependencies: + "@algolia/client-common" "5.46.2" + +"@algolia/requester-fetch@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.46.2.tgz#dee07f0131b75f30d083bafd6fb878afe7402eb9" + integrity sha512-gKE+ZFi/6y7saTr34wS0SqYFDcjHW4Wminv8PDZEi0/mE99+hSrbKgJWxo2ztb5eqGirQTgIh1AMVacGGWM1iw== + dependencies: + "@algolia/client-common" "5.46.2" + +"@algolia/requester-node-http@5.46.2": + version "5.46.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.46.2.tgz#7869d67cb2926bbdbfbfed2b4757e547c2e227eb" + integrity sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg== + dependencies: + "@algolia/client-common" "5.46.2" + +"@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.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/parser@^7.28.5": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" + integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== + dependencies: + "@babel/types" "^7.28.6" + +"@babel/types@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" + integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@docsearch/css@3.8.2": + version "3.8.2" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.8.2.tgz#7973ceb6892c30f154ba254cd05c562257a44977" + integrity sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ== + +"@docsearch/js@3.8.2": + version "3.8.2" + resolved "https://registry.yarnpkg.com/@docsearch/js/-/js-3.8.2.tgz#bdcfc9837700eb38453b88e211ab5cc5a3813cc6" + integrity sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ== + dependencies: + "@docsearch/react" "3.8.2" preact "^10.0.0" -"@docsearch/react@3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.6.2.tgz#32b16dd7d5614f0d39e6bc018549816b68d171b8" - integrity sha512-rtZce46OOkVflCQH71IdbXSFK+S8iJZlUF56XBW5rIgx/eG5qoomC7Ag3anZson1bBac/JFQn7XOBfved/IMRA== +"@docsearch/react@3.8.2": + version "3.8.2" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.8.2.tgz#7b11d39b61c976c0aa9fbde66e6b73b30f3acd42" + integrity sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg== dependencies: - "@algolia/autocomplete-core" "1.9.3" - "@algolia/autocomplete-preset-algolia" "1.9.3" - "@docsearch/css" "3.6.2" - algoliasearch "^4.19.1" + "@algolia/autocomplete-core" "1.17.7" + "@algolia/autocomplete-preset-algolia" "1.17.7" + "@docsearch/css" "3.8.2" + algoliasearch "^5.14.2" "@esbuild/aix-ppc64@0.21.5": version "0.21.5" @@ -314,10 +318,22 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== -"@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== +"@iconify-json/simple-icons@^1.2.21": + version "1.2.66" + resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.2.66.tgz#d0576ca65f69616b49491129e17132316cf3f309" + integrity sha512-D1OnnXwiQXFkVMw5M+Bt8mPsXeMkQyGmMdrmN7lsQlKMUkfLOp6JWhnUJ92po51WXT046aF/zzqSmkKqg08p4Q== + dependencies: + "@iconify/types" "*" + +"@iconify/types@*": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57" + integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg== + +"@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== "@rollup/rollup-android-arm-eabi@4.24.0": version "4.24.0" @@ -399,54 +415,69 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz#0574d7e87b44ee8511d08cc7f914bcb802b70818" integrity sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw== -"@shikijs/core@1.22.0", "@shikijs/core@^1.22.0": - version "1.22.0" - resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.22.0.tgz#74e5d4485e5f7afa85109e322b42e400686f92bb" - integrity sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q== +"@shikijs/core@2.5.0", "@shikijs/core@^2.1.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-2.5.0.tgz#e14d33961dfa3141393d4a76fc8923d0d1c4b62f" + integrity sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg== dependencies: - "@shikijs/engine-javascript" "1.22.0" - "@shikijs/engine-oniguruma" "1.22.0" - "@shikijs/types" "1.22.0" - "@shikijs/vscode-textmate" "^9.3.0" + "@shikijs/engine-javascript" "2.5.0" + "@shikijs/engine-oniguruma" "2.5.0" + "@shikijs/types" "2.5.0" + "@shikijs/vscode-textmate" "^10.0.2" "@types/hast" "^3.0.4" - hast-util-to-html "^9.0.3" + hast-util-to-html "^9.0.4" -"@shikijs/engine-javascript@1.22.0": - version "1.22.0" - resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-1.22.0.tgz#2e5db29f0421755492f5279f8224ef7a7f907a29" - integrity sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw== +"@shikijs/engine-javascript@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz#e045c6ecfbda6c99137547b0a482e0b87f1053fc" + integrity sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w== dependencies: - "@shikijs/types" "1.22.0" - "@shikijs/vscode-textmate" "^9.3.0" - oniguruma-to-js "0.4.3" + "@shikijs/types" "2.5.0" + "@shikijs/vscode-textmate" "^10.0.2" + oniguruma-to-es "^3.1.0" -"@shikijs/engine-oniguruma@1.22.0": - version "1.22.0" - resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.0.tgz#74c661fac4cd1f08f2c09b5d6e2fd2a6720d0401" - integrity sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw== +"@shikijs/engine-oniguruma@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz#230de5693cc1da6c9d59c7ad83593c2027274817" + integrity sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw== dependencies: - "@shikijs/types" "1.22.0" - "@shikijs/vscode-textmate" "^9.3.0" + "@shikijs/types" "2.5.0" + "@shikijs/vscode-textmate" "^10.0.2" -"@shikijs/transformers@^1.22.0": - version "1.22.0" - resolved "https://registry.yarnpkg.com/@shikijs/transformers/-/transformers-1.22.0.tgz#f36fa4d769e36db9a91e09877cf48b3a04d26aba" - integrity sha512-k7iMOYuGQA62KwAuJOQBgH2IQb5vP8uiB3lMvAMGUgAMMurePOx3Z7oNqJdcpxqZP6I9cc7nc4DNqSKduCxmdg== +"@shikijs/langs@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-2.5.0.tgz#97ab50c495922cc1ca06e192985b28dc73de5d50" + integrity sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w== dependencies: - shiki "1.22.0" + "@shikijs/types" "2.5.0" -"@shikijs/types@1.22.0", "@shikijs/types@^1.22.0": - version "1.22.0" - resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.22.0.tgz#d2a572381395c9308b472c8199b8e0289753b9ad" - integrity sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww== +"@shikijs/themes@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-2.5.0.tgz#8c6aecf73f5455681c8bec15797cf678162896cb" + integrity sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw== dependencies: - "@shikijs/vscode-textmate" "^9.3.0" + "@shikijs/types" "2.5.0" + +"@shikijs/transformers@^2.1.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@shikijs/transformers/-/transformers-2.5.0.tgz#190c84786ff06c417580ab79177338a947168c55" + integrity sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg== + dependencies: + "@shikijs/core" "2.5.0" + "@shikijs/types" "2.5.0" + +"@shikijs/types@2.5.0", "@shikijs/types@^2.1.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-2.5.0.tgz#e949c7384802703a48b9d6425dd41673c164df69" + integrity sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw== + dependencies: + "@shikijs/vscode-textmate" "^10.0.2" "@types/hast" "^3.0.4" -"@shikijs/vscode-textmate@^9.3.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz#b2f1776e488c1d6c2b6cd129bab62f71bbc9c7ab" - integrity sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA== +"@shikijs/vscode-textmate@^10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224" + integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg== "@types/estree@1.0.6": version "1.0.6" @@ -490,184 +521,183 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== -"@types/web-bluetooth@^0.0.20": - version "0.0.20" - resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597" - integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow== +"@types/web-bluetooth@^0.0.21": + version "0.0.21" + resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz#525433c784aed9b457aaa0ee3d92aeb71f346b63" + integrity sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA== "@ungap/structured-clone@^1.0.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vitejs/plugin-vue@^5.1.4": - version "5.1.4" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz#72b8b705cfce36b00b59af196195146e356500c4" - integrity sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A== +"@vitejs/plugin-vue@^5.2.1": + version "5.2.4" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz#9e8a512eb174bfc2a333ba959bbf9de428d89ad8" + integrity sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA== -"@vue/compiler-core@3.5.11": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.11.tgz#3dcd0c1bab10732f44ab1790735afb03a4b69edc" - integrity sha512-PwAdxs7/9Hc3ieBO12tXzmTD+Ln4qhT/56S+8DvrrZ4kLDn4Z/AMUr8tXJD0axiJBS0RKIoNaR0yMuQB9v9Udg== +"@vue/compiler-core@3.5.26": + version "3.5.26" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.26.tgz#1a91ea90980528bedff7b1c292690bfb30612485" + integrity sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w== dependencies: - "@babel/parser" "^7.25.3" - "@vue/shared" "3.5.11" - entities "^4.5.0" + "@babel/parser" "^7.28.5" + "@vue/shared" "3.5.26" + entities "^7.0.0" estree-walker "^2.0.2" - source-map-js "^1.2.0" - -"@vue/compiler-dom@3.5.11": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.11.tgz#950f8fc610e26326fed008b8d102cc8ee78a6ce5" - integrity sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew== - dependencies: - "@vue/compiler-core" "3.5.11" - "@vue/shared" "3.5.11" - -"@vue/compiler-sfc@3.5.11": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.11.tgz#68ba7bc6fed4fec6892aed118cb3ee8e4b180d06" - integrity sha512-gsbBtT4N9ANXXepprle+X9YLg2htQk1sqH/qGJ/EApl+dgpUBdTv3yP7YlR535uHZY3n6XaR0/bKo0BgwwDniw== - dependencies: - "@babel/parser" "^7.25.3" - "@vue/compiler-core" "3.5.11" - "@vue/compiler-dom" "3.5.11" - "@vue/compiler-ssr" "3.5.11" - "@vue/shared" "3.5.11" + source-map-js "^1.2.1" + +"@vue/compiler-dom@3.5.26": + version "3.5.26" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz#66c36b6ed8bdf43236d7188ea332bc9d078eb286" + integrity sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A== + dependencies: + "@vue/compiler-core" "3.5.26" + "@vue/shared" "3.5.26" + +"@vue/compiler-sfc@3.5.26": + version "3.5.26" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz#fb1c6c4bf9a9e22bb169e039e19437cb6995917a" + integrity sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA== + dependencies: + "@babel/parser" "^7.28.5" + "@vue/compiler-core" "3.5.26" + "@vue/compiler-dom" "3.5.26" + "@vue/compiler-ssr" "3.5.26" + "@vue/shared" "3.5.26" estree-walker "^2.0.2" - magic-string "^0.30.11" - postcss "^8.4.47" - source-map-js "^1.2.0" + magic-string "^0.30.21" + postcss "^8.5.6" + source-map-js "^1.2.1" -"@vue/compiler-ssr@3.5.11": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.11.tgz#02d9891c7a649bbf06490ecd8d24dd1575d53e60" - integrity sha512-P4+GPjOuC2aFTk1Z4WANvEhyOykcvEd5bIj2KVNGKGfM745LaXGr++5njpdBTzVz5pZifdlR1kpYSJJpIlSePA== +"@vue/compiler-ssr@3.5.26": + version "3.5.26" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz#f6e94bccbb5339180779036ddfb614f998a197ea" + integrity sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw== dependencies: - "@vue/compiler-dom" "3.5.11" - "@vue/shared" "3.5.11" + "@vue/compiler-dom" "3.5.26" + "@vue/shared" "3.5.26" -"@vue/devtools-api@^7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-7.4.6.tgz#5e9249d6de3cee58624e511fdc727837b1f2d273" - integrity sha512-XipBV5k0/IfTr0sNBDTg7OBUCp51cYMMXyPxLXJZ4K/wmUeMqt8cVdr2ZZGOFq+si/jTyCYnNxeKoyev5DOUUA== +"@vue/devtools-api@^7.7.0": + version "7.7.9" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz#999dbea50da6b00cf59a1336f11fdc2b43d9e063" + integrity sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g== dependencies: - "@vue/devtools-kit" "^7.4.6" + "@vue/devtools-kit" "^7.7.9" -"@vue/devtools-kit@^7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.4.6.tgz#80aa30db65bf5b2b0eda4e818749d3c37d80f709" - integrity sha512-NbYBwPWgEic1AOd9bWExz9weBzFdjiIfov0yRn4DrRfR+EQJCI9dn4I0XS7IxYGdkmUJi8mFW42LLk18WsGqew== +"@vue/devtools-kit@^7.7.9": + version "7.7.9" + resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz#bc218a815616e8987df7ab3e10fc1fb3b8706c58" + integrity sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA== dependencies: - "@vue/devtools-shared" "^7.4.6" - birpc "^0.2.17" + "@vue/devtools-shared" "^7.7.9" + birpc "^2.3.0" hookable "^5.5.3" mitt "^3.0.1" perfect-debounce "^1.0.0" speakingurl "^14.0.1" - superjson "^2.2.1" + superjson "^2.2.2" -"@vue/devtools-shared@^7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.4.6.tgz#492c2301caacc83a32542dd95dfcae3980621417" - integrity sha512-rPeSBzElnHYMB05Cc056BQiJpgocQjY8XVulgni+O9a9Gr9tNXgPteSzFFD+fT/iWMxNuUgGKs9CuW5DZewfIg== +"@vue/devtools-shared@^7.7.9": + version "7.7.9" + resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz#fa4c096b744927081a7dda5fcf05f34b1ae6ca14" + integrity sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA== dependencies: rfdc "^1.4.1" -"@vue/reactivity@3.5.11": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.11.tgz#d27df4fba10c2de1c7234701f18247a775b7a391" - integrity sha512-Nqo5VZEn8MJWlCce8XoyVqHZbd5P2NH+yuAaFzuNSR96I+y1cnuUiq7xfSG+kyvLSiWmaHTKP1r3OZY4mMD50w== - dependencies: - "@vue/shared" "3.5.11" - -"@vue/runtime-core@3.5.11": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.11.tgz#7beccd013efe5d33981ffd6b6e05d0a5b9058316" - integrity sha512-7PsxFGqwfDhfhh0OcDWBG1DaIQIVOLgkwA5q6MtkPiDFjp5gohVnJEahSktwSFLq7R5PtxDKy6WKURVN1UDbzA== - dependencies: - "@vue/reactivity" "3.5.11" - "@vue/shared" "3.5.11" - -"@vue/runtime-dom@3.5.11": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.11.tgz#14a3181ab7057de41b345b4b3d37b744b3ff8ff5" - integrity sha512-GNghjecT6IrGf0UhuYmpgaOlN7kxzQBhxWEn08c/SQDxv1yy4IXI1bn81JgEpQ4IXjRxWtPyI8x0/7TF5rPfYQ== - dependencies: - "@vue/reactivity" "3.5.11" - "@vue/runtime-core" "3.5.11" - "@vue/shared" "3.5.11" - csstype "^3.1.3" - -"@vue/server-renderer@3.5.11": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.11.tgz#74f558371dfc39f3b0f26f95d089a1a4d1676027" - integrity sha512-cVOwYBxR7Wb1B1FoxYvtjJD8X/9E5nlH4VSkJy2uMA1MzYNdzAAB//l8nrmN9py/4aP+3NjWukf9PZ3TeWULaA== - dependencies: - "@vue/compiler-ssr" "3.5.11" - "@vue/shared" "3.5.11" - -"@vue/shared@3.5.11", "@vue/shared@^3.5.11": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.11.tgz#464b840afc89be9373addff9eeb9dfc98bf3fe2e" - integrity sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ== - -"@vueuse/core@11.1.0", "@vueuse/core@^11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-11.1.0.tgz#a104f33c899a15f3b28d3eb7b20738501a3a5035" - integrity sha512-P6dk79QYA6sKQnghrUz/1tHi0n9mrb/iO1WTMk/ElLmTyNqgDeSZ3wcDf6fRBGzRJbeG1dxzEOvLENMjr+E3fg== - dependencies: - "@types/web-bluetooth" "^0.0.20" - "@vueuse/metadata" "11.1.0" - "@vueuse/shared" "11.1.0" - vue-demi ">=0.14.10" - -"@vueuse/integrations@^11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-11.1.0.tgz#1e2c1d43b2d389fc4b4d0a7ee08091665698b9ad" - integrity sha512-O2ZgrAGPy0qAjpoI2YR3egNgyEqwG85fxfwmA9BshRIGjV4G6yu6CfOPpMHAOoCD+UfsIl7Vb1bXJ6ifrHYDDA== - dependencies: - "@vueuse/core" "11.1.0" - "@vueuse/shared" "11.1.0" - vue-demi ">=0.14.10" - -"@vueuse/metadata@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-11.1.0.tgz#ad367d2a51d985129724425923b3cf95f0faf27b" - integrity sha512-l9Q502TBTaPYGanl1G+hPgd3QX5s4CGnpXriVBR5fEZ/goI6fvDaVmIl3Td8oKFurOxTmbXvBPSsgrd6eu6HYg== - -"@vueuse/shared@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-11.1.0.tgz#3bfc3aa555c2a456c21945ec7f127d71938d12e8" - integrity sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w== - dependencies: - vue-demi ">=0.14.10" - -algoliasearch@^4.19.1: - version "4.23.3" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.23.3.tgz#e09011d0a3b0651444916a3e6bbcba064ec44b60" - integrity sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg== - dependencies: - "@algolia/cache-browser-local-storage" "4.23.3" - "@algolia/cache-common" "4.23.3" - "@algolia/cache-in-memory" "4.23.3" - "@algolia/client-account" "4.23.3" - "@algolia/client-analytics" "4.23.3" - "@algolia/client-common" "4.23.3" - "@algolia/client-personalization" "4.23.3" - "@algolia/client-search" "4.23.3" - "@algolia/logger-common" "4.23.3" - "@algolia/logger-console" "4.23.3" - "@algolia/recommend" "4.23.3" - "@algolia/requester-browser-xhr" "4.23.3" - "@algolia/requester-common" "4.23.3" - "@algolia/requester-node-http" "4.23.3" - "@algolia/transporter" "4.23.3" - -birpc@^0.2.17: - version "0.2.19" - resolved "https://registry.yarnpkg.com/birpc/-/birpc-0.2.19.tgz#cdd183a4a70ba103127d49765b4a71349da5a0ca" - integrity sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ== +"@vue/reactivity@3.5.26": + version "3.5.26" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.26.tgz#59a1edf566dc80133c1c26c93711c877e8602c48" + integrity sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ== + dependencies: + "@vue/shared" "3.5.26" + +"@vue/runtime-core@3.5.26": + version "3.5.26" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.26.tgz#3f2c040bcf8018c03a1ab5adb0d788c13c986f0e" + integrity sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q== + dependencies: + "@vue/reactivity" "3.5.26" + "@vue/shared" "3.5.26" + +"@vue/runtime-dom@3.5.26": + version "3.5.26" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz#5954848614883948ecc1f631a67b32cc32f81936" + integrity sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ== + dependencies: + "@vue/reactivity" "3.5.26" + "@vue/runtime-core" "3.5.26" + "@vue/shared" "3.5.26" + csstype "^3.2.3" + +"@vue/server-renderer@3.5.26": + version "3.5.26" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.26.tgz#269055497fcc75b3984063f866f17c748b565ef4" + integrity sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA== + dependencies: + "@vue/compiler-ssr" "3.5.26" + "@vue/shared" "3.5.26" + +"@vue/shared@3.5.26", "@vue/shared@^3.5.13": + version "3.5.26" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.26.tgz#1e02ef2d64aced818cd31d81ce5175711dc90a9f" + integrity sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A== + +"@vueuse/core@12.8.2", "@vueuse/core@^12.4.0": + version "12.8.2" + resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-12.8.2.tgz#007c6dd29a7d1f6933e916e7a2f8ef3c3f968eaa" + integrity sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ== + dependencies: + "@types/web-bluetooth" "^0.0.21" + "@vueuse/metadata" "12.8.2" + "@vueuse/shared" "12.8.2" + vue "^3.5.13" + +"@vueuse/integrations@^12.4.0": + version "12.8.2" + resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-12.8.2.tgz#d04f33d86fe985c9a27c98addcfde9f30f2db1df" + integrity sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g== + dependencies: + "@vueuse/core" "12.8.2" + "@vueuse/shared" "12.8.2" + vue "^3.5.13" + +"@vueuse/metadata@12.8.2": + version "12.8.2" + resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-12.8.2.tgz#6cb3a4e97cdcf528329eebc1bda73cd7f64318d3" + integrity sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A== + +"@vueuse/shared@12.8.2": + version "12.8.2" + resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-12.8.2.tgz#b9e4611d0603629c8e151f982459da394e22f930" + integrity sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w== + dependencies: + vue "^3.5.13" + +algoliasearch@^5.14.2: + version "5.46.2" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.46.2.tgz#3afba0e53f3293e39cfde9b2ef27c583d44bf2a5" + integrity sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q== + dependencies: + "@algolia/abtesting" "1.12.2" + "@algolia/client-abtesting" "5.46.2" + "@algolia/client-analytics" "5.46.2" + "@algolia/client-common" "5.46.2" + "@algolia/client-insights" "5.46.2" + "@algolia/client-personalization" "5.46.2" + "@algolia/client-query-suggestions" "5.46.2" + "@algolia/client-search" "5.46.2" + "@algolia/ingestion" "1.46.2" + "@algolia/monitoring" "1.46.2" + "@algolia/recommend" "5.46.2" + "@algolia/requester-browser-xhr" "5.46.2" + "@algolia/requester-fetch" "5.46.2" + "@algolia/requester-node-http" "5.46.2" + +birpc@^2.3.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/birpc/-/birpc-2.9.0.tgz#b59550897e4cd96a223e2a6c1475b572236ed145" + integrity sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw== ccount@^2.0.0: version "2.0.1" @@ -689,17 +719,17 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== -copy-anything@^3.0.2: - version "3.0.5" - resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.5.tgz#2d92dce8c498f790fa7ad16b01a1ae5a45b020a0" - integrity sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w== +copy-anything@^4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.0.5.tgz#16cabafd1ea4bb327a540b750f2b4df522825aea" + integrity sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA== dependencies: - is-what "^4.1.8" + is-what "^5.2.0" -csstype@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +csstype@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== dequal@^2.0.0: version "2.0.3" @@ -713,10 +743,15 @@ devlop@^1.0.0: dependencies: dequal "^2.0.0" -entities@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +emoji-regex-xs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724" + integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg== + +entities@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.0.tgz#2ae4e443f3f17d152d3f5b0f79b932c1e59deb7a" + integrity sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ== esbuild@^0.21.3: version "0.21.5" @@ -752,22 +787,22 @@ estree-walker@^2.0.2: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== -focus-trap@^7.6.0: - version "7.6.0" - resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-7.6.0.tgz#7f3edab8135eaca92ab59b6e963eb5cc42ded715" - integrity sha512-1td0l3pMkWJLFipobUcGaf+5DTY4PLDDrcqoSaKP8ediO/CoWCCYk/fT/Y2A4e6TNB+Sh6clRJCjOPPnKoNHnQ== +focus-trap@^7.6.4: + version "7.8.0" + resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-7.8.0.tgz#b1d9463fa42b93ad7a5223d750493a6c09b672a8" + integrity sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA== dependencies: - tabbable "^6.2.0" + tabbable "^6.4.0" 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== -hast-util-to-html@^9.0.3: - version "9.0.3" - resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz#a9999a0ba6b4919576a9105129fead85d37f302b" - integrity sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg== +hast-util-to-html@^9.0.4: + 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: "@types/hast" "^3.0.0" "@types/unist" "^3.0.0" @@ -776,7 +811,7 @@ hast-util-to-html@^9.0.3: hast-util-whitespace "^3.0.0" html-void-elements "^3.0.0" mdast-util-to-hast "^13.0.0" - property-information "^6.0.0" + property-information "^7.0.0" space-separated-tokens "^2.0.0" stringify-entities "^4.0.0" zwitch "^2.0.4" @@ -798,17 +833,17 @@ html-void-elements@^3.0.0: resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== -is-what@^4.1.8: - version "4.1.16" - resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.16.tgz#1ad860a19da8b4895ad5495da3182ce2acdd7a6f" - integrity sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A== +is-what@^5.2.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-5.5.0.tgz#a3031815757cfe1f03fed990bf6355a2d3f628c4" + integrity sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw== -magic-string@^0.30.11: - version "0.30.11" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954" - integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A== +magic-string@^0.30.21: + 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: - "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/sourcemap-codec" "^1.5.5" mark.js@8.11.1: version "8.11.1" @@ -816,9 +851,9 @@ mark.js@8.11.1: integrity sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ== 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== + version "13.2.1" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053" + integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA== dependencies: "@types/hast" "^3.0.0" "@types/mdast" "^4.0.0" @@ -862,27 +897,34 @@ micromark-util-types@^2.0.0: resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.0.tgz#63b4b7ffeb35d3ecf50d1ca20e68fc7caa36d95e" integrity sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w== -minisearch@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/minisearch/-/minisearch-7.1.0.tgz#f5830e9109b5919ee7b291c29a304f381aa68770" - integrity sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA== +minisearch@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/minisearch/-/minisearch-7.2.0.tgz#3dc30e41e9464b3836553b6d969b656614f8f359" + integrity sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg== mitt@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== +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== + nanoid@^3.3.7: version "3.3.8" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== -oniguruma-to-js@0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz#8d899714c21f5c7d59a3c0008ca50e848086d740" - integrity sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ== +oniguruma-to-es@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz#480e4bac4d3bc9439ac0d2124f0725e7a0d76d17" + integrity sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ== dependencies: - regex "^4.3.2" + emoji-regex-xs "^1.0.0" + regex "^6.0.1" + regex-recursion "^6.0.2" perfect-debounce@^1.0.0: version "1.0.0" @@ -894,7 +936,12 @@ picocolors@^1.1.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== -postcss@^8.4.43, postcss@^8.4.47: +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== + +postcss@^8.4.43: version "8.4.47" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== @@ -903,20 +950,43 @@ postcss@^8.4.43, postcss@^8.4.47: picocolors "^1.1.0" source-map-js "^1.2.1" +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: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + preact@^10.0.0: version "10.21.0" resolved "https://registry.yarnpkg.com/preact/-/preact-10.21.0.tgz#5b0335c873a1724deb66e517830db4fd310c24f6" integrity sha512-aQAIxtzWEwH8ou+OovWVSVNlFImL7xUCwJX3YMqA3U8iKCNC34999fFOnWjYNsylgfPgMexpbk7WYOLtKr/mxg== -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== +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== + +regex-recursion@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-6.0.2.tgz#a0b1977a74c87f073377b938dbedfab2ea582b33" + integrity sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg== + dependencies: + regex-utilities "^2.3.0" + +regex-utilities@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/regex-utilities/-/regex-utilities-2.3.0.tgz#87163512a15dce2908cf079c8960d5158ff43280" + integrity sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng== -regex@^4.3.2: - version "4.3.3" - resolved "https://registry.yarnpkg.com/regex/-/regex-4.3.3.tgz#8cda73ccbdfa7c5691881d02f9bb142dba9daa6a" - integrity sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg== +regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/regex/-/regex-6.1.0.tgz#d7ce98f8ee32da7497c13f6601fca2bc4a6a7803" + integrity sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg== + dependencies: + regex-utilities "^2.3.0" rfdc@^1.4.1: version "1.4.1" @@ -948,23 +1018,20 @@ rollup@^4.20.0: "@rollup/rollup-win32-x64-msvc" "4.24.0" fsevents "~2.3.2" -shiki@1.22.0, shiki@^1.22.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.22.0.tgz#45d1dfff0e03a598af70e2ec8592f14ef07827b4" - integrity sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw== - dependencies: - "@shikijs/core" "1.22.0" - "@shikijs/engine-javascript" "1.22.0" - "@shikijs/engine-oniguruma" "1.22.0" - "@shikijs/types" "1.22.0" - "@shikijs/vscode-textmate" "^9.3.0" +shiki@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-2.5.0.tgz#09d01ebf3b0b06580431ce3ddc023320442cf223" + integrity sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ== + dependencies: + "@shikijs/core" "2.5.0" + "@shikijs/engine-javascript" "2.5.0" + "@shikijs/engine-oniguruma" "2.5.0" + "@shikijs/langs" "2.5.0" + "@shikijs/themes" "2.5.0" + "@shikijs/types" "2.5.0" + "@shikijs/vscode-textmate" "^10.0.2" "@types/hast" "^3.0.4" -source-map-js@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" - integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== - 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" @@ -988,22 +1055,17 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -superjson@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/superjson/-/superjson-2.2.1.tgz#9377a7fa80fedb10c851c9dbffd942d4bcf79733" - integrity sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA== +superjson@^2.2.2: + version "2.2.6" + resolved "https://registry.yarnpkg.com/superjson/-/superjson-2.2.6.tgz#a223a3a988172a5f9656e2063fe5f733af40d099" + integrity sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA== dependencies: - copy-anything "^3.0.2" - -tabbable@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" - integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + copy-anything "^4" -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 sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== +tabbable@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.4.0.tgz#36eb7a06d80b3924a22095daf45740dea3bf5581" + integrity sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg== trim-lines@^3.0.0: version "3.0.1" @@ -1064,10 +1126,10 @@ vfile@^6.0.0: "@types/unist" "^3.0.0" vfile-message "^4.0.0" -vite@^5.4.8: - version "5.4.14" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.14.tgz#ff8255edb02134df180dcfca1916c37a6abe8408" - integrity sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA== +vite@^5.4.14: + 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" @@ -1075,44 +1137,40 @@ vite@^5.4.8: optionalDependencies: fsevents "~2.3.3" -vitepress@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/vitepress/-/vitepress-1.4.0.tgz#5e879230d98e5c4e5aec91daade6945bbc18934b" - integrity sha512-JXCv4EsKTDyAFb6C/UjZr7nsGAzZ6mafVk2rx7rG5o8N+B/4QstIk+iEOe/9dKoU6V624UIC6g1pZ+K63rxhlw== - dependencies: - "@docsearch/css" "^3.6.2" - "@docsearch/js" "^3.6.2" - "@shikijs/core" "^1.22.0" - "@shikijs/transformers" "^1.22.0" - "@shikijs/types" "^1.22.0" +vitepress@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/vitepress/-/vitepress-1.6.4.tgz#1b6c68fede541a3f401a66263dce0c985e2d8d92" + integrity sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg== + dependencies: + "@docsearch/css" "3.8.2" + "@docsearch/js" "3.8.2" + "@iconify-json/simple-icons" "^1.2.21" + "@shikijs/core" "^2.1.0" + "@shikijs/transformers" "^2.1.0" + "@shikijs/types" "^2.1.0" "@types/markdown-it" "^14.1.2" - "@vitejs/plugin-vue" "^5.1.4" - "@vue/devtools-api" "^7.4.6" - "@vue/shared" "^3.5.11" - "@vueuse/core" "^11.1.0" - "@vueuse/integrations" "^11.1.0" - focus-trap "^7.6.0" + "@vitejs/plugin-vue" "^5.2.1" + "@vue/devtools-api" "^7.7.0" + "@vue/shared" "^3.5.13" + "@vueuse/core" "^12.4.0" + "@vueuse/integrations" "^12.4.0" + focus-trap "^7.6.4" mark.js "8.11.1" - minisearch "^7.1.0" - shiki "^1.22.0" - vite "^5.4.8" - vue "^3.5.11" - -vue-demi@>=0.14.10: - version "0.14.10" - resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04" - integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg== - -vue@^3.5.11: - version "3.5.11" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.11.tgz#3e307183797629f701e303a0a008f517ae031483" - integrity sha512-/8Wurrd9J3lb72FTQS7gRMNQD4nztTtKPmuDuPuhqXmmpD6+skVjAeahNpVzsuky6Sy9gy7wn8UadqPtt9SQIg== - dependencies: - "@vue/compiler-dom" "3.5.11" - "@vue/compiler-sfc" "3.5.11" - "@vue/runtime-dom" "3.5.11" - "@vue/server-renderer" "3.5.11" - "@vue/shared" "3.5.11" + minisearch "^7.1.1" + shiki "^2.1.0" + vite "^5.4.14" + vue "^3.5.13" + +vue@^3.5.13: + version "3.5.26" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.26.tgz#03a0b17311e0e593d34b9358fa249b85e3a6d9fb" + integrity sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA== + dependencies: + "@vue/compiler-dom" "3.5.26" + "@vue/compiler-sfc" "3.5.26" + "@vue/runtime-dom" "3.5.26" + "@vue/server-renderer" "3.5.26" + "@vue/shared" "3.5.26" zwitch@^2.0.4: version "2.0.4" diff --git a/frontend/.babelrc b/frontend/.babelrc deleted file mode 100644 index 54071ecd16..0000000000 --- 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 c8f4b4f98b..a9b91bc1a0 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,4 +1,24 @@ -dist +src/locale/lang + +# 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 0000000000..430644db57 --- /dev/null +++ b/frontend/biome.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.14/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 0000000000..135dab10c2 --- /dev/null +++ b/frontend/check-locales.cjs @@ -0,0 +1,164 @@ +#!/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"], + ["de", "de-DE"], + ["pt", "pt-PT"], + ["es", "es-ES"], + ["fr", "fr-FR"], + ["it", "it-IT"], + ["ja", "ja-JP"], + ["nl", "nl-NL"], + ["pl", "pl-PL"], + ["ru", "ru-RU"], + ["sk", "sk-SK"], + ["cs", "cs-CZ"], + ["vi", "vi-VN"], + ["zh", "zh-CN"], + ["ko", "ko-KR"], + ["bg", "bg-BG"], + ["id", "id-ID"], + ["tr", "tr-TR"], + ["hu", "hu-HU"], +]; + +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 440203ba22..0000000000 --- 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 96d8768ea9..0000000000 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 e97a22181c..0000000000 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 0829caef1e..0000000000 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 7c901cd845..0000000000 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 99652481a0..0000000000 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 343e5ba8d3..0000000000 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 92c3260efb..0000000000 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 d552543be1..0000000000 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 ae08b012e0..0000000000 --- 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 d24a76e352..0000000000 --- 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 7fb2bd61bf..0000000000 --- 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 c2e67a449a..0000000000 --- a/frontend/html/partials/header.ejs +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - <%- title %> - - - - - - - - - - - - - - - diff --git a/frontend/images b/frontend/images deleted file mode 120000 index 37c3185406..0000000000 --- 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 0000000000..c6a2012266 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,45 @@ + + + + + + Nginx Proxy Manager + + + + + + + + + + + + + + +
+ + + + diff --git a/frontend/js/app/api.js b/frontend/js/app/api.js deleted file mode 100644 index 6829745a48..0000000000 --- a/frontend/js/app/api.js +++ /dev/null @@ -1,791 +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, page, perPage) { - let params = []; - - if (typeof expand === 'object' && expand !== null && expand.length) { - params.push('expand=' + makeExpansionString(expand)); - } - - if (typeof query === 'string') { - params.push('query=' + query); - } - - if (page && perPage) { - params.push('page=' + page); - params.push('perPage=' + perPage); - } - - let url = path + (params.length ? '?' + params.join('&') : ''); - return fetch('get', url); -} - -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); - } - }, - - OpenappsecLog: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('openappsec-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); - } - }, - - OpenAppsecSettings: { - /** - * @returns {Promise} - */ - get: function () { - return fetch('get', 'openappsec-settings'); - }, - - /** - * @param {Object} data - * @returns {Promise} - */ - save: function (data) { - return fetch('put', 'openappsec-settings', 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 84743c8d41..0000000000 --- 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 862ffc2218..0000000000 --- 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 ec3cf2a2ba..0000000000 --- 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 9d3e26fb16..0000000000 --- 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 8d182b5950..0000000000 --- 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 0d03c5ca84..0000000000 --- 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 98a2d97347..0000000000 --- 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 815cdfac93..0000000000 --- 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 6d1fbc4f9d..0000000000 --- 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 b2d3a24489..0000000000 --- a/frontend/js/app/controller.js +++ /dev/null @@ -1,559 +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})); - }); - } - } - }, - - /** - * 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 () { - let 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})); - }); - } - }, - - /** - * openappsec Log - */ - showOpenappsecLogPage: function (page) { - page = parseInt(page) || 1; - let controller = this; - if (Cache.User.isAdmin()) { - require(['./main', './openappsec-log/main'], (App, View) => { - controller.navigate('/openappsec-log/page/' + page); - - // Show the view with the data - App.UI.showAppContent(new View({ - page: page, - perPage: 50 - })); - }); - } else { - this.showDashboard(); - } - }, - - /** - * openappsec Log Metadata - * - * @param model - */ - showOpenappsecMeta: function (model) { - if (Cache.User.isAdmin()) { - require(['./main', './openappsec-log/meta'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Settings - */ - showSettings: function () { - let 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 c00aa6d0ff..0000000000 --- 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 ba4a99a67d..0000000000 --- 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 11633dfcc6..0000000000 --- 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 74998d65bb..0000000000 --- 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 f7fd709bd4..0000000000 --- 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 6fa85fc8c5..0000000000 --- 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 6fb79e6673..0000000000 --- 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 b0f54374c6..0000000000 --- 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 c63cdc0798..0000000000 --- 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 e85b4f620b..0000000000 --- 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 3833549a64..0000000000 --- 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 4af91ab178..0000000000 --- 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 79220b14b9..0000000000 --- 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 bb0755481b..0000000000 --- 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 6b767b83fa..0000000000 --- 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 b4c00e2e3b..0000000000 --- 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 c2435ecb3d..0000000000 --- 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 f15238dccb..0000000000 --- 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 fe043c98eb..0000000000 --- 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 4f68aead7e..0000000000 --- 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 7988e0c289..0000000000 --- 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 577a77ef27..0000000000 --- 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 9758593646..0000000000 --- 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 513f58659f..0000000000 --- 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 aa4b53ad2a..0000000000 --- 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 b4e0686609..0000000000 --- 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 89a2e5e83f..0000000000 --- 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 6adb4b01c1..0000000000 --- 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 cab09155fa..0000000000 --- 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 179a819554..0000000000 --- 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 b9a927adc4..0000000000 --- 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 329b584342..0000000000 --- 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 d96b43e8f8..0000000000 --- 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 dbd6fa85d7..0000000000 --- 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 3f9f022eb3..0000000000 --- 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 4af186d096..0000000000 --- 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 7363288173..0000000000 --- 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 6661f625fd..0000000000 --- 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 0886d26f5e..0000000000 --- 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 4bebb4360c..0000000000 --- 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 d497d0685f..0000000000 --- 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 de3b87d8b2..0000000000 --- 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 a091717d9f..0000000000 --- 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 dede3b6393..0000000000 --- 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 a477dbfa36..0000000000 --- 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 e018a74b0b..0000000000 --- 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 579314199e..0000000000 --- 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 4c5d1ad1f8..0000000000 --- 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 e4d0c010e1..0000000000 --- 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 e5a7e1163a..0000000000 --- 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 74da297c2e..0000000000 --- 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 63a8e020a9..0000000000 --- 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 2f95756a70..0000000000 --- a/frontend/js/app/nginx/proxy/form.ejs +++ /dev/null @@ -1,316 +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 84804f60c6..0000000000 --- a/frontend/js/app/nginx/proxy/form.js +++ /dev/null @@ -1,410 +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"]', - use_openappsec: 'input[name="use_openappsec"]', - openappsec_mode: 'select[name="openappsec_mode"]', - minimum_confidence: 'select[name="minimum_confidence"]', - 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.use_openappsec': function () { - let checked = this.ui.use_openappsec.prop('checked'); - this.ui.openappsec_mode - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - this.ui.minimum_confidence - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - /*** check this */ - if (!checked) { - this.ui.openappsec_mode.prop('checked', false); - } - }, - - '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; - - // enable openappsec inputs for serialization. if we don't do this, serialization of custom locations will be added to the root object instead of the missing root properties with the same name. - - let topHostDisabledElements = this.ui.form.find('#details [name*="openappsec"]:disabled, [name="minimum_confidence"]:disabled'); - topHostDisabledElements.prop('disabled', false); - let data = this.ui.form.serializeJSON(); - topHostDisabledElements.prop('disabled', true); - - // Check if openappsec is enabled. serializeJSON() will only return its value attribute, not its checked attribute. - let use_openappsec = this.ui.use_openappsec.prop('checked'); - data.use_openappsec = use_openappsec; - - // Add locations using the model defined in file frontend/js/app/nginx/proxy/location.js and the template frontend/js/app/nginx/proxy/location-item.ejs. - // input fields with the class 'model' will automatically update the model. - data.locations = []; - this.locationsCollection.models.forEach((location) => { - data.locations.push(location.toJSON()); - }); - - // convert all "false" strings to false booleans in data. - Object.keys(data).forEach(key => { - if (typeof data[key] === 'string' && data[key].toLowerCase() === 'false') { - data[key] = false; - } - }); - - // 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.use_openappsec = !!data.use_openappsec; - 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'); - this.ui.use_openappsec.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 3eeaa6d268..0000000000 --- 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 37d199b4a6..0000000000 --- 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 6de5b9c684..0000000000 --- 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 09e984e620..0000000000 --- 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 c0866740a1..0000000000 --- a/frontend/js/app/nginx/proxy/location-item.ejs +++ /dev/null @@ -1,99 +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 6e1fe9ae64..0000000000 --- a/frontend/js/app/nginx/proxy/location.js +++ /dev/null @@ -1,78 +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: { - use_openappsec: 'input[name="use_openappsec"]', - openappsec_mode: 'select[name="openappsec_mode"]', - minimum_confidence: 'select[name="minimum_confidence"]', - toggle: 'input[type="checkbox"]#advanced_config_toggle', - 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 @ui.use_openappsec': function () { - let checked = this.ui.use_openappsec.prop('checked'); - this.model.set('use_openappsec', checked); - - this.ui.openappsec_mode - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - this.ui.minimum_confidence - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - /*** check this */ - if (!checked) { - this.ui.openappsec_mode.prop('checked', false); - } - }, - - // input fields with the class 'model' will automatically update the model. - '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 4ecb9036a6..0000000000 --- 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 baf6710134..0000000000 --- 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 782d8435d4..0000000000 --- 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 6d2862f68f..0000000000 --- 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 f32178bcc1..0000000000 --- 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 5d061ff7da..0000000000 --- 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 dff186fdfc..0000000000 --- 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 05adc25119..0000000000 --- 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 8b6930d60e..0000000000 --- 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 d368cf6aa1..0000000000 --- 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 87e28229ab..0000000000 --- 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 1f5351a731..0000000000 --- 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 d7ba3a2173..0000000000 --- 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 71eff18ccf..0000000000 --- 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 800945f36e..0000000000 --- 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 cd012f9b0b..0000000000 --- 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 936247ef8f..0000000000 --- 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 a6892ee2ac..0000000000 --- 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 57ab6b2ad3..0000000000 --- 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 36be621d9a..0000000000 --- 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 7dc0dbe86e..0000000000 --- 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 83bdc15e12..0000000000 --- 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/openappsec-log/list-all/item.ejs b/frontend/js/app/openappsec-log/list-all/item.ejs deleted file mode 100644 index 27f057d92d..0000000000 --- a/frontend/js/app/openappsec-log/list-all/item.ejs +++ /dev/null @@ -1,16 +0,0 @@ -open -<%- formatDbDate(eventTime, 'MMM DD YYYY, H:mm') %> -<%= createSpecificTableCell(eventSeverity) %> -<%= createSpecificTableCell(assetName) %> -<%= createSpecificTableCell(securityAction) %> -<%= createSpecificTableCell(waapIncidentType) %> -<%= createSpecificTableCell(httpSourceId) %> -<%= createSpecificTableCell(sourceIP) %> -<%= createSpecificTableCell(proxyIP) %> -<%= createSpecificTableCell(httpHostName) %> -<%= createSpecificTableCell(httpMethod) %> -<%= createSpecificTableCell(httpResponseCode) %> -<%= createSpecificTableCell(httpUriPath) %> -<%= createSpecificTableCell(matchedLocation) %> -<%= createSpecificTableCell(matchedParameter) %> -<%= createSpecificTableCell(matchedSample) %> diff --git a/frontend/js/app/openappsec-log/list-all/item.js b/frontend/js/app/openappsec-log/list-all/item.js deleted file mode 100644 index 5ed43006e7..0000000000 --- a/frontend/js/app/openappsec-log/list-all/item.js +++ /dev/null @@ -1,40 +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.showOpenappsecMeta(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 || '?'); - }, - createSpecificTableCell: function(value) { - if (value && value.trim() !== '') { - value = value.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); - return `${value}`; - } else { - return `-`; - } - } - } -}); diff --git a/frontend/js/app/openappsec-log/list-all/main.ejs b/frontend/js/app/openappsec-log/list-all/main.ejs deleted file mode 100644 index 3357f286ef..0000000000 --- a/frontend/js/app/openappsec-log/list-all/main.ejs +++ /dev/null @@ -1,21 +0,0 @@ - -   - Time - Event Severity - Asset Name - Security Action - AppSec Incident Type - Source Identifier - Source IP - Proxy IP - HTTP Host - HTTP Method - HTTP Response Code - HTTP URI Path - Matched Location - Matched Parameter - Matched Sample - - - - diff --git a/frontend/js/app/openappsec-log/list-all/main.js b/frontend/js/app/openappsec-log/list-all/main.js deleted file mode 100644 index 55b8bcdef6..0000000000 --- a/frontend/js/app/openappsec-log/list-all/main.js +++ /dev/null @@ -1,49 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -let TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView, - - initialize: function (options) { - this.options = new Backbone.Model(options); - // this.page = options.page; - // this.perPage = options.perPage; - this.updatePage(); - // this.listenTo(this.options, 'change:page', this.updatePage); - }, - - updatePage: function () { - let perPage = this.perPage || this.collection.length; - let page = this.page || 1; - let models; - if (this.perPage && this.page) { - models = this.collection.models.slice((page - 1) * perPage, page * perPage); - } else { - models = this.collection.models; - } - this.collection.reset(models); - } -}); - -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, - // page: this.options.page, - // perPage: this.options.perPage - })); - } -}); diff --git a/frontend/js/app/openappsec-log/list-important/item.ejs b/frontend/js/app/openappsec-log/list-important/item.ejs deleted file mode 100644 index a311a62c01..0000000000 --- a/frontend/js/app/openappsec-log/list-important/item.ejs +++ /dev/null @@ -1,16 +0,0 @@ -open -<%- formatDbDate(eventTime, 'MMM DD YYYY, H:mm') %> -<%= createSpecificTableCell(eventSeverity) %> -<%= createSpecificTableCell(assetName) %> -<%= createSpecificTableCell(securityAction) %> -<%= createSpecificTableCell(waapIncidentType) %> -<%= createSpecificTableCell(httpSourceId) %> -<%= createSpecificTableCell(sourceIP) %> -<%= createSpecificTableCell(proxyIP) %> -<%= createSpecificTableCell(httpHostName) %> -<%= createSpecificTableCell(httpMethod) %> -<%= createSpecificTableCell(httpResponseCode) %> -<%= createSpecificTableCell(httpUriPath) %> -<%= createSpecificTableCell(matchedLocation) %> -<%= createSpecificTableCell(matchedParameter) %> -<%= createSpecificTableCell(matchedSample) %> \ No newline at end of file diff --git a/frontend/js/app/openappsec-log/list-important/item.js b/frontend/js/app/openappsec-log/list-important/item.js deleted file mode 100644 index 49de809f46..0000000000 --- a/frontend/js/app/openappsec-log/list-important/item.js +++ /dev/null @@ -1,40 +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.showOpenappsecMeta(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 || '?'); - }, - createSpecificTableCell: function(value) { - if (value && value.trim() !== '') { - value = value.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); - return `${value}`; - } else { - return `-`; - } - } - } -}); diff --git a/frontend/js/app/openappsec-log/list-important/main.ejs b/frontend/js/app/openappsec-log/list-important/main.ejs deleted file mode 100644 index 3357f286ef..0000000000 --- a/frontend/js/app/openappsec-log/list-important/main.ejs +++ /dev/null @@ -1,21 +0,0 @@ - -   - Time - Event Severity - Asset Name - Security Action - AppSec Incident Type - Source Identifier - Source IP - Proxy IP - HTTP Host - HTTP Method - HTTP Response Code - HTTP URI Path - Matched Location - Matched Parameter - Matched Sample - - - - diff --git a/frontend/js/app/openappsec-log/list-important/main.js b/frontend/js/app/openappsec-log/list-important/main.js deleted file mode 100644 index a7ef3281c7..0000000000 --- a/frontend/js/app/openappsec-log/list-important/main.js +++ /dev/null @@ -1,42 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -let TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView, - - initialize: function (options) { - this.options = new Backbone.Model(options); - this.page = options.page; - this.perPage = options.perPage; - this.updatePage(); - this.listenTo(this.options, 'change:page', this.updatePage); - }, - - updatePage: function () { - let models = this.collection.models.slice((this.page - 1) * this.perPage, this.page * this.perPage); - this.collection.reset(models); - } -}); - -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, - page: this.options.page, - perPage: this.options.perPage - })); - } -}); diff --git a/frontend/js/app/openappsec-log/list-notifications/item.ejs b/frontend/js/app/openappsec-log/list-notifications/item.ejs deleted file mode 100644 index f97a9c49b6..0000000000 --- a/frontend/js/app/openappsec-log/list-notifications/item.ejs +++ /dev/null @@ -1,8 +0,0 @@ -open -<%- formatDbDate(eventTime, 'MMM DD YYYY, H:mm') %> -<%= createSpecificTableCell(eventSeverity) %> -<%= createSpecificTableCell(eventPriority) %> -<%= createSpecificTableCell(eventTopic) %> -<%= createSpecificTableCell(eventName) %> -<%= createSpecificTableCell(suggestedRemediation) %> -<%= createSpecificTableCell(assetName) %> \ No newline at end of file diff --git a/frontend/js/app/openappsec-log/list-notifications/item.js b/frontend/js/app/openappsec-log/list-notifications/item.js deleted file mode 100644 index 49933fe8f3..0000000000 --- a/frontend/js/app/openappsec-log/list-notifications/item.js +++ /dev/null @@ -1,40 +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.showOpenappsecMeta(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 || '?'); - }, - createSpecificTableCell: function(value) { - if (value && value.trim() !== '') { - value = value.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); - return `${value}`; - } else { - return `-`; - } - } - } -}); diff --git a/frontend/js/app/openappsec-log/list-notifications/main.ejs b/frontend/js/app/openappsec-log/list-notifications/main.ejs deleted file mode 100644 index 73c69806e6..0000000000 --- a/frontend/js/app/openappsec-log/list-notifications/main.ejs +++ /dev/null @@ -1,13 +0,0 @@ - -   - Time - Event Severity - Event Priority - Event Topic - Event Name - Suggested Remediation if Applicable - Asset Name - - - - \ No newline at end of file diff --git a/frontend/js/app/openappsec-log/list-notifications/main.js b/frontend/js/app/openappsec-log/list-notifications/main.js deleted file mode 100644 index a7ef3281c7..0000000000 --- a/frontend/js/app/openappsec-log/list-notifications/main.js +++ /dev/null @@ -1,42 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -let TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView, - - initialize: function (options) { - this.options = new Backbone.Model(options); - this.page = options.page; - this.perPage = options.perPage; - this.updatePage(); - this.listenTo(this.options, 'change:page', this.updatePage); - }, - - updatePage: function () { - let models = this.collection.models.slice((this.page - 1) * this.perPage, this.page * this.perPage); - this.collection.reset(models); - } -}); - -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, - page: this.options.page, - perPage: this.options.perPage - })); - } -}); diff --git a/frontend/js/app/openappsec-log/list/item.ejs b/frontend/js/app/openappsec-log/list/item.ejs deleted file mode 100755 index 57c4feab6a..0000000000 --- a/frontend/js/app/openappsec-log/list/item.ejs +++ /dev/null @@ -1,77 +0,0 @@ - - -
- <% var sevirityClass = 'bg-success'; - switch (eventSeverity) { - case 'Critical': - sevirityClass = 'bg-danger'; - break; - case 'Warning': - sevirityClass = 'bg-warning'; - break; - case 'Info': - sevirityClass = 'bg-success'; - //sevirityClass = 'bg-info'; - break; - case 'Debug': - sevirityClass = 'bg-success'; - break; - } - %> - <%- eventSeverity %> -
- - -
- <% - var items = []; - object_type = "test"; - 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); - items.push("test"); - } - break; - } - %><%- eventName %> -
-
- <%- formatDbDate(eventTime, 'Do MMMM YYYY, h:mm a') %> -
- - -
- <%- serviceName %> -
- - - open - diff --git a/frontend/js/app/openappsec-log/list/item.js b/frontend/js/app/openappsec-log/list/item.js deleted file mode 100755 index 009e3a3e6e..0000000000 --- a/frontend/js/app/openappsec-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.showOpenappsecMeta(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/openappsec-log/list/main.ejs b/frontend/js/app/openappsec-log/list/main.ejs deleted file mode 100755 index 0bb40fa15b..0000000000 --- a/frontend/js/app/openappsec-log/list/main.ejs +++ /dev/null @@ -1,9 +0,0 @@ - - Severity - Event - Handler -   - - - - diff --git a/frontend/js/app/openappsec-log/list/main.js b/frontend/js/app/openappsec-log/list/main.js deleted file mode 100755 index a7ef3281c7..0000000000 --- a/frontend/js/app/openappsec-log/list/main.js +++ /dev/null @@ -1,42 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -let TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView, - - initialize: function (options) { - this.options = new Backbone.Model(options); - this.page = options.page; - this.perPage = options.perPage; - this.updatePage(); - this.listenTo(this.options, 'change:page', this.updatePage); - }, - - updatePage: function () { - let models = this.collection.models.slice((this.page - 1) * this.perPage, this.page * this.perPage); - this.collection.reset(models); - } -}); - -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, - page: this.options.page, - perPage: this.options.perPage - })); - } -}); diff --git a/frontend/js/app/openappsec-log/main.ejs b/frontend/js/app/openappsec-log/main.ejs deleted file mode 100755 index 0b486f17f1..0000000000 --- a/frontend/js/app/openappsec-log/main.ejs +++ /dev/null @@ -1,45 +0,0 @@ -
-
-
-

Security Log

-
- -
-
-
- - -
- -
-
-
-
- -
-
- -
-
- -
-
- -
-
diff --git a/frontend/js/app/openappsec-log/main.js b/frontend/js/app/openappsec-log/main.js deleted file mode 100755 index bfcdc1d3e0..0000000000 --- a/frontend/js/app/openappsec-log/main.js +++ /dev/null @@ -1,229 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const OpenappsecLogModel = require('../../models/openappsec-log'); -const ListViewTab1 = require('./list-important/main'); -const ListViewTab2 = require('./list-all/main'); -const ListViewTab3 = require('./list-notifications/main'); -const template = require('./main.ejs'); -const paginationTemplate = require('./pagination.ejs'); -const ErrorView = require('../error/main'); -const EmptyView = require('../empty/main'); -const { data } = require('jquery'); -const Controller = require('../controller'); - -let PaginationView = Mn.View.extend({ - tagName: 'ul', - className: 'pagination justify-content-center mt-4 border-top pt-3', - - initialize: function (options) { - this.totalPages = parseInt(options.totalPages) || 1; - this.currentPage = parseInt(options.currentPage) || 1; - this.totalDataLines = parseInt(options.totalDataLines) || 1; - this.maxPageLinks = 15; - }, - - template: paginationTemplate, - - templateContext: function () { - return { - totalDataLines: this.totalDataLines, - totalPages: this.totalPages, - currentPage: this.currentPage, - maxPageLinks: this.maxPageLinks - }; - } -}); - -module.exports = Mn.View.extend({ - id: 'openappsec-log', - template: template, - - initialize: function (options) { - this.options = options; - }, - - ui: { - list_region: '.list-region', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]', - pagination_region: '.pagination-region' - }, - - fetch: App.Api.OpenappsecLog.getAll, - - showData: function (response, tab = 'tab1') { - - // Filter the response data for each tab - const eventSeverities = ["critical", "high"]; - const tab1Data = response.filter(item => eventSeverities.includes(item.eventSeverity.trim().toLowerCase())); - - const eventNames = ["waap telemetry", "waap attack type telemetry", "ips stats"]; - const tab2Data = response.filter(item => !eventNames.includes(item.eventName.trim().toLowerCase())); - - const eventLevels = ["action item"]; - const tab3Data = response.filter(item => eventLevels.includes(item.eventLevel.trim().toLowerCase())); - - // Sort the data for each tab by timestamp in descending order. - tab1Data.sort((a, b) => { - return new Date(b.eventTime) - new Date(a.eventTime); - }); - - tab2Data.sort((a, b) => { - return new Date(b.eventTime) - new Date(a.eventTime); - }); - - tab3Data.sort((a, b) => { - return new Date(b.eventTime) - new Date(a.eventTime); - }); - - // Store the lengths of the original collections - this.tabCollectionLengths = { - tab1: response.length, - tab2: response.length, - tab3: response.length - }; - - this.tabCollections = { - tab1: new OpenappsecLogModel.Collection(tab1Data), - tab2: new OpenappsecLogModel.Collection(tab2Data), - tab3: new OpenappsecLogModel.Collection(tab3Data) - }; - - this.tabPaginationStates = { - tab1: { page: 1, perPage: this.options.perPage, totalDataLines: tab1Data.length }, - tab2: { page: 1, perPage: this.options.perPage, totalDataLines: tab2Data.length }, - tab3: { page: 1, perPage: this.options.perPage, totalDataLines: tab3Data.length } - }; - - // Define an object mapping for the ListViews - const listViewMapping = { - tab1: ListViewTab1, - tab2: ListViewTab2, - tab3: ListViewTab3 - }; - - // Get the current tab - const currentTab = tab; // Replace this with the actual current tab - - // Select the ListView for the current tab - const CurrentListView = listViewMapping[currentTab]; - - // Show the ListView for the current tab - this.showChildView('list_region', new CurrentListView({ - collection: this.tabCollections[currentTab], - // page: 1, - // perPage: this.options.perPage - page: this.tabPaginationStates[currentTab].page, - perPage: this.tabPaginationStates[currentTab].perPage - })); - - // this.showChildView('pagination_region', new PaginationView({ - // totalDataLines: this.tabPaginationStates[currentTab].totalDataLines, - // totalPages: Math.ceil(this.tabPaginationStates[currentTab].totalDataLines / this.options.perPage), - // currentPage: this.tabPaginationStates[currentTab].page - // })); - }, - - showError: function (err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showOpenappsecLog(); - } - })); - - 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', - pagination_region: '@ui.pagination_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); - }); - }, - - 'click .nav-link': 'onTabClick', - - 'click @ui.pagination_region a': function (e) { - e.preventDefault(); - - // get the page number from the link - const newPage = $(e.currentTarget).attr('href').split('/').pop(); - Controller.navigate('/openappsec-log/page/' + newPage, { trigger: true }); - } - }, - - onTabClick: function(event) { - event.preventDefault(); - const selectedTab = event.target.id; - let view = this; - let query = this.ui.query.val() || ''; - view.ui.dimmer.addClass('active'); - view.fetch(['user'], query) - .then(response => { - if (!view.isDestroyed() && response) { - view.showData(response, selectedTab); - } else { - view.showEmpty(); - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - - // this.showChildView('list_region', new ListView({ - // collection: this.tabCollections[selectedTab], - // page: this.tabPaginationStates[selectedTab].page, - // perPage: this.tabPaginationStates[selectedTab].perPage - // })); - - // this.showChildView('pagination_region', new PaginationView({ - // totalDataLines: this.tabCollections[selectedTab].length, - // totalPages: Math.ceil(this.tabCollections[selectedTab].length / this.options.perPage), - // currentPage: this.tabPaginationStates[selectedTab].page - // })); - - }, - - onRender: function () { - let view = this; - let query = this.ui.query.val() || ''; - - view.fetch(['user'], query) - .then(response => { - if (!view.isDestroyed() && response) { - view.showData(response); - } else { - view.showEmpty(); - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/openappsec-log/meta.ejs b/frontend/js/app/openappsec-log/meta.ejs deleted file mode 100755 index fe58311767..0000000000 --- a/frontend/js/app/openappsec-log/meta.ejs +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/frontend/js/app/openappsec-log/meta.js b/frontend/js/app/openappsec-log/meta.js deleted file mode 100755 index 815cdfac93..0000000000 --- a/frontend/js/app/openappsec-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/openappsec-log/pagination.ejs b/frontend/js/app/openappsec-log/pagination.ejs deleted file mode 100644 index a4885b2e00..0000000000 --- a/frontend/js/app/openappsec-log/pagination.ejs +++ /dev/null @@ -1,23 +0,0 @@ -
  • "> - First -
  • -
  • "> - Previous -
  • -<% let startPage = Math.max(1, currentPage - Math.floor(maxPageLinks / 2)); %> -<% let endPage = Math.min(startPage + maxPageLinks - 1, totalPages); %> -<% startPage = Math.max(1, endPage - maxPageLinks + 1); %> -<% for (let i = startPage; i <= endPage; i++) { %> -
  • "> - <%= i %> -
  • -<% } %> -
  • "> - Next -
  • -
  • "> - Last -
  • -
  • - Total lines: <%= totalDataLines %> -
  • \ No newline at end of file diff --git a/frontend/js/app/router.js b/frontend/js/app/router.js deleted file mode 100644 index e782623c9c..0000000000 --- a/frontend/js/app/router.js +++ /dev/null @@ -1,21 +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', - 'openappsec-log': 'showOpenappsecLogPage', - 'openappsec-log/page/:number': 'showOpenappsecLogPage', - '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 d74ac0bd3f..0000000000 --- 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 06a45b8bcf..0000000000 --- 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 1623c4dc7d..0000000000 --- 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 03f9ac05b5..0000000000 --- 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 c96e923a55..0000000000 --- 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 9d3e26fb16..0000000000 --- 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 2031a4fee3..0000000000 --- a/frontend/js/app/settings/main.ejs +++ /dev/null @@ -1,57 +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 ea7b41c2d4..0000000000 --- a/frontend/js/app/settings/main.js +++ /dev/null @@ -1,103 +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'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - id: 'settings', - template: template, - - ui: { - local_policy_field: '#open-appsec form #local_policy', - lp_success_info: '#open-appsec form #lp_success_info', - lp_error_info: '#open-appsec form #lp_error_info', - form: '#open-appsec form', - save: 'button.save', - list_region: '.list-region', - add: '.add-item', - dimmer: '.dimmer' - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.save': function (e) { - e.preventDefault(); - - this.ui.lp_success_info.hide(); - this.ui.lp_error_info.hide(); - - let data = this.ui.form.serializeJSON(); - console.log(data); - App.Api.OpenAppsecSettings.save(data) - .then(response => { - this.showSuccess(); - }) - .catch(err => { - console.error(err); - this.showError(err); - }); - } - }, - - showSuccess: function () { - this.ui.lp_success_info.show(); - setTimeout(() => { - this.ui.lp_success_info.fadeOut(); - }, 1000); - }, - - showError: function (err) { - this.ui.lp_error_info.show(); - this.ui.lp_error_info.html(err.message); - setTimeout(() => { - this.ui.lp_error_info.fadeOut(); - }, 3000); - }, - - onRender: function () { - let view = this; - - this.ui.lp_success_info.hide(); - this.ui.lp_error_info.hide(); - - App.Api.OpenAppsecSettings.get() - .then(response => { - if (!view.isDestroyed() && response) { - view.ui.local_policy_field.val(response); - } - }) - .catch(err => { - console.error(err); - }); - - 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 4a56bcab68..0000000000 --- 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 99c2630a0b..0000000000 --- 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 73f515e685..0000000000 --- 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 456db4ef75..0000000000 --- a/frontend/js/app/ui/header/main.ejs +++ /dev/null @@ -1,36 +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 9779b45c3a..0000000000 --- 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 1addcdd9ee..0000000000 --- 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 c90c61d5d5..0000000000 --- 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 aec0ddc8c6..0000000000 --- a/frontend/js/app/ui/menu/main.ejs +++ /dev/null @@ -1,55 +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 dabe26d369..0000000000 --- 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 c10532ef87..0000000000 --- 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 e8ed5c32eb..0000000000 --- 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 9ba84438aa..0000000000 --- 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 617a75fc9e..0000000000 --- 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 a45cc7ed78..0000000000 --- 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 8403075021..0000000000 --- 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 b616179604..0000000000 --- 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 af8049ce8c..0000000000 --- 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 fab5585bc0..0000000000 --- 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 4645a5c46e..0000000000 --- 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 c85c9cb1c1..0000000000 --- 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 9d3e26fb16..0000000000 --- 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 892cb83f60..0000000000 --- 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 42cb41ef13..0000000000 --- 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 dcc919ca3c..0000000000 --- a/frontend/js/i18n/messages.json +++ /dev/null @@ -1,303 +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", - "save-settings": "Save Settings", - "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}-open-appsec", - "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": "© 2024 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", - "local-policy-warning": "open-appsec YAML-based configuration file" - } - } -} diff --git a/frontend/js/index.js b/frontend/js/index.js deleted file mode 100644 index 3d817d71dc..0000000000 --- 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 21ce742436..0000000000 --- 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 c88368f890..0000000000 --- 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 0094e2a239..0000000000 --- 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 03fdc7e563..0000000000 --- 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 3f603acf25..0000000000 --- a/frontend/js/login/ui/login.ejs +++ /dev/null @@ -1,52 +0,0 @@ -
    -
    - -
    -
    diff --git a/frontend/js/login/ui/login.js b/frontend/js/login/ui/login.js deleted file mode 100644 index 39fca4233b..0000000000 --- a/frontend/js/login/ui/login.js +++ /dev/null @@ -1,43 +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 () { - const version = $('#login').data('version'); - return version; - } - } -}); diff --git a/frontend/js/models/access-list.js b/frontend/js/models/access-list.js deleted file mode 100644 index 0c2c4abea4..0000000000 --- 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 c929a0bd8b..0000000000 --- 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 c7d0b2d97c..0000000000 --- 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 98ceef29e1..0000000000 --- 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/openappsec-log.js b/frontend/js/models/openappsec-log.js deleted file mode 100755 index 2388f92b4a..0000000000 --- a/frontend/js/models/openappsec-log.js +++ /dev/null @@ -1,41 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - name: '', - eventSeverity: '', - assetName: '', - securityAction: '', - waapIncidentType: '', - httpSourceId: '', - sourceIP: '', - // 'Proxy-IP': '', - proxyIP: '', - httpHostName: '', - httpMethod: '', - // 'HTTP-Response-Code': '', - httpResponseCode: '', - httpUriPath: '', - // 'Protection-Name': '', - protectionName: '', - matchedLocation: '', - matchedParameter: '', - matchedSample: '', - eventPriority: '', - eventTopic: '', - eventName: '', - // Suggested Remediation if Applicable - suggestedRemediation: '' - }; - } -}); - -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 80f6bc73f6..0000000000 --- a/frontend/js/models/proxy-host-location.js +++ /dev/null @@ -1,40 +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', - use_openappsec: false, - openappsec_mode: 'detect-learn', - minimum_confidence: 'high' - } - }, - - toJSON() { - const r = Object.assign({}, this.attributes); - // convert use_openappsec to boolean - r.use_openappsec = !!r.use_openappsec; - 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 f8cb616614..0000000000 --- a/frontend/js/models/proxy-host.js +++ /dev/null @@ -1,43 +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, - use_openappsec: false, - openappsec_mode: "detect-learn", - minimum_confidence: "high", - 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 1d0b0de249..0000000000 --- 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 c70a4e9cb0..0000000000 --- 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 390c4fb05a..0000000000 --- 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 a8e4ed9e93..0000000000 --- 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 493968fd73..fc50411d34 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,47 +1,67 @@ { - "name": "nginx-proxy-manager", - "version": "2.12.3", - "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", + "prebuild": "npm run locale-compile", + "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.36.1", + "@tanstack/react-query": "^5.90.21", + "@tanstack/react-table": "^8.21.3", + "@uiw/react-textarea-code-editor": "^3.1.1", + "classnames": "^2.5.1", + "country-flag-icons": "^1.6.13", + "date-fns": "^4.1.0", + "ez-modal-react": "^1.0.5", + "formik": "^2.4.9", + "generate-password-browser": "^1.1.0", + "humps": "^2.0.1", + "query-string": "^9.3.1", + "react": "^19.2.4", + "react-bootstrap": "^2.10.10", + "react-dom": "^19.2.4", + "react-intl": "^8.1.3", + "react-markdown": "^10.1.0", + "react-router-dom": "^7.13.0", + "react-select": "^5.10.2", + "react-toastify": "^11.0.5", + "rooks": "^9.5.0" + }, + "devDependencies": { + "@biomejs/biome": "^2.4.2", + "@formatjs/cli": "^6.12.2", + "@tanstack/react-query-devtools": "^5.91.3", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@types/country-flag-icons": "^1.2.2", + "@types/humps": "^2.0.6", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@types/react-table": "^7.7.20", + "@vitejs/plugin-react": "^5.1.4", + "happy-dom": "^20.6.1", + "postcss": "^8.5.6", + "postcss-simple-vars": "^7.0.1", + "sass": "^1.97.3", + "tmp": "^0.2.5", + "typescript": "5.9.3", + "vite": "^7.3.1", + "vite-plugin-checker": "^0.12.0", + "vite-tsconfig-paths": "^6.1.1", + "vitest": "^4.0.18" + } } 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 0000000000..c87396f6a4 --- /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 0000000000..dc3c1163ca --- /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 0000000000..057a83d1b7 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/app-images/open-appsec-logo.svg b/frontend/public/images/open-appsec-logo.svg old mode 100755 new mode 100644 similarity index 100% rename from frontend/app-images/open-appsec-logo.svg rename to frontend/public/images/open-appsec-logo.svg diff --git a/frontend/public/images/unhealthy.svg b/frontend/public/images/unhealthy.svg new file mode 100644 index 0000000000..6c39eeb125 --- /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 cc1e96e1b6..0000000000 --- a/frontend/scss/custom.scss +++ /dev/null @@ -1,51 +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; -} - -.page { - width: fit-content; - min-width: 100%; -} - -table.card-table thead th { - vertical-align: top; -} \ No newline at end of file diff --git a/frontend/scss/fonts.scss b/frontend/scss/fonts.scss deleted file mode 100644 index f0ec1b7393..0000000000 --- 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 e12d5b694c..0000000000 --- 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 527330972f..0000000000 --- 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 3ddd0ed4dd..0000000000 --- a/frontend/scss/tabler-extra.scss +++ /dev/null @@ -1,170 +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; -} diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000000..ad335caeab --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,118 @@ +:root { + color-scheme: light dark; +} + +.light { + color-scheme: light; +} +.dark { + color-scheme: dark; +} + +.modal-backdrop { + --tblr-backdrop-opacity: 0.8 !important; +} + +[data-bs-theme="dark"] .modal-content { + --tblr-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; +} + +[data-bs-theme="dark"] .modal-backdrop { + --tblr-backdrop-bg: #000 !important; + --tblr-backdrop-opacity: 0.65 !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); + } +} + +/* Fix for dropdown menus being clipped by table-responsive containers. */ +.table-responsive .dropdown { + position: static; +} + +/* Fix for Tabler scrollbar compensation */ +@media (min-width: 992px) { + :host, :root { + margin-left: 0; + } +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000000..b6f0bba70d --- /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 0000000000..2910413b9a --- /dev/null +++ b/frontend/src/Router.tsx @@ -0,0 +1,94 @@ +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")); +const OpenAppsecLog = lazy(() => import("src/pages/OpenAppsecLog")); +const OpenAppsecLogAll = lazy(() => import("src/pages/OpenAppsecLog/All")); +const OpenAppsecLogDetailed = lazy(() => import("src/pages/OpenAppsecLog/Detailed")); +const OpenAppsecLogImportant = lazy(() => import("src/pages/OpenAppsecLog/Important")); +const OpenAppsecLogNotifications = lazy(() => import("src/pages/OpenAppsecLog/Notifications")); + +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 0000000000..d56f120c7f --- /dev/null +++ b/frontend/src/api/backend/base.ts @@ -0,0 +1,163 @@ +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(), + }; + const signal = abortController?.signal; + const response = await fetch(apiUrl, { method, headers, signal }); + return processResponse(response); +} diff --git a/frontend/src/api/backend/checkVersion.ts b/frontend/src/api/backend/checkVersion.ts new file mode 100644 index 0000000000..5095526ef5 --- /dev/null +++ b/frontend/src/api/backend/checkVersion.ts @@ -0,0 +1,8 @@ +import * as api from "./base"; +import type { VersionCheckResponse } from "./responseTypes"; + +export async function checkVersion(): Promise { + return await api.get({ + url: "/version/check", + }); +} diff --git a/frontend/src/api/backend/createAccessList.ts b/frontend/src/api/backend/createAccessList.ts new file mode 100644 index 0000000000..4a17f6755c --- /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 0000000000..ea81c44e4b --- /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 0000000000..f2a9f4503b --- /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 0000000000..fcde7cd602 --- /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 0000000000..a797f819d7 --- /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 0000000000..adad2a1ebf --- /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 0000000000..c5ea306dbc --- /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 0000000000..838c940b83 --- /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 0000000000..2257316c4b --- /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 0000000000..8e72ca84f1 --- /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 0000000000..7b7f2d8277 --- /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 0000000000..7c594b68fd --- /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 0000000000..db9e11f14f --- /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 0000000000..2a04a6e992 --- /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 0000000000..079f335945 --- /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 0000000000..e098a49000 --- /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 0000000000..a1afd3508a --- /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 0000000000..515f1e2170 --- /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 0000000000..d2d01f708a --- /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 0000000000..bbdb2f1601 --- /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 0000000000..13de898db2 --- /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 0000000000..03e3afa2ee --- /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 0000000000..d6d215b5df --- /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 0000000000..d6c062b91a --- /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 0000000000..1ca410e465 --- /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 0000000000..055681ce8d --- /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 0000000000..938d3a747d --- /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/getOpenAppsecLogs.ts b/frontend/src/api/backend/getOpenAppsecLogs.ts new file mode 100644 index 0000000000..9fe7e13ae9 --- /dev/null +++ b/frontend/src/api/backend/getOpenAppsecLogs.ts @@ -0,0 +1,55 @@ +import * as api from "./base"; + +/** + * A single open-appsec log entry as flattened by the backend. + * + * The backend reads `*.log*` files from `/var/log/nano_agent`, parses each + * line as JSON, and wraps it with a handful of useful top-level fields + * (camelCase, sourced from the open-appsec wire format — NOT from our SQL + * database). The full original JSON record is preserved under `meta`. + * + * Field semantics come from open-appsec itself; refer to its documentation + * for the canonical descriptions. + */ +export interface OpenAppsecLogEntry { + /** Source log file basename (e.g. "openappsec-logs", "openappsec-notifications"). */ + source: string; + /** Full raw log entry from open-appsec. */ + meta: Record; + serviceName: string; + eventPriority: string; + /** Examples observed: "Critical", "High", "Medium", "Low", "Info". Casing not guaranteed. */ + eventSeverity: string; + /** Examples observed: "Action Item", "Log". Casing not guaranteed. */ + eventLevel: string; + /** ISO timestamp from open-appsec. */ + eventTime: string; + assetName: string; + securityAction: string; + waapIncidentType: string; + httpSourceId: string; + sourceIP: string; + proxyIP: string; + httpHostName: string; + httpMethod: string; + httpUriPath: string; + eventTopic: string; + matchedLocation: string; + matchedParameter: string; + matchedSample: string; + eventName: string; +} + +/** + * Fetch ALL open-appsec log entries. + * + * The backend exposes a single endpoint `GET /api/openappsec-log` that + * returns every parsed log line from disk as a flat array. No pagination, + * no filtering, no search query is supported server-side; callers must + * filter/paginate client-side. + */ +export async function getOpenAppsecLogs(): Promise { + return await api.get({ + url: "/openappsec-log", + }); +} diff --git a/frontend/src/api/backend/getOpenAppsecSettings.ts b/frontend/src/api/backend/getOpenAppsecSettings.ts new file mode 100644 index 0000000000..fcc60cbff4 --- /dev/null +++ b/frontend/src/api/backend/getOpenAppsecSettings.ts @@ -0,0 +1,16 @@ +import * as api from "./base"; + +/** + * Raw open-appsec local policy YAML content. + * + * The backend reads `/ext/appsec/local_policy.yaml` and returns its + * contents as a JSON-encoded string. Callers are responsible for + * parsing the YAML if they need structured access. + */ +export type OpenAppsecLocalPolicy = string; + +export async function getOpenAppsecSettings(): Promise { + return await api.get({ + url: "/openappsec-settings", + }); +} diff --git a/frontend/src/api/backend/getProxyHost.ts b/frontend/src/api/backend/getProxyHost.ts new file mode 100644 index 0000000000..911b89c30d --- /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 0000000000..ba9c864a5c --- /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 0000000000..01df988acf --- /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 0000000000..63e292f795 --- /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 0000000000..daa54fee1f --- /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 0000000000..b8e992c629 --- /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 0000000000..82e10a0452 --- /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 0000000000..b5e9379b52 --- /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 0000000000..7f62a0e7de --- /dev/null +++ b/frontend/src/api/backend/getToken.ts @@ -0,0 +1,22 @@ +import * as api from "./base"; +import type { TokenResponse, TwoFactorChallengeResponse } from "./responseTypes"; + +export type LoginResponse = TokenResponse | TwoFactorChallengeResponse; + +export function isTwoFactorChallenge(response: LoginResponse): response is TwoFactorChallengeResponse { + return "requires2fa" in response && response.requires2fa === true; +} + +export async function getToken(identity: string, secret: string): Promise { + return await api.post({ + url: "/tokens", + data: { identity, secret }, + }); +} + +export async function verify2FA(challengeToken: string, code: string): Promise { + return await api.post({ + url: "/tokens/2fa", + data: { challengeToken, code }, + }); +} diff --git a/frontend/src/api/backend/getUser.ts b/frontend/src/api/backend/getUser.ts new file mode 100644 index 0000000000..a006782a61 --- /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 0000000000..dab584f422 --- /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 0000000000..8d97731914 --- /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 0000000000..4307bef5cf --- /dev/null +++ b/frontend/src/api/backend/index.ts @@ -0,0 +1,66 @@ +export * from "./checkVersion"; +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 "./getOpenAppsecLogs"; +export * from "./getOpenAppsecSettings"; +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 "./updateOpenAppsecSettings"; +export * from "./updateProxyHost"; +export * from "./updateRedirectionHost"; +export * from "./updateSetting"; +export * from "./updateStream"; +export * from "./updateUser"; +export * from "./uploadCertificate"; +export * from "./validateCertificate"; +export * from "./twoFactor"; diff --git a/frontend/src/api/backend/loginAsUser.ts b/frontend/src/api/backend/loginAsUser.ts new file mode 100644 index 0000000000..2ade379d80 --- /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 0000000000..89dce1edf4 --- /dev/null +++ b/frontend/src/api/backend/models.ts @@ -0,0 +1,216 @@ +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; + useOpenappsec?: boolean; + minimumConfidence?: "low" | "medium" | "high" | "critical"; + openappsecMode?: "inactive" | "detect-learn" | "prevent-learn"; +} + +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; + trustForwardedProto: boolean; + useOpenappsec?: boolean; + minimumConfidence?: "low" | "medium" | "high" | "critical"; + openappsecMode?: "inactive" | "detect-learn" | "prevent-learn"; + // 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 0000000000..de1848c258 --- /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 0000000000..0f3d082dc8 --- /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 0000000000..2f88ede547 --- /dev/null +++ b/frontend/src/api/backend/responseTypes.ts @@ -0,0 +1,46 @@ +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; +} + +export interface VersionCheckResponse { + current: string | null; + latest: string | null; + updateAvailable: boolean; +} + +export interface TwoFactorChallengeResponse { + requires2fa: boolean; + challengeToken: string; +} + +export interface TwoFactorStatusResponse { + enabled: boolean; + backupCodesRemaining: number; +} + +export interface TwoFactorSetupResponse { + secret: string; + otpauthUrl: string; +} + +export interface TwoFactorEnableResponse { + backupCodes: string[]; +} diff --git a/frontend/src/api/backend/setPermissions.ts b/frontend/src/api/backend/setPermissions.ts new file mode 100644 index 0000000000..47fa6306f7 --- /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 0000000000..23dfa4b7d1 --- /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 0000000000..71a780ef97 --- /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 0000000000..376e788186 --- /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 0000000000..0cfa5733dd --- /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 0000000000..2b71f720cf --- /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 0000000000..d2a24c94a5 --- /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/twoFactor.ts b/frontend/src/api/backend/twoFactor.ts new file mode 100644 index 0000000000..855e9cc1ed --- /dev/null +++ b/frontend/src/api/backend/twoFactor.ts @@ -0,0 +1,37 @@ +import * as api from "./base"; +import type { TwoFactorEnableResponse, TwoFactorSetupResponse, TwoFactorStatusResponse } from "./responseTypes"; + +export async function get2FAStatus(userId: number | "me"): Promise { + return await api.get({ + url: `/users/${userId}/2fa`, + }); +} + +export async function start2FASetup(userId: number | "me"): Promise { + return await api.post({ + url: `/users/${userId}/2fa`, + }); +} + +export async function enable2FA(userId: number | "me", code: string): Promise { + return await api.post({ + url: `/users/${userId}/2fa/enable`, + data: { code }, + }); +} + +export async function disable2FA(userId: number | "me", code: string): Promise { + return await api.del({ + url: `/users/${userId}/2fa`, + params: { + code, + }, + }); +} + +export async function regenerateBackupCodes(userId: number | "me", code: string): Promise { + return await api.post({ + url: `/users/${userId}/2fa/backup-codes`, + data: { code }, + }); +} diff --git a/frontend/src/api/backend/updateAccessList.ts b/frontend/src/api/backend/updateAccessList.ts new file mode 100644 index 0000000000..7a23566b0b --- /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 0000000000..5b14b076bb --- /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 0000000000..1eb5631b14 --- /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/updateOpenAppsecSettings.ts b/frontend/src/api/backend/updateOpenAppsecSettings.ts new file mode 100644 index 0000000000..50365faf5d --- /dev/null +++ b/frontend/src/api/backend/updateOpenAppsecSettings.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { OpenAppsecLocalPolicy } from "./getOpenAppsecSettings"; + +export interface OpenAppsecSettingsUpdate { + localPolicy: OpenAppsecLocalPolicy; +} + +export async function updateOpenAppsecSettings(payload: OpenAppsecSettingsUpdate): Promise { + return await api.put({ + url: "/openappsec-settings", + data: payload, + }); +} diff --git a/frontend/src/api/backend/updateProxyHost.ts b/frontend/src/api/backend/updateProxyHost.ts new file mode 100644 index 0000000000..e7ee3d9064 --- /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 0000000000..4cc36f1e6c --- /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 0000000000..bcb94059b1 --- /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 0000000000..508bcece0b --- /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 0000000000..d063e127d7 --- /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 0000000000..b28c4e0962 --- /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 0000000000..d404e514cd --- /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 0000000000..6d02f93145 --- /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 0000000000..46a9664c2f --- /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 0000000000..a162d1cd66 --- /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 0000000000..420f6c8f31 --- /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 0000000000..9dda8c3de0 --- /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 { intl, 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={intl.formatMessage({ id: "access-list.rule-source.placeholder" })} + /> +
    +
    + +
    + ))} +
    + +
    +
    +

    + +

    +
    +
    + + + + +
    +
    +
    + + ); +} diff --git a/frontend/src/components/Form/AccessField.tsx b/frontend/src/components/Form/AccessField.tsx new file mode 100644 index 0000000000..afcbd0cf7d --- /dev/null +++ b/frontend/src/components/Form/AccessField.tsx @@ -0,0 +1,101 @@ +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 { useLocaleState } from "src/context"; +import { useAccessLists } from "src/hooks"; +import { formatDateTime, 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 { locale } = useLocaleState(); + 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 ? formatDateTime(item?.createdOn, locale) : "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 0000000000..fdf04b0180 --- /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 0000000000..182654811a --- /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 { intl, 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 0000000000..bdba2f2e23 --- /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 0000000000..4b48ef3cb9 --- /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 0000000000..c3f5b2311e --- /dev/null +++ b/frontend/src/components/Form/LocationsFields.tsx @@ -0,0 +1,292 @@ +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"; + +type LocationFieldValue = string | number | boolean; + +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, + useOpenappsec: false, + openappsecMode: "detect-learn", + minimumConfidence: "high", + }; + + 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: LocationFieldValue) => { + 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 0000000000..208a384864 --- /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/OpenAppsecFields.tsx b/frontend/src/components/Form/OpenAppsecFields.tsx new file mode 100644 index 0000000000..b35927cc8e --- /dev/null +++ b/frontend/src/components/Form/OpenAppsecFields.tsx @@ -0,0 +1,135 @@ +import cn from "classnames"; +import { Field, useFormikContext } from "formik"; +import { intl, T } from "src/locale"; + +interface Props { + /** + * Field name prefix. Empty string for proxy-host level fields + * (e.g. "useOpenappsec"). For per-location overrides use a prefix + * ending in a dot (e.g. "locations[0]."). + */ + namePrefix?: string; + color?: string; +} + +export function OpenAppsecFields({ namePrefix = "", color = "bg-lime" }: Props) { + const { values } = useFormikContext(); + + // Resolve current value of useOpenappsec under the given prefix + const getValueAtPath = (path: string): any => { + if (!path) return undefined; + // Supports simple paths like "useOpenappsec" or "locations[0].useOpenappsec" + return path + .replace(/\[(\d+)\]/g, ".$1") + .split(".") + .reduce((acc: any, key: string) => (acc == null ? acc : acc[key]), values); + }; + + const useOpenappsecName = `${namePrefix}useOpenappsec`; + const openappsecModeName = `${namePrefix}openappsecMode`; + const minimumConfidenceName = `${namePrefix}minimumConfidence`; + + const useOpenappsec = !!getValueAtPath(useOpenappsecName); + + const toggleClasses = "form-check-input"; + const toggleEnabled = cn(toggleClasses, color); + + return ( +
    +
    + +
    +
    +
    + + {({ field }: any) => ( +
    + + +
    + )} +
    +
    +
    + + {({ field }: any) => ( +
    + + +
    + )} +
    +
    +
    +
    + ); +} diff --git a/frontend/src/components/Form/SSLCertificateField.tsx b/frontend/src/components/Form/SSLCertificateField.tsx new file mode 100644 index 0000000000..6ab3ea92c1 --- /dev/null +++ b/frontend/src/components/Form/SSLCertificateField.tsx @@ -0,0 +1,140 @@ +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 { useLocaleState } from "src/context"; +import { useCertificates } from "src/hooks"; +import { formatDateTime, 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 { locale } = useLocaleState(); + 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 ? formatDateTime(cert.expiresOn, locale) : "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) => ( + + )} + +
    +
    +
    + ); + + const getHttpAdvancedOptions = () =>( +
    +
    + +
    +
    + + {({ field }: any) => ( + + )} + +
    +
    +
    +
    + ); + + return ( +
    + {forHttp ? getHttpOptions() : null} + {newCertificate ? ( + <> + + {({ field }: any) => ( + + )} + + {requireDomainNames ? : null} + {dnsChallenge ? : null} + + ) : null} + {forProxyHost && forHttp ? getHttpAdvancedOptions() : null} +
    + ); +} diff --git a/frontend/src/components/Form/index.ts b/frontend/src/components/Form/index.ts new file mode 100644 index 0000000000..6dcd284be3 --- /dev/null +++ b/frontend/src/components/Form/index.ts @@ -0,0 +1,10 @@ +export * from "./AccessClientFields"; +export * from "./AccessField"; +export * from "./BasicAuthFields"; +export * from "./DNSProviderFields"; +export * from "./DomainNamesField"; +export * from "./LocationsFields"; +export * from "./NginxConfigField"; +export * from "./OpenAppsecFields"; +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 0000000000..c038066b4f --- /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 0000000000..7a13ded3f1 --- /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 0000000000..14d96e6da1 --- /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 0000000000..7d3bec1ea1 --- /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 0000000000..6f39e7fa76 --- /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 0000000000..2d7dbb8da1 --- /dev/null +++ b/frontend/src/components/LocalePicker.tsx @@ -0,0 +1,53 @@ +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"; + +interface Props { + menuAlign?: "start" | "end"; +} + +function LocalePicker({ menuAlign = "start" }: Props) { + 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: any) => ( + { + 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 0000000000..b6a414af6a --- /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 0000000000..00086bbac4 --- /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 0000000000..a96a6b1f9a --- /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 0000000000..01a9cb52ba --- /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 0000000000..57bb21946b --- /dev/null +++ b/frontend/src/components/SiteFooter.tsx @@ -0,0 +1,78 @@ +import { useCheckVersion, useHealth } from "src/hooks"; +import { T } from "src/locale"; + +export function SiteFooter() { + const health = useHealth(); + const { data: versionData } = useCheckVersion(); + + 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 0000000000..a88f81c9e7 --- /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 0000000000..f00d38d6ed --- /dev/null +++ b/frontend/src/components/SiteHeader.tsx @@ -0,0 +1,141 @@ +import { IconLock, IconLogout, IconShieldLock, 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, showTwoFactorModal, 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 ( +
    +
    + +
    + +
    + Logo +
    + Nginx Proxy Manager +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + +
    +
    {currentUser?.nickname}
    +
    + +
    +
    +
    +
    +
    + {/* biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: This div is not interactive. */} +
    e.stopPropagation()}> +
    +
    {currentUser?.nickname}
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + { + e.preventDefault(); + showUserModal("me"); + }} + > + + + + { + e.preventDefault(); + showChangePasswordModal("me"); + }} + > + + + + { + e.preventDefault(); + showTwoFactorModal("me"); + }} + > + + + + +
    +
    +
    +
    +
    + ); +} diff --git a/frontend/src/components/SiteMenu.tsx b/frontend/src/components/SiteMenu.tsx new file mode 100644 index 0000000000..cc7c0f7be5 --- /dev/null +++ b/frontend/src/components/SiteMenu.tsx @@ -0,0 +1,214 @@ +import { + IconBook, + IconDeviceDesktop, + IconHome, + IconLock, + IconNotebook, + 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: "/open-appsec-log", + icon: IconNotebook, + label: "menu.openAppsecLog", + 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() { + const closeMenu = () => setTimeout(() => { + const navbarToggler = document.querySelector(".navbar-toggler"); + const navbarMenu = document.querySelector("#navbar-menu"); + if (navbarToggler && navbarMenu?.classList.contains("show")) { + navbarToggler.click(); + } + }, 300); + + return ( +
    + +
    + ); +} diff --git a/frontend/src/components/Table/EmptyRow.tsx b/frontend/src/components/Table/EmptyRow.tsx new file mode 100644 index 0000000000..2daee952ac --- /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 0000000000..b5830fd9c8 --- /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 0000000000..6b791bfbf6 --- /dev/null +++ b/frontend/src/components/Table/Formatter/CertificateFormatter.tsx @@ -0,0 +1,18 @@ +import type { Certificate } from "src/api/backend"; +import { T } from "src/locale"; + +interface Props { + certificate?: Certificate; +} +export function CertificateFormatter({ certificate }: Props) { + let translation = "http-only"; + if (certificate) { + translation = certificate.provider; + if (translation === "letsencrypt") { + translation = "lets-encrypt"; + } else if (translation === "other") { + translation = "certificates.custom"; + } + } + return ; +} diff --git a/frontend/src/components/Table/Formatter/CertificateInUseFormatter.tsx b/frontend/src/components/Table/Formatter/CertificateInUseFormatter.tsx new file mode 100644 index 0000000000..a49374d659 --- /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 0000000000..830b06e2ef --- /dev/null +++ b/frontend/src/components/Table/Formatter/DateFormatter.tsx @@ -0,0 +1,21 @@ +import cn from "classnames"; +import { differenceInDays, isPast } from "date-fns"; +import { useLocaleState } from "src/context"; +import { formatDateTime, parseDate } from "src/locale"; + +interface Props { + value: string; + highlightPast?: boolean; + highlistNearlyExpired?: boolean; +} +export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: Props) { + const { locale } = useLocaleState(); + const d = parseDate(value); + const dateIsPast = d ? isPast(d) : false; + const days = d ? differenceInDays(d, new Date()) : 0; + const cl = cn({ + "text-danger": highlightPast && dateIsPast, + "text-warning": highlistNearlyExpired && !dateIsPast && days <= 30 && days >= 0, + }); + return {formatDateTime(value, locale)}; +} diff --git a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx new file mode 100644 index 0000000000..35e8393e39 --- /dev/null +++ b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx @@ -0,0 +1,73 @@ +import cn from "classnames"; +import type { ReactNode } from "react"; +import { useLocaleState } from "src/context"; +import { formatDateTime, 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. + // Apparently the domain can be null or undefined sometimes. + // This try is just a safeguard to prevent the whole formatter from breaking. + if (!domain) return null; + try { + let onClick: ((e: React.MouseEvent) => void) | undefined; + if (domain.includes("*")) { + onClick = (e: React.MouseEvent) => e.preventDefault(); + } + return ( + + {domain} + + ); + } catch { + return null; + } +}; + +export function DomainsFormatter({ domains, createdOn, niceName, provider, color }: Props) { + const { locale } = useLocaleState(); + const elms: ReactNode[] = []; + + if ((!domains || domains.length === 0) && !niceName) { + elms.push( + + Unknown + , + ); + } + if (!domains || (niceName && provider !== "letsencrypt")) { + elms.push( + + {niceName} + , + ); + } + + if (domains) { + 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 0000000000..3371c66a31 --- /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 0000000000..1220fa0961 --- /dev/null +++ b/frontend/src/components/Table/Formatter/EventFormatter.tsx @@ -0,0 +1,81 @@ +import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconShield, IconUser } from "@tabler/icons-react"; +import cn from "classnames"; +import type { AuditLog } from "src/api/backend"; +import { useLocaleState } from "src/context"; +import { formatDateTime, 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) { + const { locale } = useLocaleState(); + return ( +
    +
    + {getIcon(row)} + +   — {getEventValue(row)} +
    +
    {formatDateTime(row.createdOn, locale)}
    +
    + ); +} diff --git a/frontend/src/components/Table/Formatter/GravatarFormatter.tsx b/frontend/src/components/Table/Formatter/GravatarFormatter.tsx new file mode 100644 index 0000000000..58b820ba46 --- /dev/null +++ b/frontend/src/components/Table/Formatter/GravatarFormatter.tsx @@ -0,0 +1,19 @@ +const defaultImg = "/images/default-avatar.jpg"; + +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 0000000000..2e025605dc --- /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/SeverityFormatter.tsx b/frontend/src/components/Table/Formatter/SeverityFormatter.tsx new file mode 100644 index 0000000000..dd13fb4559 --- /dev/null +++ b/frontend/src/components/Table/Formatter/SeverityFormatter.tsx @@ -0,0 +1,32 @@ +import cn from "classnames"; + +const colorForSeverity = (severity: string | undefined | null): string => { + switch ((severity ?? "").trim().toLowerCase()) { + case "critical": + return "red"; + case "high": + return "orange"; + case "medium": + return "yellow"; + case "low": + return "azure"; + case "info": + return "cyan"; + default: + return "secondary"; + } +}; + +interface Props { + value: string | undefined | null; +} + +/** + * Renders an open-appsec event severity as a coloured Tabler badge. Case + * insensitive; "Critical"/"critical" both work. + */ +export function SeverityFormatter({ value }: Props) { + const color = colorForSeverity(value); + const label = value && value.trim().length > 0 ? value : "—"; + return {label}; +} diff --git a/frontend/src/components/Table/Formatter/TrueFalseFormatter.tsx b/frontend/src/components/Table/Formatter/TrueFalseFormatter.tsx new file mode 100644 index 0000000000..633395023e --- /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 0000000000..d83ccbfa73 --- /dev/null +++ b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx @@ -0,0 +1,23 @@ +import { useLocaleState } from "src/context"; +import { formatDateTime, T } from "src/locale"; + +interface Props { + value: string; + createdOn?: string; + disabled?: boolean; +} +export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) { + const { locale } = useLocaleState(); + 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 0000000000..39cd4eeb8b --- /dev/null +++ b/frontend/src/components/Table/Formatter/index.ts @@ -0,0 +1,12 @@ +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 "./SeverityFormatter"; +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 0000000000..d8fe9bf3a4 --- /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 0000000000..bf627ce992 --- /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 0000000000..0df329c804 --- /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 0000000000..630340919d --- /dev/null +++ b/frontend/src/components/Table/TableLayout.tsx @@ -0,0 +1,24 @@ +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 0000000000..d471d45d5c --- /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 0000000000..2233d2f8fb --- /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 0000000000..9cb6697877 --- /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 0000000000..d4b6e33c93 --- /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 0000000000..861f637e14 --- /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 0000000000..34a67ec48d --- /dev/null +++ b/frontend/src/context/AuthContext.tsx @@ -0,0 +1,127 @@ +import { useQueryClient } from "@tanstack/react-query"; +import { createContext, type ReactNode, useContext, useState } from "react"; +import { useIntervalWhen } from "rooks"; +import { + getToken, + isTwoFactorChallenge, + loginAsUser, + refreshToken, + verify2FA, + type TokenResponse, +} from "src/api/backend"; +import AuthStore from "src/modules/AuthStore"; + +// 2FA challenge state +export interface TwoFactorChallenge { + challengeToken: string; +} + +// Context +export interface AuthContextType { + authenticated: boolean; + twoFactorChallenge: TwoFactorChallenge | null; + login: (username: string, password: string) => Promise; + verifyTwoFactor: (code: string) => Promise; + cancelTwoFactor: () => void; + 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 [twoFactorChallenge, setTwoFactorChallenge] = useState(null); + + const handleTokenUpdate = (response: TokenResponse) => { + AuthStore.set(response); + setAuthenticated(true); + setTwoFactorChallenge(null); + }; + + const login = async (identity: string, secret: string) => { + const response = await getToken(identity, secret); + if (isTwoFactorChallenge(response)) { + setTwoFactorChallenge({ challengeToken: response.challengeToken }); + return; + } + handleTokenUpdate(response); + }; + + const verifyTwoFactor = async (code: string) => { + if (!twoFactorChallenge) { + throw new Error("No 2FA challenge pending"); + } + const response = await verify2FA(twoFactorChallenge.challengeToken, code); + handleTokenUpdate(response); + }; + + const cancelTwoFactor = () => { + setTwoFactorChallenge(null); + }; + + 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, + twoFactorChallenge, + login, + verifyTwoFactor, + cancelTwoFactor, + loginAs, + logout, + }; + + 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 0000000000..ca30a5f313 --- /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 0000000000..e01ad022c2 --- /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 0000000000..a24e065744 --- /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 0000000000..18f423bf39 --- /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 0000000000..274b775779 --- /dev/null +++ b/frontend/src/hooks/index.ts @@ -0,0 +1,24 @@ +export * from "./useAccessList"; +export * from "./useAccessLists"; +export * from "./useAuditLog"; +export * from "./useAuditLogs"; +export * from "./useCertificate"; +export * from "./useCertificates"; +export * from "./useCheckVersion"; +export * from "./useDeadHost"; +export * from "./useDeadHosts"; +export * from "./useDnsProviders"; +export * from "./useHealth"; +export * from "./useHostReport"; +export * from "./useOpenAppsecLogs"; +export * from "./useOpenAppsecSettings"; +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 0000000000..1f6638588b --- /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 0000000000..cb052f68ce --- /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 0000000000..95e08ebcd9 --- /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 0000000000..bbe8b50636 --- /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 0000000000..fc99c8402b --- /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 0000000000..261c79d819 --- /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/useCheckVersion.ts b/frontend/src/hooks/useCheckVersion.ts new file mode 100644 index 0000000000..83ee9960be --- /dev/null +++ b/frontend/src/hooks/useCheckVersion.ts @@ -0,0 +1,18 @@ +import { useQuery } from "@tanstack/react-query"; +import { checkVersion, type VersionCheckResponse } from "src/api/backend"; + +const fetchVersion = () => checkVersion(); + +const useCheckVersion = (options = {}) => { + return useQuery({ + queryKey: ["version-check"], + queryFn: fetchVersion, + refetchOnWindowFocus: false, + retry: 5, + refetchInterval: 30 * 1000, // 30 seconds + staleTime: 5 * 60 * 1000, // 5 mins + ...options, + }); +}; + +export { fetchVersion, useCheckVersion }; diff --git a/frontend/src/hooks/useDeadHost.ts b/frontend/src/hooks/useDeadHost.ts new file mode 100644 index 0000000000..dd8355e3aa --- /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 0000000000..744d28c4b0 --- /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 0000000000..ec6a5736be --- /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 0000000000..6a261c95b6 --- /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 0000000000..c54096d20b --- /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/useOpenAppsecLogs.ts b/frontend/src/hooks/useOpenAppsecLogs.ts new file mode 100644 index 0000000000..d263d2b94f --- /dev/null +++ b/frontend/src/hooks/useOpenAppsecLogs.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { getOpenAppsecLogs, type OpenAppsecLogEntry } from "src/api/backend"; + +const fetchOpenAppsecLogs = () => { + return getOpenAppsecLogs(); +}; + +const useOpenAppsecLogs = (options = {}) => { + return useQuery({ + queryKey: ["openappsec-logs"], + queryFn: fetchOpenAppsecLogs, + staleTime: 10 * 1000, + ...options, + }); +}; + +export { fetchOpenAppsecLogs, useOpenAppsecLogs }; diff --git a/frontend/src/hooks/useOpenAppsecSettings.ts b/frontend/src/hooks/useOpenAppsecSettings.ts new file mode 100644 index 0000000000..4d6903ca46 --- /dev/null +++ b/frontend/src/hooks/useOpenAppsecSettings.ts @@ -0,0 +1,31 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + getOpenAppsecSettings, + type OpenAppsecLocalPolicy, + type OpenAppsecSettingsUpdate, + updateOpenAppsecSettings, +} from "src/api/backend"; + +const QUERY_KEY = ["openappsec-settings"]; + +const useOpenAppsecSettings = (options = {}) => { + return useQuery({ + queryKey: QUERY_KEY, + queryFn: () => getOpenAppsecSettings(), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetOpenAppsecSettings = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: OpenAppsecSettingsUpdate) => updateOpenAppsecSettings(values), + onSuccess: async () => { + queryClient.invalidateQueries({ queryKey: QUERY_KEY }); + queryClient.invalidateQueries({ queryKey: ["audit-logs"] }); + }, + }); +}; + +export { useOpenAppsecSettings, useSetOpenAppsecSettings }; diff --git a/frontend/src/hooks/useProxyHost.ts b/frontend/src/hooks/useProxyHost.ts new file mode 100644 index 0000000000..24e7f4fae2 --- /dev/null +++ b/frontend/src/hooks/useProxyHost.ts @@ -0,0 +1,68 @@ +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, + trustForwardedProto: 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 0000000000..86366fef7d --- /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 0000000000..ff2126538d --- /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 0000000000..3b7eda6071 --- /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 0000000000..ce843d6504 --- /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 0000000000..dfdddc1a08 --- /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 0000000000..0f0129dec7 --- /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 0000000000..941c0626e8 --- /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 0000000000..4f39949014 --- /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 0000000000..8c4d802fb2 --- /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/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx new file mode 100755 index 0000000000..8e65546a2a --- /dev/null +++ b/frontend/src/locale/IntlProvider.tsx @@ -0,0 +1,139 @@ +import { createIntl, createIntlCache } from "react-intl"; +import langBg from "./lang/bg.json"; +import langDe from "./lang/de.json"; +import langPt from "./lang/pt.json"; +import langEn from "./lang/en.json"; +import langEs from "./lang/es.json"; +import langFr from "./lang/fr.json"; +import langGa from "./lang/ga.json"; +import langId from "./lang/id.json"; +import langIt from "./lang/it.json"; +import langJa from "./lang/ja.json"; +import langKo from "./lang/ko.json"; +import langNl from "./lang/nl.json"; +import langPl from "./lang/pl.json"; +import langRu from "./lang/ru.json"; +import langSk from "./lang/sk.json"; +import langCs from "./lang/cs.json"; +import langVi from "./lang/vi.json"; +import langZh from "./lang/zh.json"; +import langTr from "./lang/tr.json"; +import langHu from "./lang/hu.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", langEn], + ["de", "de-DE", langDe], + ["es", "es-ES", langEs], + ["pt", "pt-PT", langPt], + ["fr", "fr-FR", langFr], + ["ga", "ga-IE", langGa], + ["ja", "ja-JP", langJa], + ["it", "it-IT", langIt], + ["nl", "nl-NL", langNl], + ["pl", "pl-PL", langPl], + ["ru", "ru-RU", langRu], + ["sk", "sk-SK", langSk], + ["cs", "cs-CZ", langCs], + ["vi", "vi-VN", langVi], + ["zh", "zh-CN", langZh], + ["ko", "ko-KR", langKo], + ["bg", "bg-BG", langBg], + ["id", "id-ID", langId], + ["tr", "tr-TR", langTr], + ["hu", "hu-HU", langHu], +]; + +const loadMessages = (locale?: string): typeof langList & typeof langEn => { + const thisLocale = (locale || "en").slice(0, 2); + + // ensure this lang exists in localeOptions above, otherwise fallback to en + if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) { + return Object.assign({}, langList, langEn); + } + + return Object.assign({}, langList, langEn, localeOptions.find(([code]) => code === thisLocale)?.[2]); +}; + +const getFlagCodeForLocale = (locale?: string) => { + const thisLocale = (locale || "en").slice(0, 2); + + // only add to this if your flag is different from the locale code + const specialCases: Record = { + ja: "jp", // Japan + zh: "cn", // China + vi: "vn", // Vietnam + ko: "kr", // Korea + cs: "cz", // Czechia + }; + + if (specialCases[thisLocale]) { + return specialCases[thisLocale].toUpperCase(); + } + return thisLocale.toUpperCase(); +}; + +const getLocale = (short = false) => { + let loc = window.localStorage.getItem("locale"); + if (!loc) { + loc = document.documentElement.lang; + } + if (short) { + return loc.slice(0, 2); + } + // finally, fallback + if (!loc) { + loc = "en"; + } + 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, + }, + )} + + ); +}; + +//console.log("L:", localeOptions); + +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 0000000000..7016cf6235 --- /dev/null +++ b/frontend/src/locale/README.md @@ -0,0 +1,50 @@ +# 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/[yourlang]/* +- frontend/src/locale/src/HelpDoc/index.tsx +- frontend/src/locale/IntlProvider.tsx +- frontend/check-locales.cjs + + +## Checking for missing translations in languages + +Run `node check-locales.cjs` in this frontend folder. diff --git a/frontend/src/locale/Utils.test.tsx b/frontend/src/locale/Utils.test.tsx new file mode 100644 index 0000000000..e998dc319c --- /dev/null +++ b/frontend/src/locale/Utils.test.tsx @@ -0,0 +1,74 @@ +import { formatDateTime } from "src/locale"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +describe("DateFormatter", () => { + // Keep a reference to the real Intl to restore later + const RealIntl = global.Intl; + const desiredTimeZone = "Europe/London"; + const desiredLocale = "en-GB"; + + beforeAll(() => { + // Ensure Node-based libs using TZ behave deterministically + try { + process.env.TZ = desiredTimeZone; + } catch { + // ignore if not available + } + + // Mock Intl.DateTimeFormat so formatting is stable regardless of host + const MockedDateTimeFormat = class extends RealIntl.DateTimeFormat { + constructor(_locales?: string | string[], options?: Intl.DateTimeFormatOptions) { + super(desiredLocale, { + ...options, + timeZone: desiredTimeZone, + }); + } + } as unknown as typeof Intl.DateTimeFormat; + + global.Intl = { + ...RealIntl, + DateTimeFormat: MockedDateTimeFormat, + }; + }); + + afterAll(() => { + // Restore original Intl after tests + global.Intl = RealIntl; + }); + + it("format date from iso date", () => { + const value = "2024-01-01T00:00:00.000Z"; + const text = formatDateTime(value); + expect(text).toBe("1 Jan 2024, 12:00:00 am"); + }); + + it("format date from unix timestamp number", () => { + const value = 1762476112; + const text = formatDateTime(value); + expect(text).toBe("7 Nov 2025, 12:41:52 am"); + }); + + it("format date from unix timestamp string", () => { + const value = "1762476112"; + const text = formatDateTime(value); + expect(text).toBe("7 Nov 2025, 12:41:52 am"); + }); + + it("catch bad format from string", () => { + const value = "this is not a good date"; + const text = formatDateTime(value); + expect(text).toBe("this is not a good date"); + }); + + it("catch bad format from number", () => { + const value = -100; + const text = formatDateTime(value); + expect(text).toBe("-100"); + }); + + it("catch bad format from number as string", () => { + const value = "-100"; + const text = formatDateTime(value); + expect(text).toBe("-100"); + }); +}); diff --git a/frontend/src/locale/Utils.ts b/frontend/src/locale/Utils.ts new file mode 100644 index 0000000000..d422427737 --- /dev/null +++ b/frontend/src/locale/Utils.ts @@ -0,0 +1,46 @@ +import { + fromUnixTime, + type IntlFormatFormatOptions, + intlFormat, + parseISO, +} from "date-fns"; + +const isUnixTimestamp = (value: unknown): boolean => { + if (typeof value !== "number" && typeof value !== "string") return false; + const num = Number(value); + if (!Number.isFinite(num)) return false; + // Check plausible Unix timestamp range: from 1970 to ~year 3000 + // Support both seconds and milliseconds + if (num > 0 && num < 10000000000) return true; // seconds (<= 10 digits) + if (num >= 10000000000 && num < 32503680000000) return true; // milliseconds (<= 13 digits) + return false; +}; + +const parseDate = (value: string | number): Date | null => { + if (typeof value !== "number" && typeof value !== "string") return null; + try { + return isUnixTimestamp(value) ? fromUnixTime(+value) : parseISO(`${value}`); + } catch { + return null; + } +}; + +const formatDateTime = (value: string | number, locale = "en-US"): string => { + const d = parseDate(value); + if (!d) return `${value}`; + try { + return intlFormat( + d, + { + dateStyle: "medium", + timeStyle: "medium", + hourCycle: "h12", + } as IntlFormatFormatOptions, + { locale }, + ); + } catch { + return `${value}`; + } +}; + +export { formatDateTime, parseDate, isUnixTimestamp }; diff --git a/frontend/src/locale/index.ts b/frontend/src/locale/index.ts new file mode 100644 index 0000000000..bdd1343e9d --- /dev/null +++ b/frontend/src/locale/index.ts @@ -0,0 +1,2 @@ +export * from "./IntlProvider"; +export * from "./Utils"; diff --git a/frontend/src/locale/scripts/locale-sort.cjs b/frontend/src/locale/scripts/locale-sort.cjs new file mode 100644 index 0000000000..f7f72cdc4c --- /dev/null +++ b/frontend/src/locale/scripts/locale-sort.cjs @@ -0,0 +1,69 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const path = require("path"); + +const DIR = path.resolve(__dirname, "../src"); + +// Function to sort object keys recursively +function sortKeys(obj) { + if (obj === null || typeof obj !== "object" || obj instanceof Array) { + return obj; + } + + const sorted = {}; + const keys = Object.keys(obj).sort(); + for (const key of keys) { + const value = obj[key]; + if (typeof value === "object" && value !== null && !(value instanceof Array)) { + sorted[key] = sortKeys(value); + } else { + sorted[key] = value; + } + } + return sorted; +} + +// Get all JSON files in the directory +const files = fs.readdirSync(DIR).filter((file) => { + return file.endsWith(".json") && file !== "lang-list.json"; +}); + +files.forEach((file) => { + const filePath = path.join(DIR, file); + const stats = fs.statSync(filePath); + + if (!stats.isFile()) { + return; + } + + if (stats.size === 0) { + console.log(`Skipping empty file ${file}`); + return; + } + + try { + // Read original content + const originalContent = fs.readFileSync(filePath, "utf8"); + const originalJson = JSON.parse(originalContent); + + // Sort keys + const sortedJson = sortKeys(originalJson); + + // Convert back to string with tabs + const sortedContent = JSON.stringify(sortedJson, null, "\t") + "\n"; + + // Compare (normalize whitespace) + if (originalContent.trim() === sortedContent.trim()) { + console.log(`${file} is already sorted`); + return; + } + + // Write sorted content + fs.writeFileSync(filePath, sortedContent, "utf8"); + console.log(`Sorted ${file}`); + } catch (error) { + console.error(`Error processing ${file}:`, error.message); + } +}); + diff --git a/frontend/src/locale/scripts/locale-sort.sh b/frontend/src/locale/scripts/locale-sort.sh new file mode 100755 index 0000000000..f7458887cf --- /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" + tmp=$(mktemp) && jq --tab --sort-keys . "$file" > "$tmp" && mv "$tmp" "$file" + fi +done diff --git a/frontend/src/locale/src/HelpDoc/bg/AccessLists.md b/frontend/src/locale/src/HelpDoc/bg/AccessLists.md new file mode 100644 index 0000000000..8e7f250773 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/bg/AccessLists.md @@ -0,0 +1,7 @@ +## Какво представлява Списъкът за достъп? + +Списъците за достъп предоставят черен или бял списък от конкретни клиентски IP адреси, както и удостоверяване за Прокси хостове чрез базова HTTP автентикация. + +Можете да конфигурирате множество клиентски правила, потребителски имена и пароли в един Списък за достъп и след това да го приложите към един или повече _Прокси хостове_. + +Това е най-полезно при препращани уеб услуги, които нямат вградени механизми за удостоверяване, или когато искате да защитите достъпа от неизвестни клиенти. diff --git a/frontend/src/locale/src/HelpDoc/bg/Certificates.md b/frontend/src/locale/src/HelpDoc/bg/Certificates.md new file mode 100644 index 0000000000..c138a753c3 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/bg/Certificates.md @@ -0,0 +1,21 @@ +## Помощ за сертификати + +### HTTP сертификат + +HTTP валидираният сертификат означава, че сървърите на Let’s Encrypt ще се опитат да достигнат вашите домейни по HTTP (не по HTTPS!) и ако успеят, ще издадат сертификата. + +За този метод трябва да имате създаден _Прокси хост_ за вашия/вашите домейни, който да е достъпен по HTTP и да сочи към тази Nginx инсталация. След като бъде издаден сертификат, можете да промените _Прокси хоста_ така, че да използва сертификата и за HTTPS връзки. Въпреки това, _Прокси хостът_ трябва да остане конфигуриран за достъп по HTTP, за да може сертификатът да се подновява. + +Този процес _не_ поддържа wildcard домейни. + +### DNS сертификат + +DNS валидираният сертификат изисква използването на DNS Provider плъгин. Този DNS Provider ще бъде използван за временно създаване на записи във вашия домейн, след което Let’s Encrypt ще ги провери, за да се увери, че сте собственикът, и при успех ще издаде сертификата. + +Не е необходимо да имате _Прокси хост_, създаден предварително, за да заявите този тип сертификат. Нито е нужно вашият _Прокси хост_ да бъде конфигуриран за достъп по HTTP. + +Този процес _поддържа_ wildcard домейни. + +### Персонализиран сертификат + +Използвайте тази опция, за да качите собствен SSL сертификат, предоставен от ваша сертификатна агенция. diff --git a/frontend/src/locale/src/HelpDoc/bg/DeadHosts.md b/frontend/src/locale/src/HelpDoc/bg/DeadHosts.md new file mode 100644 index 0000000000..79ad960b68 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/bg/DeadHosts.md @@ -0,0 +1,10 @@ +## Какво представлява 404 хост? + +404 хост е просто конфигурация на хост, който показва страница с грешка 404. + +Това може да е полезно, когато вашият домейн е индексиран в търсачките и искате +да предоставите по-приятна страница за грешка или да уведомите индексиращите системи, +че страниците на домейна вече не съществуват. + +Допълнително предимство на този хост е възможността да проследявате логовете на заявките +към него и да виждате реферерите. diff --git a/frontend/src/locale/src/HelpDoc/bg/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/bg/ProxyHosts.md new file mode 100644 index 0000000000..ef82c3936d --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/bg/ProxyHosts.md @@ -0,0 +1,7 @@ +## Какво представлява Прокси хост? + +Прокси хост е входна точка за уеб услуга, която искате да препращате. + +Той предоставя възможност за SSL терминaция на услуга, която може да няма вградена поддръжка на SSL. + +Прокси хостовете са най-често използваната функция в Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/bg/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/bg/RedirectionHosts.md new file mode 100644 index 0000000000..06890dc6e3 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/bg/RedirectionHosts.md @@ -0,0 +1,7 @@ +## Какво представлява Хост за пренасочване? + +Хостът за пренасочване пренасочва заявките от входящия домейн и прехвърля +потребителя към друг домейн. + +Най-честата причина за използване на този тип хост е, когато вашият уебсайт +промени домейна си, но все още има линкове от търсачки или реферери, които сочат към стария домейн. diff --git a/frontend/src/locale/src/HelpDoc/bg/Streams.md b/frontend/src/locale/src/HelpDoc/bg/Streams.md new file mode 100644 index 0000000000..4beb381da1 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/bg/Streams.md @@ -0,0 +1,6 @@ +## Какво представлява Потокът (Stream)? + +Относително нова функция за Nginx, Потокът позволява препращане на TCP/UDP +трафик директно към друг компютър в мрежата. + +Това е полезно, ако хоствате игрови сървъри, FTP или SSH сървъри. diff --git a/frontend/src/locale/src/HelpDoc/bg/index.ts b/frontend/src/locale/src/HelpDoc/bg/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/bg/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/cs/AccessLists.md b/frontend/src/locale/src/HelpDoc/cs/AccessLists.md new file mode 100644 index 0000000000..78d9be6e14 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/cs/AccessLists.md @@ -0,0 +1,7 @@ +## Co je seznam přístupů? + +Seznamy přístupů poskytují blacklist nebo whitelist konkrétních IP adres klientů spolu s ověřením pro proxy hostitele prostřednictvím základního ověřování HTTP. + +Můžete nakonfigurovat více pravidel pro klienty, uživatelská jména a hesla pro jeden seznam přístupu a poté ho použít na jednoho nebo více proxy hostitelů. + +Toto je nejužitečnější pro přesměrované webové služby, které nemají vestavěné ověřovací mechanismy, nebo pokud se chcete chránit před neznámými klienty. diff --git a/frontend/src/locale/src/HelpDoc/cs/Certificates.md b/frontend/src/locale/src/HelpDoc/cs/Certificates.md new file mode 100644 index 0000000000..78e67f3187 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/cs/Certificates.md @@ -0,0 +1,32 @@ +## Pomoc s certifikáty + +### Certifikát HTTP + +Certifikát ověřený prostřednictvím protokolu HTTP znamená, že servery Let's Encrypt se +pokusí připojit k vašim doménám přes protokol HTTP (nikoli HTTPS!) a v případě úspěchu +vydají váš certifikát. + +Pro tuto metodu budete muset mít pro své domény vytvořeného _Proxy Host_, který +je přístupný přes HTTP a směruje na tuto instalaci Nginx. Po vydání certifikátu +můžete změnit _Proxy Host_ tak, aby tento certifikát používal i pro HTTPS +připojení. _Proxy Host_ však bude stále potřeba nakonfigurovat pro přístup přes HTTP, +aby se certifikát mohl obnovit. + +Tento proces _nepodporuje_ domény se zástupnými znaky. + +### Certifikát DNS + +Certifikát ověřený DNS vyžaduje použití pluginu DNS Provider. Tento DNS +Provider se použije na vytvoření dočasných záznamů ve vaší doméně a poté Let's +Encrypt ověří tyto záznamy, aby se ujistil, že jste vlastníkem, a pokud bude úspěšný, +vydá váš certifikát. + +Před požádáním o tento typ certifikátu není potřeba vytvořit _Proxy Host_. +Není také potřeba mít _Proxy Host_ nakonfigurovaný pro přístup HTTP. + +Tento proces _podporuje_ domény se zástupnými znaky. + +### Vlastní certifikát + +Tuto možnost použijte na nahrání vlastního SSL certifikátu, který vám poskytla vaše +certifikační autorita. diff --git a/frontend/src/locale/src/HelpDoc/cs/DeadHosts.md b/frontend/src/locale/src/HelpDoc/cs/DeadHosts.md new file mode 100644 index 0000000000..61d4dcfa9a --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/cs/DeadHosts.md @@ -0,0 +1,10 @@ +## Co je to 404 Host? + +404 Host je jednoduše nastavení hostitele, které zobrazuje stránku 404. + +To může být užitečné, pokud je vaše doména uvedena ve vyhledávačích a chcete +poskytnout hezčí chybovou stránku nebo konkrétně oznámit vyhledávačům, že +stránky domény již neexistují. + +Další výhodou tohoto hostitele je sledování protokolů o návštěvách a +zobrazení odkazů. diff --git a/frontend/src/locale/src/HelpDoc/cs/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/cs/ProxyHosts.md new file mode 100644 index 0000000000..3637641098 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/cs/ProxyHosts.md @@ -0,0 +1,7 @@ +## Co je proxy hostitel? + +Proxy hostitel je příchozí koncový bod pro webovou službu, kterou chcete přesměrovat. + +Poskytuje volitelné ukončení SSL pro vaši službu, která nemusí mít zabudovanou podporu SSL. + +Proxy hostitelé jsou nejběžnějším použitím pro Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/cs/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/cs/RedirectionHosts.md new file mode 100644 index 0000000000..ed261663b9 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/cs/RedirectionHosts.md @@ -0,0 +1,7 @@ +## Co je přesměrovací hostitel? + +Přesměrovací hostitel přesměruje požadavky z příchozí domény a přesměruje +návštěvníka na jinou doménu. + +Nejčastějším důvodem pro použití tohoto typu hostitele je situace, kdy vaše webová stránka změní +doménu, ale stále máte odkazy ve vyhledávačích nebo referenční odkazy směřující na starou doménu. diff --git a/frontend/src/locale/src/HelpDoc/cs/Streams.md b/frontend/src/locale/src/HelpDoc/cs/Streams.md new file mode 100644 index 0000000000..8dc3f4eea4 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/cs/Streams.md @@ -0,0 +1,6 @@ +## Co je stream? + +Stream je relativně nová funkce pro Nginx, která slouží na přesměrování TCP/UDP +datového toku přímo do jiného počítače v síti. + +Pokud provozujete herní servery, FTP nebo SSH servery, tato funkce se vám může hodit. diff --git a/frontend/src/locale/src/HelpDoc/cs/index.ts b/frontend/src/locale/src/HelpDoc/cs/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/cs/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/de/AccessLists.md b/frontend/src/locale/src/HelpDoc/de/AccessLists.md new file mode 100644 index 0000000000..315406a7d2 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/de/AccessLists.md @@ -0,0 +1,7 @@ +## Was ist eine Zugriffsliste? + +Zugriffslisten bieten eine Blacklist oder Whitelist mit bestimmten Client-IP-Adressen sowie eine Authentifizierung für die Proxy-Hosts über die grundlegende HTTP-Authentifizierung. + +Sie können mehrere Client-Regeln, Benutzernamen und Passwörter für eine einzelne Zugriffsliste konfigurieren und diese dann auf einen oder mehrere Proxy-Hosts anwenden. + +Dies ist besonders nützlich für weitergeleitete Webdienste, die keine integrierten Authentifizierungsmechanismen haben, oder wenn Sie sich vor unbekannten Clients schützen möchten. diff --git a/frontend/src/locale/src/HelpDoc/de/Certificates.md b/frontend/src/locale/src/HelpDoc/de/Certificates.md new file mode 100644 index 0000000000..be15ffa618 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/de/Certificates.md @@ -0,0 +1,32 @@ +## Hilfe zu Zertifikaten + +### HTTP-Zertifikat + +Ein HTTP-validiertes Zertifikat bedeutet, dass Let's Encrypt-Server +versuchen, Ihre Domains über HTTP (nicht HTTPS!) zu erreichen, und wenn dies erfolgreich ist, +stellen sie Ihr Zertifikat aus. + +Für diese Methode müssen Sie einen _Proxy-Host_ für Ihre Domain(s) erstellen, der +über HTTP zugänglich ist und auf diese Nginx-Installation verweist. Nachdem ein Zertifikat +ausgestellt wurde, können Sie den _Proxy-Host_ so ändern, dass dieses Zertifikat auch für HTTPS-Verbindungen +verwendet wird. Der _Proxy-Host_ muss jedoch weiterhin für den HTTP-Zugriff konfiguriert sein, + damit das Zertifikat erneuert werden kann. + +Dieser Prozess unterstützt keine Wildcard-Domains. + +### DNS-Zertifikat + +Für ein DNS-validiertes Zertifikat müssen Sie ein DNS-Provider-Plugin verwenden. Dieser DNS- +Provider wird verwendet, um temporäre Einträge auf Ihrer Domain zu erstellen. Anschließend fragt Let's +Encrypt diese Einträge ab, um sicherzustellen, dass Sie der Eigentümer sind. Bei Erfolg wird +Ihr Zertifikat ausgestellt. + +Sie müssen vor der Beantragung dieser Art von Zertifikat keinen _Proxy-Host_ erstellen. +Sie müssen Ihren _Proxy-Host_ auch nicht für den HTTP-Zugriff konfigurieren. + +Dieser Prozess unterstützt Wildcard-Domains. + +### Benutzerdefiniertes Zertifikat + +Verwenden Sie diese Option, um Ihr eigenes SSL-Zertifikat hochzuladen, das Ihnen von Ihrer eigenen +Zertifizierungsstelle bereitgestellt wurde. diff --git a/frontend/src/locale/src/HelpDoc/de/DeadHosts.md b/frontend/src/locale/src/HelpDoc/de/DeadHosts.md new file mode 100644 index 0000000000..a73992c29a --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/de/DeadHosts.md @@ -0,0 +1,10 @@ +## Was ist ein 404-Host? + +Ein 404-Host ist ein Host-Setup, das eine 404-Seite anzeigt. + +Dies kann nützlich sein, wenn Ihre Domain in Suchmaschinen gelistet ist und Sie +eine ansprechendere Fehlerseite bereitstellen oder den Suchindexern ausdrücklich mitteilen möchten, dass +die Domain-Seiten nicht mehr existieren. + +Ein weiterer Vorteil dieses Hosts besteht darin, dass Sie die Protokolle für Zugriffe darauf verfolgen und +die Verweise anzeigen können. diff --git a/frontend/src/locale/src/HelpDoc/de/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/de/ProxyHosts.md new file mode 100644 index 0000000000..f116b857d3 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/de/ProxyHosts.md @@ -0,0 +1,7 @@ +## Was ist ein Proxy-Host? + +Ein Proxy-Host ist der eingehende Endpunkt für einen Webdienst, den Sie weiterleiten möchten. + +Er bietet optionale SSL-Terminierung für Ihren Dienst, der möglicherweise keine integrierte SSL-Unterstützung hat. + +Proxy-Hosts sind die häufigste Verwendung für den Nginx Proxy Manager. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/de/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/de/RedirectionHosts.md new file mode 100644 index 0000000000..3e06919be8 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/de/RedirectionHosts.md @@ -0,0 +1,7 @@ +## Was ist ein Redirection Host? + +Ein Redirection Host leitet Anfragen von der eingehenden Domain weiter und leitet den +Besucher zu einer anderen Domain weiter. + +Der häufigste Grund für die Verwendung dieses Host-Typs ist, wenn Ihre Website die +Domain wechselt, aber Sie noch Suchmaschinen- oder Referrer-Links haben, die auf die alte Domain verweisen. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/de/Streams.md b/frontend/src/locale/src/HelpDoc/de/Streams.md new file mode 100644 index 0000000000..35cd539d5e --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/de/Streams.md @@ -0,0 +1,6 @@ +## Was ist ein Stream? + +Ein Stream ist eine relativ neue Funktion von Nginx, die dazu dient, TCP/UDP-Datenverkehr +direkt an einen anderen Computer im Netzwerk weiterzuleiten. + +Wenn Sie Spielserver, FTP- oder SSH-Server betreiben, kann dies sehr nützlich sein. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/de/index.ts b/frontend/src/locale/src/HelpDoc/de/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/de/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/en/AccessLists.md b/frontend/src/locale/src/HelpDoc/en/AccessLists.md new file mode 100644 index 0000000000..cef5826371 --- /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 0000000000..d79dd04b85 --- /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 0000000000..ef4f3bc4a8 --- /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 0000000000..e9630d0576 --- /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 0000000000..e57b1b80d7 --- /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 0000000000..358f3e5aa8 --- /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 0000000000..a9bb46ba7c --- /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/es/AccessLists.md b/frontend/src/locale/src/HelpDoc/es/AccessLists.md new file mode 100644 index 0000000000..f0a089f65e --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/es/AccessLists.md @@ -0,0 +1,7 @@ +## ¿Qué es una Lista de Acceso? + +Las Listas de Acceso proporcionan una lista negra o blanca de direcciones IP de cliente específicas junto con autenticación para los Hosts Proxy a través de Autenticación HTTP Básica. + +Puede configurar múltiples reglas de cliente, nombres de usuario y contraseñas para una única Lista de Acceso y luego aplicarla a uno o más _Hosts Proxy_. + +Esto es más útil para servicios web reenviados que no tienen mecanismos de autenticación integrados o cuando desea protegerse de clientes desconocidos. diff --git a/frontend/src/locale/src/HelpDoc/es/Certificates.md b/frontend/src/locale/src/HelpDoc/es/Certificates.md new file mode 100644 index 0000000000..5907147ffc --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/es/Certificates.md @@ -0,0 +1,32 @@ +## Ayuda de Certificados + +### Certificado HTTP + +Un certificado validado por HTTP significa que los servidores de Let's Encrypt +intentarán acceder a tus dominios a través de HTTP (¡no HTTPS!) y, si tienen éxito, +emitirán tu certificado. + +Para este método, deberás tener un _Host Proxy_ creado para tu(s) dominio(s) que +sea accesible por HTTP y que apunte a esta instalación de Nginx. Después de que se +haya emitido un certificado, puedes modificar el _Host Proxy_ para que también use +este certificado para conexiones HTTPS. Sin embargo, el _Host Proxy_ seguirá +necesitando estar configurado para acceso HTTP para que el certificado se renueve. + +Este proceso _no_ admite dominios comodín. + +### Certificado DNS + +Un certificado validado por DNS requiere que uses un complemento de Proveedor de DNS. +Este Proveedor de DNS se usará para crear registros temporales en tu dominio y luego +Let's Encrypt consultará esos registros para asegurarse de que eres el propietario y, +si tiene éxito, emitirá tu certificado. + +No necesitas tener un _Host Proxy_ creado antes de solicitar este tipo de certificado. +Tampoco necesitas tener tu _Host Proxy_ configurado para acceso HTTP. + +Este proceso _sí_ admite dominios comodín. + +### Certificado Personalizado + +Usa esta opción para cargar tu propio Certificado SSL, proporcionado por tu propia +Autoridad de Certificación. diff --git a/frontend/src/locale/src/HelpDoc/es/DeadHosts.md b/frontend/src/locale/src/HelpDoc/es/DeadHosts.md new file mode 100644 index 0000000000..bc7756842c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/es/DeadHosts.md @@ -0,0 +1,10 @@ +## ¿Qué es un Host 404? + +Un Host 404 es simplemente una configuración de host que muestra una página 404. + +Esto puede ser útil cuando tu dominio está listado en los motores de búsqueda y deseas +proporcionar una página de error más agradable o específicamente para indicar a los indexadores de búsqueda que +las páginas del dominio ya no existen. + +Otro beneficio de tener este host es rastrear los registros de visitas a él y +ver los referentes. diff --git a/frontend/src/locale/src/HelpDoc/es/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/es/ProxyHosts.md new file mode 100644 index 0000000000..57994b7cd9 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/es/ProxyHosts.md @@ -0,0 +1,7 @@ +## ¿Qué es un Host Proxy? + +Un Host Proxy es el punto de entrada para un servicio web que deseas reenviar. + +Proporciona terminación SSL opcional para tu servicio que podría no tener soporte SSL integrado. + +Los Hosts Proxy son el uso más común del Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/es/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/es/RedirectionHosts.md new file mode 100644 index 0000000000..7a05a3012e --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/es/RedirectionHosts.md @@ -0,0 +1,7 @@ +## ¿Qué es un Host de Redirección? + +Un Host de Redirección redirigirá las solicitudes del dominio entrante e impulsará al +visitante a otro dominio. + +La razón más común para usar este tipo de host es cuando tu sitio web cambia de +dominios pero aún tienes enlaces de motores de búsqueda o referencias apuntando al dominio anterior. diff --git a/frontend/src/locale/src/HelpDoc/es/Streams.md b/frontend/src/locale/src/HelpDoc/es/Streams.md new file mode 100644 index 0000000000..f085352f10 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/es/Streams.md @@ -0,0 +1,6 @@ +## ¿Qué es un Stream? + +Una característica relativamente nueva para Nginx, un Stream servirá para reenviar tráfico TCP/UDP +directamente a otra computadora en la red. + +Si estás ejecutando servidores de juegos, FTP o servidores SSH esto puede ser muy útil. diff --git a/frontend/src/locale/src/HelpDoc/es/index.ts b/frontend/src/locale/src/HelpDoc/es/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/es/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/fr/AccessLists.md b/frontend/src/locale/src/HelpDoc/fr/AccessLists.md new file mode 100644 index 0000000000..ca76bf625d --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/fr/AccessLists.md @@ -0,0 +1,7 @@ +## Qu'est-ce qu'une liste d'accès ? + +Les listes d'accès permettent de définir une liste noire ou une liste blanche d'adresses IP clientes spécifiques, ainsi que l'authentification des Hôtes Proxy via l'authentification HTTP de base. + +Vous pouvez configurer plusieurs règles client, noms d'utilisateur et mots de passe pour une même liste d'accès, puis l'appliquer à un ou plusieurs Hôtes Proxy. + +Ceci est particulièrement utile pour les services web redirigés qui ne disposent pas de mécanismes d'authentification intégrés ou lorsque vous souhaitez vous protéger contre les clients inconnus. diff --git a/frontend/src/locale/src/HelpDoc/fr/Certificates.md b/frontend/src/locale/src/HelpDoc/fr/Certificates.md new file mode 100644 index 0000000000..f5fa32594d --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/fr/Certificates.md @@ -0,0 +1,23 @@ +## Aide concernant les certificats + +### Certificat HTTP + +Un certificat HTTP validé signifie que les serveurs de Let's Encrypt testeront d'accéder à vos domaines via HTTP (et non HTTPS !). En cas de succès, ils émettront votre certificat. + +Pour cette méthode, vous devrez créer un Hôte Proxy pour votre ou vos domaines. Cet Hôte Proxy devra être accessible via HTTP et pointer vers cette installation Nginx. Une fois le certificat émis, vous pourrez modifier l'Hôte Proxy pour qu'il utilise également ce certificat pour les connexions HTTPS. Cependant, l'Hôte Proxy devra toujours être configuré pour l'accès HTTP afin que le certificat puisse être renouvelé. + +Ce processus ne prend pas en charge les domaines génériques. + +### Certificat DNS + +Un certificat DNS validé nécessite l'utilisation du plugin Fournisseur DNS. Fournisseur DNS créera des enregistrements temporaires sur votre domaine. Let's Encrypt interrogera ensuite ces enregistrements pour vérifier que vous en êtes bien le propriétaire. En cas de succès, votre certificat sera émis. + +Il n'est pas nécessaire de créer un Hôte Proxy avant de demander ce type de certificat. + +Il n'est pas non plus nécessaire de configurer votre Hôte Proxy pour l'accès HTTP. + +Ce processus prend en charge les domaines génériques. + +## Certificat personnalisé + +Utilisez cette option pour importer votre propre certificat SSL, fourni par votre autorité de certification. diff --git a/frontend/src/locale/src/HelpDoc/fr/DeadHosts.md b/frontend/src/locale/src/HelpDoc/fr/DeadHosts.md new file mode 100644 index 0000000000..8d22c290ce --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/fr/DeadHosts.md @@ -0,0 +1,7 @@ +## Qu'est-ce qu'un serveur 404 ? + +Un Hôte 404 est simplement un hôte configuré pour afficher une page 404. + +Cela peut s'avérer utile lorsque votre domaine est indexé par les moteurs de recherche et que vous souhaitez fournir une page d'erreur plus conviviale ou, plus précisément, indiquer aux moteurs de recherche que les pages du domaine n'existent plus. + +Un autre avantage de cet hôte est la possibilité de suivre les journaux et de consulter les sites référenceurs. diff --git a/frontend/src/locale/src/HelpDoc/fr/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/fr/ProxyHosts.md new file mode 100644 index 0000000000..10c75b99dc --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/fr/ProxyHosts.md @@ -0,0 +1,7 @@ +## Qu'est-ce qu'un hôte proxy ? + +Un Hôte Proxy est le point de terminaison entrant d'un service web que vous souhaitez rediriger. + +Il assure la terminaison SSL optionnelle pour votre service qui ne prend pas en charge SSL nativement. + +Les Hôtes Proxy constituent l'utilisation la plus courante du Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/fr/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/fr/RedirectionHosts.md new file mode 100644 index 0000000000..f12319d4a7 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/fr/RedirectionHosts.md @@ -0,0 +1,5 @@ +## Qu'est-ce qu'un serveur de redirection ? + +Un Hôte de Redirection redirige les requêtes provenant du domaine entrant vers un autre domaine. + +On utilise généralement ce type d'hôte lorsque votre site web change de domaine, mais que des liens provenant des moteurs de recherche ou des sites référenceurs pointent toujours vers l'ancien domaine. diff --git a/frontend/src/locale/src/HelpDoc/fr/Streams.md b/frontend/src/locale/src/HelpDoc/fr/Streams.md new file mode 100644 index 0000000000..74668b9928 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/fr/Streams.md @@ -0,0 +1,5 @@ +## Qu'est-ce qu'un Stream ? + +Fonctionnalité relativement récente de Nginx, un Stream permet de rediriger le trafic TCP/UDP directement vers un autre ordinateur du réseau. + +Si vous gérez des serveurs de jeux, FTP ou SSH, cela peut s'avérer très utile. diff --git a/frontend/src/locale/src/HelpDoc/fr/index.ts b/frontend/src/locale/src/HelpDoc/fr/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/fr/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/ga/AccessLists.md b/frontend/src/locale/src/HelpDoc/ga/AccessLists.md new file mode 100644 index 0000000000..64b35e9981 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ga/AccessLists.md @@ -0,0 +1,7 @@ +## Cad is Liosta Rochtana ann? + +Soláthraíonn Liostaí Rochtana liosta dubh nó liosta bán de sheoltaí IP cliant ar leith mar aon le fíordheimhniú do na hÓstaigh Seachfhreastalaí trí Fhíordheimhniú Bunúsach HTTP. + +Is féidir leat rialacha cliant, ainmneacha úsáideora agus pasfhocail iolracha a chumrú le haghaidh Liosta Rochtana aonair agus ansin iad sin a chur i bhfeidhm ar _Óstach Seachfhreastalaí_ amháin nó níos mó. + +Tá sé seo an-úsáideach i gcás seirbhísí gréasáin atreoraithe nach bhfuil meicníochtaí fíordheimhnithe ionsuite iontu nó nuair is mian leat cosaint a dhéanamh ar chliaint anaithnide. diff --git a/frontend/src/locale/src/HelpDoc/ga/Certificates.md b/frontend/src/locale/src/HelpDoc/ga/Certificates.md new file mode 100644 index 0000000000..c69d7b4afc --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ga/Certificates.md @@ -0,0 +1,21 @@ +## Cabhair le Deimhnithe + +### Teastas HTTP + +Ciallaíonn deimhniú bailíochtaithe HTTP go ndéanfaidh freastalaithe Let's Encrypt iarracht teacht ar do fhearainn thar HTTP (ní HTTPS!) agus má éiríonn leo, eiseoidh siad do theastas. + +Chun an modh seo a dhéanamh, beidh ort _Óstach Proxy_ a chruthú do do fhearainn(eanna) atá inrochtana le HTTP agus ag pointeáil chuig an suiteáil Nginx seo. Tar éis deimhniú a thabhairt, is féidir leat an _Óstach Proxy_ a mhodhnú chun an deimhniú seo a úsáid le haghaidh naisc HTTPS freisin. Mar sin féin, beidh ort an _Óstach Proxy_ a chumrú fós le haghaidh rochtain HTTP chun go ndéanfar an deimhniú a athnuachan. + +_Ní thacaíonn_ an próiseas seo le fearainn fiáine. + +### Teastas DNS + +Éilíonn deimhniú bailíochtaithe DNS ort breiseán Soláthraí DNS a úsáid. Úsáidfear an Soláthraí DNS seo chun taifid shealadacha a chruthú ar do fhearann agus ansin déanfaidh Let's Encrypt fiosrúchán ar na taifid sin lena chinntiú gurb tusa an t-úinéir agus má éiríonn leo, eiseoidh siad do theastas. + +Ní gá duit _Óstach Proxy_ a chruthú sula n-iarrann tú an cineál seo teastais. Ní gá duit do _Óstach Proxy_ a chumrú le haghaidh rochtana HTTP ach an oiread. + +_Tacaíonn_ an próiseas seo le fearainn fiáine. + +### Teastas Saincheaptha + +Úsáid an rogha seo chun do Theastas SSL féin a uaslódáil, mar a sholáthraíonn d'Údarás Deimhnithe féin é. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/ga/DeadHosts.md b/frontend/src/locale/src/HelpDoc/ga/DeadHosts.md new file mode 100644 index 0000000000..f6d20ed466 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ga/DeadHosts.md @@ -0,0 +1,7 @@ +## Cad is Óstach 404 ann? + +Is socrú óstach a thaispeánann leathanach 404 é Óstach 404. + +Is féidir leis seo a bheith úsáideach nuair a bhíonn do fhearann liostaithe in innill chuardaigh agus más mian leat leathanach earráide níos deise a sholáthar nó a chur in iúl do na hinnéacsóirí cuardaigh go sonrach nach bhfuil na leathanaigh fearainn ann a thuilleadh. + +Buntáiste eile a bhaineann leis an óstach seo a bheith agat ná go bhfeictear na logaí le haghaidh amas agus go bhfeictear na tagairtí. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/ga/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/ga/ProxyHosts.md new file mode 100644 index 0000000000..542b3ec9da --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ga/ProxyHosts.md @@ -0,0 +1,7 @@ +## Cad is Óstach Seachfhreastalaí ann? + +Is é Óstach Seachfhreastalaí an críochphointe isteach do sheirbhís ghréasáin ar mhaith leat a atreorú. + +Soláthraíonn sé foirceannadh SSL roghnach do do sheirbhís nach bhfuil tacaíocht SSL ionsuite inti b'fhéidir. + +Is iad Óstaigh Seachfhreastalaí an úsáid is coitianta a bhaintear as Bainisteoir Seachfhreastalaí Nginx. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/ga/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/ga/RedirectionHosts.md new file mode 100644 index 0000000000..5995c14298 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ga/RedirectionHosts.md @@ -0,0 +1,5 @@ +## Cad is Óstach Athsheolta ann? + +Déanfaidh Óstach Athsheolta iarratais a atreorú ón bhfearann ag teacht isteach agus an breathnóir a bhrú chuig fearann eile. + +Is é an chúis is coitianta le húsáid a bhaint as an gcineál seo óstála ná nuair a athraíonn do shuíomh Gréasáin fearainn ach go bhfuil naisc innill chuardaigh nó atreoraithe agat fós ag tagairt don seanfhearann. diff --git a/frontend/src/locale/src/HelpDoc/ga/Streams.md b/frontend/src/locale/src/HelpDoc/ga/Streams.md new file mode 100644 index 0000000000..cac45da4e0 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ga/Streams.md @@ -0,0 +1,5 @@ +## Cad is Sruth ann? + +Gné réasúnta nua do Nginx is ea Sruth a sheolfaidh trácht TCP/UDP go díreach chuig ríomhaire eile ar an líonra. + +Más freastalaithe cluichí, freastalaithe FTP nó SSH atá á rith agat, d’fhéadfadh sé seo a bheith úsáideach. diff --git a/frontend/src/locale/src/HelpDoc/ga/index.ts b/frontend/src/locale/src/HelpDoc/ga/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ga/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/hu/AccessLists.md b/frontend/src/locale/src/HelpDoc/hu/AccessLists.md new file mode 100644 index 0000000000..c3a62dd8f1 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/hu/AccessLists.md @@ -0,0 +1,7 @@ +## Mi az a hozzáférési lista? + +A hozzáférési listák feketelistát vagy fehérlistát biztosítanak meghatározott kliens IP-címekhez, valamint alap HTTP-hitelesítést (Basic HTTP Authentication) a proxy kiszolgálókhoz. + +Egyetlen hozzáférési listához több kliensszabályt, felhasználónevet és jelszót is beállíthatsz, majd ezt alkalmazhatod egy vagy több _Proxy Kiszolgáló_-ra. + +Ez különösen hasznos olyan továbbított webszolgáltatásoknál, amelyekben nincs beépített hitelesítési mechanizmus, vagy amikor ismeretlen kliensektől szeretnél védeni. diff --git a/frontend/src/locale/src/HelpDoc/hu/Certificates.md b/frontend/src/locale/src/HelpDoc/hu/Certificates.md new file mode 100644 index 0000000000..8df4a23bda --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/hu/Certificates.md @@ -0,0 +1,21 @@ +## Tanúsítványok súgó + +### HTTP tanúsítvány + +A HTTP érvényes tanúsítvány azt jelenti, hogy a Let's Encrypt szerverek megpróbálják elérni a domaineket HTTP-n keresztül (nem HTTPS-en!), és ha sikerül, kiállítják a tanúsítványt. + +Ehhez a módszerhez létre kell hoznod egy _Proxy Kiszolgáló_-t a domain(ek)hez, amely HTTP-n keresztül elérhető és erre az Nginx telepítésre mutat. Miután a tanúsítvány megérkezett, módosíthatod a _Proxy Kiszolgáló_-t, hogy ezt a tanúsítványt használja a HTTPS kapcsolatokhoz is. Azonban a _Proxy Kiszolgáló_-nak továbbra is konfigurálva kell lennie HTTP hozzáféréshez, hogy a tanúsítvány megújulhasson. + +Ez a folyamat _nem_ támogatja a helyettesítő karakteres domaineket. + +### DNS tanúsítvány + +A DNS érvényes tanúsítvány megköveteli, hogy DNS szolgáltató plugint használj. Ez a DNS szolgáltató ideiglenes rekordokat hoz létre a domainen, majd a Let's Encrypt lekérdezi ezeket a rekordokat, hogy megbizonyosodjon a tulajdonjogról, és ha sikeres, kiállítják a tanúsítványt. + +Nem szükséges előzetesen _Proxy Kiszolgáló_-t létrehozni az ilyen típusú tanúsítvány igényléséhez. Nem is kell a _Proxy Kiszolgáló_-t HTTP hozzáférésre konfigurálni. + +Ez a folyamat _támogatja_ a helyettesítő karakteres domaineket. + +### Egyéni tanúsítvány + +Ezt az opciót használd a saját SSL tanúsítvány feltöltéséhez, amelyet a saját tanúsítványkibocsátód biztosított. diff --git a/frontend/src/locale/src/HelpDoc/hu/DeadHosts.md b/frontend/src/locale/src/HelpDoc/hu/DeadHosts.md new file mode 100644 index 0000000000..78ad57ea84 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/hu/DeadHosts.md @@ -0,0 +1,7 @@ +## Mi az a 404-es Kiszolgáló? + +A 404-es Kiszolgáló egyszerűen egy olyan kiszolgáló beállítás, amely egy 404-es oldalt jelenít meg. + +Ez akkor lehet hasznos, ha a domained szerepel a keresőmotorokban, és egy szebb hibaoldalt szeretnél nyújtani, vagy kifejezetten jelezni akarod a keresőrobotoknak, hogy a domain oldalai már nem léteznek. + +Ennek a kiszolgálónak egy további előnye, hogy nyomon követheted a rá érkező találatokat a naplókban, és megtekintheted a hivatkozó oldalakat. diff --git a/frontend/src/locale/src/HelpDoc/hu/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/hu/ProxyHosts.md new file mode 100644 index 0000000000..74a333bf1d --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/hu/ProxyHosts.md @@ -0,0 +1,7 @@ +## Mi az a Proxy Kiszolgáló? + +A Proxy Kiszolgáló egy bejövő végpont egy olyan webszolgáltatáshoz, amelyet továbbítani szeretnél. + +Opcionális SSL lezárást biztosít a szolgáltatásodhoz, amelyben esetleg nincs beépített SSL támogatás. + +A Proxy Kiszolgálók az Nginx Proxy Manager leggyakoribb felhasználási módjai. diff --git a/frontend/src/locale/src/HelpDoc/hu/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/hu/RedirectionHosts.md new file mode 100644 index 0000000000..576989a953 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/hu/RedirectionHosts.md @@ -0,0 +1,5 @@ +## Mi az az Átirányító Kiszolgáló? + +Az Átirányító Kiszolgáló a bejövő domainre érkező kéréseket átirányítja, és a látogatót egy másik domainre küldi. + +Ennek a kiszolgálótípusnak a leggyakoribb használati oka az, amikor a weboldalad domaint vált, de a keresőkben vagy a hivatkozó oldalakon még mindig a régi domainre mutató linkek vannak. diff --git a/frontend/src/locale/src/HelpDoc/hu/Streams.md b/frontend/src/locale/src/HelpDoc/hu/Streams.md new file mode 100644 index 0000000000..1785565387 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/hu/Streams.md @@ -0,0 +1,5 @@ +## Mi az a Stream? + +Az Nginx egy viszonylag új funkciója, a Stream arra szolgál, hogy a TCP/UDP forgalmat közvetlenül továbbítsa a hálózat egy másik számítógépére. + +Ha játékszervereket, FTP vagy SSH szervereket futtatsz, ez hasznos lehet. diff --git a/frontend/src/locale/src/HelpDoc/hu/index.ts b/frontend/src/locale/src/HelpDoc/hu/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/hu/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/id/AccessLists.md b/frontend/src/locale/src/HelpDoc/id/AccessLists.md new file mode 100644 index 0000000000..33c0b493d1 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/id/AccessLists.md @@ -0,0 +1,7 @@ +## Apa itu Daftar Akses? + +Daftar Akses menyediakan daftar hitam atau daftar putih alamat IP klien tertentu beserta autentikasi untuk Host Proxy melalui Autentikasi HTTP Basic. + +Anda dapat mengonfigurasi beberapa aturan klien, nama pengguna, dan kata sandi untuk satu Daftar Akses lalu menerapkannya ke satu atau lebih _Host Proxy_. + +Ini paling berguna untuk layanan web yang diteruskan yang tidak memiliki mekanisme autentikasi bawaan atau ketika Anda ingin melindungi dari klien yang tidak dikenal. diff --git a/frontend/src/locale/src/HelpDoc/id/Certificates.md b/frontend/src/locale/src/HelpDoc/id/Certificates.md new file mode 100644 index 0000000000..d58c922c22 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/id/Certificates.md @@ -0,0 +1,32 @@ +## Bantuan Sertifikat + +### Sertifikat HTTP + +Sertifikat yang divalidasi HTTP berarti server Let's Encrypt akan +mencoba menjangkau domain Anda melalui HTTP (bukan HTTPS!) dan jika berhasil, mereka +akan menerbitkan sertifikat Anda. + +Untuk metode ini, Anda harus membuat _Host Proxy_ untuk domain Anda yang +dapat diakses dengan HTTP dan mengarah ke instalasi Nginx ini. Setelah sertifikat +diberikan, Anda dapat mengubah _Host Proxy_ agar juga menggunakan sertifikat ini untuk HTTPS +koneksi. Namun, _Host Proxy_ tetap perlu dikonfigurasi untuk akses HTTP +agar sertifikat dapat diperpanjang. + +Proses ini _tidak_ mendukung domain wildcard. + +### Sertifikat DNS + +Sertifikat yang divalidasi DNS mengharuskan Anda menggunakan plugin Penyedia DNS. Penyedia DNS ini +akan digunakan untuk membuat record sementara pada domain Anda dan kemudian Let's +Encrypt akan menanyakan record tersebut untuk memastikan Anda pemiliknya dan jika berhasil, mereka +akan menerbitkan sertifikat Anda. + +Anda tidak perlu membuat _Host Proxy_ sebelum meminta jenis sertifikat ini. +Anda juga tidak perlu mengonfigurasi _Host Proxy_ untuk akses HTTP. + +Proses ini _mendukung_ domain wildcard. + +### Sertifikat Kustom + +Gunakan opsi ini untuk mengunggah Sertifikat SSL Anda sendiri, sebagaimana disediakan oleh +Certificate Authority Anda. diff --git a/frontend/src/locale/src/HelpDoc/id/DeadHosts.md b/frontend/src/locale/src/HelpDoc/id/DeadHosts.md new file mode 100644 index 0000000000..44b92bffcc --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/id/DeadHosts.md @@ -0,0 +1,10 @@ +## Apa itu Host 404? + +Host 404 adalah konfigurasi host yang menampilkan halaman 404. + +Ini dapat berguna ketika domain Anda terindeks di mesin pencari dan Anda ingin +menyediakan halaman error yang lebih baik atau secara khusus memberi tahu pengindeks pencarian bahwa +halaman domain tersebut sudah tidak ada. + +Manfaat lain memiliki host ini adalah melacak log untuk akses ke host tersebut dan +melihat perujuk. diff --git a/frontend/src/locale/src/HelpDoc/id/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/id/ProxyHosts.md new file mode 100644 index 0000000000..b05939a28a --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/id/ProxyHosts.md @@ -0,0 +1,7 @@ +## Apa itu Host Proxy? + +Host Proxy adalah endpoint masuk untuk layanan web yang ingin Anda teruskan. + +Host ini menyediakan terminasi SSL opsional untuk layanan Anda yang mungkin tidak memiliki dukungan SSL bawaan. + +Host Proxy adalah penggunaan paling umum untuk Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/id/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/id/RedirectionHosts.md new file mode 100644 index 0000000000..7e31619af9 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/id/RedirectionHosts.md @@ -0,0 +1,5 @@ +## Apa itu Host Pengalihan? + +Host Pengalihan akan mengalihkan permintaan dari domain masuk dan mengarahkan pengunjung ke domain lain. + +Alasan paling umum menggunakan jenis host ini adalah ketika situs Anda berpindah domain tetapi masih ada tautan mesin pencari atau perujuk yang mengarah ke domain lama. diff --git a/frontend/src/locale/src/HelpDoc/id/Streams.md b/frontend/src/locale/src/HelpDoc/id/Streams.md new file mode 100644 index 0000000000..e74f6b412e --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/id/Streams.md @@ -0,0 +1,6 @@ +## Apa itu Stream? + +Fitur yang relatif baru untuk Nginx, Stream berfungsi untuk meneruskan trafik TCP/UDP +langsung ke komputer lain di jaringan. + +Jika Anda menjalankan server game, FTP, atau SSH, ini bisa sangat membantu. diff --git a/frontend/src/locale/src/HelpDoc/id/index.ts b/frontend/src/locale/src/HelpDoc/id/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/id/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 0000000000..bba013d54d --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/index.ts @@ -0,0 +1,38 @@ +import * as bg from "./bg/index"; +import * as de from "./de/index"; +import * as pt from "./pt/index"; +import * as en from "./en/index"; +import * as es from "./es/index"; +import * as fr from "./fr/index"; +import * as ga from "./ga/index"; +import * as id from "./id/index"; +import * as it from "./it/index"; +import * as ja from "./ja/index"; +import * as ko from "./ko/index"; +import * as nl from "./nl/index"; +import * as pl from "./pl/index"; +import * as ru from "./ru/index"; +import * as sk from "./sk/index"; +import * as cs from "./cs/index"; +import * as vi from "./vi/index"; +import * as zh from "./zh/index"; +import * as tr from "./tr/index"; +import * as hu from "./hu/index"; + +const items: any = { en, de, pt, es, ja, sk, cs, zh, pl, ru, it, vi, nl, bg, ko, ga, id, fr, tr, hu }; + + +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/HelpDoc/it/AccessLists.md b/frontend/src/locale/src/HelpDoc/it/AccessLists.md new file mode 100644 index 0000000000..fa45ba5fa8 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/it/AccessLists.md @@ -0,0 +1,7 @@ +## Che cos'è una Lista di Accesso? + +La Lista di Accesso fornisce una blacklist o una whitelist di indirizzi IP specifici dei client insieme all'autenticazione per gli host proxy tramite autenticazione HTTP di base. + +È possibile configurare più regole client, nomi utente e password per un singolo lista di accesso e quindi applicarlo a uno o più host proxy. + +Ciò è particolarmente utile per i servizi web inoltrati che non dispongono di meccanismi di autenticazione integrati o quando si desidera proteggersi da client sconosciuti. diff --git a/frontend/src/locale/src/HelpDoc/it/Certificates.md b/frontend/src/locale/src/HelpDoc/it/Certificates.md new file mode 100644 index 0000000000..18cec7f285 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/it/Certificates.md @@ -0,0 +1,24 @@ +## Guida sui Certificati + +### Certificato HTTP + +Un certificato convalidato HTTP significa che i server Let's Encrypttenteranno di raggiungere i tuoi domini tramite HTTP (non HTTPS!) e, in caso di esito positivo, emetteranno il tuo certificato. + +Per questo metodo, dovrai creare un _Proxy Host_ per i tuoi domini chesia accessibile con HTTP e che punti a questa installazione Nginx. +Dopo che il certificato è stato rilasciato, puoi modificare il _Proxy Host_ per utilizzare questo certificato anche per le connessioni HTTPS. +Tuttavia, il _Proxy Host_ dovrà comunque essere configurato per l'accesso HTTP affinché il certificato possa essere rinnovato. + +Questo processo _non_ supporta i domini wildcard. + +### Certificato DNS + +Un certificato convalidato dal DNS richiede l'uso di un plugin DNS Provider. Questo DNS Provider verrà utilizzato per creare record temporanei sul tuo dominio, +quindi Let's Encrypt interrogherà tali record per assicurarsi che tu sia il proprietario e, in caso di esito positivo,rilascerà il tuo certificato. + +Non è necessario creare un _Proxy Host_ prima di richiedere questo tipo di certificato. Non è nemmeno necessario configurare il tuo _proxy host_ per l'accesso HTTP. + +Questo processo _supporta_ i domini wildcard. + +### Certificato personalizzato + +Utilizza questa opzione per caricare il tuo certificato SSL, fornito dalla tua autorità di certificazione. diff --git a/frontend/src/locale/src/HelpDoc/it/DeadHosts.md b/frontend/src/locale/src/HelpDoc/it/DeadHosts.md new file mode 100644 index 0000000000..c5be4b01c3 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/it/DeadHosts.md @@ -0,0 +1,9 @@ +## Che cos'è un Host 404? + +Un Host 404 è semplicemente una configurazione host che mostra una pagina 404. + +Questo può essere utile quando il tuo dominio è elencato nei motori di ricerca e desideri fornire una pagina di errore più gradevole o specificare agli +indicizzatori di ricerca che le pagine del dominio non esistono più. + +Un altro vantaggio di avere questo host è quello di tracciare i log degli accessi e +visualizzare i referrer. diff --git a/frontend/src/locale/src/HelpDoc/it/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/it/ProxyHosts.md new file mode 100644 index 0000000000..bc1dd534cf --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/it/ProxyHosts.md @@ -0,0 +1,7 @@ +## Che cos'è un Proxy Host? + +Un host proxy è l'endpoint in entrata per un servizio web che si desidera inoltrare. + +Fornisce la terminazione SSL opzionale per il servizio che potrebbe non avere il supporto SSL integrato. + +Gli host proxy sono l'uso più comune per Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/it/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/it/RedirectionHosts.md new file mode 100644 index 0000000000..9971774444 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/it/RedirectionHosts.md @@ -0,0 +1,7 @@ +## Che cos'è un Host di reindirizzamento? + +Un Host di reindirizzamento reindirizza le richieste provenienti dal dominio in entrata e indirizza il +visitatore verso un altro dominio. + +Il motivo più comune per utilizzare questo tipo di host è quando il tuo sito web cambia +dominio, ma hai ancora link di motori di ricerca o referrer che puntano al vecchio dominio. diff --git a/frontend/src/locale/src/HelpDoc/it/Streams.md b/frontend/src/locale/src/HelpDoc/it/Streams.md new file mode 100644 index 0000000000..aa95969c2e --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/it/Streams.md @@ -0,0 +1,6 @@ +## Che cos'è uno Stream? + +Una funzionalità relativamente nuova per Nginx, uno Stream serve a inoltrare il traffico TCP/UDP +direttamente a un altro computer sulla rete. + +Se gestisci server di gioco, FTP o SSH, questa funzionalità può rivelarsi molto utile. diff --git a/frontend/src/locale/src/HelpDoc/it/index.ts b/frontend/src/locale/src/HelpDoc/it/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/it/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/ja/AccessLists.md b/frontend/src/locale/src/HelpDoc/ja/AccessLists.md new file mode 100644 index 0000000000..aaeea87a81 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ja/AccessLists.md @@ -0,0 +1,8 @@ +## アクセスリストとは + +アクセスリストは特定のクライアントIPへのブラックリストとホワイトリストを提供し、ベーシック認証によるプロキシホストへの認証を可能にします。 + +複数のクライアントルールやユーザー名とパスワードを一つのアクセスリストに設定し、一つ以上の _プロキシホスト_ に適応することができます。 + +これは認証システムを持たないサービスや不明なクライアントからの保護が必要な場合に有効です。 + diff --git a/frontend/src/locale/src/HelpDoc/ja/Certificates.md b/frontend/src/locale/src/HelpDoc/ja/Certificates.md new file mode 100644 index 0000000000..dbe2cfb171 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ja/Certificates.md @@ -0,0 +1,21 @@ +## 証明書 + +### HTTP 証明書 + +HTTPによって検証された証明書はLet's EncryptサーバーがHTTPでドメインにアクセスを試みサーバーを管理していることを確認できた場合に発行される証明書です。 + +この方法では、HTTPアクセス可能でこのNginxを指しているドメインに対して _プロキシホスト_ を作成する必要があります。証明書が発行された後は、 _プロキシホスト_ を編集してこの証明書をHTTPS接続に使用するように設定できます。ただし、証明書の更新には、_プロキシホスト_ がHTTP接続用に設定された状態を維持する必要があります。 + +この方法はワイルドカードのドメインをサポート _していません_ 。 + +### DNS 証明書 + +DNSによって検証された証明書にはDNSプロバイダープラグインが必要です。このプロバイダーはドメイン上に一時レコードを作成するために使用されます。その後Let's Encryptサーバーがそのレコードを参照し、あなたが所有していることを確認できると証明書が発行されます。 + +このタイプの証明書を作成する際に、 _プロキシホスト_ を作成する必要はありません。また、_プロキシホスト_ をHTTPアクセス用に設定する必要もありません。 + +この方法はワイルドカードのドメインをサポート _します_ 。 + +### カスタム証明書 + +このオプションでは、あなたの証明書認証局によって提供された自身の証明書をアップロードして使用できます。 diff --git a/frontend/src/locale/src/HelpDoc/ja/DeadHosts.md b/frontend/src/locale/src/HelpDoc/ja/DeadHosts.md new file mode 100644 index 0000000000..4c6aad9f71 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ja/DeadHosts.md @@ -0,0 +1,7 @@ +## 404ホストとはなんですか? + +404ホストとは、単に404ページを表示するよう設定されたホストです。 + +これは、検索エンジンに登録されたドメインに分かりやすいエラーページを提供したい場合や、検索エンジンのインデクサーにドメインページがもう存在しないことを伝えたい場合に便利です。 + +このホストを持つもう一つの利点は、アクセスログを追跡し、参照元を確認できることです。 diff --git a/frontend/src/locale/src/HelpDoc/ja/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/ja/ProxyHosts.md new file mode 100644 index 0000000000..e01d0a620c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ja/ProxyHosts.md @@ -0,0 +1,7 @@ +## プロキシホストとは何ですか? + +プロキシホストは転送したいwebサービスの受信エンドポイントです。 + +サービスにSSLサーバーが組み込まれていない場合でも、オプションでSSL終端機能を提供します。 + +プロキシホストはNginx Proxy Managerのもっとも一般的な使用方法です。 \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/ja/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/ja/RedirectionHosts.md new file mode 100644 index 0000000000..e1fb58813b --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ja/RedirectionHosts.md @@ -0,0 +1,5 @@ +## リダイレクトホストとは何ですか? + +リダイレクトホストは受信したリクエストを別のドメインにリダイレクトして訪問者に表示します。 + +このタイプのもっとも一般的な使用理由は、webサイトのドメインが変更されたが検索エンジンやリンクが古いドメインを指し続けている場合です。 diff --git a/frontend/src/locale/src/HelpDoc/ja/Streams.md b/frontend/src/locale/src/HelpDoc/ja/Streams.md new file mode 100644 index 0000000000..8fc38b7348 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ja/Streams.md @@ -0,0 +1,5 @@ +## ストリームとは何ですか? + +Nginxの比較的新しい機能であるストリームは、TCP/UDPトラフィックをネットワーク上の別のコンピュータに直接転送します。 + +ゲームサーバー、FTPサーバー、SSHサーバーを運用している場合に便利です。 diff --git a/frontend/src/locale/src/HelpDoc/ja/index.ts b/frontend/src/locale/src/HelpDoc/ja/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ja/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/ko/AccessLists.md b/frontend/src/locale/src/HelpDoc/ko/AccessLists.md new file mode 100644 index 0000000000..8e96701220 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ko/AccessLists.md @@ -0,0 +1,11 @@ +## 접근 정책이란? + +접근 정책은 특정 클라이언트 IP 주소를 허용하거나 거부할 수 있으며, +프록시 호스트에 기본 HTTP 인증(Basic Auth) 을 적용할 수 있는 기능입니다. + +하나의 접근 목록에 여러 클라이언트 규칙과 사용자 이름, 비밀번호를 추가한 뒤 +이를 하나 이상의 프록시 호스트에 적용할 수 있습니다. + +이 기능은 인증 기능이 없는 웹 서비스에 인증을 추가하거나, +알 수 없는 클라이언트로부터 서비스를 보호할 때 유용합니다. + diff --git a/frontend/src/locale/src/HelpDoc/ko/Certificates.md b/frontend/src/locale/src/HelpDoc/ko/Certificates.md new file mode 100644 index 0000000000..660f39620d --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ko/Certificates.md @@ -0,0 +1,28 @@ +## 인증서 도움말 + +### HTTP 인증서 + +HTTP 검증 방식의 인증서는 Let's Encrypt 서버가 **HTTPS가 아닌 HTTP로** 해당 도메인에 접속을 시도해 응답이 확인되면 인증서를 발급하는 방식입니다. + +이 방식을 사용하려면 도메인에 대한 **프록시 호스트가 미리 생성되어 있어야 하며**, HTTP로 접근할 수 있어야 하고 Nginx Proxy Manager가 설치된 서버를 가리켜야 합니다. 인증서가 발급된 이후에는 해당 프록시 호스트에 HTTPS용 인증서를 적용할 수 있습니다. + +다만, **인증서 자동 갱신을 위해서는 HTTP 접근이 계속 필요합니다.** + +이 방식은 **와일드카드 도메인을 지원하지 않습니다.** + +--- + +### DNS 인증서 + +DNS 검증 방식의 인증서는 DNS 공급자 플러그인을 사용해야 합니다. 이 플러그인은 도메인에 임시 DNS 레코드를 생성하며, Let's Encrypt는 해당 레코드를 조회해 도메인 소유 여부를 확인합니다. 검증이 성공하면 인증서가 발급됩니다. + +이 방식은 인증서를 요청하기 전에 **프록시 호스트를 생성할 필요가 없으며**, 프록시 호스트에 HTTP 접근을 설정할 필요도 없습니다. + +이 방식은 **와일드카드 도메인을 지원합니다.** + +--- + +### 사용자 지정 인증서 + +이 옵션을 사용하면 직접 보유한 인증 기관(CA)에서 발급한 SSL 인증서를 직접 업로드하여 사용할 수 있습니다. + diff --git a/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md b/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md new file mode 100644 index 0000000000..032a23c946 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md @@ -0,0 +1,10 @@ +## 404 호스트란? + +404 호스트는 404 오류 페이지를 표시하도록 구성된 호스트입니다. + +이 기능은 도메인이 검색 엔진에 이미 색인되어 있을 때, +더 깔끔한 오류 페이지를 제공하거나 해당 페이지가 더 이상 존재하지 않음을 +검색 엔진에게 명확하게 알려야 할 때 유용합니다. + +또한 404 호스트를 사용하면 접근 로그를 확인하고, 어떤 경로(Referrer)를 통해 들어왔는지 추적할 수 있다는 장점도 있습니다. + diff --git a/frontend/src/locale/src/HelpDoc/ko/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/ko/ProxyHosts.md new file mode 100644 index 0000000000..3212fde70b --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ko/ProxyHosts.md @@ -0,0 +1,8 @@ +## 프록시 호스트란? + +프록시 호스트는 외부에서 들어오는 웹 요청을 받아 지정한 전달 대상으로 전달하는 역할을 합니다. + +원래 SSL을 지원하지 않는 대상이라도, 프록시 호스트를 통해 SSL(HTTPS) 연결을 적용할 수 있습니다. + +프록시 호스트는 Nginx Proxy Manager에서 가장 일반적으로 사용되는 기능입니다. + diff --git a/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md new file mode 100644 index 0000000000..7494885de5 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md @@ -0,0 +1,7 @@ +## 리다이렉션 호스트란? + +리다이렉션 호스트는 외부에서 들어오는 도메인 요청을 다른 도메인으로 자동 이동(리다이렉트)시키는 역할을 합니다. + +이 유형의 호스트는 주로 웹사이트의 도메인이 변경되었지만, +검색 엔진이나 다른 사이트에 이전 도메인 링크가 남아 있을 때 사용하면 가장 효과적입니다. + diff --git a/frontend/src/locale/src/HelpDoc/ko/Streams.md b/frontend/src/locale/src/HelpDoc/ko/Streams.md new file mode 100644 index 0000000000..45107b2c3d --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ko/Streams.md @@ -0,0 +1,7 @@ +## 호스트 스트림이란? + +호스트 스트림은 비교적 최근에 Nginx에 추가된 기능으로, +TCP/UDP 트래픽을 네트워크 내의 다른 컴퓨터로 직접 전달하는 데 사용됩니다. + +게임 서버나 FTP, SSH 서버 등을 운영할 때 유용하게 사용할 수 있습니다. + diff --git a/frontend/src/locale/src/HelpDoc/ko/index.ts b/frontend/src/locale/src/HelpDoc/ko/index.ts new file mode 100644 index 0000000000..33e015cc81 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ko/index.ts @@ -0,0 +1,7 @@ +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/nl/AccessLists.md b/frontend/src/locale/src/HelpDoc/nl/AccessLists.md new file mode 100644 index 0000000000..875627d23b --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/nl/AccessLists.md @@ -0,0 +1,7 @@ +## Wat is een Toegangslijst? + +Toeganslijsten bieden een zwarte- of witte lijst van specifieke client IP-adressen samen met authenticatie voor de Proxy Hosts via Basic HTTP Authenticatie. + +Je kan meerdere client regels, gebruikersnamen en wachtwoorden voor een enkele Toegangslijst configureren en toepassen op één of meerdere _Proxy Hosts_. + +Dit is het meest nuttig voor doorgestuurde webdiensten die geen authenticatiemechanismen hebben of wanneer je wilt beveiligen tegen onbekende bezoekers. diff --git a/frontend/src/locale/src/HelpDoc/nl/Certificates.md b/frontend/src/locale/src/HelpDoc/nl/Certificates.md new file mode 100644 index 0000000000..4477217084 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/nl/Certificates.md @@ -0,0 +1,31 @@ +## Certificaten Hulp + +### HTTP Certificaat + +Een HTTP gevalideerd certificaat betekent dat Let's Encrypt servers +zullen proberen om over HTTP te bereiken (niet HTTPS!) en als dat gelukt is, zal +jouw certificaat worden uitgegeven. + +Voor deze zal je een _Proxy Host_ moeten hebben die is toegankelijk via HTTP en +die naar deze Nginx installatie wijst. Nadat een certificaat is uitgegeven kan je +de _Proxy Host_ wijzigen om ook HTTPS toegang te geven. Maar de _Proxy Host_ zal +nog moeten worden geconfigureerd voor HTTP toegang om het certificaat te verlengen. + +Dit proces ondersteunt geen domeinen met wildcards. + +### DNS Certificaat + +Een DNS gevalideerd certificaat zal gebruik maken van een DNS Provider plugin. De +DNS Provider zal tijdelijke records op jouw domein maken en Let's Encrypt zal deze +records opvragen om te controleren of je de eigenaar bent. Als dat is gecontroleerd +is zal Let's Encrypt het certificaat uitgeven. + +Je hebt geen _Proxy Host_ nodig om dit soort certificaat aan te vragen. Je hebt dus +geen HTTP _Proxy Host_ nodig. + +Dit proces ondersteunt _wel_ domeinen met wildcards. + +### Aangepast Certificaat + +Gebruik deze optie om jouw eigen SSL Certificaat te uploaden, zoals +geleverd door jouw eigen Certificate Authority. diff --git a/frontend/src/locale/src/HelpDoc/nl/DeadHosts.md b/frontend/src/locale/src/HelpDoc/nl/DeadHosts.md new file mode 100644 index 0000000000..7e26fd5082 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/nl/DeadHosts.md @@ -0,0 +1,10 @@ +## Wat is een 404 Host? + +Simpel gezegd is een 404 Host een host setup die een 404 pagina weergeeft. + +Dit kan nuttig zijn wanneer jouw domein is opgegeven in zoekmachines en je wil +een betere foutpagina leveren of specifiek om te zeggen tegen de zoekmachines dat +het domein niet langer bestaat. + +Een ander voordeel van het hebben van een 404 Host is om de logs voor bezoeken +te volgen en de referenties te bekijken. diff --git a/frontend/src/locale/src/HelpDoc/nl/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/nl/ProxyHosts.md new file mode 100644 index 0000000000..68f9f4c446 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/nl/ProxyHosts.md @@ -0,0 +1,7 @@ +## Wat is een Proxy Host? + +Een Proxy Host is de inkomende endpoint voor een webdienst dat je wilt doorsturen. + +Het biedt optionele SSL voor je dienst die mogelijk geen SSL ondersteuning heeft. + +Proxy Hosts worden het meest gebruikt in Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/nl/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/nl/RedirectionHosts.md new file mode 100644 index 0000000000..6b4e1ab67a --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/nl/RedirectionHosts.md @@ -0,0 +1,7 @@ +## Wat is een Redirection Host? + +Een Redirection Host zal verzoeken van de inkomende domeinnaam doorsturen, en de bezoeker +omleiden naar een andere domeinnaam. + +Het gebruik van een Redirection Host is vooral handig wanneer je jouw website verandert +maar je nog zoekmachines of referenties naar de oude domeinnaam hebben. diff --git a/frontend/src/locale/src/HelpDoc/nl/Streams.md b/frontend/src/locale/src/HelpDoc/nl/Streams.md new file mode 100644 index 0000000000..7305754442 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/nl/Streams.md @@ -0,0 +1,6 @@ +## Wat is een Stream? + +Streams zijn een nieuwe toevoeging aan Nginx, die toelaat om TCP/UDP +verkeer naar een ander computer op het netwerk te sturen. + +Als je game servers, FTP of SSH servers draait kan dit handig zijn. diff --git a/frontend/src/locale/src/HelpDoc/nl/index.ts b/frontend/src/locale/src/HelpDoc/nl/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/nl/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/pl/AccessLists.md b/frontend/src/locale/src/HelpDoc/pl/AccessLists.md new file mode 100644 index 0000000000..9cfe97565d --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pl/AccessLists.md @@ -0,0 +1,7 @@ +## Czym jest lista dostępu? + +Listy dostępu zapewniają czarną lub białą listę określonych adresów IP klientów wraz z uwierzytelnianiem dla hostów proxy za pomocą podstawowego uwierzytelniania HTTP. + +Możesz skonfigurować wiele reguł klienta, nazw użytkowników i haseł dla pojedynczej listy dostępu, a następnie zastosować ją do jednego lub więcej hostów proxy. + +Jest to najbardziej przydatne w przypadku przekierowywanych usług internetowych, które nie mają wbudowanych mechanizmów uwierzytelniania lub gdy chcesz zabezpieczyć się przed nieznanymi klientami. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/pl/Certificates.md b/frontend/src/locale/src/HelpDoc/pl/Certificates.md new file mode 100644 index 0000000000..261dc3279b --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pl/Certificates.md @@ -0,0 +1,22 @@ +## Pomoc dotycząca certyfikatów + +### Certyfikat HTTP + +Certyfikat weryfikowany przez HTTP oznacza, że serwery Let's Encrypt będą próbowały połączyć się z twoimi domenami przez HTTP (nie HTTPS!) i jeśli się to powiedzie, wydadzą twój certyfikat. + +W przypadku tej metody musisz mieć utworzony Host proxy dla swoich domen, który jest dostępny przez HTTP i wskazuje na tę instalację Nginx. +Po otrzymaniu certyfikatu możesz zmodyfikować Host proxy, aby używał również tego certyfikatu do połączeń HTTPS. Jednak Host proxy nadal będzie musiał być skonfigurowany do dostępu przez HTTP, aby certyfikat mógł być odnawiany. + +Ten proces nie obsługuje domen wieloznacznych (wildcard). + +### Certyfikat DNS + +Certyfikat weryfikowany przez DNS wymaga użycia wtyczki dostawcy DNS. Ten dostawca DNS zostanie użyty do utworzenia tymczasowych rekordów w twojej domenie, a następnie Let's Encrypt sprawdzi te rekordy, aby upewnić się, że jesteś właścicielem i jeśli się powiedzie, wydadzą twój certyfikat. + +Nie musisz mieć utworzonego Hosta proxy przed wystąpieniem o ten typ certyfikatu. Nie musisz również mieć skonfigurowanego Hosta proxy do dostępu przez HTTP. + +Ten proces obsługuje domeny wieloznaczne (wildcard). + +### Własny certyfikat + +Użyj tej opcji, aby przesłać własny certyfikat SSL, dostarczony przez twój własny urząd certyfikacji. diff --git a/frontend/src/locale/src/HelpDoc/pl/DeadHosts.md b/frontend/src/locale/src/HelpDoc/pl/DeadHosts.md new file mode 100644 index 0000000000..ec3d4fd28c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pl/DeadHosts.md @@ -0,0 +1,7 @@ +## Czym jest host 404? + +Host 404 to po prostu konfiguracja hosta, która wyświetla stronę 404. + +Może to być przydatne, gdy twoja domena jest indeksowana w wyszukiwarkach i chcesz zapewnić ładniejszą stronę błędu lub konkretnie poinformować roboty indeksujące, że strony domeny już nie istnieją. + +Kolejną zaletą posiadania tego hosta jest możliwość śledzenia logów dla odwiedzin oraz przeglądania źródeł ruchu (referrerów). \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/pl/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/pl/ProxyHosts.md new file mode 100644 index 0000000000..43e94efb5a --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pl/ProxyHosts.md @@ -0,0 +1,7 @@ +## Czym jest host proxy? + +Host proxy to punkt wejściowy dla usługi internetowej, którą chcesz przekierować. + +Zapewnia opcjonalne zakończenie SSL dla twojej usługi, która może nie mieć wbudowanej obsługi SSL. + +Hosty proxy są najpopularniejszym zastosowaniem Nginx Proxy Manager \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/pl/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/pl/RedirectionHosts.md new file mode 100644 index 0000000000..22854dfc7b --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pl/RedirectionHosts.md @@ -0,0 +1,5 @@ +## Czym jest host przekierowania? + +Host przekierowania przekierowuje żądania z domeny przychodzącej i przenosi odwiedzającego na inną domenę. + +Najczęstszym powodem używania tego typu hosta jest sytuacja, gdy twoja strona internetowa zmienia domeny, ale nadal masz linki z wyszukiwarek lub odnośniki wskazujące na starą domenę. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/pl/Streams.md b/frontend/src/locale/src/HelpDoc/pl/Streams.md new file mode 100644 index 0000000000..793cada0f6 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pl/Streams.md @@ -0,0 +1,5 @@ +## Czym jest strumień? + +Stosunkowo nowa funkcja dla Nginx, strumień służy do przekazywania ruchu TCP/UDP bezpośrednio na inny komputer/serwer w sieci. + +Jeśli prowadzisz serwery gier, FTP lub SSH, może się to okazać przydatne \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/pl/index.ts b/frontend/src/locale/src/HelpDoc/pl/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pl/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/pt/AccessLists.md b/frontend/src/locale/src/HelpDoc/pt/AccessLists.md new file mode 100644 index 0000000000..fcedb8a586 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt/AccessLists.md @@ -0,0 +1,11 @@ +## O que é uma Access List? + +As *Access Lists* fornecem uma lista de permissões (whitelist) ou bloqueios (blacklist) +de endereços IP específicos de clientes, juntamente com autenticação para os *Proxy Hosts* +via Autenticação HTTP Básica (*Basic Auth*). + +Podes configurar múltiplas regras de cliente, nomes de utilizador e palavras-passe +para uma única *Access List*, e depois aplicá-la a um ou mais *Proxy Hosts*. + +Isto é especialmente útil para serviços web encaminhados que não têm mecanismos +de autenticação integrados ou quando pretendes proteger o acesso contra clientes desconhecidos. diff --git a/frontend/src/locale/src/HelpDoc/pt/Certificates.md b/frontend/src/locale/src/HelpDoc/pt/Certificates.md new file mode 100644 index 0000000000..da92f80325 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt/Certificates.md @@ -0,0 +1,31 @@ +## Ajuda de Certificados + +### Certificado HTTP + +Um certificado validado por HTTP significa que os servidores do Let's Encrypt irão +tentar aceder aos teus domínios via HTTP (não HTTPS!) e, se a ligação for bem-sucedida, +emitirão o certificado. + +Para este método, é necessário ter um *Proxy Host* criado para o(s) teu(s) domínio(s), +acessível via HTTP e a apontar para esta instalação do Nginx. Depois de o certificado ser +emitido, podes modificar o *Proxy Host* para também utilizar esse certificado em ligações HTTPS. +No entanto, o *Proxy Host* deve continuar configurado para acesso HTTP para que a renovação +funcione corretamente. + +Este processo **não** suporta domínios wildcard. + +### Certificado DNS + +Um certificado validado por DNS requer que uses um plugin de fornecedor DNS (*DNS Provider*). +Este fornecedor será usado para criar registos temporários no teu domínio, que serão consultados +pelo Let's Encrypt para confirmar que és o proprietário. Se tudo correr bem, o certificado será emitido. + +Não é necessário ter um *Proxy Host* criado antes de pedir este tipo de certificado. +Também não é necessário que o *Proxy Host* tenha acesso HTTP configurado. + +Este processo **suporta** domínios wildcard. + +### Certificado Personalizado + +Usa esta opção para carregar o teu próprio Certificado SSL, fornecido pela +tua Autoridade Certificadora. diff --git a/frontend/src/locale/src/HelpDoc/pt/DeadHosts.md b/frontend/src/locale/src/HelpDoc/pt/DeadHosts.md new file mode 100644 index 0000000000..c9874a1498 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt/DeadHosts.md @@ -0,0 +1,9 @@ +## O que é um 404 Host? + +Um *404 Host* é simplesmente um host configurado para apresentar uma página 404. + +Isto pode ser útil quando o teu domínio aparece em motores de busca e queres fornecer +uma página de erro mais agradável ou indicar especificamente aos indexadores de pesquisa +que as páginas desse domínio já não existem. + +Outra vantagem é permitir consultar os registos de acessos a este host e ver os referenciadores. diff --git a/frontend/src/locale/src/HelpDoc/pt/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/pt/ProxyHosts.md new file mode 100644 index 0000000000..4c221fac17 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt/ProxyHosts.md @@ -0,0 +1,7 @@ +## O que é um Proxy Host? + +Um *Proxy Host* é o ponto de entrada para um serviço web que pretendes encaminhar. + +Permite, opcionalmente, fazer terminação SSL para um serviço que possa não ter suporte SSL nativo. + +Os *Proxy Hosts* são a utilização mais comum do Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/pt/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/pt/RedirectionHosts.md new file mode 100644 index 0000000000..10418ba087 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt/RedirectionHosts.md @@ -0,0 +1,7 @@ +## O que é um Redirection Host? + +Um *Redirection Host* redireciona pedidos recebidos no domínio de entrada e envia +o utilizador para outro domínio. + +A razão mais comum para usar este tipo de host é quando o teu site muda de domínio +mas ainda tens motores de busca ou links de referência a apontar para o domínio antigo. diff --git a/frontend/src/locale/src/HelpDoc/pt/Streams.md b/frontend/src/locale/src/HelpDoc/pt/Streams.md new file mode 100644 index 0000000000..83857c636e --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt/Streams.md @@ -0,0 +1,6 @@ +## O que é um Stream? + +Uma funcionalidade relativamente recente no Nginx, um *Stream* serve para encaminhar +tráfego TCP/UDP diretamente para outro computador na rede. + +Se estiveres a executar servidores de jogos, FTP ou SSH, isto pode ser bastante útil. diff --git a/frontend/src/locale/src/HelpDoc/pt/index.ts b/frontend/src/locale/src/HelpDoc/pt/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt/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/ru/AccessLists.md b/frontend/src/locale/src/HelpDoc/ru/AccessLists.md new file mode 100644 index 0000000000..5fba3831d5 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ru/AccessLists.md @@ -0,0 +1,7 @@ +## Что такое список доступа? + +Списки доступа позволяют задавать белый/чёрный список IP‑адресов клиентов и настраивать аутентификацию для прокси‑хостов через базовую HTTP‑аутентификацию. + +Для одного списка доступа можно настроить несколько правил клиентов, логины и пароли, а затем применить его к одному или нескольким _прокси‑хостам_. + +Это особенно полезно для проксируемых веб‑сервисов без встроенной аутентификации или когда нужно защититься от неизвестных клиентов. diff --git a/frontend/src/locale/src/HelpDoc/ru/Certificates.md b/frontend/src/locale/src/HelpDoc/ru/Certificates.md new file mode 100644 index 0000000000..5cd8960450 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ru/Certificates.md @@ -0,0 +1,21 @@ +## Справка по сертификатам + +### HTTP-сертификат + +Сертификат, подтверждённый по HTTP, означает, что серверы Let's Encrypt попытаются обратиться к вашим доменам по HTTP (не HTTPS!) и при успехе выпустят сертификат. + +Для этого метода должен существовать _прокси‑хост_ для ваших доменов, доступный по HTTP и указывающий на эту установку Nginx. После выдачи сертификата вы можете настроить _прокси‑хост_ на использование этого сертификата для HTTPS‑подключений. Однако доступ по HTTP должен сохраняться, чтобы сертификат мог обновляться. + +Этот способ _не_ поддерживает wildcard‑домены. + +### DNS-сертификат + +Сертификат, подтверждённый по DNS, требует использования плагина DNS‑провайдера. Такой провайдер создаст временные записи в вашем домене, затем Let's Encrypt проверит эти записи, чтобы убедиться, что вы владелец домена, и при успехе выпустит сертификат. + +Для запроса такого сертификата предварительно создавать _прокси‑хост_ не требуется. Также не нужен доступ по HTTP для вашего _прокси‑хоста_. + +Этот способ _поддерживает_ wildcard‑домены. + +### Свой сертификат + +Используйте этот вариант, чтобы загрузить собственный SSL‑сертификат, выданный вашим удостоверяющим центром (CA). diff --git a/frontend/src/locale/src/HelpDoc/ru/DeadHosts.md b/frontend/src/locale/src/HelpDoc/ru/DeadHosts.md new file mode 100644 index 0000000000..9fafe5fa3b --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ru/DeadHosts.md @@ -0,0 +1,7 @@ +## Что такое 404‑хост? + +404‑хост — это конфигурация, которая показывает страницу 404. + +Это полезно, когда ваш домен присутствует в поисковых системах и вы хотите показать более дружелюбную страницу ошибки или явно сообщить индексаторам, что страницы домена больше не существуют. + +Ещё одно преимущество — можно отдельно отслеживать обращения в журналах и смотреть источники переходов. diff --git a/frontend/src/locale/src/HelpDoc/ru/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/ru/ProxyHosts.md new file mode 100644 index 0000000000..34e5497d72 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ru/ProxyHosts.md @@ -0,0 +1,7 @@ +## Что такое прокси‑хост? + +Прокси‑хост — это входная точка веб‑сервиса, который вы проксируете. + +Он может выполнять терминaцию SSL для сервиса, у которого нет собственной поддержки SSL. + +Прокси‑хосты — самый распространённый сценарий использования Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/ru/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/ru/RedirectionHosts.md new file mode 100644 index 0000000000..3ccdbdccb6 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ru/RedirectionHosts.md @@ -0,0 +1,5 @@ +## Что такое редирект‑хост? + +Редирект‑хост перенаправляет запросы, поступающие на входящий домен, на другой домен. + +Чаще всего это используют, когда сайт сменил домен, а в поиске или на сторонних ресурсах всё ещё остаются ссылки на старый домен. diff --git a/frontend/src/locale/src/HelpDoc/ru/Streams.md b/frontend/src/locale/src/HelpDoc/ru/Streams.md new file mode 100644 index 0000000000..a3951a2121 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ru/Streams.md @@ -0,0 +1,5 @@ +## Что такое поток? + +Относительно новая возможность Nginx: поток позволяет напрямую проксировать TCP/UDP‑трафик на другой компьютер в сети. + +Полезно для игровых серверов, FTP или SSH‑серверов. diff --git a/frontend/src/locale/src/HelpDoc/ru/index.ts b/frontend/src/locale/src/HelpDoc/ru/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/ru/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/sk/AccessLists.md b/frontend/src/locale/src/HelpDoc/sk/AccessLists.md new file mode 100644 index 0000000000..8f4eea3b27 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/sk/AccessLists.md @@ -0,0 +1,7 @@ +## Čo je zoznam prístupov? + +Zoznamy prístupov poskytujú čiernu alebo bielu listinu konkrétnych IP adries klientov spolu s overovaním pre proxy hostiteľov prostredníctvom základného overovania HTTP. + +Môžete nakonfigurovať viacero pravidiel pre klientov, používateľských mien a hesiel pre jeden zoznam prístupov a potom ho použiť na jeden alebo viacero proxy hostiteľov. + +Toto je najužitočnejšie pre presmerované webové služby, ktoré nemajú zabudované overovacie mechanizmy, alebo ak sa chcete chrániť pred neznámymi klientmi. diff --git a/frontend/src/locale/src/HelpDoc/sk/Certificates.md b/frontend/src/locale/src/HelpDoc/sk/Certificates.md new file mode 100644 index 0000000000..a2e5797819 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/sk/Certificates.md @@ -0,0 +1,32 @@ +## Pomoc s certifikátmi + +### Certifikát HTTP + +Certifikát overený protokolom HTTP znamená, že servery Let's Encrypt sa +pokúsia pripojiť k vašim doménam cez protokol HTTP (nie HTTPS!) a v prípade úspechu +vydajú váš certifikát. + +Pre túto metódu budete musieť mať pre svoje domény vytvorený _Proxy Host_, ktorý +je prístupný cez HTTP a smeruje na túto inštaláciu Nginx. Po vydaní certifikátu +môžete zmeniť _Proxy Host_ tak, aby tento certifikát používal aj pre HTTPS +pripojenia. _Proxy Host_ však bude stále potrebné nakonfigurovať pre prístup cez HTTP, +aby sa certifikát mohol obnoviť. + +Tento proces _nepodporuje_ domény s divokými kartami. + +### Certifikát DNS + +Certifikát overený DNS vyžaduje použitie pluginu DNS Provider. Tento DNS +Provider sa použije na vytvorenie dočasných záznamov vo vašej doméne a potom Let's +Encrypt overí tieto záznamy, aby sa uistil, že ste vlastníkom, a ak bude úspešný, +vydá váš certifikát. + +Pred požiadaním o tento typ certifikátu nie je potrebné vytvoriť _Proxy Host_. +Tiež nie je potrebné mať _Proxy Host_ nakonfigurovaný pre prístup HTTP. + +Tento proces _podporuje_ domény s divokými kartami. + +### Vlastný certifikát + +Túto možnosť použite na nahratie vlastného SSL certifikátu, ktorý vám poskytla vaša +certifikačná autorita. diff --git a/frontend/src/locale/src/HelpDoc/sk/DeadHosts.md b/frontend/src/locale/src/HelpDoc/sk/DeadHosts.md new file mode 100644 index 0000000000..a232d23196 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/sk/DeadHosts.md @@ -0,0 +1,10 @@ +## Čo je to 404 Hostiteľ? + +404 Hostiteľ je jednoducho nastavenie hostiteľa, ktoré zobrazuje stránku 404. + +To môže byť užitočné, ak je vaša doména uvedená vo vyhľadávačoch a chcete +poskytnúť krajšiu stránku s chybou alebo konkrétne oznámiť vyhľadávačom, že +stránky domény už neexistujú. + +Ďalšou výhodou tohto hostiteľa je sledovanie protokolov o návštevách a +zobrazenie odkazov. diff --git a/frontend/src/locale/src/HelpDoc/sk/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/sk/ProxyHosts.md new file mode 100644 index 0000000000..631a1b49ae --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/sk/ProxyHosts.md @@ -0,0 +1,7 @@ +## Čo je proxy hostiteľ? + +Proxy hostiteľ je prichádzajúci koncový bod pre webovú službu, ktorú chcete presmerovať. + +Poskytuje voliteľné ukončenie SSL pre vašu službu, ktorá nemusí mať zabudovanú podporu SSL. + +Proxy hostitelia sú najbežnejším použitím pre Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/sk/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/sk/RedirectionHosts.md new file mode 100644 index 0000000000..92b5e540b8 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/sk/RedirectionHosts.md @@ -0,0 +1,7 @@ +## Čo je presmerovací hostiteľ? + +Presmerovací hostiteľ presmeruje požiadavky z prichádzajúcej domény a presmeruje +návštevníka na inú doménu. + +Najčastejším dôvodom na použitie tohto typu hostiteľa je situácia, keď vaša webová stránka zmení +doménu, ale stále máte odkazy vo vyhľadávačoch alebo referenčné odkazy smerujúce na starú doménu. diff --git a/frontend/src/locale/src/HelpDoc/sk/Streams.md b/frontend/src/locale/src/HelpDoc/sk/Streams.md new file mode 100644 index 0000000000..39b3b1ccc0 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/sk/Streams.md @@ -0,0 +1,6 @@ +## Čo je stream? + +Stream je relatívne nová funkcia pre Nginx, ktorá slúži na presmerovanie TCP/UDP +dátového toku priamo do iného počítača v sieti. + +Ak prevádzkujete herné servery, FTP alebo SSH servery, táto funkcia sa vám môže hodiť. diff --git a/frontend/src/locale/src/HelpDoc/sk/index.ts b/frontend/src/locale/src/HelpDoc/sk/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/sk/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/tr/AccessLists.md b/frontend/src/locale/src/HelpDoc/tr/AccessLists.md new file mode 100644 index 0000000000..7c02322e84 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tr/AccessLists.md @@ -0,0 +1,8 @@ +## Erişim Listesi Nedir? + +Erişim Listeleri, Temel HTTP Kimlik Doğrulama aracılığıyla Proxy Host'lar için belirli istemci IP adreslerinin kara listesi veya beyaz listesini ve kimlik doğrulamasını sağlar. + +Tek bir Erişim Listesi için birden fazla istemci kuralı, kullanıcı adı ve şifre yapılandırabilir ve bunu bir veya daha fazla _Proxy Host_'a uygulayabilirsiniz. + +Bu, yerleşik kimlik doğrulama mekanizmaları olmayan veya bilinmeyen istemcilerden korunmak istediğinizde iletilen web hizmetleri için en kullanışlıdır. + diff --git a/frontend/src/locale/src/HelpDoc/tr/Certificates.md b/frontend/src/locale/src/HelpDoc/tr/Certificates.md new file mode 100644 index 0000000000..8817d98b04 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tr/Certificates.md @@ -0,0 +1,29 @@ +## Sertifika Yardımı + +### HTTP Sertifikası + +Bir HTTP doğrulanmış sertifika, Let's Encrypt sunucularının +alan adlarınıza HTTP (HTTPS değil!) üzerinden ulaşmaya çalışacağı ve başarılı olursa, +sertifikanızı verecekleri anlamına gelir. + +Bu yöntem için, alan adlarınız için HTTP ile erişilebilir ve bu Nginx kurulumuna işaret eden bir _Proxy Host_ oluşturulmuş olmalıdır. Bir sertifika +verildikten sonra, _Proxy Host_'u HTTPS +bağlantıları için de bu sertifikayı kullanacak şekilde değiştirebilirsiniz. Ancak, sertifikanın yenilenmesi için _Proxy Host_'un hala HTTP erişimi için yapılandırılmış olması gerekecektir. + +Bu işlem joker karakter alan adlarını _desteklemez_. + +### DNS Sertifikası + +Bir DNS doğrulanmış sertifika, bir DNS Sağlayıcı eklentisi kullanmanızı gerektirir. Bu DNS +Sağlayıcı, alan adınızda geçici kayıtlar oluşturmak için kullanılacak ve ardından Let's +Encrypt bu kayıtları sorgulayarak sahibi olduğunuzdan emin olacak ve başarılı olursa, +sertifikanızı verecektir. + +Bu tür bir sertifika talep etmeden önce bir _Proxy Host_ oluşturulmasına gerek yoktur. Ayrıca _Proxy Host_'unuzun HTTP erişimi için yapılandırılmasına da gerek yoktur. + +Bu işlem joker karakter alan adlarını _destekler_. + +### Özel Sertifika + +Kendi Sertifika Otoriteniz tarafından sağlanan kendi SSL Sertifikanızı yüklemek için bu seçeneği kullanın. + diff --git a/frontend/src/locale/src/HelpDoc/tr/DeadHosts.md b/frontend/src/locale/src/HelpDoc/tr/DeadHosts.md new file mode 100644 index 0000000000..6d8d4334f3 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tr/DeadHosts.md @@ -0,0 +1,10 @@ +## 404 Host Nedir? + +404 Host, basitçe bir 404 sayfası gösteren bir host kurulumudur. + +Bu, alan adınız arama motorlarında listelendiğinde ve daha güzel bir hata sayfası sağlamak veya özellikle arama dizinleyicilerine +alan adı sayfalarının artık mevcut olmadığını söylemek istediğinizde yararlı olabilir. + +Bu host'un bir başka faydası da, ona yapılan isteklerin loglarını takip etmek ve +referansları görüntülemektir. + diff --git a/frontend/src/locale/src/HelpDoc/tr/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/tr/ProxyHosts.md new file mode 100644 index 0000000000..224a76a2f8 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tr/ProxyHosts.md @@ -0,0 +1,8 @@ +## Proxy Host Nedir? + +Proxy Host, iletilmek istediğiniz bir web hizmeti için gelen uç noktadır. + +SSL desteği yerleşik olmayan hizmetiniz için isteğe bağlı SSL sonlandırma sağlar. + +Proxy Host'lar, Nginx Proxy Manager'ın en yaygın kullanımıdır. + diff --git a/frontend/src/locale/src/HelpDoc/tr/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/tr/RedirectionHosts.md new file mode 100644 index 0000000000..01e3bf2c56 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tr/RedirectionHosts.md @@ -0,0 +1,8 @@ +## Yönlendirme Host'u Nedir? + +Yönlendirme Host'u, gelen alan adından gelen istekleri yönlendirir ve +görüntüleyiciyi başka bir alan adına yönlendirir. + +Bu tür bir host kullanmanın en yaygın nedeni, web sitenizin alan adı değiştiğinde +ancak hala eski alan adına işaret eden arama motoru veya referans bağlantılarınız olduğunda ortaya çıkar. + diff --git a/frontend/src/locale/src/HelpDoc/tr/Streams.md b/frontend/src/locale/src/HelpDoc/tr/Streams.md new file mode 100644 index 0000000000..696c27b526 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tr/Streams.md @@ -0,0 +1,7 @@ +## Akış Nedir? + +Nginx için nispeten yeni bir özellik olan Akış, TCP/UDP +trafiğini doğrudan ağdaki başka bir bilgisayara iletmek için hizmet edecektir. + +Oyun sunucuları, FTP veya SSH sunucuları çalıştırıyorsanız bu işinize yarayabilir. + diff --git a/frontend/src/locale/src/HelpDoc/tr/index.ts b/frontend/src/locale/src/HelpDoc/tr/index.ts new file mode 100644 index 0000000000..33e015cc81 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tr/index.ts @@ -0,0 +1,7 @@ +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/vi/AccessLists.md b/frontend/src/locale/src/HelpDoc/vi/AccessLists.md new file mode 100644 index 0000000000..50e5375a4c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/vi/AccessLists.md @@ -0,0 +1,11 @@ +## Khái niệm Access List là gì? + +Access List (Danh sách truy cập) cung cấp cơ chế chặn (blacklist) hoặc cho phép (whitelist) các địa chỉ IP của client, đồng thời hỗ trợ xác thực Basic HTTP Authentication cho các Proxy Host. + +Bạn có thể cấu hình nhiều quy tắc client, nhiều tên người dùng và mật khẩu trong một Access List duy nhất, sau đó áp dụng Access List đó cho một hoặc nhiều Proxy Host. + +Tính năng này đặc biệt hữu ích đối với: + +các dịch vụ web được forward mà không có cơ chế xác thực tích hợp, hoặc + +khi bạn muốn bảo vệ tài nguyên khỏi những client không xác định. diff --git a/frontend/src/locale/src/HelpDoc/vi/Certificates.md b/frontend/src/locale/src/HelpDoc/vi/Certificates.md new file mode 100644 index 0000000000..d25ef00784 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/vi/Certificates.md @@ -0,0 +1,25 @@ +## Hỗ trợ Chứng chỉ + +### Chứng chỉ HTTP (HTTP Certificate) + +Chứng chỉ được xác thực qua HTTP nghĩa là máy chủ của Let's Encrypt sẽ cố gắng truy cập vào tên miền của bạn thông qua HTTP (không phải HTTPS!). Nếu kiểm tra thành công, chứng chỉ sẽ được cấp. + +Với phương thức này, bạn phải tạo trước một Proxy Host cho tên miền, có thể truy cập qua HTTP và trỏ về đúng cài đặt Nginx này. +Sau khi chứng chỉ được cấp, bạn có thể chỉnh sửa Proxy Host để sử dụng chứng chỉ đó cho kết nối HTTPS. + +Tuy nhiên, Proxy Host vẫn phải hỗ trợ truy cập HTTP để việc gia hạn chứng chỉ diễn ra bình thường. + +Phương thức này _không hỗ trợ_ wildcard domain. + +### Chứng chỉ DNS (DNS Certificate) + +Chứng chỉ được xác thực qua DNS yêu cầu bạn sử dụng plugin của DNS Provider. +Plugin này sẽ tạo các bản ghi tạm thời trong DNS của bạn để Let's Encrypt kiểm tra quyền sở hữu tên miền. Nếu hợp lệ, chứng chỉ sẽ được cấp. + +Khi dùng phương thức này: Bạn không cần tạo sẵn Proxy Host trước và bạn không cần mở HTTP cho Proxy Host. + +Phương thức DNS _có hỗ trợ_ wildcard domain. + +### Chứng chỉ tùy chỉnh (Custom Certificate) + +Tùy chọn này cho phép bạn tải lên chứng chỉ SSL của riêng mình, được cung cấp bởi Certificate Authority (CA) mà bạn tự chọn. diff --git a/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md b/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md new file mode 100644 index 0000000000..4c0aae86c2 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md @@ -0,0 +1,8 @@ +## 404 Host là gì? + +404 Host đơn giản là một host được thiết lập để hiển thị trang 404. + +Điều này có thể hữu ích khi tên miền của bạn vẫn xuất hiện trên các công cụ tìm kiếm và bạn muốn hiển thị một trang lỗi đẹp hơn, hoặc muốn thông báo rõ ràng cho các trình thu thập dữ liệu tìm kiếm rằng các trang thuộc tên miền đó không còn tồn tại. + +Một lợi ích khác của việc có 404 Host là bạn có thể theo dõi nhật ký truy cập vào nó và +xem các nguồn giới thiệu (referrers). \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md new file mode 100644 index 0000000000..969f8a7734 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md @@ -0,0 +1,7 @@ +## Proxy Host là gì? + +Proxy Host là điểm truy cập đầu vào cho một dịch vụ web mà bạn muốn chuyển tiếp. + +Nó cung cấp khả năng kết thúc SSL (SSL termination) tùy chọn cho các dịch vụ vốn không hỗ trợ SSL tích hợp. + +Proxy Host là loại cấu hình phổ biến nhất trong Nginx Proxy Manager. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md new file mode 100644 index 0000000000..4339923643 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md @@ -0,0 +1,5 @@ +## Redirection Host là gì? + +Redirection Host sẽ chuyển hướng các yêu cầu từ tên miền truy cập vào và đưa người xem sang một tên miền khác + +Lý do phổ biến nhất để sử dụng loại host này là khi trang web của bạn đổi sang tên miền mới nhưng vẫn còn các liên kết từ công cụ tìm kiếm hoặc nguồn giới thiệu trỏ về tên miền cũ. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/vi/Streams.md b/frontend/src/locale/src/HelpDoc/vi/Streams.md new file mode 100644 index 0000000000..43cf97b2f6 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/vi/Streams.md @@ -0,0 +1,6 @@ +## Stream là gì? + +Stream là một tính năng tương đối mới của Nginx, dùng để chuyển tiếp lưu lượng +TCP/UDP trực tiếp tới một máy khác trong mạng. + +Nếu bạn đang vận hành các máy chủ game, FTP hoặc SSH thì tính năng này sẽ rất hữu ích. \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/vi/index.ts b/frontend/src/locale/src/HelpDoc/vi/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/vi/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/zh/AccessLists.md b/frontend/src/locale/src/HelpDoc/zh/AccessLists.md new file mode 100755 index 0000000000..5d508366be --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh/AccessLists.md @@ -0,0 +1,7 @@ +## 什么是通信规则? + +通信规则提供了一个特定客户IP地址的黑名单或白名单,以及通过基本HTTP认证对代理服务的认证。 + +你可以为一个通信规则配置多个客户规则、用户名和密码,然后将其应用于代理服务。 + +这对那些没有内置认证机制的转发网络服务或你想保护其免受未知客户的访问是最有用的。 diff --git a/frontend/src/locale/src/HelpDoc/zh/Certificates.md b/frontend/src/locale/src/HelpDoc/zh/Certificates.md new file mode 100755 index 0000000000..1c51c6e834 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh/Certificates.md @@ -0,0 +1,21 @@ +## 证书帮助 + +### HTTP 证书 + +HTTP 验证的证书表示 Let's Encrypt 服务器将尝试通过 HTTP(而非 HTTPS!)访问您的域名,如果成功,它们将为您颁发证书。 + +使用此方法时,您必须为您的域名创建一个可通过 HTTP 访问并指向此 Nginx 安装的 代理主机。在获得证书后,您可以修改该 代理主机,使其也使用此证书处理 HTTPS 连接。然而,为了证书能够续期,该 代理主机 仍需配置为支持 HTTP 访问。 + +此过程_不支持_通配符域名。 + +### DNS 证书 + +DNS 验证的证书要求您使用一个 DNS 服务商插件。该 DNS 服务商将用于在您的域名下创建临时记录,随后 Let's Encrypt 将查询这些记录以确认您是域名所有者,如果成功,它们将为您颁发证书。 + +请求此类证书前,您无需预先创建 代理主机,也无需将您的 代理主机 配置为支持 HTTP 访问。 + +此过程_支持_通配符域名。 + +### 自定义证书 + +使用此选项上传您自己的 SSL 证书,该证书由您自己的证书颁发机构提供。 diff --git a/frontend/src/locale/src/HelpDoc/zh/DeadHosts.md b/frontend/src/locale/src/HelpDoc/zh/DeadHosts.md new file mode 100755 index 0000000000..9d1660235b --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh/DeadHosts.md @@ -0,0 +1,7 @@ +## 什么是错误页面? + +错误页面是一个简单的主机设置,显示错误页面。 + +当你的域名被列入搜索引擎,而你想提供一个更好的错误页面或特别是告诉搜索索引者域名页面不再存在时,这可能是有用的。 + +拥有这种主机的另一个好处是可以跟踪点击它的日志并查看访问来源。 diff --git a/frontend/src/locale/src/HelpDoc/zh/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/zh/ProxyHosts.md new file mode 100755 index 0000000000..36da09e7c8 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh/ProxyHosts.md @@ -0,0 +1,7 @@ +## 什么是代理服务? + +代理服务是你想转发网络应用的主机。 + +代理服务可以为没有SSL服务的网络应用提供SSL服务(可选)。 + +代理服务是Nginx代理管理器的最常见用途之一。 diff --git a/frontend/src/locale/src/HelpDoc/zh/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/zh/RedirectionHosts.md new file mode 100755 index 0000000000..ef3a4d4039 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh/RedirectionHosts.md @@ -0,0 +1,5 @@ +## 什么是重定向? + +重定向是将接入域名的请求推送到另一个域名。 + +使用这种类型的主机最常见的原因是当你的网站改变了域名,但你仍然有链接指向旧域名的应用。 diff --git a/frontend/src/locale/src/HelpDoc/zh/Streams.md b/frontend/src/locale/src/HelpDoc/zh/Streams.md new file mode 100755 index 0000000000..24cac7cfb2 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh/Streams.md @@ -0,0 +1,5 @@ +## 什么是端口转发? + +端口转发是Nginx的一个相对较新的功能,可以直接转发 TCP/UDP 流量到网络上的另一台计算机。 + +如果你正在运行游戏服务器、FTP或SSH服务器,这个功能就会很有用。 diff --git a/frontend/src/locale/src/HelpDoc/zh/index.ts b/frontend/src/locale/src/HelpDoc/zh/index.ts new file mode 100755 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh/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/bg.json b/frontend/src/locale/src/bg.json new file mode 100644 index 0000000000..a530d3ea99 --- /dev/null +++ b/frontend/src/locale/src/bg.json @@ -0,0 +1,809 @@ +{ + "access-list": { + "defaultMessage": "Списък за достъп" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {правило} other {правила}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {потребител} other {потребители}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Когато съществува поне 1 правило, това правило за отказ се добавя последно" + }, + "access-list.help.rules-order": { + "defaultMessage": "Обърнете внимание, че правилата Позволяване и Отказване се прилагат в реда, в който са зададени." + }, + "access-list.pass-auth": { + "defaultMessage": "Предаване на автентикация към Upstream" + }, + "access-list.public": { + "defaultMessage": "Публичен достъп" + }, + "access-list.public.subtitle": { + "defaultMessage": "Без базова автентикация" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 или 192.168.1.0/24 или 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "Удовлетворяване на което и да е" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {потребител} other {потребители}}, {rules} {rules, plural, one {правило} other {правила}} - Създадено: {date}" + }, + "access-lists": { + "defaultMessage": "Списъци за достъп" + }, + "action.add": { + "defaultMessage": "Добавяне" + }, + "action.add-location": { + "defaultMessage": "Добавяне на маршрут" + }, + "action.allow": { + "defaultMessage": "Разрешаване" + }, + "action.close": { + "defaultMessage": "Затваряне" + }, + "action.delete": { + "defaultMessage": "Изтриване" + }, + "action.deny": { + "defaultMessage": "Отказване" + }, + "action.disable": { + "defaultMessage": "Деактивиране" + }, + "action.download": { + "defaultMessage": "Изтегляне" + }, + "action.edit": { + "defaultMessage": "Редактиране" + }, + "action.enable": { + "defaultMessage": "Активиране" + }, + "action.permissions": { + "defaultMessage": "Права" + }, + "action.renew": { + "defaultMessage": "Подновяване" + }, + "action.view-details": { + "defaultMessage": "Преглед на детайли" + }, + "auditlogs": { + "defaultMessage": "Журнали за одит" + }, + "auto": { + "defaultMessage": "Автоматично" + }, + "cancel": { + "defaultMessage": "Отказ" + }, + "certificate": { + "defaultMessage": "Сертификат" + }, + "certificate.custom-certificate": { + "defaultMessage": "Сертификат" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Ключ на сертификата" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Междинен сертификат" + }, + "certificate.in-use": { + "defaultMessage": "Използва се" + }, + "certificate.none.subtitle": { + "defaultMessage": "Не е назначен сертификат" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Този хост няма да използва HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Без сертификат" + }, + "certificate.not-in-use": { + "defaultMessage": "Не се използва" + }, + "certificate.renew": { + "defaultMessage": "Подновяване на сертификат" + }, + "certificates": { + "defaultMessage": "Сертификати" + }, + "certificates.custom": { + "defaultMessage": "Потребителски сертификат" + }, + "certificates.custom.warning": { + "defaultMessage": "Ключове, защитени с парола, не се поддържат." + }, + "certificates.dns.credentials": { + "defaultMessage": "Съдържание на файл с удостоверения" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Този плъгин изисква конфигурационен файл с API токен или други идентификационни данни." + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Тези данни ще бъдат съхранени като обикновен текст в базата и във файл!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Секунди за разпространение" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Оставете празно, за да се използва стойността по подразбиране. Брой секунди за изчакване на DNS разпространение." + }, + "certificates.dns.provider": { + "defaultMessage": "DNS доставчик" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Изберете доставчик..." + }, + "certificates.dns.warning": { + "defaultMessage": "Този раздел изисква познания за Certbot и неговите DNS плъгини. Моля, консултирайте се с документацията." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Сървър е намерен на този домейн, но не изглежда да е Nginx Proxy Manager. Уверете се, че домейнът сочи към IP адреса, където работи NPM." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Неуспешна проверка поради грешка в комуникацията със site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Няма достъпен сървър на този домейн. Проверете, че домейнът съществува и сочи към IP-та, където се изпълнява NPM, и ако е необходимо, че порт 80 е пренасочен." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Вашият сървър е достъпен и създаването на сертификати е възможно." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Намерен е сървър, но върна неочакван код {code}. Това NPM ли е? Уверете се, че домейнът сочи към вашия NPM сървър." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Намерен е сървър, но върна неочаквани данни. Това NPM ли е? Уверете се, че домейнът сочи към вашия NPM сървър." + }, + "certificates.http.test-results": { + "defaultMessage": "Резултати от теста" + }, + "certificates.http.warning": { + "defaultMessage": "Тези домейни трябва вече да сочат към тази инсталация." + }, + "certificates.key-type": { + "defaultMessage": "Тип ключ" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA е широко съвместим, ECDSA е по-бърз и по-сигурен, но може да не се поддържа от по-стари системи" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "с Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Заявка за нов сертификат" + }, + "column.access": { + "defaultMessage": "Достъп" + }, + "column.authorization": { + "defaultMessage": "Автентикация" + }, + "column.authorizations": { + "defaultMessage": "Автентикации" + }, + "column.custom-locations": { + "defaultMessage": "Персонализирани маршрути" + }, + "column.destination": { + "defaultMessage": "Дестинация" + }, + "column.details": { + "defaultMessage": "Детайли" + }, + "column.email": { + "defaultMessage": "Имейл" + }, + "column.event": { + "defaultMessage": "Събитие" + }, + "column.expires": { + "defaultMessage": "Изтича" + }, + "column.http-code": { + "defaultMessage": "HTTP код" + }, + "column.incoming-port": { + "defaultMessage": "Входящ порт" + }, + "column.name": { + "defaultMessage": "Име" + }, + "column.protocol": { + "defaultMessage": "Протокол" + }, + "column.provider": { + "defaultMessage": "Доставчик" + }, + "column.roles": { + "defaultMessage": "Роли" + }, + "column.rules": { + "defaultMessage": "Правила" + }, + "column.satisfy": { + "defaultMessage": "Удовлетворяване" + }, + "column.satisfy-all": { + "defaultMessage": "Всички" + }, + "column.satisfy-any": { + "defaultMessage": "Кое и да е" + }, + "column.scheme": { + "defaultMessage": "Схема" + }, + "column.source": { + "defaultMessage": "Източник" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Статус" + }, + "created-on": { + "defaultMessage": "Създадено: {date}" + }, + "dashboard": { + "defaultMessage": "Табло" + }, + "dead-host": { + "defaultMessage": "404 хост" + }, + "dead-hosts": { + "defaultMessage": "404 хостове" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {404 хост} other {404 хостове}}" + }, + "disabled": { + "defaultMessage": "Деактивиран" + }, + "domain-names": { + "defaultMessage": "Домейн имена" + }, + "domain-names.max": { + "defaultMessage": "Максимум {count} домейна" + }, + "domain-names.placeholder": { + "defaultMessage": "Започнете да въвеждате, за да добавите домейн..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Wildcard не е разрешен за този тип" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Wildcard не се поддържа от това CA" + }, + "domains.force-ssl": { + "defaultMessage": "Принудително SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS активирано" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS за поддомейни" + }, + "domains.http2-support": { + "defaultMessage": "Поддръжка на HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Използване на DNS Challenge" + }, + "email-address": { + "defaultMessage": "Имейл адрес" + }, + "empty-search": { + "defaultMessage": "Няма резултати" + }, + "empty-subtitle": { + "defaultMessage": "Защо не създадете един?" + }, + "enabled": { + "defaultMessage": "Активиран" + }, + "error.access.at-least-one": { + "defaultMessage": "Необходимо е поне една Автентикация или едно Правило за достъп" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Потребителските имена за достъп трябва да са уникални" + }, + "error.invalid-auth": { + "defaultMessage": "Невалиден имейл или парола" + }, + "error.invalid-domain": { + "defaultMessage": "Невалиден домейн: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Невалиден имейл адрес" + }, + "error.max-character-length": { + "defaultMessage": "Максималната дължина е {max} знак{max, plural, one {} other {а}}" + }, + "error.max-domains": { + "defaultMessage": "Твърде много домейни, максимум {max}" + }, + "error.maximum": { + "defaultMessage": "Максимум {max}" + }, + "error.min-character-length": { + "defaultMessage": "Минималната дължина е {min} знак{min, plural, one {} other {а}}" + }, + "error.minimum": { + "defaultMessage": "Минимум e {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Паролите трябва да съвпадат" + }, + "error.required": { + "defaultMessage": "Това поле е задължително" + }, + "expires.on": { + "defaultMessage": "Изтича: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Fork в GitHub" + }, + "host.flags.block-exploits": { + "defaultMessage": "Блокиране на често срещани експлойти" + }, + "host.flags.cache-assets": { + "defaultMessage": "Кеширане на ресурси" + }, + "host.flags.preserve-path": { + "defaultMessage": "Запазване на пътя" + }, + "host.flags.protocols": { + "defaultMessage": "Протоколи" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Поддръжка на WebSockets" + }, + "host.forward-port": { + "defaultMessage": "Порт" + }, + "host.forward-scheme": { + "defaultMessage": "Схема" + }, + "hosts": { + "defaultMessage": "Хостове" + }, + "http-only": { + "defaultMessage": "Само HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt чрез DNS" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt чрез HTTP" + }, + "loading": { + "defaultMessage": "Зареждане…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Вход в акаунта" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Персонализирана Nginx конфигурация" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Въведете вашата персонализирана Nginx конфигурация на собствен риск!" + }, + "no-permission-error": { + "defaultMessage": "Нямате достъп до тази страница." + }, + "notfound.action": { + "defaultMessage": "Към началната страница" + }, + "notfound.content": { + "defaultMessage": "Страницата, която търсите, не беше намерена" + }, + "notfound.title": { + "defaultMessage": "Упс… Намерихте грешка" + }, + "notification.error": { + "defaultMessage": "Грешка" + }, + "notification.object-deleted": { + "defaultMessage": "{object} беше изтрит" + }, + "notification.object-disabled": { + "defaultMessage": "{object} беше деактивиран" + }, + "notification.object-enabled": { + "defaultMessage": "{object} беше активиран" + }, + "notification.object-renewed": { + "defaultMessage": "{object} беше подновен" + }, + "notification.object-saved": { + "defaultMessage": "{object} беше запазен" + }, + "notification.success": { + "defaultMessage": "Успех" + }, + "object.actions-title": { + "defaultMessage": "{object} №{id}" + }, + "object.add": { + "defaultMessage": "Добавяне: {object}" + }, + "object.delete": { + "defaultMessage": "Изтриване: {object}" + }, + "object.delete.content": { + "defaultMessage": "Сигурни ли сте, че искате да изтриете {object}?" + }, + "object.edit": { + "defaultMessage": "Редактиране: {object}" + }, + "object.empty": { + "defaultMessage": "Няма налични {objects}" + }, + "object.event.created": { + "defaultMessage": "Създаден {object}" + }, + "object.event.deleted": { + "defaultMessage": "Изтрит {object}" + }, + "object.event.disabled": { + "defaultMessage": "Деактивиран {object}" + }, + "object.event.enabled": { + "defaultMessage": "Активиран {object}" + }, + "object.event.renewed": { + "defaultMessage": "Подновен {object}" + }, + "object.event.updated": { + "defaultMessage": "Актуализиран {object}" + }, + "offline": { + "defaultMessage": "Офлайн" + }, + "online": { + "defaultMessage": "Онлайн" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Опции" + }, + "password": { + "defaultMessage": "Парола" + }, + "password.generate": { + "defaultMessage": "Генериране на случайна парола" + }, + "password.hide": { + "defaultMessage": "Скриване на паролата" + }, + "password.show": { + "defaultMessage": "Показване на паролата" + }, + "permissions.hidden": { + "defaultMessage": "Скрито" + }, + "permissions.manage": { + "defaultMessage": "Управление" + }, + "permissions.view": { + "defaultMessage": "Само преглед" + }, + "permissions.visibility.all": { + "defaultMessage": "Всички елементи" + }, + "permissions.visibility.title": { + "defaultMessage": "Видимост на елементите" + }, + "permissions.visibility.user": { + "defaultMessage": "Само създадените от потребителя" + }, + "proxy-host": { + "defaultMessage": "Прокси хост" + }, + "proxy-host.forward-host": { + "defaultMessage": "Хост/IP за препращане" + }, + "proxy-hosts": { + "defaultMessage": "Прокси хостове" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {прокси хост} other {прокси хостове}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Публичен" + }, + "redirection-host": { + "defaultMessage": "Хост за пренасочване" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Домейн за пренасочване" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP код" + }, + "redirection-hosts": { + "defaultMessage": "Хостове за пренасочване" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {хост за пренасочване} other {хостове за пренасочване}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Multiple Choices" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Преместено постоянно" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Преместено временно" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 See other" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Временно пренасочване" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Постоянно пренасочване" + }, + "role.admin": { + "defaultMessage": "Администратор" + }, + "role.standard-user": { + "defaultMessage": "Обикновен потребител" + }, + "save": { + "defaultMessage": "Запазване" + }, + "setting": { + "defaultMessage": "Настройка" + }, + "settings": { + "defaultMessage": "Настройки" + }, + "settings.default-site": { + "defaultMessage": "Сайт по подразбиране" + }, + "settings.default-site.404": { + "defaultMessage": "404 страница" + }, + "settings.default-site.444": { + "defaultMessage": "Без отговор (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Страница поздравление" + }, + "settings.default-site.description": { + "defaultMessage": "Какво да се показва при заявка към неизвестен хост" + }, + "settings.default-site.html": { + "defaultMessage": "Персонализиран HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Пренасочване" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Започнете, като създадете администраторски акаунт." + }, + "setup.title": { + "defaultMessage": "Добре дошли!" + }, + "sign-in": { + "defaultMessage": "Вход" + }, + "ssl-certificate": { + "defaultMessage": "SSL сертификат" + }, + "stream": { + "defaultMessage": "Поток" + }, + "stream.forward-host": { + "defaultMessage": "Хост за препращане" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com или 10.0.0.1 или 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "Входящ порт" + }, + "streams": { + "defaultMessage": "Потоци" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {поток} other {потоци}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Тест" + }, + "update-available": { + "defaultMessage": "Налична актуализация: {latestVersion}" + }, + "user": { + "defaultMessage": "Потребител" + }, + "user.change-password": { + "defaultMessage": "Смяна на парола" + }, + "user.confirm-password": { + "defaultMessage": "Потвърждение на парола" + }, + "user.current-password": { + "defaultMessage": "Текуща парола" + }, + "user.edit-profile": { + "defaultMessage": "Редактиране на профил" + }, + "user.full-name": { + "defaultMessage": "Пълно име" + }, + "user.login-as": { + "defaultMessage": "Вход като {name}" + }, + "user.logout": { + "defaultMessage": "Изход" + }, + "user.new-password": { + "defaultMessage": "Нова парола" + }, + "user.nickname": { + "defaultMessage": "Псевдоним" + }, + "user.set-password": { + "defaultMessage": "Задаване на парола" + }, + "user.set-permissions": { + "defaultMessage": "Настройка на права за {name}" + }, + "user.switch-dark": { + "defaultMessage": "Тъмна тема" + }, + "user.switch-light": { + "defaultMessage": "Светла тема" + }, + "username": { + "defaultMessage": "Потребителско име" + }, + "users": { + "defaultMessage": "Потребители" + } +} diff --git a/frontend/src/locale/src/cs.json b/frontend/src/locale/src/cs.json new file mode 100644 index 0000000000..0b4ad55dab --- /dev/null +++ b/frontend/src/locale/src/cs.json @@ -0,0 +1,884 @@ +{ + "2fa.backup-codes-remaining": { + "defaultMessage": "Počet zbývajících záložních kódů: {count}" + }, + "2fa.backup-warning": { + "defaultMessage": "Tyto záložní kódy si uložte na bezpečném místě. Každý kód lze použít pouze jednou." + }, + "2fa.disable": { + "defaultMessage": "Vypnout dvoufaktorové ověřování" + }, + "2fa.disable-confirm": { + "defaultMessage": "Vypnout 2FA" + }, + "2fa.disable-warning": { + "defaultMessage": "Vypnutím dvoufaktorového ověřování snížíte bezpečnost svého účtu." + }, + "2fa.disabled": { + "defaultMessage": "Vypnuto" + }, + "2fa.done": { + "defaultMessage": "Uložil jsem si své záložní kódy." + }, + "2fa.enable": { + "defaultMessage": "Zapnout dvoufaktorové ověřování" + }, + "2fa.enabled": { + "defaultMessage": "Zapnuto" + }, + "2fa.enter-code": { + "defaultMessage": "Zadejte ověřovací kód" + }, + "2fa.enter-code-disable": { + "defaultMessage": "Zadejte ověřovací kód pro vypnutí" + }, + "2fa.regenerate": { + "defaultMessage": "Znovu vytvořit" + }, + "2fa.regenerate-backup": { + "defaultMessage": "Znovu vytvořit záložní kódy" + }, + "2fa.regenerate-instructions": { + "defaultMessage": "Zadejte ověřovací kód pro vytvoření nových záložních kódů. Vaše staré kódy budou neplatné." + }, + "2fa.secret-key": { + "defaultMessage": "Tajný klíč" + }, + "2fa.setup-instructions": { + "defaultMessage": "Naskenujte tento QR kód pomocí své ověřovací aplikace nebo zadejte tajný klíč ručně." + }, + "2fa.status": { + "defaultMessage": "Stav" + }, + "2fa.title": { + "defaultMessage": "Dvoufaktorové ověření" + }, + "2fa.verify-enable": { + "defaultMessage": "Ověřit a zapnout" + }, + "access-list": { + "defaultMessage": "seznam přístupů" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {pravidlo} few {pravidla} other {pravidel}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {uživatel} few {uživatelé} other {uživatelů}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Když existuje alespoň jedno pravidlo, toto pravidlo „zamítnout vše“ bude přidáno jako poslední" + }, + "access-list.help.rules-order": { + "defaultMessage": "Upozornění: pravidla povolit a zamítnout budou uplatňována v pořadí, v jakém jsou definována." + }, + "access-list.pass-auth": { + "defaultMessage": "Odeslat ověření na Upstream" + }, + "access-list.public": { + "defaultMessage": "Veřejně přístupné" + }, + "access-list.public.subtitle": { + "defaultMessage": "Není potřeba základní ověření" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 nebo 192.168.1.0/24 nebo 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "Splnit kterékoliv" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {uživatel} few {uživatelé} other {uživatelů}}, {rules} {rules, plural, one {pravidlo} few {pravidla} other {pravidel}} - Vytvořeno: {date}" + }, + "access-lists": { + "defaultMessage": "Seznamy přístupů" + }, + "action.add": { + "defaultMessage": "Přidat" + }, + "action.add-location": { + "defaultMessage": "Přidat umístění" + }, + "action.allow": { + "defaultMessage": "Povolit" + }, + "action.close": { + "defaultMessage": "Zavřít" + }, + "action.delete": { + "defaultMessage": "Smazat" + }, + "action.deny": { + "defaultMessage": "Zamítnout" + }, + "action.disable": { + "defaultMessage": "Deaktivovat" + }, + "action.download": { + "defaultMessage": "Stáhnout" + }, + "action.edit": { + "defaultMessage": "Upravit" + }, + "action.enable": { + "defaultMessage": "Aktivovat" + }, + "action.permissions": { + "defaultMessage": "Oprávnění" + }, + "action.renew": { + "defaultMessage": "Obnovit" + }, + "action.view-details": { + "defaultMessage": "Zobrazit podrobnosti" + }, + "auditlogs": { + "defaultMessage": "Záznamy auditu" + }, + "auto": { + "defaultMessage": "Automaticky" + }, + "cancel": { + "defaultMessage": "Zrušit" + }, + "certificate": { + "defaultMessage": "certifikát" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certifikát" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Klíč certifikátu" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Zprostředkovatelský certifikát" + }, + "certificate.in-use": { + "defaultMessage": "Používá se" + }, + "certificate.none.subtitle": { + "defaultMessage": "Není přiřazen žádný certifikát" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Tento hostitel nebude používat HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Žádný" + }, + "certificate.not-in-use": { + "defaultMessage": "Nepoužívá se" + }, + "certificate.renew": { + "defaultMessage": "Obnovit certifikát" + }, + "certificates": { + "defaultMessage": "Certifikáty" + }, + "certificates.custom": { + "defaultMessage": "Vlastní certifikát" + }, + "certificates.custom.warning": { + "defaultMessage": "Soubory klíčů chráněné heslem nejsou podporovány." + }, + "certificates.dns.credentials": { + "defaultMessage": "Obsah souboru s přihlašovacími údaji" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Tento doplněk vyžaduje konfigurační soubor obsahující API token nebo jiné přihlašovací údaje vašeho poskytovatele" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Tyto údaje budou uloženy v databázi a v souboru jako obyčejný text!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Propagace v sekundách" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Nechte prázdné pro výchozí hodnotu doplňku. Počet sekund, po které se čeká na propagaci DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "DNS poskytovatel" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Vyberte poskytovatele..." + }, + "certificates.dns.warning": { + "defaultMessage": "Tato sekce vyžaduje znalost Certbotu a jeho DNS doplňků. Prosím, podívejte se do dokumentace příslušného doplňku." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Na této doméně byl nalezen server, ale nezdá se, že jde o Nginx Proxy Manager. Ujistěte se, že vaše doména směřuje na IP, kde běží vaše instance NPM." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Nepodařilo se ověřit dostupnost kvůli chybě komunikace se službou site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Na této doméně není dostupný žádný server. Ujistěte se, že doména existuje a směřuje na IP adresu s NPM a pokud je to potřeba, port 80 je přesměrován ve vašem routeru." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Váš server je dostupný a vytvoření certifikátu by mělo být možné." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Na této doméně byl nalezen server, ale vrátil neočekávaný stavový kód {code}. Je to NPM server? Ujistěte se prosím, že doména směřuje na IP, kde běží vaše instance NPM." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Na této doméně byl nalezen server, ale vrátil neočekávaná data. Je to NPM server? Ujistěte se, že doména směřuje na IP, kde běží vaše instance NPM." + }, + "certificates.http.test-results": { + "defaultMessage": "Výsledky testu" + }, + "certificates.http.warning": { + "defaultMessage": "Tyto domény musí být již nakonfigurovány tak, aby směřovaly na tuto instalaci." + }, + "certificates.key-type": { + "defaultMessage": "Typ klíče" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA je široce kompatibilní, ECDSA je rychlejší a bezpečnější, ale nemusí být podporován staršími systémy" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "pomocí Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Vyžádat nový certifikát" + }, + "column.access": { + "defaultMessage": "Přístup" + }, + "column.authorization": { + "defaultMessage": "Autorizace" + }, + "column.authorizations": { + "defaultMessage": "Autorizace" + }, + "column.custom-locations": { + "defaultMessage": "Vlastní umístění" + }, + "column.destination": { + "defaultMessage": "Cíl" + }, + "column.details": { + "defaultMessage": "Podrobnosti" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.event": { + "defaultMessage": "Událost" + }, + "column.expires": { + "defaultMessage": "Platnost do" + }, + "column.http-code": { + "defaultMessage": "Přístup" + }, + "column.incoming-port": { + "defaultMessage": "Vstupní port" + }, + "column.name": { + "defaultMessage": "Název" + }, + "column.protocol": { + "defaultMessage": "Protokol" + }, + "column.provider": { + "defaultMessage": "Poskytovatel" + }, + "column.roles": { + "defaultMessage": "Role" + }, + "column.rules": { + "defaultMessage": "Pravidla" + }, + "column.satisfy": { + "defaultMessage": "Splnit" + }, + "column.satisfy-all": { + "defaultMessage": "Všechny" + }, + "column.satisfy-any": { + "defaultMessage": "Kterékoliv" + }, + "column.scheme": { + "defaultMessage": "Schéma" + }, + "column.source": { + "defaultMessage": "Zdroj" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Stav" + }, + "created-on": { + "defaultMessage": "Vytvořeno: {date}" + }, + "dashboard": { + "defaultMessage": "Panel" + }, + "dead-host": { + "defaultMessage": "404 hostitel" + }, + "dead-hosts": { + "defaultMessage": "404 Hostitelé" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {404 hostitel} few {404 hostitelé} other {404 hostitelů}}" + }, + "disabled": { + "defaultMessage": "Deaktivováno" + }, + "domain-names": { + "defaultMessage": "Doménová jména" + }, + "domain-names.max": { + "defaultMessage": "Maximálně {count} doménových jmen" + }, + "domain-names.placeholder": { + "defaultMessage": "Začněte psát pro přidání domény..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Wildcards nejsou pro tento typ povoleny" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Wildcards nejsou podporovány pro tuto certifikační autoritu" + }, + "domains.force-ssl": { + "defaultMessage": "Vynutit SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS povoleno" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS pro subdomény" + }, + "domains.http2-support": { + "defaultMessage": "Podpora HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Použít DNS výzvu" + }, + "email-address": { + "defaultMessage": "Emailová adresa" + }, + "empty-search": { + "defaultMessage": "Nebyly nalezeny žádné výsledky" + }, + "empty-subtitle": { + "defaultMessage": "Proč nevytvoříte nějaký?" + }, + "enabled": { + "defaultMessage": "Aktivováno" + }, + "error.access.at-least-one": { + "defaultMessage": "Je vyžadována alespoň jedna autorizace nebo jedno přístupové pravidlo" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Uživatelská jména pro autorizaci musí být jedinečná" + }, + "error.invalid-auth": { + "defaultMessage": "Neplatný email nebo heslo" + }, + "error.invalid-domain": { + "defaultMessage": "Neplatná doména: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Neplatná emailová adresa" + }, + "error.max-character-length": { + "defaultMessage": "Maximální délka je {max} znak{max, plural, one {} few {y} other {ů}}" + }, + "error.max-domains": { + "defaultMessage": "Příliš mnoho domén, maximum je {max}" + }, + "error.maximum": { + "defaultMessage": "Maximum je {max}" + }, + "error.min-character-length": { + "defaultMessage": "Minimální délka je {min} znak{min, plural, one {} few {y} other {ů}}" + }, + "error.minimum": { + "defaultMessage": "Minimum je {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Hesla se musí shodovat" + }, + "error.required": { + "defaultMessage": "Toto pole je povinné" + }, + "expires.on": { + "defaultMessage": "Platnost do: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Forkněte mě na GitHubu" + }, + "host.flags.block-exploits": { + "defaultMessage": "Blokovat běžné exploity" + }, + "host.flags.cache-assets": { + "defaultMessage": "Uložit zdroje do mezipaměti" + }, + "host.flags.preserve-path": { + "defaultMessage": "Zachovat cestu" + }, + "host.flags.protocols": { + "defaultMessage": "Protokoly" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Podpora WebSockets" + }, + "host.forward-port": { + "defaultMessage": "Port přesměrování" + }, + "host.forward-scheme": { + "defaultMessage": "Schéma" + }, + "hosts": { + "defaultMessage": "Hostitelé" + }, + "http-only": { + "defaultMessage": "Pouze HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt přes DNS" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt přes HTTP" + }, + "loading": { + "defaultMessage": "Načítá se…" + }, + "login.2fa-code": { + "defaultMessage": "Ověřovací kód" + }, + "login.2fa-code-placeholder": { + "defaultMessage": "Vložit kód" + }, + "login.2fa-description": { + "defaultMessage": "Vložte kód z vaší ověřovací aplikace" + }, + "login.2fa-title": { + "defaultMessage": "Dvoufaktorové ověření" + }, + "login.2fa-verify": { + "defaultMessage": "Ověřit" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Přihlaste se ke svému účtu" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Vlastní Nginx konfigurace" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Zadejte vlastní Nginx konfiguraci na vlastní riziko!" + }, + "no-permission-error": { + "defaultMessage": "Nemáte oprávnění k zobrazení tohoto obsahu." + }, + "notfound.action": { + "defaultMessage": "Zpět na hlavní stránku" + }, + "notfound.content": { + "defaultMessage": "Omlouváme se, stránka, kterou hledáte, nebyla nalezena" + }, + "notfound.title": { + "defaultMessage": "Ups… Našli jste chybovou stránku" + }, + "notification.error": { + "defaultMessage": "Chyba" + }, + "notification.object-deleted": { + "defaultMessage": "{object} byl odstraněn" + }, + "notification.object-disabled": { + "defaultMessage": "{object} byl deaktivován" + }, + "notification.object-enabled": { + "defaultMessage": "{object} byl aktivován" + }, + "notification.object-renewed": { + "defaultMessage": "{object} byl obnoven" + }, + "notification.object-saved": { + "defaultMessage": "{object} byl uložen" + }, + "notification.success": { + "defaultMessage": "Úspěch" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Přidat {object}" + }, + "object.delete": { + "defaultMessage": "Smazat {object}" + }, + "object.delete.content": { + "defaultMessage": "Opravdu chcete smazat tento {object}?" + }, + "object.edit": { + "defaultMessage": "Upravit {object}" + }, + "object.empty": { + "defaultMessage": "Nejsou {objects}" + }, + "object.event.created": { + "defaultMessage": "Vytvořen {object}" + }, + "object.event.deleted": { + "defaultMessage": "Smazán {object}" + }, + "object.event.disabled": { + "defaultMessage": "Deaktivován {object}" + }, + "object.event.enabled": { + "defaultMessage": "Aktivován {object}" + }, + "object.event.renewed": { + "defaultMessage": "Obnoven {object}" + }, + "object.event.updated": { + "defaultMessage": "Aktualizován {object}" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Možnosti" + }, + "password": { + "defaultMessage": "Heslo" + }, + "password.generate": { + "defaultMessage": "Vygenerovat náhodné heslo" + }, + "password.hide": { + "defaultMessage": "Skrýt heslo" + }, + "password.show": { + "defaultMessage": "Zobrazit heslo" + }, + "permissions.hidden": { + "defaultMessage": "Skryté" + }, + "permissions.manage": { + "defaultMessage": "Spravovat" + }, + "permissions.view": { + "defaultMessage": "Pouze pro zobrazení" + }, + "permissions.visibility.all": { + "defaultMessage": "Všechny položky" + }, + "permissions.visibility.title": { + "defaultMessage": "Viditelnost položky" + }, + "permissions.visibility.user": { + "defaultMessage": "Pouze vytvořené položky" + }, + "proxy-host": { + "defaultMessage": "proxy hostitele" + }, + "proxy-host.forward-host": { + "defaultMessage": "Cílový název hostitele / IP" + }, + "proxy-hosts": { + "defaultMessage": "Proxy hostitelé" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {proxy hostitel} few {proxy hostitelé} other {proxy hostitelů}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Veřejné" + }, + "redirection-host": { + "defaultMessage": "přesměrovacího hostitele" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Cílová doména" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP kód" + }, + "redirection-hosts": { + "defaultMessage": "Přesměrovací hostitelé" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {přesměrovací hostitel} few {přesměrovací hostitelé} other {přesměrovacích hostitelů}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Více možností" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Trvale přesunuto" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Dočasně přesunuto" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Podívat se na jiné" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Dočasné přesměrování" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Trvalé přesměrování" + }, + "role.admin": { + "defaultMessage": "Administrátor" + }, + "role.standard-user": { + "defaultMessage": "Běžný uživatel" + }, + "save": { + "defaultMessage": "Uložit" + }, + "setting": { + "defaultMessage": "Nastavení" + }, + "settings": { + "defaultMessage": "Nastavení" + }, + "settings.default-site": { + "defaultMessage": "Výchozí stránka" + }, + "settings.default-site.404": { + "defaultMessage": "Stránka 404" + }, + "settings.default-site.444": { + "defaultMessage": "Bez odpovědi (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Gratulační stránka" + }, + "settings.default-site.description": { + "defaultMessage": "Co zobrazit, když Nginx zachytí neznámého hostitele" + }, + "settings.default-site.html": { + "defaultMessage": "Vlastní HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Přesměrovat" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Začněte vytvořením administrátorského účtu." + }, + "setup.title": { + "defaultMessage": "Vítejte!" + }, + "sign-in": { + "defaultMessage": "Přihlásit se" + }, + "ssl-certificate": { + "defaultMessage": "SSL certifikát" + }, + "stream": { + "defaultMessage": "stream" + }, + "stream.forward-host": { + "defaultMessage": "Cílový hostitel" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "napriklad.cz nebo 10.0.0.1 nebo 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "Vstupní port" + }, + "streams": { + "defaultMessage": "Streamy" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {stream} few {streamy} other {streamů}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Test" + }, + "update-available": { + "defaultMessage": "Dostupná aktualizace: {latestVersion}" + }, + "user": { + "defaultMessage": "uživatele" + }, + "user.change-password": { + "defaultMessage": "Změnit heslo" + }, + "user.confirm-password": { + "defaultMessage": "Potvrdit heslo" + }, + "user.current-password": { + "defaultMessage": "Aktuální heslo" + }, + "user.edit-profile": { + "defaultMessage": "Upravit profil" + }, + "user.full-name": { + "defaultMessage": "Celé jméno" + }, + "user.login-as": { + "defaultMessage": "Přihlásit se jako {name}" + }, + "user.logout": { + "defaultMessage": "Odhlásit se" + }, + "user.new-password": { + "defaultMessage": "Nové heslo" + }, + "user.nickname": { + "defaultMessage": "Přezdívka" + }, + "user.set-password": { + "defaultMessage": "Nastavit heslo" + }, + "user.set-permissions": { + "defaultMessage": "Nastavit oprávnění pro {name}" + }, + "user.switch-dark": { + "defaultMessage": "Přepnout na tmavý režim" + }, + "user.switch-light": { + "defaultMessage": "Přepnout na světlý režim" + }, + "user.two-factor": { + "defaultMessage": "Dvoufaktorové ověření" + }, + "username": { + "defaultMessage": "Uživatelské jméno" + }, + "users": { + "defaultMessage": "Uživatelé" + } +} diff --git a/frontend/src/locale/src/de.json b/frontend/src/locale/src/de.json new file mode 100644 index 0000000000..33db259070 --- /dev/null +++ b/frontend/src/locale/src/de.json @@ -0,0 +1,770 @@ +{ + "access-list": { + "defaultMessage": "Zugriffsliste" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Regel} other {Regeln}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {User} other {Users}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Wenn mindestens eine Regel vorhanden ist, wird diese Regel zum Ablehnen aller Anfragen als letzte hinzugefügt." + }, + "access-list.help.rules-order": { + "defaultMessage": "Beachten Sie, dass die Anweisungen „Erlauben“ und „Verbieten“ in der Reihenfolge ihrer Definition angewendet werden." + }, + "access-list.pass-auth": { + "defaultMessage": "Authentifizierung an Upstream weiterleiten" + }, + "access-list.public": { + "defaultMessage": "Öffentlich" + }, + "access-list.public.subtitle": { + "defaultMessage": "Keine Authentifizierung erforderlich" + }, + "access-list.satisfy-any": { + "defaultMessage": "Satisfy Any" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Regel} other {Regeln}} - Erstellt: {date}" + }, + "access-lists": { + "defaultMessage": "Zugriffslisten" + }, + "action.add": { + "defaultMessage": "Hinzufügen" + }, + "action.add-location": { + "defaultMessage": "Pfad hinzufügen" + }, + "action.close": { + "defaultMessage": "Schließen" + }, + "action.delete": { + "defaultMessage": "Löschen" + }, + "action.disable": { + "defaultMessage": "Deaktivieren" + }, + "action.download": { + "defaultMessage": "Herunterladen" + }, + "action.edit": { + "defaultMessage": "Bearbeiten" + }, + "action.enable": { + "defaultMessage": "Aktivieren" + }, + "action.permissions": { + "defaultMessage": "Berechtigungen" + }, + "action.renew": { + "defaultMessage": "Erneuern" + }, + "action.view-details": { + "defaultMessage": "Details anzeigen" + }, + "auditlogs": { + "defaultMessage": "Protokolle" + }, + "cancel": { + "defaultMessage": "Abbrechen" + }, + "certificate": { + "defaultMessage": "Zertifikat" + }, + "certificate.custom-certificate": { + "defaultMessage": "Zertifikat" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Privater Schlüssel" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Zwischenzertifikat" + }, + "certificate.in-use": { + "defaultMessage": "In Benutzung" + }, + "certificate.none.subtitle": { + "defaultMessage": "Kein Zertifikat zugewiesen" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Dieser Host verwendet kein HTTPS." + }, + "certificate.none.title": { + "defaultMessage": "Kein" + }, + "certificate.not-in-use": { + "defaultMessage": "Nicht in Benutzung" + }, + "certificate.renew": { + "defaultMessage": "Zertifikat erneuern" + }, + "certificates": { + "defaultMessage": "Zertifikate" + }, + "certificates.custom": { + "defaultMessage": "Benutzerdefiniertes Zertifikat" + }, + "certificates.custom.warning": { + "defaultMessage": "Mit einem Passwort geschützte Schlüsseldateien werden nicht unterstützt." + }, + "certificates.dns.credentials": { + "defaultMessage": "Inhalt der Anmeldedaten-Datei" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Dieses Plugin erfordert eine Konfigurationsdatei, die einen API-Token oder andere Anmeldedaten für Ihren Anbieter enthält." + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Diese Daten werden als Klartext in der Datenbank und in einer Datei gespeichert!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Wartezeit in Sekunden" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Leer lassen um die Standardwartezeit des Plugins zu nutzen" + }, + "certificates.dns.provider": { + "defaultMessage": "DNS Provider" + }, + "certificates.dns.warning": { + "defaultMessage": "Dieser Abschnitt erfordert einige Kenntnisse über Certbot und seine DNS-Plugins. Bitte konsultieren Sie die jeweilige Plugin-Dokumentation." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Unter dieser Domain wurde ein Server gefunden, aber es scheint sich nicht um Nginx Proxy Manager zu handeln. Bitte stellen Sie sicher, dass Ihre Domain auf die IP-Adresse verweist, unter der Ihre NPM-Instanz ausgeführt wird." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Die Erreichbarkeit konnte aufgrund eines Kommunikationsfehlers mit site24x7.com nicht überprüft werden." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Unter dieser Domain ist kein Server verfügbar. Bitte stellen Sie sicher, dass Ihre Domain existiert und auf die IP-Adresse verweist, unter der Ihre NPM-Instanz läuft, und dass gegebenenfalls Port 80 in Ihrem Router weitergeleitet wird." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Ihr Server ist erreichbar und die Erstellung von Zertifikaten sollte möglich sein." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Unter dieser Domain wurde ein Server gefunden, der jedoch einen unerwarteten Statuscode {code} zurückgegeben hat. Handelt es sich um den NPM-Server? Bitte stellen Sie sicher, dass Ihre Domain auf die IP-Adresse verweist, unter der Ihre NPM-Instanz ausgeführt wird." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Unter dieser Domain wurde ein Server gefunden, der jedoch unerwartete Daten zurückgegeben hat. Handelt es sich um den NPM-Server? Bitte stellen Sie sicher, dass Ihre Domain auf die IP-Adresse verweist, unter der Ihre NPM-Instanz ausgeführt wird." + }, + "certificates.http.test-results": { + "defaultMessage": "Testergebnisse" + }, + "certificates.http.warning": { + "defaultMessage": "Diese Domänen müssen bereits so konfiguriert sein, dass sie auf diese Installation verweisen." + }, + "certificates.key-type": { + "defaultMessage": "Schlüsseltyp" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA ist weit verbreitet, ECDSA ist schneller und sicherer, wird aber möglicherweise von älteren Systemen nicht unterstützt" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "Über Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Anfordern eines neuen Zertifikates" + }, + "column.access": { + "defaultMessage": "Zugriff" + }, + "column.authorization": { + "defaultMessage": "Genehmigung" + }, + "column.authorizations": { + "defaultMessage": "Genehmigungen" + }, + "column.custom-locations": { + "defaultMessage": "Benutzerdefinierte Pfade" + }, + "column.destination": { + "defaultMessage": "Ziel" + }, + "column.details": { + "defaultMessage": "Details" + }, + "column.email": { + "defaultMessage": "E-Mail" + }, + "column.event": { + "defaultMessage": "Ereignis" + }, + "column.expires": { + "defaultMessage": "Verfällt am" + }, + "column.http-code": { + "defaultMessage": "HTTP Code" + }, + "column.incoming-port": { + "defaultMessage": "Eingehender Port" + }, + "column.name": { + "defaultMessage": "Name" + }, + "column.protocol": { + "defaultMessage": "Protokoll" + }, + "column.provider": { + "defaultMessage": "Provider" + }, + "column.roles": { + "defaultMessage": "Rollen" + }, + "column.rules": { + "defaultMessage": "Regeln" + }, + "column.satisfy": { + "defaultMessage": "Satisfy" + }, + "column.satisfy-all": { + "defaultMessage": "Alle" + }, + "column.satisfy-any": { + "defaultMessage": "Jeder" + }, + "column.scheme": { + "defaultMessage": "Schema" + }, + "column.source": { + "defaultMessage": "Quelle" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Status" + }, + "created-on": { + "defaultMessage": "Erstelldatum: {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": "Deaktiviert" + }, + "domain-names": { + "defaultMessage": "Domain Names" + }, + "domain-names.max": { + "defaultMessage": "{count} Maximale Anzahl von Domainnamen" + }, + "domain-names.placeholder": { + "defaultMessage": "Eintragen der Domain..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Wildcards sind für diesen Typ nicht zulässig." + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Wildcards werden für diese Zertifizierungsstelle nicht unterstützt." + }, + "domains.force-ssl": { + "defaultMessage": "Erzwinge SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS aktiviert" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS Sub-domains" + }, + "domains.http2-support": { + "defaultMessage": "HTTP/2 Support" + }, + "domains.use-dns": { + "defaultMessage": "Nutze DNS Challenge" + }, + "email-address": { + "defaultMessage": "E-Mail-Adresse" + }, + "empty-search": { + "defaultMessage": "Keine Ergebnisse gefunden" + }, + "empty-subtitle": { + "defaultMessage": "Warum erstellen Sie nicht eine?" + }, + "enabled": { + "defaultMessage": "aktiviert" + }, + "error.access.at-least-one": { + "defaultMessage": "Entweder eine Genehmigung oder eine Zugriffsregel ist erforderlich." + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Autorisierung Benutzernamen müssen eindeutig sein" + }, + "error.invalid-auth": { + "defaultMessage": "Ungültige E-Mail-Adresse oder Passwort" + }, + "error.invalid-domain": { + "defaultMessage": "Ungültige Domain: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Ungültige E-Mail-Adresse" + }, + "error.max-character-length": { + "defaultMessage": "Die maximale Länge beträgt {max} Zeichen{max, plural, one {} other {s}}" + }, + "error.max-domains": { + "defaultMessage": "Zu viele Domains, maximal sind {max}" + }, + "error.maximum": { + "defaultMessage": "Maximum ist {max}" + }, + "error.min-character-length": { + "defaultMessage": "Die minimale Länge beträgt {min} Zeichen{min, plural, one {} other {s}}" + }, + "error.minimum": { + "defaultMessage": "Minimum ist {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Passwörter müssen übereinstimmen" + }, + "error.required": { + "defaultMessage": "Dies ist erforderlich." + }, + "expires.on": { + "defaultMessage": "Ablauf am: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Fork me on Github" + }, + "host.flags.block-exploits": { + "defaultMessage": "Gängige Exploits blockieren" + }, + "host.flags.cache-assets": { + "defaultMessage": "Cache Assets" + }, + "host.flags.preserve-path": { + "defaultMessage": "Pfad beibehalten" + }, + "host.flags.protocols": { + "defaultMessage": "Protokole" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Websockets Support" + }, + "host.forward-port": { + "defaultMessage": "Forward Port" + }, + "host.forward-scheme": { + "defaultMessage": "Schema" + }, + "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": "Laden…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Anmelden" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Benutzerdefinierte Nginx Konfiguration" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Geben Sie hier Ihre benutzerdefinierte Nginx-Konfiguration auf eigene Gefahr ein!" + }, + "no-permission-error": { + "defaultMessage": "Sie haben keinen Zugriff, um dies anzuzeigen." + }, + "notfound.action": { + "defaultMessage": "Take me home" + }, + "notfound.content": { + "defaultMessage": "Es tut uns leid, aber die gesuchte Seite wurde nicht gefunden" + }, + "notfound.title": { + "defaultMessage": "Oops… You just found an error page" + }, + "notification.error": { + "defaultMessage": "Error" + }, + "notification.object-deleted": { + "defaultMessage": "{object} wurde gelöscht" + }, + "notification.object-disabled": { + "defaultMessage": "{object} wurde deaktiviert" + }, + "notification.object-enabled": { + "defaultMessage": "{object} wurde aktiviert" + }, + "notification.object-renewed": { + "defaultMessage": "{object} wurde erneuert" + }, + "notification.object-saved": { + "defaultMessage": "{object} wurde gespeichert" + }, + "notification.success": { + "defaultMessage": "Erfolgreich" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "{object} hinzufügen" + }, + "object.delete": { + "defaultMessage": "{object} löschen" + }, + "object.delete.content": { + "defaultMessage": "{object} wirklich löschen?" + }, + "object.edit": { + "defaultMessage": "{object} bearbeiten" + }, + "object.empty": { + "defaultMessage": "Keine {objects} vorhanden" + }, + "object.event.created": { + "defaultMessage": "{object} erstellt" + }, + "object.event.deleted": { + "defaultMessage": "{object} gelöscht" + }, + "object.event.disabled": { + "defaultMessage": "{object} deaktiviert" + }, + "object.event.enabled": { + "defaultMessage": "{object} aktiviert" + }, + "object.event.renewed": { + "defaultMessage": "{object} erneuert" + }, + "object.event.updated": { + "defaultMessage": "{object} aktualisiert" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Optionen" + }, + "password": { + "defaultMessage": "Passwort" + }, + "password.generate": { + "defaultMessage": "Zufälliges Passwort generieren" + }, + "password.hide": { + "defaultMessage": "Passwort verstecken" + }, + "password.show": { + "defaultMessage": "Passwort anzeigen" + }, + "permissions.hidden": { + "defaultMessage": "Versteckt" + }, + "permissions.manage": { + "defaultMessage": "Verwalten" + }, + "permissions.view": { + "defaultMessage": "Nur anzeigen" + }, + "permissions.visibility.all": { + "defaultMessage": "Alle Elemente" + }, + "permissions.visibility.title": { + "defaultMessage": "Objektsichtbarkeit" + }, + "permissions.visibility.user": { + "defaultMessage": "Nur erstellte Elemente" + }, + "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}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Öffentlich" + }, + "redirection-host": { + "defaultMessage": "Redirection Host" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Forward Domain" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP Code" + }, + "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": "Standardbenutzer" + }, + "save": { + "defaultMessage": "Speichern" + }, + "setting": { + "defaultMessage": "Einstellung" + }, + "settings": { + "defaultMessage": "Einstellungen" + }, + "settings.default-site": { + "defaultMessage": "Standardseite" + }, + "settings.default-site.404": { + "defaultMessage": "404 Page" + }, + "settings.default-site.444": { + "defaultMessage": "No Response (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Willkommensseite" + }, + "settings.default-site.description": { + "defaultMessage": "Was angezeigt wird, wenn der Nginx eine unbekannte Webseitenanfrage bekommt" + }, + "settings.default-site.html": { + "defaultMessage": "Benutzerdefinierte HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Weiterleitung" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Beginnen Sie mit der Erstellung Ihres Administratorkontos." + }, + "setup.title": { + "defaultMessage": "Willkommen!" + }, + "sign-in": { + "defaultMessage": "Login" + }, + "ssl-certificate": { + "defaultMessage": "SSL-Zertifikate" + }, + "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": "Passwort ändern" + }, + "user.confirm-password": { + "defaultMessage": "Passwort wiederholen" + }, + "user.current-password": { + "defaultMessage": "Aktuelles Passwort" + }, + "user.edit-profile": { + "defaultMessage": "Profil bearbeiten" + }, + "user.full-name": { + "defaultMessage": "Name" + }, + "user.login-as": { + "defaultMessage": "Einloggen als {name}" + }, + "user.logout": { + "defaultMessage": "Ausloggen" + }, + "user.new-password": { + "defaultMessage": "Neues Password" + }, + "user.nickname": { + "defaultMessage": "Nickname" + }, + "user.set-password": { + "defaultMessage": "Passwort setzen" + }, + "user.set-permissions": { + "defaultMessage": "Berechtigungen für {name} setzen" + }, + "user.switch-dark": { + "defaultMessage": "Zum Dark Mode wechseln" + }, + "user.switch-light": { + "defaultMessage": "Zum Light Mode wechseln" + }, + "username": { + "defaultMessage": "Benutzername" + }, + "users": { + "defaultMessage": "Benutzer" + } +} diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json new file mode 100644 index 0000000000..4b5b569312 --- /dev/null +++ b/frontend/src/locale/src/en.json @@ -0,0 +1,890 @@ +{ + "2fa.backup-codes-remaining": { + "defaultMessage": "Backup codes remaining: {count}" + }, + "2fa.backup-warning": { + "defaultMessage": "Save these backup codes in a secure place. Each code can only be used once." + }, + "2fa.disable": { + "defaultMessage": "Disable Two-Factor Authentication" + }, + "2fa.disable-confirm": { + "defaultMessage": "Disable 2FA" + }, + "2fa.disable-warning": { + "defaultMessage": "Disabling two-factor authentication will make your account less secure." + }, + "2fa.disabled": { + "defaultMessage": "Disabled" + }, + "2fa.done": { + "defaultMessage": "I have saved my backup codes" + }, + "2fa.enable": { + "defaultMessage": "Enable Two-Factor Authentication" + }, + "2fa.enabled": { + "defaultMessage": "Enabled" + }, + "2fa.enter-code": { + "defaultMessage": "Enter verification code" + }, + "2fa.enter-code-disable": { + "defaultMessage": "Enter verification code to disable" + }, + "2fa.regenerate": { + "defaultMessage": "Regenerate" + }, + "2fa.regenerate-backup": { + "defaultMessage": "Regenerate Backup Codes" + }, + "2fa.regenerate-instructions": { + "defaultMessage": "Enter a verification code to generate new backup codes. Your old codes will be invalidated." + }, + "2fa.secret-key": { + "defaultMessage": "Secret Key" + }, + "2fa.setup-instructions": { + "defaultMessage": "Scan this QR code with your authenticator app, or enter the secret manually." + }, + "2fa.status": { + "defaultMessage": "Status" + }, + "2fa.title": { + "defaultMessage": "Two-Factor Authentication" + }, + "2fa.verify-enable": { + "defaultMessage": "Verify and Enable" + }, + "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.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 or 192.168.1.0/24 or 2001:0db8::/32" + }, + "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.allow": { + "defaultMessage": "Allow" + }, + "action.close": { + "defaultMessage": "Close" + }, + "action.delete": { + "defaultMessage": "Delete" + }, + "action.deny": { + "defaultMessage": "Deny" + }, + "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" + }, + "auto": { + "defaultMessage": "Auto" + }, + "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" + }, + "certificate.renew": { + "defaultMessage": "Renew Certificate" + }, + "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.provider.placeholder": { + "defaultMessage": "Select a 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.key-type": { + "defaultMessage": "Key Type" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA is widely compatible, ECDSA is faster and more secure but may not be supported by older systems" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "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": "HTTP Code" + }, + "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.advanced": { + "defaultMessage": "Advanced" + }, + "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.trust-forwarded-proto": { + "defaultMessage": "Trust Upstream Forwarded Proto Headers" + }, + "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.2fa-code": { + "defaultMessage": "Verification Code" + }, + "login.2fa-code-placeholder": { + "defaultMessage": "Enter code" + }, + "login.2fa-description": { + "defaultMessage": "Enter the code from your authenticator app" + }, + "login.2fa-title": { + "defaultMessage": "Two-Factor Authentication" + }, + "login.2fa-verify": { + "defaultMessage": "Verify" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Login to your account" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "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" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "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}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Public" + }, + "redirection-host": { + "defaultMessage": "Redirection Host" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Forward Domain" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP Code" + }, + "redirection-hosts": { + "defaultMessage": "Redirection Hosts" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Multiple Choices" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Moved permanently" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Moved temporarily" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 See other" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Temporary redirect" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Permanent redirect" + }, + "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" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "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.forward-host.placeholder": { + "defaultMessage": "example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "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" + }, + "update-available": { + "defaultMessage": "Update Available: {latestVersion}" + }, + "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" + }, + "user.two-factor": { + "defaultMessage": "Two-Factor Auth" + }, + "username": { + "defaultMessage": "Username" + }, + "users": { + "defaultMessage": "Users" + } +} diff --git a/frontend/src/locale/src/es.json b/frontend/src/locale/src/es.json new file mode 100644 index 0000000000..78d8e88c33 --- /dev/null +++ b/frontend/src/locale/src/es.json @@ -0,0 +1,806 @@ +{ + "access-list": { + "defaultMessage": "Lista de Acceso" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Regla} other {Reglas}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {Usuario} other {Usuarios}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Cuando exista al menos 1 regla, esta regla de denegar todo se añadirá al final" + }, + "access-list.help.rules-order": { + "defaultMessage": "Ten en cuenta que las directivas de permitir y denegar se aplicarán en el orden en que estén definidas." + }, + "access-list.pass-auth": { + "defaultMessage": "Pasar Autenticación al Upstream" + }, + "access-list.public": { + "defaultMessage": "Accesible Públicamente" + }, + "access-list.public.subtitle": { + "defaultMessage": "No se requiere autenticación básica" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 o 192.168.1.0/24 o 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "Satisfacer Cualquiera" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {Usuario} other {Usuarios}}, {rules} {rules, plural, one {Regla} other {Reglas}} - Creado: {date}" + }, + "access-lists": { + "defaultMessage": "Listas de Acceso" + }, + "action.add": { + "defaultMessage": "Añadir" + }, + "action.add-location": { + "defaultMessage": "Añadir Ubicación" + }, + "action.allow": { + "defaultMessage": "Permitir" + }, + "action.close": { + "defaultMessage": "Cerrar" + }, + "action.delete": { + "defaultMessage": "Eliminar" + }, + "action.deny": { + "defaultMessage": "Denegar" + }, + "action.disable": { + "defaultMessage": "Deshabilitar" + }, + "action.download": { + "defaultMessage": "Descargar" + }, + "action.edit": { + "defaultMessage": "Editar" + }, + "action.enable": { + "defaultMessage": "Habilitar" + }, + "action.permissions": { + "defaultMessage": "Permisos" + }, + "action.renew": { + "defaultMessage": "Renovar" + }, + "action.view-details": { + "defaultMessage": "Ver Detalles" + }, + "auditlogs": { + "defaultMessage": "Registros de Auditoría" + }, + "auto": { + "defaultMessage": "Auto" + }, + "cancel": { + "defaultMessage": "Cancelar" + }, + "certificate": { + "defaultMessage": "Certificado" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certificado" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Clave del Certificado" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Certificado Intermedio" + }, + "certificate.in-use": { + "defaultMessage": "En Uso" + }, + "certificate.none.subtitle": { + "defaultMessage": "Sin certificado asignado" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Este host no usará HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Ninguno" + }, + "certificate.not-in-use": { + "defaultMessage": "Sin Usar" + }, + "certificate.renew": { + "defaultMessage": "Renovar Certificado" + }, + "certificates": { + "defaultMessage": "Certificados" + }, + "certificates.custom": { + "defaultMessage": "Certificado Personalizado" + }, + "certificates.custom.warning": { + "defaultMessage": "No se admiten archivos de claves protegidos con contraseña." + }, + "certificates.dns.credentials": { + "defaultMessage": "Contenido del Archivo de Credenciales" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Este plugin requiere un archivo de configuración que contenga un token de API u otras credenciales para tu proveedor" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "¡Estos datos se almacenarán como texto plano en la base de datos y en un archivo!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Segundos de Propagación" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Dejar vacío para usar el valor predeterminado del plugin. Número de segundos a esperar para la propagación DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "Proveedor DNS" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Selecciona un Proveedor..." + }, + "certificates.dns.warning": { + "defaultMessage": "Esta sección requiere algunos conocimientos sobre Certbot y sus plugins DNS. Consulta la documentación de los plugins respectivos." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Se encontró un servidor en este dominio pero no parece ser Nginx Proxy Manager. Asegúrate de que tu dominio apunte a la IP donde se está ejecutando tu instancia de NPM." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "No se pudo verificar la accesibilidad debido a un error de comunicación con site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "No hay ningún servidor disponible en este dominio. Asegúrate de que tu dominio existe y apunta a la IP donde se está ejecutando tu instancia de NPM y, si es necesario, que el puerto 80 esté redirigido en tu router." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Tu servidor es accesible y debería ser posible crear certificados." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Se encontró un servidor en este dominio pero devolvió un código de estado inesperado {code}. ¿Es el servidor NPM? Asegúrate de que tu dominio apunte a la IP donde se está ejecutando tu instancia de NPM." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Se encontró un servidor en este dominio pero devolvió datos inesperados. ¿Es el servidor NPM? Asegúrate de que tu dominio apunte a la IP donde se está ejecutando tu instancia de NPM." + }, + "certificates.http.test-results": { + "defaultMessage": "Resultados de la Prueba" + }, + "certificates.http.warning": { + "defaultMessage": "Estos dominios ya deben estar configurados para apuntar a esta instalación." + }, + "certificates.key-type": { + "defaultMessage": "Tipo de Clave" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA es ampliamente compatible, ECDSA es más rápido y seguro pero puede no ser compatible con sistemas antiguos" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "con Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Solicitar un nuevo Certificado" + }, + "column.access": { + "defaultMessage": "Acceso" + }, + "column.authorization": { + "defaultMessage": "Autorización" + }, + "column.authorizations": { + "defaultMessage": "Autorizaciones" + }, + "column.custom-locations": { + "defaultMessage": "Ubicaciones Personalizadas" + }, + "column.destination": { + "defaultMessage": "Destino" + }, + "column.details": { + "defaultMessage": "Detalles" + }, + "column.email": { + "defaultMessage": "Correo Electrónico" + }, + "column.event": { + "defaultMessage": "Evento" + }, + "column.expires": { + "defaultMessage": "Expira" + }, + "column.http-code": { + "defaultMessage": "Código HTTP" + }, + "column.incoming-port": { + "defaultMessage": "Puerto de Entrada" + }, + "column.name": { + "defaultMessage": "Nombre" + }, + "column.protocol": { + "defaultMessage": "Protocolo" + }, + "column.provider": { + "defaultMessage": "Proveedor" + }, + "column.roles": { + "defaultMessage": "Roles" + }, + "column.rules": { + "defaultMessage": "Reglas" + }, + "column.satisfy": { + "defaultMessage": "Satisfacer" + }, + "column.satisfy-all": { + "defaultMessage": "Todo" + }, + "column.satisfy-any": { + "defaultMessage": "Cualquiera" + }, + "column.scheme": { + "defaultMessage": "Esquema" + }, + "column.source": { + "defaultMessage": "Origen" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Estado" + }, + "created-on": { + "defaultMessage": "Creado: {date}" + }, + "dashboard": { + "defaultMessage": "Panel de Control" + }, + "dead-host": { + "defaultMessage": "Host 404" + }, + "dead-hosts": { + "defaultMessage": "Hosts 404" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host 404} other {Hosts 404}}" + }, + "disabled": { + "defaultMessage": "Deshabilitado" + }, + "domain-names": { + "defaultMessage": "Nombres de Dominio" + }, + "domain-names.max": { + "defaultMessage": "{count} nombres de dominio como máximo" + }, + "domain-names.placeholder": { + "defaultMessage": "Comienza a escribir para añadir dominio..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "No se permiten comodines para este tipo" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "No se admiten comodines para esta CA" + }, + "domains.force-ssl": { + "defaultMessage": "Forzar SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS Habilitado" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS en Subdominios" + }, + "domains.http2-support": { + "defaultMessage": "Soporte HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Usar Desafío DNS" + }, + "email-address": { + "defaultMessage": "Dirección de correo electrónico" + }, + "empty-search": { + "defaultMessage": "No se encontraron resultados" + }, + "empty-subtitle": { + "defaultMessage": "¿Por qué no creas uno?" + }, + "enabled": { + "defaultMessage": "Habilitado" + }, + "error.access.at-least-one": { + "defaultMessage": "Se requiere al menos una Autorización o una Regla de Acceso" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Los nombres de usuario de autorización deben ser únicos" + }, + "error.invalid-auth": { + "defaultMessage": "Correo electrónico o contraseña no válidos" + }, + "error.invalid-domain": { + "defaultMessage": "Dominio no válido: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Dirección de correo electrónico no válida" + }, + "error.max-character-length": { + "defaultMessage": "La longitud máxima es {max} caracter{max, plural, one {} other {es}}" + }, + "error.max-domains": { + "defaultMessage": "Demasiados dominios, el máximo es {max}" + }, + "error.maximum": { + "defaultMessage": "El máximo es {max}" + }, + "error.min-character-length": { + "defaultMessage": "La longitud mínima es {min} caracter{min, plural, one {} other {es}}" + }, + "error.minimum": { + "defaultMessage": "El mínimo es {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Las contraseñas deben coincidir" + }, + "error.required": { + "defaultMessage": "Este campo es obligatorio" + }, + "expires.on": { + "defaultMessage": "Expira: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Bifúrcame en Github" + }, + "host.flags.block-exploits": { + "defaultMessage": "Bloquear Exploits Comunes" + }, + "host.flags.cache-assets": { + "defaultMessage": "Cachear Recursos" + }, + "host.flags.preserve-path": { + "defaultMessage": "Preservar Ruta" + }, + "host.flags.protocols": { + "defaultMessage": "Protocolos" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Soporte de Websockets" + }, + "host.forward-port": { + "defaultMessage": "Puerto" + }, + "host.forward-scheme": { + "defaultMessage": "Esquema" + }, + "hosts": { + "defaultMessage": "Hosts" + }, + "http-only": { + "defaultMessage": "Solo HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt vía DNS" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt vía HTTP" + }, + "loading": { + "defaultMessage": "Cargando…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Inicia sesión en tu cuenta" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Configuración Personalizada de Nginx" + }, + "nginx-config.placeholder": { + "defaultMessage": "# ¡Introduce aquí tu configuración personalizada de Nginx bajo tu propio riesgo!" + }, + "no-permission-error": { + "defaultMessage": "No tienes acceso para ver esto." + }, + "notfound.action": { + "defaultMessage": "Llévame al inicio" + }, + "notfound.content": { + "defaultMessage": "Lo sentimos, pero la página que buscas no fue encontrada" + }, + "notfound.title": { + "defaultMessage": "Ups… Has encontrado una página de error" + }, + "notification.error": { + "defaultMessage": "Error" + }, + "notification.object-deleted": { + "defaultMessage": "{object} ha sido eliminado" + }, + "notification.object-disabled": { + "defaultMessage": "{object} ha sido deshabilitado" + }, + "notification.object-enabled": { + "defaultMessage": "{object} ha sido habilitado" + }, + "notification.object-renewed": { + "defaultMessage": "{object} ha sido renovado" + }, + "notification.object-saved": { + "defaultMessage": "{object} ha sido guardado" + }, + "notification.success": { + "defaultMessage": "Éxito" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Añadir {object}" + }, + "object.delete": { + "defaultMessage": "Eliminar {object}" + }, + "object.delete.content": { + "defaultMessage": "¿Estás seguro de que quieres eliminar este {object}?" + }, + "object.edit": { + "defaultMessage": "Editar {object}" + }, + "object.empty": { + "defaultMessage": "No hay {objects}" + }, + "object.event.created": { + "defaultMessage": "{object} Creado" + }, + "object.event.deleted": { + "defaultMessage": "{object} Eliminado" + }, + "object.event.disabled": { + "defaultMessage": "{object} Deshabilitado" + }, + "object.event.enabled": { + "defaultMessage": "{object} Habilitado" + }, + "object.event.renewed": { + "defaultMessage": "{object} Renovado" + }, + "object.event.updated": { + "defaultMessage": "{object} Actualizado" + }, + "offline": { + "defaultMessage": "Desconectado" + }, + "online": { + "defaultMessage": "Conectado" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Opciones" + }, + "password": { + "defaultMessage": "Contraseña" + }, + "password.generate": { + "defaultMessage": "Generar contraseña aleatoria" + }, + "password.hide": { + "defaultMessage": "Ocultar Contraseña" + }, + "password.show": { + "defaultMessage": "Mostrar Contraseña" + }, + "permissions.hidden": { + "defaultMessage": "Oculto" + }, + "permissions.manage": { + "defaultMessage": "Gestionar" + }, + "permissions.view": { + "defaultMessage": "Solo Ver" + }, + "permissions.visibility.all": { + "defaultMessage": "Todos los Elementos" + }, + "permissions.visibility.title": { + "defaultMessage": "Visibilidad de Elementos" + }, + "permissions.visibility.user": { + "defaultMessage": "Solo Elementos Creados" + }, + "proxy-host": { + "defaultMessage": "Host Proxy" + }, + "proxy-host.forward-host": { + "defaultMessage": "Nombre de Host / IP de Reenvío" + }, + "proxy-hosts": { + "defaultMessage": "Hosts Proxy" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host Proxy} other {Hosts Proxy}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Público" + }, + "redirection-host": { + "defaultMessage": "Host de Redirección" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Dominio de Reenvío" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "Código HTTP" + }, + "redirection-hosts": { + "defaultMessage": "Hosts de Redirección" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host de Redirección} other {Hosts de Redirección}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Multiples Opciones" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Movido permanentemente" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Movido temporalmente" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Ver otro" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Redirección temporal" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Redirección permanente" + }, + "role.admin": { + "defaultMessage": "Administrador" + }, + "role.standard-user": { + "defaultMessage": "Usuario Estándar" + }, + "save": { + "defaultMessage": "Guardar" + }, + "setting": { + "defaultMessage": "Configuración" + }, + "settings": { + "defaultMessage": "Configuración" + }, + "settings.default-site": { + "defaultMessage": "Sitio Predeterminado" + }, + "settings.default-site.404": { + "defaultMessage": "Página 404" + }, + "settings.default-site.444": { + "defaultMessage": "Sin Respuesta (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Página de Felicitaciones" + }, + "settings.default-site.description": { + "defaultMessage": "Qué mostrar cuando Nginx recibe un Host desconocido" + }, + "settings.default-site.html": { + "defaultMessage": "HTML Personalizado" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Redirigir" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Comienza creando tu cuenta de administrador." + }, + "setup.title": { + "defaultMessage": "¡Bienvenido!" + }, + "sign-in": { + "defaultMessage": "Iniciar Sesión" + }, + "ssl-certificate": { + "defaultMessage": "Certificado SSL" + }, + "stream": { + "defaultMessage": "Stream" + }, + "stream.forward-host": { + "defaultMessage": "Host de Reenvío" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com o 10.0.0.1 o 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "Puerto de Entrada" + }, + "streams": { + "defaultMessage": "Streams" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Stream} other {Streams}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Probar" + }, + "user": { + "defaultMessage": "Usuario" + }, + "user.change-password": { + "defaultMessage": "Cambiar Contraseña" + }, + "user.confirm-password": { + "defaultMessage": "Confirmar Contraseña" + }, + "user.current-password": { + "defaultMessage": "Contraseña Actual" + }, + "user.edit-profile": { + "defaultMessage": "Editar Perfil" + }, + "user.full-name": { + "defaultMessage": "Nombre Completo" + }, + "user.login-as": { + "defaultMessage": "Iniciar sesión como {name}" + }, + "user.logout": { + "defaultMessage": "Cerrar Sesión" + }, + "user.new-password": { + "defaultMessage": "Nueva Contraseña" + }, + "user.nickname": { + "defaultMessage": "Apodo" + }, + "user.set-password": { + "defaultMessage": "Establecer Contraseña" + }, + "user.set-permissions": { + "defaultMessage": "Establecer Permisos para {name}" + }, + "user.switch-dark": { + "defaultMessage": "Cambiar a modo Oscuro" + }, + "user.switch-light": { + "defaultMessage": "Cambiar a modo Claro" + }, + "username": { + "defaultMessage": "Nombre de Usuario" + }, + "users": { + "defaultMessage": "Usuarios" + } +} diff --git a/frontend/src/locale/src/fr.json b/frontend/src/locale/src/fr.json new file mode 100644 index 0000000000..6b2c8ac2f5 --- /dev/null +++ b/frontend/src/locale/src/fr.json @@ -0,0 +1,761 @@ +{ + "access-list": { + "defaultMessage": "Liste d'accès" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Règle} other {Règles}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {Utilisateur} other {Utilisateurs}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "S'il existe au moins une règle, cette règle de refuser tout sera ajoutée en dernier." + }, + "access-list.help.rules-order": { + "defaultMessage": "Notez que les directives autoriser et refuser seront appliquées dans l'ordre où elles sont définies." + }, + "access-list.pass-auth": { + "defaultMessage": "Transmettre l'authentification au serveur en amont" + }, + "access-list.public": { + "defaultMessage": "Accessible au public" + }, + "access-list.public.subtitle": { + "defaultMessage": "Aucune authentification de base requise" + }, + "access-list.satisfy-any": { + "defaultMessage": "Valide n'importe quelle règle" + }, + "access-list.subtitle": { + "defaultMessage": "{utilisateurs} {utilisateurs, plural, one {Utilisateur} other {Utilisateurs}}, {règles} {règles, plural, one {Règle} other {Règles}} - Crée : {date}" + }, + "access-lists": { + "defaultMessage": "Listes d'accès" + }, + "action.add": { + "defaultMessage": "Ajouter" + }, + "action.add-location": { + "defaultMessage": "Ajouter localisation" + }, + "action.close": { + "defaultMessage": "Fermer" + }, + "action.delete": { + "defaultMessage": "Supprimer" + }, + "action.disable": { + "defaultMessage": "Désactiver" + }, + "action.download": { + "defaultMessage": "Télécharger" + }, + "action.edit": { + "defaultMessage": "Modifier" + }, + "action.enable": { + "defaultMessage": "Activer" + }, + "action.permissions": { + "defaultMessage": "Permissions" + }, + "action.renew": { + "defaultMessage": "Renouveler" + }, + "action.view-details": { + "defaultMessage": "Voir les Détails" + }, + "auditlogs": { + "defaultMessage": "Journaux d'audit" + }, + "cancel": { + "defaultMessage": "Annuler" + }, + "certificate": { + "defaultMessage": "Certificat" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certificat" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Clé du Certificat" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Certificat intermédiaire" + }, + "certificate.in-use": { + "defaultMessage": "Utilisé" + }, + "certificate.none.subtitle": { + "defaultMessage": "Aucun certificat assigné" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Cet hôte n'utilisera pas le HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Aucun" + }, + "certificate.not-in-use": { + "defaultMessage": "Non utilisé" + }, + "certificate.renew": { + "defaultMessage": "Renouveler Certificat" + }, + "certificates": { + "defaultMessage": "Certificats" + }, + "certificates.custom": { + "defaultMessage": "Certificat personnalisé" + }, + "certificates.custom.warning": { + "defaultMessage": "Les fichiers de clé protégés par une passphrase ne sont pas acceptés." + }, + "certificates.dns.credentials": { + "defaultMessage": "Contenu du fichier d'identifiants" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Ce plugin nécessite un fichier de configuration contenant un jeton d'API ou d'autres informations d'identification pour votre fournisseur." + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Ces données seront stockées en clair dans la base de données et dans un fichier !" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Propagation Seconds" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Laisser vide pour utiliser la valeur par défaut du plugin. Nombre de secondes à attendre pour la propagation DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "Fournisseur DNS" + }, + "certificates.dns.warning": { + "defaultMessage": "Cette section requiert une certaine connaissance de Certbot et de ses plugins DNS. Veuillez consulter la documentation des plugins correspondants." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il ne semble pas s'agir de Nginx Proxy Manager. Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Impossible de vérifier l'accessibilité en raison d'une erreur de communication avec site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Aucun serveur n'est disponible pour ce domaine. Veuillez vérifier que votre domaine existe et pointe vers l'adresse IP où votre instance NPM est exécutée. Si nécessaire, le port 80 est ouvert dans votre routeur." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Votre serveur est accessible et la création de certificats devrait être possible." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il a renvoyé un code d'état inattendu {code}. S'agit-il du serveur NPM ? Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il a renvoyé des données inattendues. S'agit-il du serveur NPM ? Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée." + }, + "certificates.http.test-results": { + "defaultMessage": "Résultats du test" + }, + "certificates.http.warning": { + "defaultMessage": "Ces domaines doivent déjà être configurés pour pointer vers cette installation." + }, + "certificates.request.subtitle": { + "defaultMessage": "avec Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Demander un nouveau certificat" + }, + "column.access": { + "defaultMessage": "Accès" + }, + "column.authorization": { + "defaultMessage": "Autorisation" + }, + "column.authorizations": { + "defaultMessage": "Autorisations" + }, + "column.custom-locations": { + "defaultMessage": "Emplacement personnalisé" + }, + "column.destination": { + "defaultMessage": "Destination" + }, + "column.details": { + "defaultMessage": "Détails" + }, + "column.email": { + "defaultMessage": "eMail" + }, + "column.event": { + "defaultMessage": "Évènement" + }, + "column.expires": { + "defaultMessage": "Expire" + }, + "column.http-code": { + "defaultMessage": "Code HTTP" + }, + "column.incoming-port": { + "defaultMessage": "Port entrant" + }, + "column.name": { + "defaultMessage": "Nom" + }, + "column.protocol": { + "defaultMessage": "Protocole" + }, + "column.provider": { + "defaultMessage": "Fournisseur" + }, + "column.roles": { + "defaultMessage": "Rôles" + }, + "column.rules": { + "defaultMessage": "Règles" + }, + "column.satisfy": { + "defaultMessage": "Valide" + }, + "column.satisfy-all": { + "defaultMessage": "All" + }, + "column.satisfy-any": { + "defaultMessage": "Any" + }, + "column.scheme": { + "defaultMessage": "Schéma" + }, + "column.source": { + "defaultMessage": "Source" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Statut" + }, + "created-on": { + "defaultMessage": "Créé : {date}" + }, + "dashboard": { + "defaultMessage": "Tableau de bord" + }, + "dead-host": { + "defaultMessage": "Hôte 404" + }, + "dead-hosts": { + "defaultMessage": "Hôtes 404" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Hôte 404} other {Hôtes 404}}" + }, + "disabled": { + "defaultMessage": "Désactivé" + }, + "domain-names": { + "defaultMessage": "Noms de domaine" + }, + "domain-names.max": { + "defaultMessage": "{count} noms de domaine au maximum" + }, + "domain-names.placeholder": { + "defaultMessage": "Commencez à écrire pour ajouter un domaine…" + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Les Wildcards ne sont pas permises dans ce cas" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Les Wildcards ne sont pas prises en charge par cette autorité de certification." + }, + "domains.force-ssl": { + "defaultMessage": "Forcer SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS activé" + }, + "domains.hsts-subdomains": { + "defaultMessage": "Sous-domaines HSTS" + }, + "domains.http2-support": { + "defaultMessage": "Prise en charge de HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Utiliser le challenge DNS" + }, + "email-address": { + "defaultMessage": "Adresse eMail" + }, + "empty-search": { + "defaultMessage": "Aucun résultat trouvé" + }, + "empty-subtitle": { + "defaultMessage": "Pourquoi n'en créez-vous pas un ?" + }, + "enabled": { + "defaultMessage": "Activé" + }, + "error.access.at-least-one": { + "defaultMessage": "Une autorisation ou une règle d'accès est requise." + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Les noms d'utilisateurs autorisés doivent être uniques" + }, + "error.invalid-auth": { + "defaultMessage": "Adresse eMail ou mot de passe invalide" + }, + "error.invalid-domain": { + "defaultMessage": "Domaine invalide : {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Adresse eMail invalide" + }, + "error.max-character-length": { + "defaultMessage": "La longueur maximale est {max} caractère{max, plural, one {} other {s}}" + }, + "error.max-domains": { + "defaultMessage": "Trop de domaines, le maximum est {max}" + }, + "error.maximum": { + "defaultMessage": "Le maximum est {max}" + }, + "error.min-character-length": { + "defaultMessage": "La longueur minimale est {min} caractère{min, plural, one {} other {s}}" + }, + "error.minimum": { + "defaultMessage": "Le minimum est {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Les mots de passe doivent correspondre" + }, + "error.required": { + "defaultMessage": "Ceci est obligatoire" + }, + "expires.on": { + "defaultMessage": "Expire : {date}" + }, + "footer.github-fork": { + "defaultMessage": "Forkez-moi sur Github" + }, + "host.flags.block-exploits": { + "defaultMessage": "Bloquer les exploits courants" + }, + "host.flags.cache-assets": { + "defaultMessage": "Ressources du cache" + }, + "host.flags.preserve-path": { + "defaultMessage": "Préserver le chemin" + }, + "host.flags.protocols": { + "defaultMessage": "Protocoles" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Prise en charge de Websockets" + }, + "host.forward-port": { + "defaultMessage": "Port de redirection" + }, + "host.forward-scheme": { + "defaultMessage": "Schéma" + }, + "hosts": { + "defaultMessage": "Hôtes" + }, + "http-only": { + "defaultMessage": "HTTP uniquement" + }, + "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": "Chargement…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protégé par open-appsec" + }, + "login.title": { + "defaultMessage": "Connectez-vous à votre compte" + }, + "menu.openAppsecLog": { + "defaultMessage": "Journaux open-appsec" + }, + "nginx-config.label": { + "defaultMessage": "Configuration Nginx personnalisée" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Mettez ici votre configuration Nginx personnalisé à vos risques et périls !" + }, + "no-permission-error": { + "defaultMessage": "Vous n'avez pas la permission de voir ce contenu." + }, + "notfound.action": { + "defaultMessage": "Ramenez-moi à l'accueil" + }, + "notfound.content": { + "defaultMessage": "Nous sommes désolés, mais la page que vous cherchez est introuvable" + }, + "notfound.title": { + "defaultMessage": "Oops… Vous avez découvert une page d'erreur" + }, + "notification.error": { + "defaultMessage": "Erreur" + }, + "notification.object-deleted": { + "defaultMessage": "{object} a été supprimé" + }, + "notification.object-disabled": { + "defaultMessage": "{object} a été désactivé" + }, + "notification.object-enabled": { + "defaultMessage": "{object} a été activé" + }, + "notification.object-renewed": { + "defaultMessage": "{object} a été renouvelé" + }, + "notification.object-saved": { + "defaultMessage": "{object} a été enregistré" + }, + "notification.success": { + "defaultMessage": "Réussi" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Ajouter {object}" + }, + "object.delete": { + "defaultMessage": "Supprimer {object}" + }, + "object.delete.content": { + "defaultMessage": "Êtes-vous sûr de vouloir supprimer {object} ?" + }, + "object.edit": { + "defaultMessage": "Modifier {object}" + }, + "object.empty": { + "defaultMessage": "Il n'y a aucun {objects}" + }, + "object.event.created": { + "defaultMessage": "{object} créé" + }, + "object.event.deleted": { + "defaultMessage": "{object} supprimé" + }, + "object.event.disabled": { + "defaultMessage": "{object} désactivé" + }, + "object.event.enabled": { + "defaultMessage": "{object} activé" + }, + "object.event.renewed": { + "defaultMessage": "{object} renouvelé" + }, + "object.event.updated": { + "defaultMessage": "{object} mis à jour" + }, + "offline": { + "defaultMessage": "Hors ligne" + }, + "online": { + "defaultMessage": "En ligne" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Détails" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Événement" + }, + "openappsec-log.col.host": { + "defaultMessage": "Hôte" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Type d'incident" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Emplacement détecté" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Paramètre détecté" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Échantillon détecté" + }, + "openappsec-log.col.method": { + "defaultMessage": "Méthode" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Sévérité" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "IP Source" + }, + "openappsec-log.col.time": { + "defaultMessage": "Heure" + }, + "openappsec-log.col.uri": { + "defaultMessage": "Chemin URI" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Brut" + }, + "openappsec-log.details.title": { + "defaultMessage": "Détails de l'entrée de journal" + }, + "openappsec-log.tab.all": { + "defaultMessage": "Tous" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Détaillé" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "Journaux open-appsec" + }, + "options": { + "defaultMessage": "Options" + }, + "password": { + "defaultMessage": "Mot de passe" + }, + "password.generate": { + "defaultMessage": "Générer un mot de passe aléatoire" + }, + "password.hide": { + "defaultMessage": "Masquer le mot de passe" + }, + "password.show": { + "defaultMessage": "Afficher le mot de passe" + }, + "permissions.hidden": { + "defaultMessage": "Masquer" + }, + "permissions.manage": { + "defaultMessage": "Gérer" + }, + "permissions.view": { + "defaultMessage": "Voir uniquement" + }, + "permissions.visibility.all": { + "defaultMessage": "Tous les éléments" + }, + "permissions.visibility.title": { + "defaultMessage": "Éléments visibles" + }, + "permissions.visibility.user": { + "defaultMessage": "Éléments créés uniquement" + }, + "proxy-host": { + "defaultMessage": "Hôte proxy" + }, + "proxy-host.forward-host": { + "defaultMessage": "Nom d'hôte de redirection / IP" + }, + "proxy-hosts": { + "defaultMessage": "Hôtes proxy" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Hôte proxy} other {Hôtes proxy}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Confiance minimum" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critique" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "Élevée" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Faible" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Moyenne" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Activer la protection open-appsec" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Détecter-Apprendre" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactif" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prévenir-Apprendre" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Publique" + }, + "redirection-host": { + "defaultMessage": "Hôte de redirection" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Domaine de redirection" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "Code HTTP" + }, + "redirection-hosts": { + "defaultMessage": "Hôtes de redirection" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Hôte de redirection} other {Hôtes de redirection}}" + }, + "role.admin": { + "defaultMessage": "Administrateur" + }, + "role.standard-user": { + "defaultMessage": "Utilisateur standard" + }, + "save": { + "defaultMessage": "Enregistrer" + }, + "setting": { + "defaultMessage": "Paramètre" + }, + "settings": { + "defaultMessage": "Paramètres" + }, + "settings.default-site": { + "defaultMessage": "Site par défaut" + }, + "settings.default-site.404": { + "defaultMessage": "Page 404" + }, + "settings.default-site.444": { + "defaultMessage": "Aucune réponse (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Page de félicitations" + }, + "settings.default-site.description": { + "defaultMessage": "ce qu'il faut afficher lorsqu'un hôte inconnu est détecté par Nginx" + }, + "settings.default-site.html": { + "defaultMessage": "HTML personnalisé" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Redirection" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Politique locale" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "Fichier de configuration YAML d'open-appsec. Les modifications seront écrites dans /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Enregistrer" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Commencez par créer votre compte administrateur." + }, + "setup.title": { + "defaultMessage": "Bienvenue !" + }, + "sign-in": { + "defaultMessage": "Se connecter" + }, + "ssl-certificate": { + "defaultMessage": "Certificat SSL" + }, + "stream": { + "defaultMessage": "Stream" + }, + "stream.forward-host": { + "defaultMessage": "Hôte destinataire" + }, + "stream.incoming-port": { + "defaultMessage": "Port d'entrée" + }, + "streams": { + "defaultMessage": "Streams" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Stream} other {Streams}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Test" + }, + "update-available": { + "defaultMessage": "Mise à jour disponible : {latestVersion}" + }, + "user": { + "defaultMessage": "Utilisateur" + }, + "user.change-password": { + "defaultMessage": "Modifier le mot de passe" + }, + "user.confirm-password": { + "defaultMessage": "Confirmer le mot de passe" + }, + "user.current-password": { + "defaultMessage": "Mot de passe actuel" + }, + "user.edit-profile": { + "defaultMessage": "Modifier le profil" + }, + "user.full-name": { + "defaultMessage": "Nom complet" + }, + "user.login-as": { + "defaultMessage": "Se connecter en tant que {name}" + }, + "user.logout": { + "defaultMessage": "Déconnexion" + }, + "user.new-password": { + "defaultMessage": "Nouveau mot de passe" + }, + "user.nickname": { + "defaultMessage": "Pseudonyme" + }, + "user.set-password": { + "defaultMessage": "Définir le mot de passe" + }, + "user.set-permissions": { + "defaultMessage": "Définir les autorisations pour {name}" + }, + "user.switch-dark": { + "defaultMessage": "Passer au mode Sombre" + }, + "user.switch-light": { + "defaultMessage": "Passer au mode Lumineux" + }, + "username": { + "defaultMessage": "Nom d'utilisateur" + }, + "users": { + "defaultMessage": "Utilisateurs" + } +} diff --git a/frontend/src/locale/src/ga.json b/frontend/src/locale/src/ga.json new file mode 100644 index 0000000000..91d77a7851 --- /dev/null +++ b/frontend/src/locale/src/ga.json @@ -0,0 +1,797 @@ +{ + "access-list": { + "defaultMessage": "Liosta Rochtana" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Rial} other {Rialacha}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {Úsáideoir} other {Úsáideoirí}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Nuair a bhíonn riail amháin ar a laghad ann, cuirfear an riail seo chun gach rud a dhiúltú leis an gceann deireanach." + }, + "access-list.help.rules-order": { + "defaultMessage": "Tabhair faoi deara go gcuirfear na treoracha ceadaigh agus diúltaigh i bhfeidhm san ord a shainmhínítear iad." + }, + "access-list.pass-auth": { + "defaultMessage": "Tabhair Údarú chuig an Sruth Uachtarach" + }, + "access-list.public": { + "defaultMessage": "Inrochtana don Phobal" + }, + "access-list.public.subtitle": { + "defaultMessage": "Níl aon údarú bunúsach ag teastáil" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 nó 192.168.1.0/24 nó 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "Sásaigh Aon" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {Úsáideoir} other {Úsáideoirí}}, {rules} {rules, plural, one {Riail} other {Rialacha}} - Cruthaithe: {date}" + }, + "access-lists": { + "defaultMessage": "Liostaí Rochtana" + }, + "action.add": { + "defaultMessage": "Cuir leis" + }, + "action.add-location": { + "defaultMessage": "Cuir Suíomh leis" + }, + "action.allow": { + "defaultMessage": "Ceadaigh" + }, + "action.close": { + "defaultMessage": "Dún" + }, + "action.delete": { + "defaultMessage": "Scrios" + }, + "action.deny": { + "defaultMessage": "Diúltaigh" + }, + "action.disable": { + "defaultMessage": "Díchumasaigh" + }, + "action.download": { + "defaultMessage": "Íoslódáil" + }, + "action.edit": { + "defaultMessage": "Cuir in Eagar" + }, + "action.enable": { + "defaultMessage": "Cumasaigh" + }, + "action.permissions": { + "defaultMessage": "Ceadanna" + }, + "action.renew": { + "defaultMessage": "Athnuachan" + }, + "action.view-details": { + "defaultMessage": "Féach Sonraí" + }, + "auditlogs": { + "defaultMessage": "Logaí Iniúchta" + }, + "auto": { + "defaultMessage": "Uath" + }, + "cancel": { + "defaultMessage": "Cealaigh" + }, + "certificate": { + "defaultMessage": "Teastas" + }, + "certificate.custom-certificate": { + "defaultMessage": "Teastas" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Eochair Teastais" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Teastas Idirmheánach" + }, + "certificate.in-use": { + "defaultMessage": "In Úsáid" + }, + "certificate.none.subtitle": { + "defaultMessage": "Níor sannadh aon deimhniú" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Ní úsáidfidh an t-óstach seo HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Dada" + }, + "certificate.not-in-use": { + "defaultMessage": "Níor Úsáideadh" + }, + "certificate.renew": { + "defaultMessage": "Athnuachan an Teastais" + }, + "certificates": { + "defaultMessage": "Teastais" + }, + "certificates.custom": { + "defaultMessage": "Teastas Saincheaptha" + }, + "certificates.custom.warning": { + "defaultMessage": "Ní thacaítear le comhaid eochair atá cosanta le frása faire." + }, + "certificates.dns.credentials": { + "defaultMessage": "Ábhar Comhaid Dintiúir" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Éilíonn an breiseán seo comhad cumraíochta ina bhfuil comhartha API nó dintiúir eile do do sholáthraí." + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Stórálfar an fhaisnéis seo mar théacs simplí sa bhunachar sonraí agus i gcomhad!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Soicindí Iolraithe" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Fág folamh chun luach réamhshocraithe na mbreiseán a úsáid. Líon na soicindí le fanacht le haghaidh iomadú DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "Soláthraí DNS" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Roghnaigh Soláthraí..." + }, + "certificates.dns.warning": { + "defaultMessage": "Éilíonn an chuid seo roinnt eolais faoi Certbot agus a bhreiseáin DNS. Féach ar dhoiciméadacht na mbreiseán faoi seach, le do thoil." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Tá freastalaí aimsithe ag an bhfearann seo ach ní cosúil gur Bainisteoir Proxy Nginx atá ann. Déan cinnte go bhfuil do fhearann ag pointeáil chuig an seoladh IP ina bhfuil d'eispéireas NPM ag rith." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Theip ar sheiceáil an inrochtaineachta mar gheall ar earráid chumarsáide le site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Níl aon fhreastalaí ar fáil ag an bhfearann seo. Cinntigh le do thoil go bhfuil do fhearann ann agus go bhfuil sé ag pointeáil chuig an seoladh IP ina bhfuil d'eispéireas NPM ag rith agus más gá, go bhfuil port 80 curtha ar aghaidh i do ródaire." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Tá rochtain ar do fhreastalaí agus ba cheart go mbeadh sé indéanta deimhnithe a chruthú." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Tá freastalaí aimsithe ag an bhfearann seo ach thug sé cód stádais gan choinne {code} ar ais. An é an freastalaí NPM atá ann? Déan cinnte go bhfuil do fhearann ag pointeáil chuig an seoladh IP ina bhfuil d'eispéireas NPM ag rith." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Tá freastalaí aimsithe ag an bhfearann seo ach thug sé sonraí gan choinne ar ais. An é an freastalaí NPM atá ann? Déan cinnte go bhfuil do fhearann ag pointeáil chuig an seoladh IP ina bhfuil d'eispéireas NPM ag rith." + }, + "certificates.http.test-results": { + "defaultMessage": "Torthaí Tástála" + }, + "certificates.http.warning": { + "defaultMessage": "Ní mór na fearainn seo a bheith cumraithe cheana féin chun pointeáil chuig an suiteáil seo." + }, + "certificates.request.subtitle": { + "defaultMessage": "le Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Iarr Teastas nua" + }, + "column.access": { + "defaultMessage": "Rochtain" + }, + "column.authorization": { + "defaultMessage": "Údarú" + }, + "column.authorizations": { + "defaultMessage": "Údaruithe" + }, + "column.custom-locations": { + "defaultMessage": "Suíomhanna Saincheaptha" + }, + "column.destination": { + "defaultMessage": "Ceann Scríbe" + }, + "column.details": { + "defaultMessage": "Sonraí" + }, + "column.email": { + "defaultMessage": "Ríomhphost" + }, + "column.event": { + "defaultMessage": "Imeacht" + }, + "column.expires": { + "defaultMessage": "Éagaíonn" + }, + "column.http-code": { + "defaultMessage": "Cód HTTP" + }, + "column.incoming-port": { + "defaultMessage": "Port Isteach" + }, + "column.name": { + "defaultMessage": "Ainm" + }, + "column.protocol": { + "defaultMessage": "Prótacal" + }, + "column.provider": { + "defaultMessage": "Soláthraí" + }, + "column.roles": { + "defaultMessage": "Róil" + }, + "column.rules": { + "defaultMessage": "Rialacha" + }, + "column.satisfy": { + "defaultMessage": "Sásamh" + }, + "column.satisfy-all": { + "defaultMessage": "Gach" + }, + "column.satisfy-any": { + "defaultMessage": "Aon" + }, + "column.scheme": { + "defaultMessage": "Scéim" + }, + "column.source": { + "defaultMessage": "Foinse" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Stádas" + }, + "created-on": { + "defaultMessage": "Cruthaithe: {date}" + }, + "dashboard": { + "defaultMessage": "Painéal Rialaithe" + }, + "dead-host": { + "defaultMessage": "Óstach 404" + }, + "dead-hosts": { + "defaultMessage": "404 Óstaigh" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Óstach 404} other {Óstaigh 404}}" + }, + "disabled": { + "defaultMessage": "Míchumasaithe" + }, + "domain-names": { + "defaultMessage": "Ainmneacha Fearainn" + }, + "domain-names.max": { + "defaultMessage": "Uasmhéid d'ainmneacha fearainn: {count}" + }, + "domain-names.placeholder": { + "defaultMessage": "Tosaigh ag clóscríobh chun fearann a chur leis..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Ní cheadaítear cártaí fiáine don chineál seo" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Ní thacaítear le cártaí fiáine don ÚD seo" + }, + "domains.force-ssl": { + "defaultMessage": "Fórsáil SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "Cumasaithe HSTS" + }, + "domains.hsts-subdomains": { + "defaultMessage": "Fo-fhearainn HSTS" + }, + "domains.http2-support": { + "defaultMessage": "Tacaíocht HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Úsáid Dúshlán DNS" + }, + "email-address": { + "defaultMessage": "Seoladh ríomhphoist" + }, + "empty-search": { + "defaultMessage": "Níor aimsíodh aon torthaí" + }, + "empty-subtitle": { + "defaultMessage": "Cén fáth nach gcruthaíonn tú ceann?" + }, + "enabled": { + "defaultMessage": "Cumasaithe" + }, + "error.access.at-least-one": { + "defaultMessage": "Tá Údarú amháin nó Riail Rochtana amháin ag teastáil" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Ní mór d’ainmneacha úsáideora údaraithe a bheith uathúil" + }, + "error.invalid-auth": { + "defaultMessage": "Ríomhphost nó pasfhocal neamhbhailí" + }, + "error.invalid-domain": { + "defaultMessage": "Fearann neamhbhailí: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Seoladh ríomhphoist neamhbhailí" + }, + "error.max-character-length": { + "defaultMessage": "Is é an fad uasta ná {max} carachtar{max, plural, one {} other {anna}}" + }, + "error.max-domains": { + "defaultMessage": "An iomarca fearainn, is é {max} an t-uasmhéid" + }, + "error.maximum": { + "defaultMessage": "Is é {max} an t-uasmhéid" + }, + "error.min-character-length": { + "defaultMessage": "Is é an fad íosta ná {min} carachtar{min, plural, one {} other {anna}}" + }, + "error.minimum": { + "defaultMessage": "Is é {min} an t-íosmhéid" + }, + "error.passwords-must-match": { + "defaultMessage": "Ní mór pasfhocail a bheith mar a chéile" + }, + "error.required": { + "defaultMessage": "Tá sé seo riachtanach" + }, + "expires.on": { + "defaultMessage": "Éagaíonn: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Forc mé ar Github" + }, + "host.flags.block-exploits": { + "defaultMessage": "Blocáil Easnaimh Choitianta" + }, + "host.flags.cache-assets": { + "defaultMessage": "Sócmhainní Taisce" + }, + "host.flags.preserve-path": { + "defaultMessage": "Cosán a Chaomhnú" + }, + "host.flags.protocols": { + "defaultMessage": "Prótacail" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Tacaíocht Websockets" + }, + "host.forward-port": { + "defaultMessage": "Port Ar Aghaidh" + }, + "host.forward-scheme": { + "defaultMessage": "Scéim" + }, + "hosts": { + "defaultMessage": "Óstaigh" + }, + "http-only": { + "defaultMessage": "HTTP Amháin" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt trí DNS" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt trí HTTP" + }, + "loading": { + "defaultMessage": "Ag lódáil…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Logáil isteach i do chuntas" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Cumraíocht Nginx Saincheaptha" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Cuir isteach do chumraíocht saincheaptha Nginx anseo ar do phriacal féin!" + }, + "no-permission-error": { + "defaultMessage": "Níl rochtain agat chun seo a fheiceáil." + }, + "notfound.action": { + "defaultMessage": "Tabhair abhaile mé" + }, + "notfound.content": { + "defaultMessage": "Tá brón orainn ach níor aimsíodh an leathanach atá á lorg agat" + }, + "notfound.title": { + "defaultMessage": "Úps… Fuair tú leathanach earráide díreach anois." + }, + "notification.error": { + "defaultMessage": "Earráid" + }, + "notification.object-deleted": { + "defaultMessage": "Scriosadh {object}" + }, + "notification.object-disabled": { + "defaultMessage": "Tá {object} díchumasaithe" + }, + "notification.object-enabled": { + "defaultMessage": "Tá {object} cumasaithe" + }, + "notification.object-renewed": { + "defaultMessage": "Tá {object} athnuaite" + }, + "notification.object-saved": { + "defaultMessage": "Tá {object} sábháilte" + }, + "notification.success": { + "defaultMessage": "Rath" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Cuir {object} leis" + }, + "object.delete": { + "defaultMessage": "Scrios {object}" + }, + "object.delete.content": { + "defaultMessage": "An bhfuil tú cinnte gur mian leat an {object} seo a scriosadh?" + }, + "object.edit": { + "defaultMessage": "Cuir in eagar {object}" + }, + "object.empty": { + "defaultMessage": "Níl aon {objects} ann" + }, + "object.event.created": { + "defaultMessage": "Cruthaithe {object}" + }, + "object.event.deleted": { + "defaultMessage": "Scriosadh {object}" + }, + "object.event.disabled": { + "defaultMessage": "Díchumasaithe {object}" + }, + "object.event.enabled": { + "defaultMessage": "Cumasaithe {object}" + }, + "object.event.renewed": { + "defaultMessage": "Athnuaite {object}" + }, + "object.event.updated": { + "defaultMessage": "Nuashonraithe {object}" + }, + "offline": { + "defaultMessage": "As líne" + }, + "online": { + "defaultMessage": "Ar líne" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Roghanna" + }, + "password": { + "defaultMessage": "Pasfhocal" + }, + "password.generate": { + "defaultMessage": "Gin pasfhocal randamach" + }, + "password.hide": { + "defaultMessage": "Folaigh Pasfhocal" + }, + "password.show": { + "defaultMessage": "Taispeáin Pasfhocal" + }, + "permissions.hidden": { + "defaultMessage": "I bhfolach" + }, + "permissions.manage": { + "defaultMessage": "Bainistigh" + }, + "permissions.view": { + "defaultMessage": "Amharc Amháin" + }, + "permissions.visibility.all": { + "defaultMessage": "Gach Míreanna" + }, + "permissions.visibility.title": { + "defaultMessage": "Infheictheacht Míre" + }, + "permissions.visibility.user": { + "defaultMessage": "Míreanna Cruthaithe Amháin" + }, + "proxy-host": { + "defaultMessage": "Óstach Seachfhreastalaí" + }, + "proxy-host.forward-host": { + "defaultMessage": "Ainm Óstach / IP Ar Aghaidh" + }, + "proxy-hosts": { + "defaultMessage": "Óstaigh Seachfhreastalaí" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Óstach Seachfhreastalaí} other {Óstaigh Seachfhreastalaí}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Poiblí" + }, + "redirection-host": { + "defaultMessage": "Óstach Athsheolta" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Fearann Ar Aghaidh" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "Cód HTTP" + }, + "redirection-hosts": { + "defaultMessage": "Óstaigh Athsheolta" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Athsheoladh Óstach} other {Athsheoladh Óstaigh}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Rogha Ilghnéitheach" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Bogtha go buan" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Bogtha go sealadach" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Féach eile" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Atreorú sealadach" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Athsheoladh buan" + }, + "role.admin": { + "defaultMessage": "Riarthóir" + }, + "role.standard-user": { + "defaultMessage": "Úsáideoir Caighdeánach" + }, + "save": { + "defaultMessage": "Sábháil" + }, + "setting": { + "defaultMessage": "Socrú" + }, + "settings": { + "defaultMessage": "Socruithe" + }, + "settings.default-site": { + "defaultMessage": "Suíomh Réamhshocraithe" + }, + "settings.default-site.404": { + "defaultMessage": "Leathanach 404" + }, + "settings.default-site.444": { + "defaultMessage": "Gan Freagra (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Leathanach Comhghairdeas" + }, + "settings.default-site.description": { + "defaultMessage": "Cad atá le taispeáint nuair a bhuaileann óstach anaithnid Nginx" + }, + "settings.default-site.html": { + "defaultMessage": "HTML saincheaptha" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Atreorú" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Tosaigh trí do chuntas riarthóra a chruthú." + }, + "setup.title": { + "defaultMessage": "Fáilte!" + }, + "sign-in": { + "defaultMessage": "Sínigh isteach" + }, + "ssl-certificate": { + "defaultMessage": "Teastas SSL" + }, + "stream": { + "defaultMessage": "Sruth" + }, + "stream.forward-host": { + "defaultMessage": "Óstach Ar Aghaidh" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com nó 10.0.0.1 nó 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "Port Isteach" + }, + "streams": { + "defaultMessage": "Sruthanna" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Sruth} other {Sruthanna}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Tástáil" + }, + "update-available": { + "defaultMessage": "Nuashonrú ar Fáil: {latestVersion}" + }, + "user": { + "defaultMessage": "Úsáideoir" + }, + "user.change-password": { + "defaultMessage": "Athraigh Pasfhocal" + }, + "user.confirm-password": { + "defaultMessage": "Deimhnigh Pasfhocal" + }, + "user.current-password": { + "defaultMessage": "Pasfhocal Reatha" + }, + "user.edit-profile": { + "defaultMessage": "Cuir Próifíl in Eagar" + }, + "user.full-name": { + "defaultMessage": "Ainm Iomlán" + }, + "user.login-as": { + "defaultMessage": "Sínigh isteach mar {name}" + }, + "user.logout": { + "defaultMessage": "Logáil Amach" + }, + "user.new-password": { + "defaultMessage": "Pasfhocal Nua" + }, + "user.nickname": { + "defaultMessage": "Leasainm" + }, + "user.set-password": { + "defaultMessage": "Socraigh Pasfhocal" + }, + "user.set-permissions": { + "defaultMessage": "Socraigh Ceadanna do {name}" + }, + "user.switch-dark": { + "defaultMessage": "Athraigh go Mód Dorcha" + }, + "user.switch-light": { + "defaultMessage": "Athraigh go mód Solais" + }, + "username": { + "defaultMessage": "Ainm úsáideora" + }, + "users": { + "defaultMessage": "Úsáideoirí" + } +} diff --git a/frontend/src/locale/src/hu.json b/frontend/src/locale/src/hu.json new file mode 100644 index 0000000000..0568bc1b5d --- /dev/null +++ b/frontend/src/locale/src/hu.json @@ -0,0 +1,884 @@ +{ + "2fa.backup-codes-remaining": { + "defaultMessage": "Hátralévő tartalék kódok: {count}" + }, + "2fa.backup-warning": { + "defaultMessage": "Mentse el ezeket a tartalék kódokat biztonságos helyre. Minden kód csak egyszer használható." + }, + "2fa.disable": { + "defaultMessage": "Kétfaktoros hitelesítés letiltása" + }, + "2fa.disable-confirm": { + "defaultMessage": "2FA letiltása" + }, + "2fa.disable-warning": { + "defaultMessage": "A kétfaktoros hitelesítés letiltása kevésbé teszi biztonságossá a fiókját." + }, + "2fa.disabled": { + "defaultMessage": "Letiltva" + }, + "2fa.done": { + "defaultMessage": "Elmentettem a tartalék kódjaimat" + }, + "2fa.enable": { + "defaultMessage": "Kétfaktoros hitelesítés engedélyezése" + }, + "2fa.enabled": { + "defaultMessage": "Engedélyezve" + }, + "2fa.enter-code": { + "defaultMessage": "Adja meg az ellenőrző kódot" + }, + "2fa.enter-code-disable": { + "defaultMessage": "Adja meg az ellenőrző kódot a letiltáshoz" + }, + "2fa.regenerate": { + "defaultMessage": "Újragenerálás" + }, + "2fa.regenerate-backup": { + "defaultMessage": "Tartalék kódok újragenerálása" + }, + "2fa.regenerate-instructions": { + "defaultMessage": "Adjon meg egy ellenőrző kódot az új tartalék kódok generálásához. A régi kódok érvénytelenné válnak." + }, + "2fa.secret-key": { + "defaultMessage": "Titkos kulcs" + }, + "2fa.setup-instructions": { + "defaultMessage": "Olvassa be ezt a QR kódot a hitelesítő alkalmazásával, vagy adja meg a titkot manuálisan." + }, + "2fa.status": { + "defaultMessage": "Állapot" + }, + "2fa.title": { + "defaultMessage": "Kétfaktoros hitelesítés" + }, + "2fa.verify-enable": { + "defaultMessage": "Ellenőrzés és engedélyezés" + }, + "access-list": { + "defaultMessage": "Hozzáférési lista" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {szabály} other {szabály}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {felhasználó} other {felhasználó}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Ha legalább 1 szabály létezik, ez a mindent tiltó szabály utolsóként lesz hozzáadva" + }, + "access-list.help.rules-order": { + "defaultMessage": "Vegye figyelembe, hogy az engedélyező és tiltó direktívák a meghatározásuk sorrendjében lesznek alkalmazva." + }, + "access-list.pass-auth": { + "defaultMessage": "Hitelesítés továbbítása az upstream felé" + }, + "access-list.public": { + "defaultMessage": "Nyilvánosan elérhető" + }, + "access-list.public.subtitle": { + "defaultMessage": "Alapszintű hitelesítés nem szükséges" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 vagy 192.168.1.0/24 vagy 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "Bármely teljesítése" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {felhasználó} other {felhasználó}}, {rules} {rules, plural, one {szabály} other {szabály}} - Létrehozva: {date}" + }, + "access-lists": { + "defaultMessage": "Hozzáférési listák" + }, + "action.add": { + "defaultMessage": "Hozzáadás" + }, + "action.add-location": { + "defaultMessage": "Útvonal hozzáadása" + }, + "action.allow": { + "defaultMessage": "Engedélyezés" + }, + "action.close": { + "defaultMessage": "Bezárás" + }, + "action.delete": { + "defaultMessage": "Törlés" + }, + "action.deny": { + "defaultMessage": "Tiltás" + }, + "action.disable": { + "defaultMessage": "Letiltás" + }, + "action.download": { + "defaultMessage": "Letöltés" + }, + "action.edit": { + "defaultMessage": "Szerkesztés" + }, + "action.enable": { + "defaultMessage": "Engedélyezés" + }, + "action.permissions": { + "defaultMessage": "Engedélyek" + }, + "action.renew": { + "defaultMessage": "Megújítás" + }, + "action.view-details": { + "defaultMessage": "Részletek megtekintése" + }, + "auditlogs": { + "defaultMessage": "Audit naplók" + }, + "auto": { + "defaultMessage": "Automatikus" + }, + "cancel": { + "defaultMessage": "Mégse" + }, + "certificate": { + "defaultMessage": "Tanúsítvány" + }, + "certificate.custom-certificate": { + "defaultMessage": "Tanúsítvány" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Tanúsítvány kulcs" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Köztes tanúsítvány" + }, + "certificate.in-use": { + "defaultMessage": "Használatban" + }, + "certificate.none.subtitle": { + "defaultMessage": "Nincs tanúsítvány hozzárendelve" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Ez a kiszolgáló nem fog HTTPS-t használni" + }, + "certificate.none.title": { + "defaultMessage": "Nincs" + }, + "certificate.not-in-use": { + "defaultMessage": "Nincs használatban" + }, + "certificate.renew": { + "defaultMessage": "Tanúsítvány megújítása" + }, + "certificates": { + "defaultMessage": "Tanúsítványok" + }, + "certificates.custom": { + "defaultMessage": "Egyéni tanúsítvány" + }, + "certificates.custom.warning": { + "defaultMessage": "Jelszóval védett kulcsfájlok nem támogatottak." + }, + "certificates.dns.credentials": { + "defaultMessage": "Hitelesítő fájl tartalma" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Ez a plugin egy konfigurációs fájlt igényel, amely API tokent vagy egyéb hitelesítő adatokat tartalmaz a szolgáltatóhoz" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Ezek az adatok sima szövegként lesznek tárolva az adatbázisban és egy fájlban!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Propagálási másodpercek" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Hagyja üresen a plugin alapértelmezett értékének használatához. Másodpercek száma a DNS propagálás megvárásához." + }, + "certificates.dns.provider": { + "defaultMessage": "DNS szolgáltató" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Válasszon szolgáltatót..." + }, + "certificates.dns.warning": { + "defaultMessage": "Ez a szakasz némi ismeretet igényel a Certbot-ról és a DNS plugin-jeiről. Kérjük, olvassa el a megfelelő plugin dokumentációját." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Található szerver ezen a domain-en, de nem úgy tűnik, hogy Nginx Proxy Manager lenne. Kérjük, győződjön meg róla, hogy a domain arra az IP címre mutat, ahol az NPM példánya fut." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Az elérhetőség ellenőrzése sikertelen a site24x7.com kommunikációs hiba miatt." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Nincs elérhető szerver ezen a domain-en. Kérjük, győződjön meg róla, hogy a domain létezik és arra az IP címre mutat, ahol az NPM példánya fut, és szükség esetén a 80-as port továbbítva van a routerében." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "A szerver elérhető és a tanúsítványok létrehozása lehetséges lesz." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Található szerver ezen a domain-en, de váratlan {code} státuszkódot adott vissza. Ez az NPM szerver? Kérjük, győződjön meg róla, hogy a domain arra az IP címre mutat, ahol az NPM példánya fut." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Található szerver ezen a domain-en, de váratlan adatot adott vissza. Ez az NPM szerver? Kérjük, győződjön meg róla, hogy a domain arra az IP címre mutat, ahol az NPM példánya fut." + }, + "certificates.http.test-results": { + "defaultMessage": "Teszt eredmények" + }, + "certificates.http.warning": { + "defaultMessage": "Ezeknek a domain-eknek már konfigurálva kell lenniük, hogy erre a telepítésre mutassanak." + }, + "certificates.key-type": { + "defaultMessage": "Kulcs típus" + }, + "certificates.key-type-description": { + "defaultMessage": "Az RSA széles körben kompatibilis, az ECDSA gyorsabb és biztonságosabb, de nem biztos, hogy régebbi rendszerek támogatják" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "Let's Encrypt-tel" + }, + "certificates.request.title": { + "defaultMessage": "Új tanúsítvány kérelmezése" + }, + "column.access": { + "defaultMessage": "Hozzáférés" + }, + "column.authorization": { + "defaultMessage": "Jogosultság" + }, + "column.authorizations": { + "defaultMessage": "Jogosultságok" + }, + "column.custom-locations": { + "defaultMessage": "Egyéni útvonalak" + }, + "column.destination": { + "defaultMessage": "Cél" + }, + "column.details": { + "defaultMessage": "Részletek" + }, + "column.email": { + "defaultMessage": "E-mail" + }, + "column.event": { + "defaultMessage": "Esemény" + }, + "column.expires": { + "defaultMessage": "Lejár" + }, + "column.http-code": { + "defaultMessage": "HTTP kód" + }, + "column.incoming-port": { + "defaultMessage": "Bejövő port" + }, + "column.name": { + "defaultMessage": "Név" + }, + "column.protocol": { + "defaultMessage": "Protokoll" + }, + "column.provider": { + "defaultMessage": "Szolgáltató" + }, + "column.roles": { + "defaultMessage": "Szerepkörök" + }, + "column.rules": { + "defaultMessage": "Szabályok" + }, + "column.satisfy": { + "defaultMessage": "Teljesítés" + }, + "column.satisfy-all": { + "defaultMessage": "Összes" + }, + "column.satisfy-any": { + "defaultMessage": "Bármely" + }, + "column.scheme": { + "defaultMessage": "Séma" + }, + "column.source": { + "defaultMessage": "Forrás" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Állapot" + }, + "created-on": { + "defaultMessage": "Létrehozva: {date}" + }, + "dashboard": { + "defaultMessage": "Vezérlőpult" + }, + "dead-host": { + "defaultMessage": "404-es Kiszolgáló" + }, + "dead-hosts": { + "defaultMessage": "404-es Kiszolgálók" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {404-es Kiszolgáló} other {404-es Kiszolgálók}}" + }, + "disabled": { + "defaultMessage": "Letiltva" + }, + "domain-names": { + "defaultMessage": "Domain nevek" + }, + "domain-names.max": { + "defaultMessage": "Maximum {count} domain név" + }, + "domain-names.placeholder": { + "defaultMessage": "Kezdjen el gépelni domain hozzáadásához..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Helyettesítő karakterek nem engedélyezettek ennél a típusnál" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Helyettesítő karakterek nem támogatottak ennél a CA-nál" + }, + "domains.force-ssl": { + "defaultMessage": "SSL kényszerítése" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS engedélyezve" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS aldomain-ek" + }, + "domains.http2-support": { + "defaultMessage": "HTTP/2 támogatás" + }, + "domains.use-dns": { + "defaultMessage": "DNS Challenge használata" + }, + "email-address": { + "defaultMessage": "E-mail cím" + }, + "empty-search": { + "defaultMessage": "Nincs találat" + }, + "empty-subtitle": { + "defaultMessage": "Miért nem hoz létre egyet?" + }, + "enabled": { + "defaultMessage": "Engedélyezve" + }, + "error.access.at-least-one": { + "defaultMessage": "Legalább egy jogosultság vagy egy hozzáférési szabály szükséges" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "A jogosultsági felhasználóneveknek egyedieknek kell lenniük" + }, + "error.invalid-auth": { + "defaultMessage": "Érvénytelen e-mail vagy jelszó" + }, + "error.invalid-domain": { + "defaultMessage": "Érvénytelen domain: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Érvénytelen e-mail cím" + }, + "error.max-character-length": { + "defaultMessage": "Maximális hossz {max} karakter" + }, + "error.max-domains": { + "defaultMessage": "Túl sok domain, a maximum {max}" + }, + "error.maximum": { + "defaultMessage": "A maximum {max}" + }, + "error.min-character-length": { + "defaultMessage": "Minimális hossz {min} karakter" + }, + "error.minimum": { + "defaultMessage": "A minimum {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "A jelszavaknak egyezniük kell" + }, + "error.required": { + "defaultMessage": "Ez kötelező" + }, + "expires.on": { + "defaultMessage": "Lejár: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Fork-olj a GitHub-on" + }, + "host.flags.block-exploits": { + "defaultMessage": "Gyakori exploitok blokkolása" + }, + "host.flags.cache-assets": { + "defaultMessage": "Erőforrások gyorsítótárazása" + }, + "host.flags.preserve-path": { + "defaultMessage": "Útvonal megőrzése" + }, + "host.flags.protocols": { + "defaultMessage": "Protokollok" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Websockets támogatás" + }, + "host.forward-port": { + "defaultMessage": "Továbbító port" + }, + "host.forward-scheme": { + "defaultMessage": "Séma" + }, + "hosts": { + "defaultMessage": "Kiszolgálók" + }, + "http-only": { + "defaultMessage": "Csak HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt DNS-en keresztül" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt HTTP-n keresztül" + }, + "loading": { + "defaultMessage": "Betöltés…" + }, + "login.2fa-code": { + "defaultMessage": "Ellenőrző kód" + }, + "login.2fa-code-placeholder": { + "defaultMessage": "Adja meg a kódot" + }, + "login.2fa-description": { + "defaultMessage": "Adja meg a kódot a hitelesítő alkalmazásából" + }, + "login.2fa-title": { + "defaultMessage": "Kétfaktoros hitelesítés" + }, + "login.2fa-verify": { + "defaultMessage": "Ellenőrzés" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Jelentkezzen be a fiókjába" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Egyéni Nginx konfiguráció" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Adja meg az egyéni Nginx konfigurációját itt, saját felelősségére!" + }, + "no-permission-error": { + "defaultMessage": "Nincs jogosultsága ennek megtekintéséhez." + }, + "notfound.action": { + "defaultMessage": "Vigyen haza" + }, + "notfound.content": { + "defaultMessage": "Sajnáljuk, de a keresett oldal nem található" + }, + "notfound.title": { + "defaultMessage": "Hoppá… Hibás oldalra talált" + }, + "notification.error": { + "defaultMessage": "Hiba" + }, + "notification.object-deleted": { + "defaultMessage": "{object} törölve lett" + }, + "notification.object-disabled": { + "defaultMessage": "{object} letiltva lett" + }, + "notification.object-enabled": { + "defaultMessage": "{object} engedélyezve lett" + }, + "notification.object-renewed": { + "defaultMessage": "{object} megújítva lett" + }, + "notification.object-saved": { + "defaultMessage": "{object} mentve lett" + }, + "notification.success": { + "defaultMessage": "Sikeres" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "{object} hozzáadása" + }, + "object.delete": { + "defaultMessage": "{object} törlése" + }, + "object.delete.content": { + "defaultMessage": "Biztosan törölni szeretné ezt: {object}?" + }, + "object.edit": { + "defaultMessage": "{object} szerkesztése" + }, + "object.empty": { + "defaultMessage": "Nincsenek {objects}" + }, + "object.event.created": { + "defaultMessage": "{object} létrehozva" + }, + "object.event.deleted": { + "defaultMessage": "{object} törölve" + }, + "object.event.disabled": { + "defaultMessage": "{object} letiltva" + }, + "object.event.enabled": { + "defaultMessage": "{object} engedélyezve" + }, + "object.event.renewed": { + "defaultMessage": "{object} megújítva" + }, + "object.event.updated": { + "defaultMessage": "{object} frissítve" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Beállítások" + }, + "password": { + "defaultMessage": "Jelszó" + }, + "password.generate": { + "defaultMessage": "Véletlenszerű jelszó generálása" + }, + "password.hide": { + "defaultMessage": "Jelszó elrejtése" + }, + "password.show": { + "defaultMessage": "Jelszó megjelenítése" + }, + "permissions.hidden": { + "defaultMessage": "Rejtett" + }, + "permissions.manage": { + "defaultMessage": "Kezelés" + }, + "permissions.view": { + "defaultMessage": "Csak megtekintés" + }, + "permissions.visibility.all": { + "defaultMessage": "Összes elem" + }, + "permissions.visibility.title": { + "defaultMessage": "Elemek láthatósága" + }, + "permissions.visibility.user": { + "defaultMessage": "Csak létrehozott elemek" + }, + "proxy-host": { + "defaultMessage": "Proxy Kiszolgáló" + }, + "proxy-host.forward-host": { + "defaultMessage": "Továbbító hostnév / IP" + }, + "proxy-hosts": { + "defaultMessage": "Proxy Kiszolgálók" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Proxy Kiszolgáló} other {Proxy Kiszolgálók}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Nyilvános" + }, + "redirection-host": { + "defaultMessage": "Átirányító Kiszolgáló" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Továbbító domain" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP kód" + }, + "redirection-hosts": { + "defaultMessage": "Átirányító Kiszolgálók" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Átirányító Kiszolgáló} other {Átirányító Kiszolgálók}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Többszörös választás" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Véglegesen áthelyezve" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Ideiglenesen áthelyezve" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Lásd másik" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Ideiglenes átirányítás" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Végleges átirányítás" + }, + "role.admin": { + "defaultMessage": "Adminisztrátor" + }, + "role.standard-user": { + "defaultMessage": "Általános felhasználó" + }, + "save": { + "defaultMessage": "Mentés" + }, + "setting": { + "defaultMessage": "Beállítás" + }, + "settings": { + "defaultMessage": "Beállítások" + }, + "settings.default-site": { + "defaultMessage": "Alapértelmezett oldal" + }, + "settings.default-site.404": { + "defaultMessage": "404-es oldal" + }, + "settings.default-site.444": { + "defaultMessage": "Nincs válasz (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Gratulálunk oldal" + }, + "settings.default-site.description": { + "defaultMessage": "Mit mutasson az Nginx ismeretlen Kiszolgáló esetén" + }, + "settings.default-site.html": { + "defaultMessage": "Egyéni HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Átirányítás" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Kezdje az admin fiók létrehozásával." + }, + "setup.title": { + "defaultMessage": "Üdvözöljük!" + }, + "sign-in": { + "defaultMessage": "Bejelentkezés" + }, + "ssl-certificate": { + "defaultMessage": "SSL tanúsítvány" + }, + "stream": { + "defaultMessage": "Stream" + }, + "stream.forward-host": { + "defaultMessage": "Továbbító kiszolgáló" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com vagy 10.0.0.1 vagy 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "Bejövő port" + }, + "streams": { + "defaultMessage": "Streamek" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Stream} other {Stream}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Teszt" + }, + "update-available": { + "defaultMessage": "Frissítés elérhető: {latestVersion}" + }, + "user": { + "defaultMessage": "Felhasználó" + }, + "user.change-password": { + "defaultMessage": "Jelszó megváltoztatása" + }, + "user.confirm-password": { + "defaultMessage": "Jelszó megerősítése" + }, + "user.current-password": { + "defaultMessage": "Jelenlegi jelszó" + }, + "user.edit-profile": { + "defaultMessage": "Profil szerkesztése" + }, + "user.full-name": { + "defaultMessage": "Teljes név" + }, + "user.login-as": { + "defaultMessage": "Bejelentkezés mint {name}" + }, + "user.logout": { + "defaultMessage": "Kijelentkezés" + }, + "user.new-password": { + "defaultMessage": "Új jelszó" + }, + "user.nickname": { + "defaultMessage": "Becenév" + }, + "user.set-password": { + "defaultMessage": "Jelszó beállítása" + }, + "user.set-permissions": { + "defaultMessage": "Engedélyek beállítása {name} számára" + }, + "user.switch-dark": { + "defaultMessage": "Váltás sötét módra" + }, + "user.switch-light": { + "defaultMessage": "Váltás világos módra" + }, + "user.two-factor": { + "defaultMessage": "Kétfaktoros hitelesítés" + }, + "username": { + "defaultMessage": "Felhasználónév" + }, + "users": { + "defaultMessage": "Felhasználók" + } +} diff --git a/frontend/src/locale/src/id.json b/frontend/src/locale/src/id.json new file mode 100644 index 0000000000..e5a6075166 --- /dev/null +++ b/frontend/src/locale/src/id.json @@ -0,0 +1,797 @@ +{ + "access-list": { + "defaultMessage": "Daftar Akses" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Aturan} other {Aturan}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {Pengguna} other {Pengguna}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Jika setidaknya 1 aturan ada, aturan tolak semua ini akan ditambahkan paling akhir" + }, + "access-list.help.rules-order": { + "defaultMessage": "Perhatikan bahwa direktif izinkan dan tolak akan diterapkan sesuai urutan yang didefinisikan." + }, + "access-list.pass-auth": { + "defaultMessage": "Teruskan Auth ke Upstream" + }, + "access-list.public": { + "defaultMessage": "Dapat Diakses Publik" + }, + "access-list.public.subtitle": { + "defaultMessage": "Tidak perlu basic auth" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 atau 192.168.1.0/24 atau 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "Penuhi Salah Satu" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {Pengguna} other {Pengguna}}, {rules} {rules, plural, one {Aturan} other {Aturan}} - Dibuat: {date}" + }, + "access-lists": { + "defaultMessage": "Daftar Akses" + }, + "action.add": { + "defaultMessage": "Tambah" + }, + "action.add-location": { + "defaultMessage": "Tambah Lokasi" + }, + "action.allow": { + "defaultMessage": "Izinkan" + }, + "action.close": { + "defaultMessage": "Tutup" + }, + "action.delete": { + "defaultMessage": "Hapus" + }, + "action.deny": { + "defaultMessage": "Tolak" + }, + "action.disable": { + "defaultMessage": "Nonaktifkan" + }, + "action.download": { + "defaultMessage": "Unduh" + }, + "action.edit": { + "defaultMessage": "Edit" + }, + "action.enable": { + "defaultMessage": "Aktifkan" + }, + "action.permissions": { + "defaultMessage": "Izin" + }, + "action.renew": { + "defaultMessage": "Perpanjang" + }, + "action.view-details": { + "defaultMessage": "Lihat Detail" + }, + "auditlogs": { + "defaultMessage": "Log Audit" + }, + "auto": { + "defaultMessage": "Otomatis" + }, + "cancel": { + "defaultMessage": "Batal" + }, + "certificate": { + "defaultMessage": "Sertifikat" + }, + "certificate.custom-certificate": { + "defaultMessage": "Sertifikat" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Kunci Sertifikat" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Sertifikat Intermediate" + }, + "certificate.in-use": { + "defaultMessage": "Digunakan" + }, + "certificate.none.subtitle": { + "defaultMessage": "Tidak ada sertifikat yang ditetapkan" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Host ini tidak akan menggunakan HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Tidak Ada" + }, + "certificate.not-in-use": { + "defaultMessage": "Tidak Digunakan" + }, + "certificate.renew": { + "defaultMessage": "Perpanjang Sertifikat" + }, + "certificates": { + "defaultMessage": "Sertifikat" + }, + "certificates.custom": { + "defaultMessage": "Sertifikat Kustom" + }, + "certificates.custom.warning": { + "defaultMessage": "Berkas kunci yang dilindungi frasa sandi tidak didukung." + }, + "certificates.dns.credentials": { + "defaultMessage": "Konten File Kredensial" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Plugin ini memerlukan file konfigurasi yang berisi token API atau kredensial lain untuk penyedia Anda" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Data ini akan disimpan sebagai teks biasa di database dan dalam file!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Detik Propagasi" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Biarkan kosong untuk menggunakan nilai baku plugin. Jumlah detik menunggu propagasi DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "Penyedia DNS" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Pilih Penyedia..." + }, + "certificates.dns.warning": { + "defaultMessage": "Bagian ini memerlukan pengetahuan tentang Certbot dan plugin DNS-nya. Silakan merujuk dokumentasi plugin terkait." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Ada server yang ditemukan pada domain ini tetapi tampaknya bukan Nginx Proxy Manager. Pastikan domain Anda mengarah ke IP tempat instance NPM berjalan." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Gagal memeriksa keterjangkauan karena kesalahan komunikasi dengan site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Tidak ada server yang tersedia pada domain ini. Pastikan domain Anda ada dan mengarah ke IP tempat instance NPM berjalan dan bila perlu port 80 diteruskan di router Anda." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Server Anda dapat dijangkau dan pembuatan sertifikat seharusnya memungkinkan." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Ada server yang ditemukan pada domain ini tetapi mengembalikan kode status tak terduga {code}. Apakah itu server NPM? Pastikan domain Anda mengarah ke IP tempat instance NPM berjalan." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Ada server yang ditemukan pada domain ini tetapi mengembalikan data yang tidak terduga. Apakah itu server NPM? Pastikan domain Anda mengarah ke IP tempat instance NPM berjalan." + }, + "certificates.http.test-results": { + "defaultMessage": "Hasil Uji" + }, + "certificates.http.warning": { + "defaultMessage": "Domain ini harus sudah dikonfigurasi agar mengarah ke instalasi ini." + }, + "certificates.request.subtitle": { + "defaultMessage": "dengan Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Minta Sertifikat Baru" + }, + "column.access": { + "defaultMessage": "Akses" + }, + "column.authorization": { + "defaultMessage": "Otorisasi" + }, + "column.authorizations": { + "defaultMessage": "Otorisasi" + }, + "column.custom-locations": { + "defaultMessage": "Lokasi Kustom" + }, + "column.destination": { + "defaultMessage": "Tujuan" + }, + "column.details": { + "defaultMessage": "Detail" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.event": { + "defaultMessage": "Peristiwa" + }, + "column.expires": { + "defaultMessage": "Kedaluwarsa" + }, + "column.http-code": { + "defaultMessage": "Kode HTTP" + }, + "column.incoming-port": { + "defaultMessage": "Port Masuk" + }, + "column.name": { + "defaultMessage": "Nama" + }, + "column.protocol": { + "defaultMessage": "Protokol" + }, + "column.provider": { + "defaultMessage": "Penyedia" + }, + "column.roles": { + "defaultMessage": "Peran" + }, + "column.rules": { + "defaultMessage": "Aturan" + }, + "column.satisfy": { + "defaultMessage": "Pemenuhan" + }, + "column.satisfy-all": { + "defaultMessage": "Semua" + }, + "column.satisfy-any": { + "defaultMessage": "Salah Satu" + }, + "column.scheme": { + "defaultMessage": "Skema" + }, + "column.source": { + "defaultMessage": "Sumber" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Status" + }, + "created-on": { + "defaultMessage": "Dibuat: {date}" + }, + "dashboard": { + "defaultMessage": "Dasbor" + }, + "dead-host": { + "defaultMessage": "Host 404" + }, + "dead-hosts": { + "defaultMessage": "Host 404" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host 404} other {Host 404}}" + }, + "disabled": { + "defaultMessage": "Nonaktif" + }, + "domain-names": { + "defaultMessage": "Nama Domain" + }, + "domain-names.max": { + "defaultMessage": "Maksimum {count} nama domain" + }, + "domain-names.placeholder": { + "defaultMessage": "Mulai mengetik untuk menambahkan domain..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Wildcard tidak diizinkan untuk tipe ini" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Wildcard tidak didukung untuk CA ini" + }, + "domains.force-ssl": { + "defaultMessage": "Paksa SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS Diaktifkan" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS Subdomain" + }, + "domains.http2-support": { + "defaultMessage": "Dukungan HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Gunakan DNS Challenge" + }, + "email-address": { + "defaultMessage": "Alamat email" + }, + "empty-search": { + "defaultMessage": "Tidak ada hasil" + }, + "empty-subtitle": { + "defaultMessage": "Mengapa tidak membuatnya?" + }, + "enabled": { + "defaultMessage": "Aktif" + }, + "error.access.at-least-one": { + "defaultMessage": "Setidaknya satu Otorisasi atau satu Aturan Akses diperlukan" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Nama pengguna otorisasi harus unik" + }, + "error.invalid-auth": { + "defaultMessage": "Email atau kata sandi tidak valid" + }, + "error.invalid-domain": { + "defaultMessage": "Domain tidak valid: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Alamat email tidak valid" + }, + "error.max-character-length": { + "defaultMessage": "Panjang maksimum adalah {max} karakter{max, plural, one {} other {}}" + }, + "error.max-domains": { + "defaultMessage": "Terlalu banyak domain, maksimum {max}" + }, + "error.maximum": { + "defaultMessage": "Maksimum adalah {max}" + }, + "error.min-character-length": { + "defaultMessage": "Panjang minimum adalah {min} karakter{min, plural, one {} other {}}" + }, + "error.minimum": { + "defaultMessage": "Minimum adalah {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Kata sandi harus cocok" + }, + "error.required": { + "defaultMessage": "Ini wajib diisi" + }, + "expires.on": { + "defaultMessage": "Kedaluwarsa: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Fork saya di GitHub" + }, + "host.flags.block-exploits": { + "defaultMessage": "Blokir Eksploit Umum" + }, + "host.flags.cache-assets": { + "defaultMessage": "Cache Aset" + }, + "host.flags.preserve-path": { + "defaultMessage": "Pertahankan Path" + }, + "host.flags.protocols": { + "defaultMessage": "Protokol" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Dukungan Websocket" + }, + "host.forward-port": { + "defaultMessage": "Port Terusan" + }, + "host.forward-scheme": { + "defaultMessage": "Skema" + }, + "hosts": { + "defaultMessage": "Host" + }, + "http-only": { + "defaultMessage": "HTTP Saja" + }, + "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": "Memuat…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Masuk ke akun Anda" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Konfigurasi Nginx Kustom" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Masukkan konfigurasi Nginx kustom Anda di sini dengan risiko Anda sendiri!" + }, + "no-permission-error": { + "defaultMessage": "Anda tidak memiliki akses untuk melihat ini." + }, + "notfound.action": { + "defaultMessage": "Bawa saya pulang" + }, + "notfound.content": { + "defaultMessage": "Maaf, halaman yang Anda cari tidak ditemukan" + }, + "notfound.title": { + "defaultMessage": "Ups… Anda baru saja menemukan halaman error" + }, + "notification.error": { + "defaultMessage": "Kesalahan" + }, + "notification.object-deleted": { + "defaultMessage": "{object} telah dihapus" + }, + "notification.object-disabled": { + "defaultMessage": "{object} telah dinonaktifkan" + }, + "notification.object-enabled": { + "defaultMessage": "{object} telah diaktifkan" + }, + "notification.object-renewed": { + "defaultMessage": "{object} telah diperpanjang" + }, + "notification.object-saved": { + "defaultMessage": "{object} telah disimpan" + }, + "notification.success": { + "defaultMessage": "Berhasil" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Tambah {object}" + }, + "object.delete": { + "defaultMessage": "Hapus {object}" + }, + "object.delete.content": { + "defaultMessage": "Apakah Anda yakin ingin menghapus {object} ini?" + }, + "object.edit": { + "defaultMessage": "Edit {object}" + }, + "object.empty": { + "defaultMessage": "Tidak ada {objects}" + }, + "object.event.created": { + "defaultMessage": "{object} dibuat" + }, + "object.event.deleted": { + "defaultMessage": "{object} dihapus" + }, + "object.event.disabled": { + "defaultMessage": "{object} dinonaktifkan" + }, + "object.event.enabled": { + "defaultMessage": "{object} diaktifkan" + }, + "object.event.renewed": { + "defaultMessage": "{object} diperpanjang" + }, + "object.event.updated": { + "defaultMessage": "{object} diperbarui" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Opsi" + }, + "password": { + "defaultMessage": "Kata sandi" + }, + "password.generate": { + "defaultMessage": "Buat kata sandi acak" + }, + "password.hide": { + "defaultMessage": "Sembunyikan Kata Sandi" + }, + "password.show": { + "defaultMessage": "Tampilkan Kata Sandi" + }, + "permissions.hidden": { + "defaultMessage": "Tersembunyi" + }, + "permissions.manage": { + "defaultMessage": "Kelola" + }, + "permissions.view": { + "defaultMessage": "Hanya Lihat" + }, + "permissions.visibility.all": { + "defaultMessage": "Semua Item" + }, + "permissions.visibility.title": { + "defaultMessage": "Visibilitas Item" + }, + "permissions.visibility.user": { + "defaultMessage": "Hanya Item yang Dibuat" + }, + "proxy-host": { + "defaultMessage": "Host Proxy" + }, + "proxy-host.forward-host": { + "defaultMessage": "Hostname / IP Terusan" + }, + "proxy-hosts": { + "defaultMessage": "Host Proxy" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host Proxy} other {Host Proxy}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Publik" + }, + "redirection-host": { + "defaultMessage": "Host Pengalihan" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Domain Terusan" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "Kode HTTP" + }, + "redirection-hosts": { + "defaultMessage": "Host Pengalihan" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host Pengalihan} other {Host Pengalihan}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Banyak Pilihan" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Pindah permanen" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Pindah sementara" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Lihat lainnya" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Pengalihan sementara" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Pengalihan permanen" + }, + "role.admin": { + "defaultMessage": "Administrator" + }, + "role.standard-user": { + "defaultMessage": "Pengguna Standar" + }, + "save": { + "defaultMessage": "Simpan" + }, + "setting": { + "defaultMessage": "Pengaturan" + }, + "settings": { + "defaultMessage": "Pengaturan" + }, + "settings.default-site": { + "defaultMessage": "Situs Default" + }, + "settings.default-site.404": { + "defaultMessage": "Halaman 404" + }, + "settings.default-site.444": { + "defaultMessage": "Tidak Ada Respons (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Halaman Ucapan Selamat" + }, + "settings.default-site.description": { + "defaultMessage": "Apa yang ditampilkan saat Nginx diakses dengan Host yang tidak dikenal" + }, + "settings.default-site.html": { + "defaultMessage": "HTML Kustom" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Alihkan" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Mulai dengan membuat akun admin Anda." + }, + "setup.title": { + "defaultMessage": "Selamat datang!" + }, + "sign-in": { + "defaultMessage": "Masuk" + }, + "ssl-certificate": { + "defaultMessage": "Sertifikat SSL" + }, + "stream": { + "defaultMessage": "Stream" + }, + "stream.forward-host": { + "defaultMessage": "Host Terusan" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com atau 10.0.0.1 atau 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "Port Masuk" + }, + "streams": { + "defaultMessage": "Stream" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Stream} other {Stream}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Uji" + }, + "update-available": { + "defaultMessage": "Pembaruan Tersedia: {latestVersion}" + }, + "user": { + "defaultMessage": "Pengguna" + }, + "user.change-password": { + "defaultMessage": "Ubah Kata Sandi" + }, + "user.confirm-password": { + "defaultMessage": "Konfirmasi Kata Sandi" + }, + "user.current-password": { + "defaultMessage": "Kata Sandi Saat Ini" + }, + "user.edit-profile": { + "defaultMessage": "Edit Profil" + }, + "user.full-name": { + "defaultMessage": "Nama Lengkap" + }, + "user.login-as": { + "defaultMessage": "Masuk sebagai {name}" + }, + "user.logout": { + "defaultMessage": "Keluar" + }, + "user.new-password": { + "defaultMessage": "Kata Sandi Baru" + }, + "user.nickname": { + "defaultMessage": "Nama Panggilan" + }, + "user.set-password": { + "defaultMessage": "Atur Kata Sandi" + }, + "user.set-permissions": { + "defaultMessage": "Atur Izin untuk {name}" + }, + "user.switch-dark": { + "defaultMessage": "Beralih ke mode gelap" + }, + "user.switch-light": { + "defaultMessage": "Beralih ke mode terang" + }, + "username": { + "defaultMessage": "Nama pengguna" + }, + "users": { + "defaultMessage": "Pengguna" + } +} diff --git a/frontend/src/locale/src/it.json b/frontend/src/locale/src/it.json new file mode 100644 index 0000000000..b14c9fca52 --- /dev/null +++ b/frontend/src/locale/src/it.json @@ -0,0 +1,773 @@ +{ + "access-list": { + "defaultMessage": "Lista di Accesso" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Regola} other {Regole}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {Utente} other {Utenti}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Quando esiste almeno 1 regola, questa regola di negazione verrà aggiunta per ultima" + }, + "access-list.help.rules-order": { + "defaultMessage": "Nota che le direttive di allow e deny saranno applicate nell'ordine in cui sono definite." + }, + "access-list.pass-auth": { + "defaultMessage": "Passa Autenticazione all'Upstream" + }, + "access-list.public": { + "defaultMessage": "Accessibile Pubblicamente" + }, + "access-list.public.subtitle": { + "defaultMessage": "Nessuna autenticazione base richiesta" + }, + "access-list.satisfy-any": { + "defaultMessage": "Soddisfa Qualsiasi" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {Utente} other {Utenti}}, {rules} {rules, plural, one {Regola} other {Regole}} - Creato: {date}" + }, + "access-lists": { + "defaultMessage": "Liste di Accesso" + }, + "action.add": { + "defaultMessage": "Aggiungi" + }, + "action.add-location": { + "defaultMessage": "Aggiungi Percorso" + }, + "action.close": { + "defaultMessage": "Chiudi" + }, + "action.delete": { + "defaultMessage": "Elimina" + }, + "action.disable": { + "defaultMessage": "Disabilita" + }, + "action.download": { + "defaultMessage": "Scarica" + }, + "action.edit": { + "defaultMessage": "Modifica" + }, + "action.enable": { + "defaultMessage": "Abilita" + }, + "action.permissions": { + "defaultMessage": "Permessi" + }, + "action.renew": { + "defaultMessage": "Rinnova" + }, + "action.view-details": { + "defaultMessage": "Visualizza Dettagli" + }, + "auditlogs": { + "defaultMessage": "Log di Audit" + }, + "cancel": { + "defaultMessage": "Annulla" + }, + "certificate": { + "defaultMessage": "Certificato" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certificato" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Chiave del Certificato" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Certificato Intermedio" + }, + "certificate.in-use": { + "defaultMessage": "In Uso" + }, + "certificate.none.subtitle": { + "defaultMessage": "Nessun certificato assegnato" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Questo host non utilizzerà HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Nessuno" + }, + "certificate.not-in-use": { + "defaultMessage": "Non in Uso" + }, + "certificate.renew": { + "defaultMessage": "Rinnova Certificato" + }, + "certificates": { + "defaultMessage": "Certificati" + }, + "certificates.custom": { + "defaultMessage": "Certificato Personalizzato" + }, + "certificates.custom.warning": { + "defaultMessage": "I file di chiave protetti da passphrase non sono supportati." + }, + "certificates.dns.credentials": { + "defaultMessage": "Contenuto File Credenziali" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Questo plugin richiede un file di configurazione contenente un token API o altre credenziali per il tuo provider" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Questi dati saranno memorizzati in chiaro nel database e in un file!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Secondi di Propagazione" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Lascia vuoto per usare il valore predefinito del plugin. Numero di secondi da attendere per la propagazione DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "Provider DNS" + }, + "certificates.dns.warning": { + "defaultMessage": "Questa sezione richiede conoscenze su Certbot e i relativi plugin DNS. Consulta la documentazione del plugin." + }, + "certificates.http.reachability-404": { + "defaultMessage": "È stato trovato un server su questo dominio, ma non sembra essere Nginx Proxy Manager. Assicurati che il dominio punti all'IP dove è in esecuzione NPM." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Verifica di raggiungibilità fallita per errore di comunicazione con site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Nessun server disponibile su questo dominio. Assicurati che il dominio esista e punti all'IP corretto e che la porta 80 sia inoltrata." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Il server è raggiungibile e la creazione dei certificati è possibile." + }, + "certificates.http.reachability-other": { + "defaultMessage": "È stato trovato un server su questo dominio ma ha restituito un codice di stato imprevisto {code}. È il server NPM? Controlla che il dominio punti correttamente all'IP." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "È stato trovato un server su questo dominio ma ha restituito dati imprevisti. È il server NPM? Controlla che il dominio punti correttamente all'IP." + }, + "certificates.http.test-results": { + "defaultMessage": "Risultati Test" + }, + "certificates.http.warning": { + "defaultMessage": "Questi domini devono già essere configurati per puntare a questa installazione." + }, + "certificates.key-type": { + "defaultMessage": "Tipo di Chiave" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA è ampiamente compatibile, ECDSA è più veloce e sicuro ma potrebbe non essere supportato da sistemi più vecchi" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "con Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Richiedi un nuovo Certificato" + }, + "column.access": { + "defaultMessage": "Accesso" + }, + "column.authorization": { + "defaultMessage": "Autorizzazione" + }, + "column.authorizations": { + "defaultMessage": "Autorizzazioni" + }, + "column.custom-locations": { + "defaultMessage": "Percorsi Personalizzati" + }, + "column.destination": { + "defaultMessage": "Destinazione" + }, + "column.details": { + "defaultMessage": "Dettagli" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.event": { + "defaultMessage": "Evento" + }, + "column.expires": { + "defaultMessage": "Scadenza" + }, + "column.http-code": { + "defaultMessage": "Codice HTTP" + }, + "column.incoming-port": { + "defaultMessage": "Porta in Ingresso" + }, + "column.name": { + "defaultMessage": "Nome" + }, + "column.protocol": { + "defaultMessage": "Protocollo" + }, + "column.provider": { + "defaultMessage": "Provider" + }, + "column.roles": { + "defaultMessage": "Ruoli" + }, + "column.rules": { + "defaultMessage": "Regole" + }, + "column.satisfy": { + "defaultMessage": "Condizione" + }, + "column.satisfy-all": { + "defaultMessage": "Tutte" + }, + "column.satisfy-any": { + "defaultMessage": "Qualsiasi" + }, + "column.scheme": { + "defaultMessage": "Schema" + }, + "column.source": { + "defaultMessage": "Origine" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Stato" + }, + "created-on": { + "defaultMessage": "Creato: {date}" + }, + "dashboard": { + "defaultMessage": "Dashboard" + }, + "dead-host": { + "defaultMessage": "Host 404" + }, + "dead-hosts": { + "defaultMessage": "Hosts 404" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host 404} other {Hosts 404}}" + }, + "disabled": { + "defaultMessage": "Disabilitato" + }, + "domain-names": { + "defaultMessage": "Nomi di Dominio" + }, + "domain-names.max": { + "defaultMessage": "Massimo {count} nomi di dominio" + }, + "domain-names.placeholder": { + "defaultMessage": "Inizia a digitare per aggiungere un dominio..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Wildcard non consentite per questo tipo" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Wildcard non supportate per questa CA" + }, + "domains.force-ssl": { + "defaultMessage": "Forza SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS Abilitato" + }, + "domains.hsts-subdomains": { + "defaultMessage": "Sottodomini HSTS" + }, + "domains.http2-support": { + "defaultMessage": "Supporto HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Usa Challenge DNS" + }, + "email-address": { + "defaultMessage": "Indirizzo Email" + }, + "empty-search": { + "defaultMessage": "Nessun risultato trovato" + }, + "empty-subtitle": { + "defaultMessage": "Perché non ne crei uno?" + }, + "enabled": { + "defaultMessage": "Abilitato" + }, + "error.access.at-least-one": { + "defaultMessage": "È richiesta almeno un'Autorizzazione o una Regola di Accesso" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "I nomi utente devono essere unici" + }, + "error.invalid-auth": { + "defaultMessage": "Email o password non validi" + }, + "error.invalid-domain": { + "defaultMessage": "Dominio non valido: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Indirizzo email non valido" + }, + "error.max-character-length": { + "defaultMessage": "Lunghezza massima {max} caratter{max, plural, one {e} other {i}}" + }, + "error.max-domains": { + "defaultMessage": "Troppi domini, massimo {max}" + }, + "error.maximum": { + "defaultMessage": "Massimo {max}" + }, + "error.min-character-length": { + "defaultMessage": "Lunghezza minima {min} caratter{min, plural, one {e} other {i}}" + }, + "error.minimum": { + "defaultMessage": "Minimo {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Le password devono coincidere" + }, + "error.required": { + "defaultMessage": "Campo obbligatorio" + }, + "expires.on": { + "defaultMessage": "Scade: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Forkami su GitHub" + }, + "host.flags.block-exploits": { + "defaultMessage": "Blocca Exploit Comuni" + }, + "host.flags.cache-assets": { + "defaultMessage": "Cache degli Asset" + }, + "host.flags.preserve-path": { + "defaultMessage": "Preserva Percorso" + }, + "host.flags.protocols": { + "defaultMessage": "Protocolli" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Supporto WebSockets" + }, + "host.forward-port": { + "defaultMessage": "Porta di Destinazione" + }, + "host.forward-scheme": { + "defaultMessage": "Schema" + }, + "hosts": { + "defaultMessage": "Host" + }, + "http-only": { + "defaultMessage": "Solo HTTP" + }, + "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": "Caricamento…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Accedi al tuo account" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Configurazione Nginx Personalizzata" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Inserisci qui la configurazione Nginx personalizzata a tuo rischio!" + }, + "no-permission-error": { + "defaultMessage": "Non hai accesso per visualizzare questa pagina." + }, + "notfound.action": { + "defaultMessage": "Torna alla Home" + }, + "notfound.content": { + "defaultMessage": "Spiacenti, la pagina richiesta non è stata trovata" + }, + "notfound.title": { + "defaultMessage": "Oops… Hai trovato una pagina di errore" + }, + "notification.error": { + "defaultMessage": "Errore" + }, + "notification.object-deleted": { + "defaultMessage": "{object} è stato eliminato" + }, + "notification.object-disabled": { + "defaultMessage": "{object} è stato disabilitato" + }, + "notification.object-enabled": { + "defaultMessage": "{object} è stato abilitato" + }, + "notification.object-renewed": { + "defaultMessage": "{object} è stato rinnovato" + }, + "notification.object-saved": { + "defaultMessage": "{object} è stato salvato" + }, + "notification.success": { + "defaultMessage": "Successo" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Aggiungi {object}" + }, + "object.delete": { + "defaultMessage": "Elimina {object}" + }, + "object.delete.content": { + "defaultMessage": "Sei sicuro di voler eliminare questo {object}?" + }, + "object.edit": { + "defaultMessage": "Modifica {object}" + }, + "object.empty": { + "defaultMessage": "Non ci sono {objects} presenti" + }, + "object.event.created": { + "defaultMessage": "{object} creato" + }, + "object.event.deleted": { + "defaultMessage": "{object} eliminato" + }, + "object.event.disabled": { + "defaultMessage": "{object} disabilitato" + }, + "object.event.enabled": { + "defaultMessage": "{object} abilitato" + }, + "object.event.renewed": { + "defaultMessage": "{object} rinnovato" + }, + "object.event.updated": { + "defaultMessage": "{object} aggiornato" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Opzioni" + }, + "password": { + "defaultMessage": "Password" + }, + "password.generate": { + "defaultMessage": "Genera password casuale" + }, + "password.hide": { + "defaultMessage": "Nascondi Password" + }, + "password.show": { + "defaultMessage": "Mostra Password" + }, + "permissions.hidden": { + "defaultMessage": "Nascosto" + }, + "permissions.manage": { + "defaultMessage": "Gestisci" + }, + "permissions.view": { + "defaultMessage": "Sola Lettura" + }, + "permissions.visibility.all": { + "defaultMessage": "Tutti gli Elementi" + }, + "permissions.visibility.title": { + "defaultMessage": "Visibilità Elementi" + }, + "permissions.visibility.user": { + "defaultMessage": "Solo Elementi Creati" + }, + "proxy-host": { + "defaultMessage": "Proxy Host" + }, + "proxy-host.forward-host": { + "defaultMessage": "Hostname / IP di Destinazione" + }, + "proxy-hosts": { + "defaultMessage": "Proxy Hosts" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host Proxy} other {Host Proxy}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Pubblico" + }, + "redirection-host": { + "defaultMessage": "Host di Reindirizzamento" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Dominio di Destinazione" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "Codice HTTP" + }, + "redirection-hosts": { + "defaultMessage": "Host di Reindirizzamento" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host di Reindirizzamento} other {Host di Reindirizzamento}}" + }, + "role.admin": { + "defaultMessage": "Amministratore" + }, + "role.standard-user": { + "defaultMessage": "Utente Standard" + }, + "save": { + "defaultMessage": "Salva" + }, + "setting": { + "defaultMessage": "Impostazione" + }, + "settings": { + "defaultMessage": "Impostazioni" + }, + "settings.default-site": { + "defaultMessage": "Sito Predefinito" + }, + "settings.default-site.404": { + "defaultMessage": "Pagina 404" + }, + "settings.default-site.444": { + "defaultMessage": "Nessuna Risposta (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Pagina di Congratulazioni" + }, + "settings.default-site.description": { + "defaultMessage": "Cosa mostrare quando Nginx riceve una richiesta da un host sconosciuto" + }, + "settings.default-site.html": { + "defaultMessage": "HTML Personalizzato" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Reindirizza" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Inizia creando il tuo account amministratore." + }, + "setup.title": { + "defaultMessage": "Benvenuto!" + }, + "sign-in": { + "defaultMessage": "Accedi" + }, + "ssl-certificate": { + "defaultMessage": "Certificato SSL" + }, + "stream": { + "defaultMessage": "Stream" + }, + "stream.forward-host": { + "defaultMessage": "Host di Destinazione" + }, + "stream.incoming-port": { + "defaultMessage": "Porta in Ingresso" + }, + "streams": { + "defaultMessage": "Stream" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Stream} other {Stream}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Test" + }, + "update-available": { + "defaultMessage": "Aggiornamento Disponibile: {latestVersion}" + }, + "user": { + "defaultMessage": "Utente" + }, + "user.change-password": { + "defaultMessage": "Cambia Password" + }, + "user.confirm-password": { + "defaultMessage": "Conferma Password" + }, + "user.current-password": { + "defaultMessage": "Password Attuale" + }, + "user.edit-profile": { + "defaultMessage": "Modifica Profilo" + }, + "user.full-name": { + "defaultMessage": "Nome Completo" + }, + "user.login-as": { + "defaultMessage": "Accedi come {name}" + }, + "user.logout": { + "defaultMessage": "Disconnetti" + }, + "user.new-password": { + "defaultMessage": "Nuova Password" + }, + "user.nickname": { + "defaultMessage": "Soprannome" + }, + "user.set-password": { + "defaultMessage": "Imposta Password" + }, + "user.set-permissions": { + "defaultMessage": "Imposta Permessi per {name}" + }, + "user.switch-dark": { + "defaultMessage": "Passa alla modalità Scura" + }, + "user.switch-light": { + "defaultMessage": "Passa alla modalità Chiara" + }, + "username": { + "defaultMessage": "Nome Utente" + }, + "users": { + "defaultMessage": "Utenti" + } +} diff --git a/frontend/src/locale/src/ja.json b/frontend/src/locale/src/ja.json new file mode 100644 index 0000000000..0cc7b9645a --- /dev/null +++ b/frontend/src/locale/src/ja.json @@ -0,0 +1,767 @@ +{ + "access-list": { + "defaultMessage": "アクセスリスト" + }, + "access-list.access-count": { + "defaultMessage": "{count} ルール" + }, + "access-list.auth-count": { + "defaultMessage": "{count} ユーザー" + }, + "access-list.help-rules-last": { + "defaultMessage": "少なくとも 1 つのルールが存在する場合、 他のすべてを拒否するルールが最後に追加されます" + }, + "access-list.help.rules-order": { + "defaultMessage": "許可コマンドと拒否コマンドは定義された順番で適用されます" + }, + "access-list.pass-auth": { + "defaultMessage": "認証情報をアップストリームに送信する" + }, + "access-list.public": { + "defaultMessage": "公開されたアクセス" + }, + "access-list.public.subtitle": { + "defaultMessage": "ベーシック認証を使用しません" + }, + "access-list.satisfy-any": { + "defaultMessage": "いずれかを満たす" + }, + "access-list.subtitle": { + "defaultMessage": "{users} ユーザー, {rules} ルール - 作成日時: {date}" + }, + "access-lists": { + "defaultMessage": "アクセスリスト" + }, + "action.add": { + "defaultMessage": "追加" + }, + "action.add-location": { + "defaultMessage": "場所を追加" + }, + "action.close": { + "defaultMessage": "閉じる" + }, + "action.delete": { + "defaultMessage": "削除" + }, + "action.disable": { + "defaultMessage": "無効化" + }, + "action.download": { + "defaultMessage": "ダウンロード" + }, + "action.edit": { + "defaultMessage": "編集" + }, + "action.enable": { + "defaultMessage": "有効化" + }, + "action.permissions": { + "defaultMessage": "権限" + }, + "action.renew": { + "defaultMessage": "更新" + }, + "action.view-details": { + "defaultMessage": "詳細" + }, + "auditlogs": { + "defaultMessage": "監査ログ" + }, + "cancel": { + "defaultMessage": "キャンセル" + }, + "certificate": { + "defaultMessage": "証明書" + }, + "certificate.custom-certificate": { + "defaultMessage": "証明書" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "証明書キー" + }, + "certificate.custom-intermediate": { + "defaultMessage": "中間証明書" + }, + "certificate.in-use": { + "defaultMessage": "使用中" + }, + "certificate.none.subtitle": { + "defaultMessage": "証明書が割り当てられていません" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "このホストはHTTPSを使用しません" + }, + "certificate.none.title": { + "defaultMessage": "無し" + }, + "certificate.not-in-use": { + "defaultMessage": "未使用" + }, + "certificate.renew": { + "defaultMessage": "証明書を更新" + }, + "certificates": { + "defaultMessage": "証明書" + }, + "certificates.custom": { + "defaultMessage": "カスタム証明書" + }, + "certificates.custom.warning": { + "defaultMessage": "パスワードによって保護されたキーファイルはサポートされていません" + }, + "certificates.dns.credentials": { + "defaultMessage": "資格情報ファイルの内容" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "このプラグインはプロバイダーのAPIキーか認証情報を含む設定ファイルが必要です" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "このデータはファイルとデータベースにプレーンテキストとして保存されます" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "DNS伝播時間(秒)" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "DNSの伝搬時間を秒で指定します。空にするとデフォルトの値を使用します。" + }, + "certificates.dns.provider": { + "defaultMessage": "DNSプロバイダー" + }, + "certificates.dns.warning": { + "defaultMessage": "このセクションはCertbotとそのDNSプラグインの知識が必要です。各プラグインのドキュメントを参照してください。" + }, + "certificates.http.reachability-404": { + "defaultMessage": "このドメインはNginx Proxy Managerではないサーバーを指しているようです。ドメインがこのNPMインスタンスを指していることを確認してください。" + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "site24x7.comへの接続でエラーが発生し、到達性チェックに失敗しました" + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "このドメインには利用可能なサーバーがありません。ドメインが存在し、NPMインスタンスのIPアドレスを指していること、必要に応じてルーターでポート80が転送されていることを確認してください。" + }, + "certificates.http.reachability-ok": { + "defaultMessage": "サーバーへ到達可能であり、証明書の作成が可能です。" + }, + "certificates.http.reachability-other": { + "defaultMessage": "このドメインでサーバーが見つかりましたが予期しないステータスコード {code} を返しました. NPMサーバーが動いていますか? ドメインがこのNPMインスタンスを指していることを確認してください。" + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "このドメインでサーバーが見つかりましたが予期しないデータを返しました. NPMサーバーが動いていますか? ドメインがこのNPMインスタンスを指していることを確認してください。" + }, + "certificates.http.test-results": { + "defaultMessage": "テスト結果" + }, + "certificates.http.warning": { + "defaultMessage": "これらのドメインは、すでにこのインストール先を指すように設定されている必要がありますあ." + }, + "certificates.key-type": { + "defaultMessage": "鍵タイプ" + }, + "certificates.key-type-description": { + "defaultMessage": "RSAは広く互換性があり、ECDSAはより高速で安全ですが、古いシステムではサポートされていない場合があります" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "Let's Encryptを使用する" + }, + "certificates.request.title": { + "defaultMessage": "新しい証明書を作成" + }, + "column.access": { + "defaultMessage": "アクセス" + }, + "column.authorization": { + "defaultMessage": "認証" + }, + "column.authorizations": { + "defaultMessage": "認証" + }, + "column.custom-locations": { + "defaultMessage": "カスタムロケーション" + }, + "column.destination": { + "defaultMessage": "宛先" + }, + "column.details": { + "defaultMessage": "詳細" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.event": { + "defaultMessage": "イベント" + }, + "column.expires": { + "defaultMessage": "期限切れ" + }, + "column.http-code": { + "defaultMessage": "アクセス" + }, + "column.incoming-port": { + "defaultMessage": "受信ポート" + }, + "column.name": { + "defaultMessage": "名前" + }, + "column.protocol": { + "defaultMessage": "プロトコル" + }, + "column.provider": { + "defaultMessage": "プロバイダー" + }, + "column.roles": { + "defaultMessage": "Roles" + }, + "column.rules": { + "defaultMessage": "ルール" + }, + "column.satisfy": { + "defaultMessage": "Satisfy" + }, + "column.satisfy-all": { + "defaultMessage": "すべて" + }, + "column.satisfy-any": { + "defaultMessage": "いずれか" + }, + "column.scheme": { + "defaultMessage": "スキーム" + }, + "column.source": { + "defaultMessage": "ソース" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "ステータス" + }, + "created-on": { + "defaultMessage": "作成日時: {date}" + }, + "dashboard": { + "defaultMessage": "ダッシュボード" + }, + "dead-host": { + "defaultMessage": "404 ホスト" + }, + "dead-hosts": { + "defaultMessage": "404 ホスト" + }, + "dead-hosts.count": { + "defaultMessage": "{count} 404 ホスト" + }, + "disabled": { + "defaultMessage": "無効化" + }, + "domain-names": { + "defaultMessage": "ドメイン名" + }, + "domain-names.max": { + "defaultMessage": "{count}のドメイン名が最大です" + }, + "domain-names.placeholder": { + "defaultMessage": "追加するドメインを入力..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "ワイルドカードはこのタイプでは許可されていません" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "ワイルドカードはこのCAではサポートされていません" + }, + "domains.force-ssl": { + "defaultMessage": "SSLを強制" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTSを有効化" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTSサブドメイン" + }, + "domains.http2-support": { + "defaultMessage": "HTTP/2サポート" + }, + "domains.use-dns": { + "defaultMessage": "DNSチャレンジを使用" + }, + "email-address": { + "defaultMessage": "Emailアドレス" + }, + "empty-search": { + "defaultMessage": "見つかりませんでした" + }, + "empty-subtitle": { + "defaultMessage": "作ってみましょう" + }, + "enabled": { + "defaultMessage": "有効" + }, + "error.access.at-least-one": { + "defaultMessage": "少なくとも一つの認証またはアクセスルールが必要です" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "認証のユーザー名は他と同じ名前は使用できません" + }, + "error.invalid-auth": { + "defaultMessage": "無効なemailまたはパスワード" + }, + "error.invalid-domain": { + "defaultMessage": "無効なドメイン: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "無効なemailアドレス" + }, + "error.max-character-length": { + "defaultMessage": "文字数は長くとも{max}文字です" + }, + "error.max-domains": { + "defaultMessage": "ドメインが多すぎます, 最大値は{max}です" + }, + "error.maximum": { + "defaultMessage": "最大値は{max}です" + }, + "error.min-character-length": { + "defaultMessage": "文字数は少なくとも{min}文字です" + }, + "error.minimum": { + "defaultMessage": "最小値は{min}です" + }, + "error.passwords-must-match": { + "defaultMessage": "パスワードは一致する必要があります" + }, + "error.required": { + "defaultMessage": "必須項目です" + }, + "expires.on": { + "defaultMessage": "有効期限: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Fork me on Github" + }, + "host.flags.block-exploits": { + "defaultMessage": "一般的なエクスプロイトをブロックする" + }, + "host.flags.cache-assets": { + "defaultMessage": "アセットをキャッシュする" + }, + "host.flags.preserve-path": { + "defaultMessage": "パスワードは一致する必要があります" + }, + "host.flags.protocols": { + "defaultMessage": "プロトコル" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Websocketsサポート" + }, + "host.forward-port": { + "defaultMessage": "転送ポート" + }, + "host.forward-scheme": { + "defaultMessage": "スキーム" + }, + "hosts": { + "defaultMessage": "ホスト" + }, + "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.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "アカウントにログイン" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "カスタムNginx設定" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Enter your custom Nginx configuration here at your own risk!" + }, + "no-permission-error": { + "defaultMessage": "これを表示する権限がありません" + }, + "notfound.action": { + "defaultMessage": "ホームに戻る" + }, + "notfound.content": { + "defaultMessage": "申し訳ありませんが探しているページは見つかりませんでした" + }, + "notfound.title": { + "defaultMessage": "おっと... エラーページにたどり着いてしまったようです" + }, + "notification.error": { + "defaultMessage": "エラー" + }, + "notification.object-deleted": { + "defaultMessage": "{object}は削除されました" + }, + "notification.object-disabled": { + "defaultMessage": "{object}は無効化されました" + }, + "notification.object-enabled": { + "defaultMessage": "{object}は有効化されました" + }, + "notification.object-renewed": { + "defaultMessage": "{object}は再作成されました" + }, + "notification.object-saved": { + "defaultMessage": "{object}は保存されました" + }, + "notification.success": { + "defaultMessage": "成功" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "{object}を追加" + }, + "object.delete": { + "defaultMessage": "{object}を削除" + }, + "object.delete.content": { + "defaultMessage": "本当に{object}を削除しますか?" + }, + "object.edit": { + "defaultMessage": "{object}を編集" + }, + "object.empty": { + "defaultMessage": "{objects}はありません" + }, + "object.event.created": { + "defaultMessage": "{object}を作成済み" + }, + "object.event.deleted": { + "defaultMessage": "{object}を削除済み" + }, + "object.event.disabled": { + "defaultMessage": "{object}を無効化済み" + }, + "object.event.enabled": { + "defaultMessage": "{object}を有効化済み" + }, + "object.event.renewed": { + "defaultMessage": "{object}を再作成済み" + }, + "object.event.updated": { + "defaultMessage": "{object}を更新済み" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Options" + }, + "password": { + "defaultMessage": "パスワード" + }, + "password.generate": { + "defaultMessage": "ランダムなパスワードを生成" + }, + "password.hide": { + "defaultMessage": "パスワードを隠す" + }, + "password.show": { + "defaultMessage": "パスワードを表示する" + }, + "permissions.hidden": { + "defaultMessage": "非公開" + }, + "permissions.manage": { + "defaultMessage": "管理" + }, + "permissions.view": { + "defaultMessage": "表示のみ" + }, + "permissions.visibility.all": { + "defaultMessage": "すべて" + }, + "permissions.visibility.title": { + "defaultMessage": "可視性" + }, + "permissions.visibility.user": { + "defaultMessage": "作成したもののみ" + }, + "proxy-host": { + "defaultMessage": "プロキシホスト" + }, + "proxy-host.forward-host": { + "defaultMessage": "転送ホスト名/IP" + }, + "proxy-hosts": { + "defaultMessage": "プロキシホスト" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} プロキシホスト" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Public" + }, + "redirection-host": { + "defaultMessage": "リダイレクトホスト" + }, + "redirection-host.forward-domain": { + "defaultMessage": "転送ホスト" + }, + "redirection-hosts": { + "defaultMessage": "リダイレクトホスト" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} リダイレクトホスト" + }, + "role.admin": { + "defaultMessage": "管理者" + }, + "role.standard-user": { + "defaultMessage": "一般ユーザー" + }, + "save": { + "defaultMessage": "保存" + }, + "setting": { + "defaultMessage": "設定" + }, + "settings": { + "defaultMessage": "設定" + }, + "settings.default-site": { + "defaultMessage": "デフォルトサイト" + }, + "settings.default-site.404": { + "defaultMessage": "404ページ" + }, + "settings.default-site.444": { + "defaultMessage": "返答しない (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "設定ページ" + }, + "settings.default-site.description": { + "defaultMessage": "不明なホストを要求されたときにNginxが何を返すかを設定します" + }, + "settings.default-site.html": { + "defaultMessage": "カスタムHTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "リダイレクト" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "管理者アカウントを作成して始めましょう" + }, + "setup.title": { + "defaultMessage": "ようこそ!" + }, + "sign-in": { + "defaultMessage": "サインイン" + }, + "ssl-certificate": { + "defaultMessage": "SSL証明書" + }, + "stream": { + "defaultMessage": "ストリーム" + }, + "stream.forward-host": { + "defaultMessage": "転送ホスト" + }, + "stream.incoming-port": { + "defaultMessage": "受信ポート" + }, + "streams": { + "defaultMessage": "ストリーム" + }, + "streams.count": { + "defaultMessage": "{count} ストリーム" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "テスト" + }, + "user": { + "defaultMessage": "ユーザー" + }, + "user.change-password": { + "defaultMessage": "変更するパスワード" + }, + "user.confirm-password": { + "defaultMessage": "変更するパスワードを確認" + }, + "user.current-password": { + "defaultMessage": "現在のパスワード" + }, + "user.edit-profile": { + "defaultMessage": "プロフィールを編集" + }, + "user.full-name": { + "defaultMessage": "フルネーム" + }, + "user.login-as": { + "defaultMessage": "{name}としてサインイン" + }, + "user.logout": { + "defaultMessage": "ログアウト" + }, + "user.new-password": { + "defaultMessage": "新しいパスワード" + }, + "user.nickname": { + "defaultMessage": "ニックネーム" + }, + "user.set-password": { + "defaultMessage": "パスワードを設定" + }, + "user.set-permissions": { + "defaultMessage": "{name}に権限を設定" + }, + "user.switch-dark": { + "defaultMessage": "ダークモードに変更" + }, + "user.switch-light": { + "defaultMessage": "ライトモードに変更" + }, + "username": { + "defaultMessage": "ユーザー名" + }, + "users": { + "defaultMessage": "ユーザー" + } +} diff --git a/frontend/src/locale/src/ko.json b/frontend/src/locale/src/ko.json new file mode 100644 index 0000000000..a118357642 --- /dev/null +++ b/frontend/src/locale/src/ko.json @@ -0,0 +1,809 @@ +{ + "access-list": { + "defaultMessage": "접근 정책" + }, + "access-list.access-count": { + "defaultMessage": "{count}개의 정책" + }, + "access-list.auth-count": { + "defaultMessage": "{count}명의 사용자" + }, + "access-list.help-rules-last": { + "defaultMessage": "규칙이 하나라도 있으면 아래 ‘전체 거부’ 규칙이 마지막에 추가됩니다." + }, + "access-list.help.rules-order": { + "defaultMessage": "허용/거부 규칙은 정의된 순서대로 적용됩니다." + }, + "access-list.pass-auth": { + "defaultMessage": "인증 정보를 원본 서버로 전달" + }, + "access-list.public": { + "defaultMessage": "누구나 접근 가능" + }, + "access-list.public.subtitle": { + "defaultMessage": "기본 인증 필요 없음" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 / 192.168.1.0/24 / IPv6" + }, + "access-list.satisfy-any": { + "defaultMessage": "조건 중 하나라도 충족" + }, + "access-list.subtitle": { + "defaultMessage": "{users}명 {users, plural, one {사용자} other {사용자}}, {rules}개 {rules, plural, one {규칙} other {규칙}} - 생성일: {date}" + }, + "access-lists": { + "defaultMessage": "접근 정책" + }, + "action.add": { + "defaultMessage": "추가" + }, + "action.add-location": { + "defaultMessage": "경로 추가" + }, + "action.allow": { + "defaultMessage": "허용" + }, + "action.close": { + "defaultMessage": "닫기" + }, + "action.delete": { + "defaultMessage": "삭제" + }, + "action.deny": { + "defaultMessage": "거부" + }, + "action.disable": { + "defaultMessage": "비활성화" + }, + "action.download": { + "defaultMessage": "다운로드" + }, + "action.edit": { + "defaultMessage": "편집" + }, + "action.enable": { + "defaultMessage": "활성화" + }, + "action.permissions": { + "defaultMessage": "권한" + }, + "action.renew": { + "defaultMessage": "갱신" + }, + "action.view-details": { + "defaultMessage": "자세히 보기" + }, + "auditlogs": { + "defaultMessage": "감사 로그" + }, + "auto": { + "defaultMessage": "자동" + }, + "cancel": { + "defaultMessage": "취소" + }, + "certificate": { + "defaultMessage": "인증서" + }, + "certificate.custom-certificate": { + "defaultMessage": "인증서" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "인증서 키" + }, + "certificate.custom-intermediate": { + "defaultMessage": "중간 인증서" + }, + "certificate.in-use": { + "defaultMessage": "사용 중" + }, + "certificate.none.subtitle": { + "defaultMessage": "지정된 인증서 없음" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "이 호스트는 HTTPS를 사용하지 않습니다." + }, + "certificate.none.title": { + "defaultMessage": "없음" + }, + "certificate.not-in-use": { + "defaultMessage": "사용 안 함" + }, + "certificate.renew": { + "defaultMessage": "인증서 갱신" + }, + "certificates": { + "defaultMessage": "인증서" + }, + "certificates.custom": { + "defaultMessage": "사용자 지정 인증서" + }, + "certificates.custom.warning": { + "defaultMessage": "비밀번호로 보호된 키 파일은 지원되지 않습니다." + }, + "certificates.dns.credentials": { + "defaultMessage": "DNS 자격 증명 입력" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "이 플러그인은 API 토큰 등이 포함된 설정 파일이 필요합니다." + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "입력한 정보는 데이터베이스와 파일에 평문으로 저장됩니다." + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "DNS 전파 시간" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "비워두면 기본값을 사용합니다. DNS 전파를 기다리는 시간(초)입니다." + }, + "certificates.dns.provider": { + "defaultMessage": "DNS 공급자" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "공급자를 선택하세요..." + }, + "certificates.dns.warning": { + "defaultMessage": "이 기능을 사용하려면 Certbot과 DNS 플러그인에 대한 기본적인 이해가 필요합니다. 자세한 내용은 관련 문서를 참고해 주세요." + }, + "certificates.http.reachability-404": { + "defaultMessage": "해당 도메인에서 서버가 탐지되었지만 Nginx Proxy Manager가 아닌 것으로 보입니다. 도메인이 NPM이 실행 중인 IP를 가리키는지 확인하세요." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "site24x7.com과의 통신 오류로 인해 도달 가능 여부를 확인할 수 없습니다." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "해당 도메인에 접근 가능한 서버가 없습니다. 도메인이 존재하며 NPM이 실행되는 IP를 가리키고, 필요하면 라우터에서 80포트가 포워딩되어 있는지 확인하세요." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "서버에 정상적으로 접근할 수 있으며 인증서 발급이 가능합니다." + }, + "certificates.http.reachability-other": { + "defaultMessage": "해당 도메인에서 서버가 발견되었지만 예상치 못한 상태 코드 {code}를 반환했습니다. NPM 서버가 맞는지 확인하세요." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "서버가 응답했지만 예상치 못한 데이터를 반환했습니다. NPM 서버가 맞는지 확인하세요." + }, + "certificates.http.test-results": { + "defaultMessage": "테스트 결과" + }, + "certificates.http.warning": { + "defaultMessage": "도메인이 이 서버를 가리키도록 설정되어 있어야 합니다." + }, + "certificates.key-type": { + "defaultMessage": "키 유형" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA는 호환성이 넓고, ECDSA는 더 빠르고 안전하지만 오래된 시스템에서 지원되지 않을 수 있습니다" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "Let's Encrypt 사용" + }, + "certificates.request.title": { + "defaultMessage": "새 인증서 요청" + }, + "column.access": { + "defaultMessage": "접근 정책" + }, + "column.authorization": { + "defaultMessage": "인증 사용자" + }, + "column.authorizations": { + "defaultMessage": "인증 사용자" + }, + "column.custom-locations": { + "defaultMessage": "사용자 지정 경로" + }, + "column.destination": { + "defaultMessage": "전달 대상" + }, + "column.details": { + "defaultMessage": "기본 설정" + }, + "column.email": { + "defaultMessage": "이메일" + }, + "column.event": { + "defaultMessage": "이벤트" + }, + "column.expires": { + "defaultMessage": "만료일" + }, + "column.http-code": { + "defaultMessage": "HTTP 코드" + }, + "column.incoming-port": { + "defaultMessage": "수신 포트" + }, + "column.name": { + "defaultMessage": "이름" + }, + "column.protocol": { + "defaultMessage": "프로토콜" + }, + "column.provider": { + "defaultMessage": "공급자" + }, + "column.roles": { + "defaultMessage": "권한" + }, + "column.rules": { + "defaultMessage": "IP 정책" + }, + "column.satisfy": { + "defaultMessage": "조건 방식" + }, + "column.satisfy-all": { + "defaultMessage": "모두 충족" + }, + "column.satisfy-any": { + "defaultMessage": "하나라도 충족" + }, + "column.scheme": { + "defaultMessage": "프로토콜" + }, + "column.source": { + "defaultMessage": "도메인" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "상태" + }, + "created-on": { + "defaultMessage": "생성일: {date}" + }, + "dashboard": { + "defaultMessage": "대시보드" + }, + "dead-host": { + "defaultMessage": "404 호스트" + }, + "dead-hosts": { + "defaultMessage": "404 호스트" + }, + "dead-hosts.count": { + "defaultMessage": "{count}개의 404 호스트" + }, + "disabled": { + "defaultMessage": "비활성화" + }, + "domain-names": { + "defaultMessage": "도메인 이름" + }, + "domain-names.max": { + "defaultMessage": "최대 {count}개의 도메인 이름" + }, + "domain-names.placeholder": { + "defaultMessage": "도메인을 입력해주세요." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "HTTP 방식으로는 와일드카드 인증서를 발급할 수 없습니다." + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "이 인증 기관(CA)은 와일드카드를 지원하지 않습니다." + }, + "domains.force-ssl": { + "defaultMessage": "SSL 강제 적용" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS 활성화" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS 서브도메인 포함" + }, + "domains.http2-support": { + "defaultMessage": "HTTP/2 지원" + }, + "domains.use-dns": { + "defaultMessage": "DNS 챌린지 사용" + }, + "email-address": { + "defaultMessage": "이메일 주소" + }, + "empty-search": { + "defaultMessage": "검색 결과 없음" + }, + "empty-subtitle": { + "defaultMessage": "하나 만들어 보는 건 어떨까요?" + }, + "enabled": { + "defaultMessage": "활성화" + }, + "error.access.at-least-one": { + "defaultMessage": "인증 또는 접근 규칙 중 하나는 반드시 필요합니다." + }, + "error.access.duplicate-usernames": { + "defaultMessage": "인증 사용자 이름은 중복될 수 없습니다." + }, + "error.invalid-auth": { + "defaultMessage": "이메일 또는 비밀번호가 잘못되었습니다." + }, + "error.invalid-domain": { + "defaultMessage": "잘못된 도메인: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "잘못된 이메일 주소입니다." + }, + "error.max-character-length": { + "defaultMessage": "최대 길이는 {max}자입니다." + }, + "error.max-domains": { + "defaultMessage": "도메인이 너무 많습니다. 최대 {max}개까지 가능합니다." + }, + "error.maximum": { + "defaultMessage": "최댓값은 {max}입니다." + }, + "error.min-character-length": { + "defaultMessage": "최소 길이는 {min}자입니다." + }, + "error.minimum": { + "defaultMessage": "최솟값은 {min}입니다." + }, + "error.passwords-must-match": { + "defaultMessage": "비밀번호가 일치해야 합니다." + }, + "error.required": { + "defaultMessage": "필수 항목입니다." + }, + "expires.on": { + "defaultMessage": "만료일: {date}" + }, + "footer.github-fork": { + "defaultMessage": "GitHub에서 포크하기" + }, + "host.flags.block-exploits": { + "defaultMessage": "일반적인 공격 차단" + }, + "host.flags.cache-assets": { + "defaultMessage": "정적 에셋 캐싱" + }, + "host.flags.preserve-path": { + "defaultMessage": "요청 경로 유지" + }, + "host.flags.protocols": { + "defaultMessage": "프로토콜" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "웹소켓 지원" + }, + "host.forward-port": { + "defaultMessage": "전달할 포트" + }, + "host.forward-scheme": { + "defaultMessage": "프로토콜" + }, + "hosts": { + "defaultMessage": "호스트 목록" + }, + "http-only": { + "defaultMessage": "HTTP 전용" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt (DNS 방식)" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt (HTTP 방식)" + }, + "loading": { + "defaultMessage": "불러오는 중…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "로그인" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "사용자 지정 Nginx 설정" + }, + "nginx-config.placeholder": { + "defaultMessage": "# 위험을 감수하고 여기에 사용자 지정 Nginx 설정을 입력하세요!" + }, + "no-permission-error": { + "defaultMessage": "이 내용을 볼 권한이 없습니다." + }, + "notfound.action": { + "defaultMessage": "홈으로 이동" + }, + "notfound.content": { + "defaultMessage": "죄송합니다. 찾으시는 페이지를 찾을 수 없습니다." + }, + "notfound.title": { + "defaultMessage": "이런… 오류 페이지에 도착했습니다." + }, + "notification.error": { + "defaultMessage": "오류" + }, + "notification.object-deleted": { + "defaultMessage": "{object}이(가) 삭제되었습니다." + }, + "notification.object-disabled": { + "defaultMessage": "{object}이(가) 비활성화되었습니다." + }, + "notification.object-enabled": { + "defaultMessage": "{object}이(가) 활성화되었습니다." + }, + "notification.object-renewed": { + "defaultMessage": "{object}이(가) 갱신되었습니다." + }, + "notification.object-saved": { + "defaultMessage": "{object}이(가) 저장되었습니다." + }, + "notification.success": { + "defaultMessage": "성공" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "{object} 추가" + }, + "object.delete": { + "defaultMessage": "{object} 삭제" + }, + "object.delete.content": { + "defaultMessage": "이 {object}을(를) 정말 삭제하시겠습니까?" + }, + "object.edit": { + "defaultMessage": "{object} 편집" + }, + "object.empty": { + "defaultMessage": "{objects}이(가) 없습니다." + }, + "object.event.created": { + "defaultMessage": "{object}이(가) 생성됨" + }, + "object.event.deleted": { + "defaultMessage": "{object}이(가) 삭제됨" + }, + "object.event.disabled": { + "defaultMessage": "{object}이(가) 비활성화됨" + }, + "object.event.enabled": { + "defaultMessage": "{object}이(가) 활성화됨" + }, + "object.event.renewed": { + "defaultMessage": "{object}이(가) 갱신됨" + }, + "object.event.updated": { + "defaultMessage": "{object}이(가) 업데이트됨" + }, + "offline": { + "defaultMessage": "비활성화" + }, + "online": { + "defaultMessage": "활성화" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "옵션" + }, + "password": { + "defaultMessage": "비밀번호" + }, + "password.generate": { + "defaultMessage": "무작위 비밀번호 생성" + }, + "password.hide": { + "defaultMessage": "비밀번호 숨기기" + }, + "password.show": { + "defaultMessage": "비밀번호 표시" + }, + "permissions.hidden": { + "defaultMessage": "숨김" + }, + "permissions.manage": { + "defaultMessage": "관리" + }, + "permissions.view": { + "defaultMessage": "보기 전용" + }, + "permissions.visibility.all": { + "defaultMessage": "모든 항목" + }, + "permissions.visibility.title": { + "defaultMessage": "항목 표시 설정" + }, + "permissions.visibility.user": { + "defaultMessage": "내가 만든 항목만" + }, + "proxy-host": { + "defaultMessage": "프록시 호스트" + }, + "proxy-host.forward-host": { + "defaultMessage": "전달할 호스트명 / IP" + }, + "proxy-hosts": { + "defaultMessage": "프록시 호스트" + }, + "proxy-hosts.count": { + "defaultMessage": "{count}개의 프록시 호스트" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "공개" + }, + "redirection-host": { + "defaultMessage": "리다이렉션 호스트" + }, + "redirection-host.forward-domain": { + "defaultMessage": "전달할 도메인" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP 코드" + }, + "redirection-hosts": { + "defaultMessage": "리다이렉션 호스트" + }, + "redirection-hosts.count": { + "defaultMessage": "{count}개의 리다이렉션 호스트" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Multiple Choices" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Moved permanently" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Moved temporarily" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 See other" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Temporary redirect" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Permanent redirect" + }, + "role.admin": { + "defaultMessage": "관리자" + }, + "role.standard-user": { + "defaultMessage": "일반 사용자" + }, + "save": { + "defaultMessage": "저장" + }, + "setting": { + "defaultMessage": "설정" + }, + "settings": { + "defaultMessage": "설정" + }, + "settings.default-site": { + "defaultMessage": "기본 사이트" + }, + "settings.default-site.404": { + "defaultMessage": "404 페이지" + }, + "settings.default-site.444": { + "defaultMessage": "응답 없음 (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "축하 페이지" + }, + "settings.default-site.description": { + "defaultMessage": "알 수 없는 호스트로 요청이 들어왔을 때 표시할 내용" + }, + "settings.default-site.html": { + "defaultMessage": "사용자 지정 HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "리다이렉트" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "관리자 계정을 만들어 시작하세요." + }, + "setup.title": { + "defaultMessage": "환영합니다!" + }, + "sign-in": { + "defaultMessage": "로그인" + }, + "ssl-certificate": { + "defaultMessage": "SSL 인증서" + }, + "stream": { + "defaultMessage": "호스트 스트림" + }, + "stream.forward-host": { + "defaultMessage": "전달할 호스트" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com / 10.0.0.1 / IPv6" + }, + "stream.incoming-port": { + "defaultMessage": "수신 포트" + }, + "streams": { + "defaultMessage": "호스트 스트림" + }, + "streams.count": { + "defaultMessage": "{count}개의 호스트 스트림" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "테스트" + }, + "update-available": { + "defaultMessage": "업데이트 가능: {latestVersion}" + }, + "user": { + "defaultMessage": "사용자" + }, + "user.change-password": { + "defaultMessage": "비밀번호 변경" + }, + "user.confirm-password": { + "defaultMessage": "비밀번호 확인" + }, + "user.current-password": { + "defaultMessage": "현재 비밀번호" + }, + "user.edit-profile": { + "defaultMessage": "프로필 편집" + }, + "user.full-name": { + "defaultMessage": "전체 이름" + }, + "user.login-as": { + "defaultMessage": "{name}으로 로그인" + }, + "user.logout": { + "defaultMessage": "로그아웃" + }, + "user.new-password": { + "defaultMessage": "새 비밀번호" + }, + "user.nickname": { + "defaultMessage": "닉네임" + }, + "user.set-password": { + "defaultMessage": "비밀번호 설정" + }, + "user.set-permissions": { + "defaultMessage": "{name}의 권한 설정" + }, + "user.switch-dark": { + "defaultMessage": "다크 모드로 전환" + }, + "user.switch-light": { + "defaultMessage": "라이트 모드로 전환" + }, + "username": { + "defaultMessage": "사용자 이름" + }, + "users": { + "defaultMessage": "사용자" + } +} diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json new file mode 100644 index 0000000000..408d06eb80 --- /dev/null +++ b/frontend/src/locale/src/lang-list.json @@ -0,0 +1,62 @@ +{ + "locale-en-US": { + "defaultMessage": "English" + }, + "locale-es-ES": { + "defaultMessage": "Español" + }, + "locale-ie-GA": { + "defaultMessage": "Gaeilge" + }, + "locale-de-DE": { + "defaultMessage": "German" + }, + "locale-pt-PT": { + "defaultMessage": "Português (Europeu)" + }, + "locale-fr-FR": { + "defaultMessage": "Français" + }, + "locale-id-ID": { + "defaultMessage": "Bahasa Indonesia" + }, + "locale-ja-JP": { + "defaultMessage": "日本語" + }, + "locale-ru-RU": { + "defaultMessage": "Русский" + }, + "locale-sk-SK": { + "defaultMessage": "Slovenčina" + }, + "locale-cs-CZ": { + "defaultMessage": "Čeština" + }, + "locale-zh-CN": { + "defaultMessage": "中文" + }, + "locale-pl-PL": { + "defaultMessage": "Polski" + }, + "locale-it-IT": { + "defaultMessage": "Italiano" + }, + "locale-vi-VN": { + "defaultMessage": "Tiếng Việt" + }, + "locale-nl-NL": { + "defaultMessage": "Nederlands" + }, + "locale-ko-KR": { + "defaultMessage": "한국어" + }, + "locale-bg-BG": { + "defaultMessage": "Български" + }, + "locale-tr-TR": { + "defaultMessage": "Türkçe" + }, + "locale-hu-HU": { + "defaultMessage": "Magyar" + } +} diff --git a/frontend/src/locale/src/nl.json b/frontend/src/locale/src/nl.json new file mode 100644 index 0000000000..83488053ef --- /dev/null +++ b/frontend/src/locale/src/nl.json @@ -0,0 +1,773 @@ +{ + "access-list": { + "defaultMessage": "Toegangslijst" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Regel} other {Regels}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {Gebruiker} other {Gebruikers}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Als er minimaal 1 regel bestaat, wordt deze regel als laatste toegevoegd" + }, + "access-list.help.rules-order": { + "defaultMessage": "Onthoud dat de regels van boven naar beneden worden toegevoegd." + }, + "access-list.pass-auth": { + "defaultMessage": "Pass Auth to Upstream" + }, + "access-list.public": { + "defaultMessage": "Publiekelijk toegankelijk" + }, + "access-list.public.subtitle": { + "defaultMessage": "Geen basisautentificatie vereist" + }, + "access-list.satisfy-any": { + "defaultMessage": "Voldoe aan elke" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {Gebruiker} other {Gebruikers}}, {rules} {rules, plural, one {Regel} other {Regels}} - Aangemaakt: {date}" + }, + "access-lists": { + "defaultMessage": "Toegangslijsten" + }, + "action.add": { + "defaultMessage": "Toevoegen" + }, + "action.add-location": { + "defaultMessage": "Locatie Toevoegen" + }, + "action.close": { + "defaultMessage": "Sluiten" + }, + "action.delete": { + "defaultMessage": "Verwijderen" + }, + "action.disable": { + "defaultMessage": "Uitzetten" + }, + "action.download": { + "defaultMessage": "Download" + }, + "action.edit": { + "defaultMessage": "Bewerken" + }, + "action.enable": { + "defaultMessage": "Aanzetten" + }, + "action.permissions": { + "defaultMessage": "Rechten" + }, + "action.renew": { + "defaultMessage": "Vernieuwen" + }, + "action.view-details": { + "defaultMessage": "Bekijk Details" + }, + "auditlogs": { + "defaultMessage": "Logboeken" + }, + "cancel": { + "defaultMessage": "Annuleren" + }, + "certificate": { + "defaultMessage": "Certificaat" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certificaat" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Certificaat Sleutel" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Intermediate Certificaat" + }, + "certificate.in-use": { + "defaultMessage": "In Gebruik" + }, + "certificate.none.subtitle": { + "defaultMessage": "Geen certificaat toegewezen" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Deze host gebruikt geen HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Geen" + }, + "certificate.not-in-use": { + "defaultMessage": "Niet Gebruikt" + }, + "certificate.renew": { + "defaultMessage": "Certificaat Vernieuwen" + }, + "certificates": { + "defaultMessage": "Certificaten" + }, + "certificates.custom": { + "defaultMessage": "Aangepast Certificaat" + }, + "certificates.custom.warning": { + "defaultMessage": "Sleutels met een wachtzin zijn niet ondersteund." + }, + "certificates.dns.credentials": { + "defaultMessage": "Credentials File Content" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Deze plugin vereist een configuratiebestand met een API token of andere gegevens van de provider." + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Deze data zal worden opgeslagen als plaintext in de database en in een bestand!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Verwerkingstijd (seconden)" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Laat leeg om de standaardwaarde van de plugin te gebruiken. Aantal seconden om te wachten op DNS propagatie." + }, + "certificates.dns.provider": { + "defaultMessage": "DNS Provider" + }, + "certificates.dns.warning": { + "defaultMessage": "Deze sectie vereist wat informatie over Certbot en zijn DNS plugins. Gebruik de documentatie van de bijbehorende plugins." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Er is een server gevonden op deze domeinnaam, maar dat lijkt niet Nginx Proxy Manager te zijn. Zorg ervoor dat je domein naar het IP waar je NPM instance draait wijst." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Bereikbaarheid kan niet worden bepaald door een communicatiefout met site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Er is geen server beschikbaar op dit domein. Zorg ervoor dat je domein bestaat en naar het IP waar je NPM instance draait wijst en eventueel port 80 wordt doorgegeven in je router." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Jouw server is bereikbaar en certificaten kunnen worden aangemaakt." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Er is een server gevonden op deze domeinnaam, maar heeft een onverwachte statuscode ({code}) teruggegeven. Is dat de NPM server? Zorg ervoor dat je domein naar het IP waar je NPM instance draait wijst." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Er is een server gevonden op deze domeinnaam, maar heeft een onverwachte gegevens teruggegeven. Is dat de NPM server? Zorg ervoor dat je domein naar het IP waar je NPM instance draait wijst." + }, + "certificates.http.test-results": { + "defaultMessage": "Testresultaten" + }, + "certificates.http.warning": { + "defaultMessage": "Deze domeinen moeten al worden geconfigureerd om naar deze installatie te wijzen." + }, + "certificates.key-type": { + "defaultMessage": "Sleuteltype" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA is breed compatibel, ECDSA is sneller en veiliger maar wordt mogelijk niet ondersteund door oudere systemen" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "met Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Vraag een nieuwe Certificaat aan" + }, + "column.access": { + "defaultMessage": "Toegang" + }, + "column.authorization": { + "defaultMessage": "Authorizatie" + }, + "column.authorizations": { + "defaultMessage": "Authorizaties" + }, + "column.custom-locations": { + "defaultMessage": "Aangepaste Locaties" + }, + "column.destination": { + "defaultMessage": "Doel" + }, + "column.details": { + "defaultMessage": "Details" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.event": { + "defaultMessage": "Gebeurtenis" + }, + "column.expires": { + "defaultMessage": "Verloopt" + }, + "column.http-code": { + "defaultMessage": "HTTP Code" + }, + "column.incoming-port": { + "defaultMessage": "Inkomende Poort" + }, + "column.name": { + "defaultMessage": "Naam" + }, + "column.protocol": { + "defaultMessage": "Protocol" + }, + "column.provider": { + "defaultMessage": "Provider" + }, + "column.roles": { + "defaultMessage": "Rollen" + }, + "column.rules": { + "defaultMessage": "Regels" + }, + "column.satisfy": { + "defaultMessage": "Vervul" + }, + "column.satisfy-all": { + "defaultMessage": "Alle" + }, + "column.satisfy-any": { + "defaultMessage": "Elke" + }, + "column.scheme": { + "defaultMessage": "Schema" + }, + "column.source": { + "defaultMessage": "Bron" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Status" + }, + "created-on": { + "defaultMessage": "Aangemaakt: {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": "Uitgezet" + }, + "domain-names": { + "defaultMessage": "Domeinnamen" + }, + "domain-names.max": { + "defaultMessage": "{count} domeinnamen toegestaan" + }, + "domain-names.placeholder": { + "defaultMessage": "Voeg een domeinnaam toe..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Wildcards zijn niet toegestaan voor dit type" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Wildcards zijn niet ondersteund voor deze CA" + }, + "domains.force-ssl": { + "defaultMessage": "Forceer SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS Aangezet" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS Subdomein" + }, + "domains.http2-support": { + "defaultMessage": "HTTP/2 Ondersteuning" + }, + "domains.use-dns": { + "defaultMessage": "Gebruik DNS Challenge" + }, + "email-address": { + "defaultMessage": "E-mailadres" + }, + "empty-search": { + "defaultMessage": "Geen resultaten gevonden" + }, + "empty-subtitle": { + "defaultMessage": "Waarom niet een maken?" + }, + "enabled": { + "defaultMessage": "Aangezet" + }, + "error.access.at-least-one": { + "defaultMessage": "Minimaal één authorizatie- of één toegangsregel is vereist" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Gebruikersnamen moeten uniek zijn" + }, + "error.invalid-auth": { + "defaultMessage": "Ongeldige email of wachtwoord" + }, + "error.invalid-domain": { + "defaultMessage": "Ongeldige domeinnaam: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Ongeldig e-mailadres" + }, + "error.max-character-length": { + "defaultMessage": "Maximale lengte is {max} karakter{max, plural, one {} other {s}}" + }, + "error.max-domains": { + "defaultMessage": "Te veel domeinnamen, max is {max}" + }, + "error.maximum": { + "defaultMessage": "Maximale is {max}" + }, + "error.min-character-length": { + "defaultMessage": "Minimale lengte is {min} karakter{min, plural, one {} other {s}}" + }, + "error.minimum": { + "defaultMessage": "Minimale is {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Wachtwoorden moeten overeenkomen" + }, + "error.required": { + "defaultMessage": "Dit is verplicht" + }, + "expires.on": { + "defaultMessage": "Verloopt: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Maak een Fork op Github" + }, + "host.flags.block-exploits": { + "defaultMessage": "Blokkeer Veelvoorkomende Kwetsbaarheden" + }, + "host.flags.cache-assets": { + "defaultMessage": "Cache Assets" + }, + "host.flags.preserve-path": { + "defaultMessage": "Pad Behouden" + }, + "host.flags.protocols": { + "defaultMessage": "Protocollen" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Websockets Ondersteuning" + }, + "host.forward-port": { + "defaultMessage": "Poort Doorsturen" + }, + "host.forward-scheme": { + "defaultMessage": "Schema" + }, + "hosts": { + "defaultMessage": "Hosts" + }, + "http-only": { + "defaultMessage": "Alleen HTTP" + }, + "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": "Laden…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Inloggen" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Aangepaste Nginx Configuratie" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Voeg jouw aangepaste Nginx configuratie hier op eigen risico toe!" + }, + "no-permission-error": { + "defaultMessage": "Jij hebt geen toegang om dit te bekijken." + }, + "notfound.action": { + "defaultMessage": "Thuis" + }, + "notfound.content": { + "defaultMessage": "De pagina waar je naar op zoek bent kan niet worden gevonden" + }, + "notfound.title": { + "defaultMessage": "Oeps… Je hebt een foutpagina gevonden" + }, + "notification.error": { + "defaultMessage": "Fout" + }, + "notification.object-deleted": { + "defaultMessage": "{object} is verwijderd" + }, + "notification.object-disabled": { + "defaultMessage": "{object} is uitgezet" + }, + "notification.object-enabled": { + "defaultMessage": "{object} is aangezet" + }, + "notification.object-renewed": { + "defaultMessage": "{object} is vernieuwd" + }, + "notification.object-saved": { + "defaultMessage": "{object} is opgeslagen" + }, + "notification.success": { + "defaultMessage": "Succes" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Voeg {object} toe" + }, + "object.delete": { + "defaultMessage": "Verwijder {object}" + }, + "object.delete.content": { + "defaultMessage": "Weet je zeker dat je {object} wilt verwijderen?" + }, + "object.edit": { + "defaultMessage": "Bewerk {object}" + }, + "object.empty": { + "defaultMessage": "Er zijn geen {objects}" + }, + "object.event.created": { + "defaultMessage": "{object} is aangemaakt" + }, + "object.event.deleted": { + "defaultMessage": "{object} is verwijderd" + }, + "object.event.disabled": { + "defaultMessage": "{object} is uitgezet" + }, + "object.event.enabled": { + "defaultMessage": "{object} is aangezet" + }, + "object.event.renewed": { + "defaultMessage": "{object} is vernieuwd" + }, + "object.event.updated": { + "defaultMessage": "{object} is bijgewerkt" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Opties" + }, + "password": { + "defaultMessage": "Wachtwoord" + }, + "password.generate": { + "defaultMessage": "Willekeurig wachtwoord genereren" + }, + "password.hide": { + "defaultMessage": "Wachtwoord Verbergen" + }, + "password.show": { + "defaultMessage": "Toon Wachtwoord" + }, + "permissions.hidden": { + "defaultMessage": "Verborgen" + }, + "permissions.manage": { + "defaultMessage": "Beheer" + }, + "permissions.view": { + "defaultMessage": "Alleen Bekijken" + }, + "permissions.visibility.all": { + "defaultMessage": "Alle Items" + }, + "permissions.visibility.title": { + "defaultMessage": "Item Zichtbaarheid" + }, + "permissions.visibility.user": { + "defaultMessage": "Alleen Aangemaakte Items" + }, + "proxy-host": { + "defaultMessage": "Proxy Host" + }, + "proxy-host.forward-host": { + "defaultMessage": "Hostname / IP Doorsturen" + }, + "proxy-hosts": { + "defaultMessage": "Proxy Hosts" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Openbaar" + }, + "redirection-host": { + "defaultMessage": "Redirection Host" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Doorgestuurd Domein" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP Code" + }, + "redirection-hosts": { + "defaultMessage": "Redirection Hosts" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}" + }, + "role.admin": { + "defaultMessage": "Beheerder" + }, + "role.standard-user": { + "defaultMessage": "Standaard Gebruiker" + }, + "save": { + "defaultMessage": "Opslaan" + }, + "setting": { + "defaultMessage": "Instelling" + }, + "settings": { + "defaultMessage": "Instellingen" + }, + "settings.default-site": { + "defaultMessage": "Standaard Site" + }, + "settings.default-site.404": { + "defaultMessage": "404 Pagina" + }, + "settings.default-site.444": { + "defaultMessage": "Geen Antwoord (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Felicitatiepagina" + }, + "settings.default-site.description": { + "defaultMessage": "Wat te tonen als Nginx een onbekende Host ontvangt" + }, + "settings.default-site.html": { + "defaultMessage": "Aangepaste HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Omleiding" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Begin met het aanmaken van je beheerder account." + }, + "setup.title": { + "defaultMessage": "Welkom!" + }, + "sign-in": { + "defaultMessage": "Inloggen" + }, + "ssl-certificate": { + "defaultMessage": "SSL Certificaten" + }, + "stream": { + "defaultMessage": "Stream" + }, + "stream.forward-host": { + "defaultMessage": "Doorgestuurde Host" + }, + "stream.incoming-port": { + "defaultMessage": "Inkomende Poort" + }, + "streams": { + "defaultMessage": "Streams" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Stream} other {Streams}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Test" + }, + "update-available": { + "defaultMessage": "Update Beschikbaar: {latestVersion}" + }, + "user": { + "defaultMessage": "Gebruiker" + }, + "user.change-password": { + "defaultMessage": "Verander Wachtwoord" + }, + "user.confirm-password": { + "defaultMessage": "Bevestig Wachtwoord" + }, + "user.current-password": { + "defaultMessage": "Huidig Wachtwoord" + }, + "user.edit-profile": { + "defaultMessage": "Profiel Bewerken" + }, + "user.full-name": { + "defaultMessage": "Volledige Naam" + }, + "user.login-as": { + "defaultMessage": "Inloggen als {name}" + }, + "user.logout": { + "defaultMessage": "Uitloggen" + }, + "user.new-password": { + "defaultMessage": "Nieuw Wachtwoord" + }, + "user.nickname": { + "defaultMessage": "Bijnaam" + }, + "user.set-password": { + "defaultMessage": "Zet Wachtwoord" + }, + "user.set-permissions": { + "defaultMessage": "Zet machtigingen voor {name}" + }, + "user.switch-dark": { + "defaultMessage": "Verander naar donkere modus" + }, + "user.switch-light": { + "defaultMessage": "Verander naar lichte modus" + }, + "username": { + "defaultMessage": "Gebruikersnaam" + }, + "users": { + "defaultMessage": "Gebruikers" + } +} diff --git a/frontend/src/locale/src/pl.json b/frontend/src/locale/src/pl.json new file mode 100644 index 0000000000..b54b1fccb3 --- /dev/null +++ b/frontend/src/locale/src/pl.json @@ -0,0 +1,776 @@ +{ + "access-list": { + "defaultMessage": "wpis listy dostępu" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Reguła} few {Reguły} other {Reguł}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {Użytkownik} few {Użytkownicy} other {Użytkowników}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Gdy istnieje co najmniej 1 reguła, ta reguła blokująca wszystko zostanie dodana na końcu" + }, + "access-list.help.rules-order": { + "defaultMessage": "Należy pamiętać, że dyrektywy zezwolenia i odmowy będą stosowane w kolejności, w jakiej zostały zdefiniowane." + }, + "access-list.pass-auth": { + "defaultMessage": "Przekaż uwierzytelnienie do serwera docelowego" + }, + "access-list.public": { + "defaultMessage": "Publicznie dostępne" + }, + "access-list.public.subtitle": { + "defaultMessage": "Nie wymaga uwierzytelnienia podstawowego" + }, + "access-list.satisfy-any": { + "defaultMessage": "Spełnij dowolny warunek" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {Użytkownik} few {Użytkowników} other {Użytkowników}}, {rules} {rules, plural, one {Reguła} few {Reguły} other {Reguł}} - Utworzono: {date}" + }, + "access-lists": { + "defaultMessage": "Listy dostępu" + }, + "action.add": { + "defaultMessage": "Dodaj" + }, + "action.add-location": { + "defaultMessage": "Dodaj lokalizację" + }, + "action.allow": { + "defaultMessage": "Zezwól" + }, + "action.close": { + "defaultMessage": "Zamknij" + }, + "action.delete": { + "defaultMessage": "Usuń" + }, + "action.deny": { + "defaultMessage": "Odrzuć" + }, + "action.disable": { + "defaultMessage": "Wyłącz" + }, + "action.download": { + "defaultMessage": "Pobierz" + }, + "action.edit": { + "defaultMessage": "Edytuj" + }, + "action.enable": { + "defaultMessage": "Włącz" + }, + "action.permissions": { + "defaultMessage": "Uprawnienia" + }, + "action.renew": { + "defaultMessage": "Odnów" + }, + "action.view-details": { + "defaultMessage": "Pokaż szczegóły" + }, + "auditlogs": { + "defaultMessage": "Logi" + }, + "cancel": { + "defaultMessage": "Anuluj" + }, + "certificate": { + "defaultMessage": "certyfikat" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certyfikat" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Klucz certyfikatu" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Certyfikat pośredni" + }, + "certificate.in-use": { + "defaultMessage": "W użyciu" + }, + "certificate.none.subtitle": { + "defaultMessage": "Nie przypisano certyfikatu" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Ten host nie będzie używał HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Brak" + }, + "certificate.not-in-use": { + "defaultMessage": "Nie używany" + }, + "certificate.renew": { + "defaultMessage": "Odnów certyfikat" + }, + "certificates": { + "defaultMessage": "Certyfikaty" + }, + "certificates.custom": { + "defaultMessage": "Własny certyfikat" + }, + "certificates.custom.warning": { + "defaultMessage": "Pliki kluczy chronione hasłem nie są obsługiwane." + }, + "certificates.dns.credentials": { + "defaultMessage": "Zawartość pliku z poświadczeniami" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Ta wtyczka wymaga pliku konfiguracyjnego zawierającego token API lub inne poświadczenia dla twojego dostawcy" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Te dane zostaną zapisane jako zwykły tekst w bazie danych i pliku!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Sekundy propagacji" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Pozostaw puste, aby użyć domyślnej wartości wtyczki. Liczba sekund oczekiwania na propagację DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "Dostawca DNS" + }, + "certificates.dns.warning": { + "defaultMessage": "Ta sekcja wymaga pewnej wiedzy na temat Certbot i jego wtyczek DNS. Zapoznaj się z dokumentacją odpowiednich wtyczek." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Znaleziono serwer pod tą domeną, ale nie wygląda na to, że jest to Nginx Proxy Manager. Upewnij się, że twoja domena wskazuje na adres IP, gdzie działa twoja instancja NPM." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Nie udało się sprawdzić dostępności z powodu błędu komunikacji z site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Brak dostępnego serwera pod tą domeną. Upewnij się, że twoja domena istnieje i wskazuje na adres IP, gdzie działa twoja instancja NPM, oraz w razie potrzeby, że port 80 jest przekierowany w routerze lub owarty w firewall-u." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Twój serwer jest dostępny i tworzenie certyfikatów powinno być możliwe." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Znaleziono serwer pod tą domeną, ale zwrócił nieoczekiwany kod statusu {code}. Czy to serwer NPM? Upewnij się, że twoja domena wskazuje na adres IP, gdzie działa twoja instancja NPM." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Znaleziono serwer pod tą domeną, ale zwrócił nieoczekiwane dane. Czy to serwer NPM? Upewnij się, że twoja domena wskazuje na adres IP, gdzie działa twoja instancja NPM." + }, + "certificates.http.test-results": { + "defaultMessage": "Wyniki testu" + }, + "certificates.http.warning": { + "defaultMessage": "Te domeny muszą być już skonfigurowane tak, aby wskazywały na ten serwer" + }, + "certificates.key-type": { + "defaultMessage": "Typ klucza" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA jest szeroko kompatybilny, ECDSA jest szybszy i bezpieczniejszy, ale może nie być obsługiwany przez starsze systemy" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "z Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Wygeneruj nowy certyfikat" + }, + "column.access": { + "defaultMessage": "Dostęp" + }, + "column.authorization": { + "defaultMessage": "Autoryzacja" + }, + "column.authorizations": { + "defaultMessage": "Autoryzacje" + }, + "column.custom-locations": { + "defaultMessage": "Własne ustawienia lokalizacji" + }, + "column.destination": { + "defaultMessage": "Cel" + }, + "column.details": { + "defaultMessage": "Szczegóły" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.event": { + "defaultMessage": "Zdarzenie" + }, + "column.expires": { + "defaultMessage": "Wygasa" + }, + "column.http-code": { + "defaultMessage": "Kod HTTP" + }, + "column.incoming-port": { + "defaultMessage": "Port przychodzący" + }, + "column.name": { + "defaultMessage": "Nazwa" + }, + "column.protocol": { + "defaultMessage": "Protokół" + }, + "column.provider": { + "defaultMessage": "Dostawca" + }, + "column.roles": { + "defaultMessage": "Rola" + }, + "column.rules": { + "defaultMessage": "Reguły" + }, + "column.satisfy": { + "defaultMessage": "Spełnij" + }, + "column.satisfy-all": { + "defaultMessage": "Wszystkie" + }, + "column.satisfy-any": { + "defaultMessage": "Dowolny" + }, + "column.scheme": { + "defaultMessage": "Schemat" + }, + "column.source": { + "defaultMessage": "Źródło" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Status" + }, + "created-on": { + "defaultMessage": "Utworzono: {date}" + }, + "dashboard": { + "defaultMessage": "Panel" + }, + "dead-host": { + "defaultMessage": "host 404" + }, + "dead-hosts": { + "defaultMessage": "404" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {host 404} few {hosty 404} other {hostów 404}}" + }, + "disabled": { + "defaultMessage": "Wyłączone" + }, + "domain-names": { + "defaultMessage": "Nazwy domen" + }, + "domain-names.max": { + "defaultMessage": "Maksymalnie {count} nazw domen" + }, + "domain-names.placeholder": { + "defaultMessage": "Zacznij pisać, aby dodać domenę..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Symbole wieloznaczne nie są dozwolone dla tego typu" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Symbole wieloznaczne nie są obsługiwane dla tego CA" + }, + "domains.force-ssl": { + "defaultMessage": "Wymuś SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "Włącz HSTS " + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS dla subdomen" + }, + "domains.http2-support": { + "defaultMessage": "Obsługa HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Użyj wyzwania DNS" + }, + "email-address": { + "defaultMessage": "Adres email" + }, + "empty-search": { + "defaultMessage": "Nie znaleziono wyników" + }, + "empty-subtitle": { + "defaultMessage": "Może utworzysz nowy?" + }, + "enabled": { + "defaultMessage": "Włączone" + }, + "error.access.at-least-one": { + "defaultMessage": "Wymagana jest co najmniej jedna autoryzacja lub jedna reguła dostępu" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Nazwy użytkowników autoryzacji muszą być unikalne" + }, + "error.invalid-auth": { + "defaultMessage": "Nieprawidłowy email lub hasło" + }, + "error.invalid-domain": { + "defaultMessage": "Nieprawidłowa domena: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Nieprawidłowy adres email" + }, + "error.max-character-length": { + "defaultMessage": "Maksymalna długość to {max} {max, plural, one {znak} few {znaki} other {znaków}}" + }, + "error.max-domains": { + "defaultMessage": "Zbyt wiele domen, maksimum to {max}" + }, + "error.maximum": { + "defaultMessage": "Maksimum to {max}" + }, + "error.min-character-length": { + "defaultMessage": "Minimalna długość to {min} {min, plural, one {znak} few {znaki} other {znaków}}" + }, + "error.minimum": { + "defaultMessage": "Minimum to {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Hasła muszą się zgadzać" + }, + "error.required": { + "defaultMessage": "To pole jest wymagane" + }, + "expires.on": { + "defaultMessage": "Wygasa: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Forkuj mnie na Github" + }, + "host.flags.block-exploits": { + "defaultMessage": "Blokuj typowe exploity" + }, + "host.flags.cache-assets": { + "defaultMessage": "Buforuj zasoby statyczne (ang. cache)" + }, + "host.flags.preserve-path": { + "defaultMessage": "Zachowaj ścieżkę" + }, + "host.flags.protocols": { + "defaultMessage": "Protokoły" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Obsługa WebSockets" + }, + "host.forward-port": { + "defaultMessage": "Port docelowy" + }, + "host.forward-scheme": { + "defaultMessage": "Schemat" + }, + "hosts": { + "defaultMessage": "Hosty" + }, + "http-only": { + "defaultMessage": "Tylko HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt przez DNS" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt przez HTTP" + }, + "loading": { + "defaultMessage": "Ładowanie…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Zaloguj się na swoje konto" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Własna konfiguracja Nginx" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Wprowadź tutaj własną konfigurację Nginx na własną odpowiedzialność!" + }, + "no-permission-error": { + "defaultMessage": "Nie masz uprawnień do wyświetlenia tego." + }, + "notfound.action": { + "defaultMessage": "Zabierz mnie do strony głównej" + }, + "notfound.content": { + "defaultMessage": "Przepraszamy, ale strona, której szukasz, nie została znaleziona" + }, + "notfound.title": { + "defaultMessage": "Ups… Właśnie znalazłeś stronę błędu" + }, + "notification.error": { + "defaultMessage": "Błąd" + }, + "notification.object-deleted": { + "defaultMessage": "{object} został usunięty" + }, + "notification.object-disabled": { + "defaultMessage": "{object} został wyłączony" + }, + "notification.object-enabled": { + "defaultMessage": "{object} został włączony" + }, + "notification.object-renewed": { + "defaultMessage": "{object} został odnowiony" + }, + "notification.object-saved": { + "defaultMessage": "{object} został zapisany" + }, + "notification.success": { + "defaultMessage": "Sukces" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Nowy {object}" + }, + "object.delete": { + "defaultMessage": "Usuń {object}" + }, + "object.delete.content": { + "defaultMessage": "Czy na pewno chcesz usunąć {object}?" + }, + "object.edit": { + "defaultMessage": "Edytuj {object}" + }, + "object.empty": { + "defaultMessage": "Brak {objects}" + }, + "object.event.created": { + "defaultMessage": "Utworzono {object}" + }, + "object.event.deleted": { + "defaultMessage": "Usunięto {object}" + }, + "object.event.disabled": { + "defaultMessage": "Wyłączono {object}" + }, + "object.event.enabled": { + "defaultMessage": "Włączono {object}" + }, + "object.event.renewed": { + "defaultMessage": "Odnowiono {object}" + }, + "object.event.updated": { + "defaultMessage": "Zaktualizowano {object}" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Opcje" + }, + "password": { + "defaultMessage": "Hasło" + }, + "password.generate": { + "defaultMessage": "Wygeneruj losowe hasło" + }, + "password.hide": { + "defaultMessage": "Ukryj hasło" + }, + "password.show": { + "defaultMessage": "Pokaż hasło" + }, + "permissions.hidden": { + "defaultMessage": "Ukryte" + }, + "permissions.manage": { + "defaultMessage": "Zarządzaj" + }, + "permissions.view": { + "defaultMessage": "Tylko podgląd" + }, + "permissions.visibility.all": { + "defaultMessage": "Wszystkie elementy" + }, + "permissions.visibility.title": { + "defaultMessage": "Widoczność elementów" + }, + "permissions.visibility.user": { + "defaultMessage": "Tylko utworzone elementy" + }, + "proxy-host": { + "defaultMessage": "host proxy" + }, + "proxy-host.forward-host": { + "defaultMessage": "Przekieruj na hostname / IP" + }, + "proxy-hosts": { + "defaultMessage": "Proxy" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {host proxy} few {hosty proxy} many {hostów proxy} other {hostów proxy}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Publiczne" + }, + "redirection-host": { + "defaultMessage": "adres przekierowania" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Domena docelowa" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "Kod HTTP" + }, + "redirection-hosts": { + "defaultMessage": "Przekierowania" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {przekierowanie} few {przekierowania} many {przekierowań} other {przekierowań}}" + }, + "role.admin": { + "defaultMessage": "Administrator" + }, + "role.standard-user": { + "defaultMessage": "Standardowy użytkownik" + }, + "save": { + "defaultMessage": "Zapisz" + }, + "setting": { + "defaultMessage": "Ustawienie" + }, + "settings": { + "defaultMessage": "Ustawienia" + }, + "settings.default-site": { + "defaultMessage": "Domyślna strona" + }, + "settings.default-site.404": { + "defaultMessage": "Strona 404" + }, + "settings.default-site.444": { + "defaultMessage": "Brak odpowiedzi (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Strona gratulacyjna" + }, + "settings.default-site.description": { + "defaultMessage": "Co wyświetlić, gdy Nginx otrzyma nieznany Host" + }, + "settings.default-site.html": { + "defaultMessage": "Własny HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Przekierowanie" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Zacznij od utworzenia konta administratora." + }, + "setup.title": { + "defaultMessage": "Witaj!" + }, + "sign-in": { + "defaultMessage": "Zaloguj się" + }, + "ssl-certificate": { + "defaultMessage": "Certyfikat SSL" + }, + "stream": { + "defaultMessage": "strumień" + }, + "stream.forward-host": { + "defaultMessage": "Host docelowy" + }, + "stream.incoming-port": { + "defaultMessage": "Port przychodzący" + }, + "streams": { + "defaultMessage": "Strumienie" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {strumień} few {strumienie} many {strumieni} other {strumieni}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Test" + }, + "user": { + "defaultMessage": "użytkownik" + }, + "user.change-password": { + "defaultMessage": "Zmień hasło" + }, + "user.confirm-password": { + "defaultMessage": "Potwierdź nowe hasło" + }, + "user.current-password": { + "defaultMessage": "Aktualne hasło" + }, + "user.edit-profile": { + "defaultMessage": "Edytuj profil" + }, + "user.full-name": { + "defaultMessage": "Imię / Nazwisko" + }, + "user.login-as": { + "defaultMessage": "Zaloguj jako {name}" + }, + "user.logout": { + "defaultMessage": "Wyloguj" + }, + "user.new-password": { + "defaultMessage": "Nowe hasło" + }, + "user.nickname": { + "defaultMessage": "Pseudonim" + }, + "user.set-password": { + "defaultMessage": "Ustaw hasło" + }, + "user.set-permissions": { + "defaultMessage": "Ustaw uprawnienia dla {name}" + }, + "user.switch-dark": { + "defaultMessage": "Przełącz na tryb ciemny" + }, + "user.switch-light": { + "defaultMessage": "Przełącz na tryb jasny" + }, + "username": { + "defaultMessage": "Nazwa użytkownika" + }, + "users": { + "defaultMessage": "Użytkownicy" + } +} diff --git a/frontend/src/locale/src/pt.json b/frontend/src/locale/src/pt.json new file mode 100644 index 0000000000..834af735b7 --- /dev/null +++ b/frontend/src/locale/src/pt.json @@ -0,0 +1,797 @@ +{ + "access-list": { + "defaultMessage": "Lista de Controlo de Acesso (ACL)" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Regra} other {Regras}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {Utilizador} other {Utilizadores}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Quando existir pelo menos 1 regra, esta regra de negação geral será aplicada em último lugar" + }, + "access-list.help.rules-order": { + "defaultMessage": "Nota: as diretivas allow e deny são aplicadas pela ordem em que forem definidas." + }, + "access-list.pass-auth": { + "defaultMessage": "Passar Autenticação para o Upstream" + }, + "access-list.public": { + "defaultMessage": "Acesso Público" + }, + "access-list.public.subtitle": { + "defaultMessage": "Sem autenticação básica" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 ou 192.168.1.0/24 ou 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "Satisfazer Qualquer" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {Utilizador} other {Utilizadores}}, {rules} {rules, plural, one {Regra} other {Regras}} – Criado em: {date}" + }, + "access-lists": { + "defaultMessage": "Listas de Controlo de Acesso (ACL)" + }, + "action.add": { + "defaultMessage": "Adicionar" + }, + "action.add-location": { + "defaultMessage": "Adicionar Location" + }, + "action.allow": { + "defaultMessage": "Permitir" + }, + "action.close": { + "defaultMessage": "Fechar" + }, + "action.delete": { + "defaultMessage": "Eliminar" + }, + "action.deny": { + "defaultMessage": "Negar" + }, + "action.disable": { + "defaultMessage": "Desativar" + }, + "action.download": { + "defaultMessage": "Descarregar" + }, + "action.edit": { + "defaultMessage": "Editar" + }, + "action.enable": { + "defaultMessage": "Ativar" + }, + "action.permissions": { + "defaultMessage": "Permissões" + }, + "action.renew": { + "defaultMessage": "Renovar" + }, + "action.view-details": { + "defaultMessage": "Ver Detalhes" + }, + "auditlogs": { + "defaultMessage": "Registos de Auditoria" + }, + "auto": { + "defaultMessage": "Automático" + }, + "cancel": { + "defaultMessage": "Cancelar" + }, + "certificate": { + "defaultMessage": "Certificado" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certificado Personalizado" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Chave do Certificado" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Certificado Intermédio" + }, + "certificate.in-use": { + "defaultMessage": "Em Utilização" + }, + "certificate.none.subtitle": { + "defaultMessage": "Nenhum certificado atribuído" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Este host não irá utilizar HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Nenhum" + }, + "certificate.not-in-use": { + "defaultMessage": "Não Utilizado" + }, + "certificate.renew": { + "defaultMessage": "Renovar Certificado" + }, + "certificates": { + "defaultMessage": "Certificados" + }, + "certificates.custom": { + "defaultMessage": "Certificado Personalizado" + }, + "certificates.custom.warning": { + "defaultMessage": "Ficheiros de chave protegidos por palavra-passe não são suportados." + }, + "certificates.dns.credentials": { + "defaultMessage": "Conteúdo do Ficheiro de Credenciais" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Este plugin requer um ficheiro de configuração contendo um token API ou outras credenciais do fornecedor DNS." + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Estes dados serão guardados em texto simples na base de dados e num ficheiro!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Segundos de Propagação" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Deixe em branco para usar o valor predefinido do plugin. Número de segundos a aguardar pela propagação DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "Fornecedor DNS" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Selecionar fornecedor..." + }, + "certificates.dns.warning": { + "defaultMessage": "Esta secção requer conhecimentos sobre o Certbot e os seus plugins DNS. Consulte a documentação dos plugins." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Foi encontrado um servidor neste domínio, mas não parece ser o Nginx Proxy Manager. Certifique-se de que o domínio aponta para o IP onde a sua instância está a correr." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Falha ao verificar acessibilidade devido a um erro de comunicação com site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Não existe nenhum servidor acessível neste domínio. Certifique-se de que o domínio existe, aponta para o IP correto e que a porta 80 está encaminhada no seu router." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "O servidor está acessível e a criação de certificados deverá ser possível." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Foi encontrado um servidor neste domínio, mas devolveu um código inesperado ({code}). Será o servidor NPM? Confirme que o domínio aponta para o IP correto." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Foi encontrado um servidor neste domínio, mas devolveu dados inesperados. Será o servidor NPM? Confirme que o domínio aponta para o IP correto." + }, + "certificates.http.test-results": { + "defaultMessage": "Resultados do Teste" + }, + "certificates.http.warning": { + "defaultMessage": "Estes domínios devem já estar configurados para apontar para esta instalação." + }, + "certificates.request.subtitle": { + "defaultMessage": "com o Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Pedir Novo Certificado" + }, + "column.access": { + "defaultMessage": "Acesso" + }, + "column.authorization": { + "defaultMessage": "Autorização" + }, + "column.authorizations": { + "defaultMessage": "Autorizações" + }, + "column.custom-locations": { + "defaultMessage": "Locations Personalizados" + }, + "column.destination": { + "defaultMessage": "Destino" + }, + "column.details": { + "defaultMessage": "Detalhes" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.event": { + "defaultMessage": "Evento" + }, + "column.expires": { + "defaultMessage": "Expira" + }, + "column.http-code": { + "defaultMessage": "Código HTTP" + }, + "column.incoming-port": { + "defaultMessage": "Porta de Entrada" + }, + "column.name": { + "defaultMessage": "Nome" + }, + "column.protocol": { + "defaultMessage": "Protocolo" + }, + "column.provider": { + "defaultMessage": "Fornecedor" + }, + "column.roles": { + "defaultMessage": "Funções" + }, + "column.rules": { + "defaultMessage": "Regras" + }, + "column.satisfy": { + "defaultMessage": "Satisfazer" + }, + "column.satisfy-all": { + "defaultMessage": "Todos" + }, + "column.satisfy-any": { + "defaultMessage": "Qualquer" + }, + "column.scheme": { + "defaultMessage": "Esquema" + }, + "column.source": { + "defaultMessage": "Origem" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Estado" + }, + "created-on": { + "defaultMessage": "Criado em: {date}" + }, + "dashboard": { + "defaultMessage": "Painel" + }, + "dead-host": { + "defaultMessage": "Host 404" + }, + "dead-hosts": { + "defaultMessage": "Hosts 404" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host 404} other {Hosts 404}}" + }, + "disabled": { + "defaultMessage": "Desativado" + }, + "domain-names": { + "defaultMessage": "Nomes de Domínio" + }, + "domain-names.max": { + "defaultMessage": "Máximo de {count} domínios" + }, + "domain-names.placeholder": { + "defaultMessage": "Comece a escrever para adicionar um domínio..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Wildcards não permitidos para este tipo" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Wildcards não suportados por esta AC" + }, + "domains.force-ssl": { + "defaultMessage": "Forçar SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS Ativado" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS para Subdomínios" + }, + "domains.http2-support": { + "defaultMessage": "Suporte HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Utilizar DNS Challenge" + }, + "email-address": { + "defaultMessage": "Endereço de Email" + }, + "empty-search": { + "defaultMessage": "Nenhum resultado encontrado" + }, + "empty-subtitle": { + "defaultMessage": "Porque não cria um?" + }, + "enabled": { + "defaultMessage": "Ativado" + }, + "error.access.at-least-one": { + "defaultMessage": "É necessária pelo menos uma Autorização ou uma Regra de Acesso" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Os nomes de utilizador de autorização devem ser únicos" + }, + "error.invalid-auth": { + "defaultMessage": "Email ou palavra-passe inválidos" + }, + "error.invalid-domain": { + "defaultMessage": "Domínio inválido: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Endereço de email inválido" + }, + "error.max-character-length": { + "defaultMessage": "Tamanho máximo: {max} caractere{max, plural, one {} other {s}}" + }, + "error.max-domains": { + "defaultMessage": "Demasiados domínios; o máximo é {max}" + }, + "error.maximum": { + "defaultMessage": "Máximo permitido: {max}" + }, + "error.min-character-length": { + "defaultMessage": "Tamanho mínimo: {min} caractere{min, plural, one {} other {s}}" + }, + "error.minimum": { + "defaultMessage": "Mínimo permitido: {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "As palavras-passe têm de coincidir" + }, + "error.required": { + "defaultMessage": "Campo obrigatório" + }, + "expires.on": { + "defaultMessage": "Expira em: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Faz fork no GitHub" + }, + "host.flags.block-exploits": { + "defaultMessage": "Bloquear Exploits Comuns" + }, + "host.flags.cache-assets": { + "defaultMessage": "Cache de Conteúdos Estáticos" + }, + "host.flags.preserve-path": { + "defaultMessage": "Preservar Caminho" + }, + "host.flags.protocols": { + "defaultMessage": "Protocolos" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Suporte para WebSockets" + }, + "host.forward-port": { + "defaultMessage": "Porta de Encaminhamento" + }, + "host.forward-scheme": { + "defaultMessage": "Esquema" + }, + "hosts": { + "defaultMessage": "Hosts" + }, + "http-only": { + "defaultMessage": "Apenas HTTP" + }, + "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": "A carregar…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Iniciar sessão na sua conta" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Configuração Nginx Personalizada" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Insira aqui a sua configuração Nginx personalizada (utilize por sua conta e risco!)" + }, + "no-permission-error": { + "defaultMessage": "Não tem permissões para ver esta página." + }, + "notfound.action": { + "defaultMessage": "Voltar à página inicial" + }, + "notfound.content": { + "defaultMessage": "A página que procura não foi encontrada." + }, + "notfound.title": { + "defaultMessage": "Oops… Encontrou uma página de erro" + }, + "notification.error": { + "defaultMessage": "Erro" + }, + "notification.object-deleted": { + "defaultMessage": "{object} foi eliminado" + }, + "notification.object-disabled": { + "defaultMessage": "{object} foi desativado" + }, + "notification.object-enabled": { + "defaultMessage": "{object} foi ativado" + }, + "notification.object-renewed": { + "defaultMessage": "{object} foi renovado" + }, + "notification.object-saved": { + "defaultMessage": "{object} foi guardado" + }, + "notification.success": { + "defaultMessage": "Sucesso" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Adicionar {object}" + }, + "object.delete": { + "defaultMessage": "Eliminar {object}" + }, + "object.delete.content": { + "defaultMessage": "Tem a certeza de que deseja eliminar este {object}?" + }, + "object.edit": { + "defaultMessage": "Editar {object}" + }, + "object.empty": { + "defaultMessage": "Não existem {objects}" + }, + "object.event.created": { + "defaultMessage": "{object} criado" + }, + "object.event.deleted": { + "defaultMessage": "{object} eliminado" + }, + "object.event.disabled": { + "defaultMessage": "{object} desativado" + }, + "object.event.enabled": { + "defaultMessage": "{object} ativado" + }, + "object.event.renewed": { + "defaultMessage": "{object} renovado" + }, + "object.event.updated": { + "defaultMessage": "{object} atualizado" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Opções" + }, + "password": { + "defaultMessage": "Palavra-passe" + }, + "password.generate": { + "defaultMessage": "Gerar palavra-passe aleatória" + }, + "password.hide": { + "defaultMessage": "Esconder Palavra-passe" + }, + "password.show": { + "defaultMessage": "Mostrar Palavra-passe" + }, + "permissions.hidden": { + "defaultMessage": "Oculto" + }, + "permissions.manage": { + "defaultMessage": "Gerir" + }, + "permissions.view": { + "defaultMessage": "Apenas Visualização" + }, + "permissions.visibility.all": { + "defaultMessage": "Todos os Itens" + }, + "permissions.visibility.title": { + "defaultMessage": "Visibilidade do Item" + }, + "permissions.visibility.user": { + "defaultMessage": "Apenas Itens Criados" + }, + "proxy-host": { + "defaultMessage": "Proxy Host" + }, + "proxy-host.forward-host": { + "defaultMessage": "Hostname/IP de Encaminhamento" + }, + "proxy-hosts": { + "defaultMessage": "Proxy Hosts" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Público" + }, + "redirection-host": { + "defaultMessage": "Host de Redirecionamento" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Domínio de Destino" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "Código HTTP" + }, + "redirection-hosts": { + "defaultMessage": "Hosts de Redirecionamento" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host de Redirecionamento} other {Hosts de Redirecionamento}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Múltiplas Escolhas" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Movido Permanentemente" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Movido Temporariamente" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Ver Outro" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Redirecionamento Temporário" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Redirecionamento Permanente" + }, + "role.admin": { + "defaultMessage": "Administrador" + }, + "role.standard-user": { + "defaultMessage": "Utilizador Comum" + }, + "save": { + "defaultMessage": "Guardar" + }, + "setting": { + "defaultMessage": "Definição" + }, + "settings": { + "defaultMessage": "Definições" + }, + "settings.default-site": { + "defaultMessage": "Site Predefinido" + }, + "settings.default-site.404": { + "defaultMessage": "Página 404" + }, + "settings.default-site.444": { + "defaultMessage": "Sem Resposta (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Página de Boas-vindas" + }, + "settings.default-site.description": { + "defaultMessage": "O que apresentar quando o Nginx recebe um Host desconhecido" + }, + "settings.default-site.html": { + "defaultMessage": "HTML Personalizado" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Redirecionar" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Comece por criar a sua conta de administrador." + }, + "setup.title": { + "defaultMessage": "Bem-vindo!" + }, + "sign-in": { + "defaultMessage": "Iniciar Sessão" + }, + "ssl-certificate": { + "defaultMessage": "Certificado SSL" + }, + "stream": { + "defaultMessage": "Stream" + }, + "stream.forward-host": { + "defaultMessage": "Host de Destino" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com ou 10.0.0.1 ou 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "Porta de Entrada" + }, + "streams": { + "defaultMessage": "Streams" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Stream} other {Streams}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Testar" + }, + "update-available": { + "defaultMessage": "Atualização Disponível: {latestVersion}" + }, + "user": { + "defaultMessage": "Utilizador" + }, + "user.change-password": { + "defaultMessage": "Alterar Palavra-passe" + }, + "user.confirm-password": { + "defaultMessage": "Confirmar Palavra-passe" + }, + "user.current-password": { + "defaultMessage": "Palavra-passe Atual" + }, + "user.edit-profile": { + "defaultMessage": "Editar Perfil" + }, + "user.full-name": { + "defaultMessage": "Nome Completo" + }, + "user.login-as": { + "defaultMessage": "Iniciar sessão como {name}" + }, + "user.logout": { + "defaultMessage": "Terminar Sessão" + }, + "user.new-password": { + "defaultMessage": "Nova Palavra-passe" + }, + "user.nickname": { + "defaultMessage": "Alcunha" + }, + "user.set-password": { + "defaultMessage": "Definir Palavra-passe" + }, + "user.set-permissions": { + "defaultMessage": "Definir Permissões para {name}" + }, + "user.switch-dark": { + "defaultMessage": "Ativar Modo Escuro" + }, + "user.switch-light": { + "defaultMessage": "Ativar Modo Claro" + }, + "username": { + "defaultMessage": "Nome de Utilizador" + }, + "users": { + "defaultMessage": "Utilizadores" + } +} diff --git a/frontend/src/locale/src/ru.json b/frontend/src/locale/src/ru.json new file mode 100644 index 0000000000..6e9be98ea0 --- /dev/null +++ b/frontend/src/locale/src/ru.json @@ -0,0 +1,770 @@ +{ + "access-list": { + "defaultMessage": "Список доступа" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {правило} few {правила} many {правил} other {правила}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {пользователь} few {пользователя} many {пользователей} other {пользователя}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Если есть хотя бы одно правило, правило 'запретить всё' будет добавлено последним" + }, + "access-list.help.rules-order": { + "defaultMessage": "Обратите внимание: разрешающие и запрещающие директивы применяются в порядке их определения." + }, + "access-list.pass-auth": { + "defaultMessage": "Передавать авторизацию на upstream-сервер" + }, + "access-list.public": { + "defaultMessage": "Общедоступный" + }, + "access-list.public.subtitle": { + "defaultMessage": "Без аутентификации" + }, + "access-list.satisfy-any": { + "defaultMessage": "Любое совпадение" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {пользователь} few {пользователя} many {пользователей} other {пользователя}}, {rules} {rules, plural, one {правило} few {правила} many {правил} other {правила}} - создан: {date}" + }, + "access-lists": { + "defaultMessage": "Списки доступа" + }, + "action.add": { + "defaultMessage": "Добавить" + }, + "action.add-location": { + "defaultMessage": "Добавить маршрут" + }, + "action.close": { + "defaultMessage": "Закрыть" + }, + "action.delete": { + "defaultMessage": "Удалить" + }, + "action.disable": { + "defaultMessage": "Выключить" + }, + "action.download": { + "defaultMessage": "Скачать" + }, + "action.edit": { + "defaultMessage": "Изменить" + }, + "action.enable": { + "defaultMessage": "Включить" + }, + "action.permissions": { + "defaultMessage": "Разрешения" + }, + "action.renew": { + "defaultMessage": "Продлить" + }, + "action.view-details": { + "defaultMessage": "Просмотреть сведения" + }, + "auditlogs": { + "defaultMessage": "Журнал аудита" + }, + "cancel": { + "defaultMessage": "Отменить" + }, + "certificate": { + "defaultMessage": "Сертификат" + }, + "certificate.custom-certificate": { + "defaultMessage": "Сертификат" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Ключ сертификата" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Промежуточный сертификат" + }, + "certificate.in-use": { + "defaultMessage": "Используется" + }, + "certificate.none.subtitle": { + "defaultMessage": "Сертификат не назначен" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Этот хост не будет использовать HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Нет" + }, + "certificate.not-in-use": { + "defaultMessage": "Не используется" + }, + "certificate.renew": { + "defaultMessage": "Продлить сертификат" + }, + "certificates": { + "defaultMessage": "Сертификаты" + }, + "certificates.custom": { + "defaultMessage": "Свой сертификат" + }, + "certificates.custom.warning": { + "defaultMessage": "Файлы ключей, защищённые паролем, не поддерживаются." + }, + "certificates.dns.credentials": { + "defaultMessage": "Содержимое файла учётных данных" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Этот плагин требует файл конфигурации, содержащий API-токен или другие учётные данные вашего провайдера" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Эти данные будут храниться в незашифрованном виде в базе данных и файле!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Ожидание распространения (сек.)" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Оставьте пустым для значения по умолчанию плагина. Секунды ожидания распространения DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "DNS-провайдер" + }, + "certificates.dns.warning": { + "defaultMessage": "Этот раздел требует знаний о Certbot и его DNS-плагинах. Пожалуйста, обратитесь к документации соответствующих плагинов." + }, + "certificates.http.reachability-404": { + "defaultMessage": "На этом домене найден сервер, но, похоже, это не Nginx Proxy Manager. Убедитесь, что ваш домен указывает на IP-адрес, где запущен ваш экземпляр NPM." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Не удалось проверить доступность из‑за ошибки связи с site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "На этом домене недоступен сервер. Убедитесь, что домен существует и указывает на IP-адрес, где запущен ваш экземпляр NPM, и при необходимости порт 80 проброшен на вашем роутере." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Сервер доступен, выпуск сертификатов возможен." + }, + "certificates.http.reachability-other": { + "defaultMessage": "На этом домене найден сервер, но он вернул неожиданный статус‑код {code}. Это сервер NPM? Убедитесь, что ваш домен указывает на IP-адрес, где запущен ваш экземпляр NPM." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "На этом домене найден сервер, но он вернул неожиданные данные. Это сервер NPM? Убедитесь, что ваш домен указывает на IP-адрес, где запущен ваш экземпляр NPM." + }, + "certificates.http.test-results": { + "defaultMessage": "Результаты проверки" + }, + "certificates.http.warning": { + "defaultMessage": "Эти домены должны быть настроены и указывать на этот экземпляр." + }, + "certificates.key-type": { + "defaultMessage": "Тип ключа" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA широко совместим, ECDSA быстрее и безопаснее, но может не поддерживаться старыми системами" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "через Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Получить новый сертификат" + }, + "column.access": { + "defaultMessage": "Доступ" + }, + "column.authorization": { + "defaultMessage": "Авторизация" + }, + "column.authorizations": { + "defaultMessage": "Авторизации" + }, + "column.custom-locations": { + "defaultMessage": "Свои маршруты" + }, + "column.destination": { + "defaultMessage": "Назначение" + }, + "column.details": { + "defaultMessage": "Сведения" + }, + "column.email": { + "defaultMessage": "Эл. почта" + }, + "column.event": { + "defaultMessage": "Событие" + }, + "column.expires": { + "defaultMessage": "Истекает" + }, + "column.http-code": { + "defaultMessage": "HTTP-код" + }, + "column.incoming-port": { + "defaultMessage": "Входящий порт" + }, + "column.name": { + "defaultMessage": "Имя" + }, + "column.protocol": { + "defaultMessage": "Протокол" + }, + "column.provider": { + "defaultMessage": "Провайдер" + }, + "column.roles": { + "defaultMessage": "Роли" + }, + "column.rules": { + "defaultMessage": "Правила" + }, + "column.satisfy": { + "defaultMessage": "Условия" + }, + "column.satisfy-all": { + "defaultMessage": "Все" + }, + "column.satisfy-any": { + "defaultMessage": "Любое" + }, + "column.scheme": { + "defaultMessage": "Схема" + }, + "column.source": { + "defaultMessage": "Источник" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Статус" + }, + "created-on": { + "defaultMessage": "Создан: {date}" + }, + "dashboard": { + "defaultMessage": "Обзор" + }, + "dead-host": { + "defaultMessage": "404-хост" + }, + "dead-hosts": { + "defaultMessage": "404-хосты" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {404-хост} few {404-хоста} many {404-хостов} other {404-хоста}}" + }, + "disabled": { + "defaultMessage": "Выключен" + }, + "domain-names": { + "defaultMessage": "Домены" + }, + "domain-names.max": { + "defaultMessage": "Максимум {count} доменов" + }, + "domain-names.placeholder": { + "defaultMessage": "Начните ввод, чтобы добавить домен..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Подстановочные домены не разрешены для этого типа" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Подстановочные домены не поддерживаются этим центром сертификации" + }, + "domains.force-ssl": { + "defaultMessage": "Всегда SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "Поддержка HSTS" + }, + "domains.hsts-subdomains": { + "defaultMessage": "Поддомены HSTS" + }, + "domains.http2-support": { + "defaultMessage": "Поддержка HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Проверка через DNS" + }, + "email-address": { + "defaultMessage": "Адрес эл. почты" + }, + "empty-search": { + "defaultMessage": "Ничего не найдено" + }, + "empty-subtitle": { + "defaultMessage": "Почему бы не создать его?" + }, + "enabled": { + "defaultMessage": "Включён" + }, + "error.access.at-least-one": { + "defaultMessage": "Требуется хотя бы одна авторизация или одно правило доступа" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Имена пользователей для авторизации должны быть уникальными" + }, + "error.invalid-auth": { + "defaultMessage": "Неверный адрес эл. почты или пароль" + }, + "error.invalid-domain": { + "defaultMessage": "Неверный домен: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Неверный адрес эл. почты" + }, + "error.max-character-length": { + "defaultMessage": "Максимальная длина {max} {max, plural, one {символ} few {символа} many {символов} other {символа}}" + }, + "error.max-domains": { + "defaultMessage": "Слишком много доменов, максимум {max}" + }, + "error.maximum": { + "defaultMessage": "Максимум {max}" + }, + "error.min-character-length": { + "defaultMessage": "Минимальная длина {min} {min, plural, one {символ} few {символа} many {символов} other {символа}}" + }, + "error.minimum": { + "defaultMessage": "Минимум {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Пароли должны совпадать" + }, + "error.required": { + "defaultMessage": "Обязательное поле" + }, + "expires.on": { + "defaultMessage": "Истекает: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Сделать форк на GitHub" + }, + "host.flags.block-exploits": { + "defaultMessage": "Блокировать известные эксплойты" + }, + "host.flags.cache-assets": { + "defaultMessage": "Кэшировать ресурсы" + }, + "host.flags.preserve-path": { + "defaultMessage": "Сохранять путь" + }, + "host.flags.protocols": { + "defaultMessage": "Протоколы" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Поддержка WebSocket" + }, + "host.forward-port": { + "defaultMessage": "Порт перенаправления" + }, + "host.forward-scheme": { + "defaultMessage": "Схема" + }, + "hosts": { + "defaultMessage": "Хосты" + }, + "http-only": { + "defaultMessage": "Только HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt через DNS" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt через HTTP" + }, + "loading": { + "defaultMessage": "Загрузка…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Авторизация" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Своя Nginx-конфигурация" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Введите здесь свою Nginx-конфигурацию, будьте осторожны!" + }, + "no-permission-error": { + "defaultMessage": "У вас нет доступа для просмотра." + }, + "notfound.action": { + "defaultMessage": "Вернуться на главную" + }, + "notfound.content": { + "defaultMessage": "Извините, но страница, которую вы ищете, не найдена" + }, + "notfound.title": { + "defaultMessage": "Упс… Вы попали на страницу ошибки" + }, + "notification.error": { + "defaultMessage": "Ошибка" + }, + "notification.object-deleted": { + "defaultMessage": "{object} удален" + }, + "notification.object-disabled": { + "defaultMessage": "{object} выключен" + }, + "notification.object-enabled": { + "defaultMessage": "{object} включен" + }, + "notification.object-renewed": { + "defaultMessage": "{object} продлен" + }, + "notification.object-saved": { + "defaultMessage": "{object} сохранен" + }, + "notification.success": { + "defaultMessage": "Успешно" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Добавить {object}" + }, + "object.delete": { + "defaultMessage": "Удалить {object}" + }, + "object.delete.content": { + "defaultMessage": "Вы уверены, что хотите удалить {object}?" + }, + "object.edit": { + "defaultMessage": "Изменить {object}" + }, + "object.empty": { + "defaultMessage": "{objects} отсутствуют" + }, + "object.event.created": { + "defaultMessage": "{object} создан" + }, + "object.event.deleted": { + "defaultMessage": "{object} удален" + }, + "object.event.disabled": { + "defaultMessage": "{object} выключен" + }, + "object.event.enabled": { + "defaultMessage": "{object} включен" + }, + "object.event.renewed": { + "defaultMessage": "{object} продлен" + }, + "object.event.updated": { + "defaultMessage": "{object} обновлен" + }, + "offline": { + "defaultMessage": "Офлайн" + }, + "online": { + "defaultMessage": "Онлайн" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Параметры" + }, + "password": { + "defaultMessage": "Пароль" + }, + "password.generate": { + "defaultMessage": "Сгенерировать случайный пароль" + }, + "password.hide": { + "defaultMessage": "Скрыть пароль" + }, + "password.show": { + "defaultMessage": "Показать пароль" + }, + "permissions.hidden": { + "defaultMessage": "Скрыто" + }, + "permissions.manage": { + "defaultMessage": "Управление" + }, + "permissions.view": { + "defaultMessage": "Только просмотр" + }, + "permissions.visibility.all": { + "defaultMessage": "Все элементы" + }, + "permissions.visibility.title": { + "defaultMessage": "Видимость элементов" + }, + "permissions.visibility.user": { + "defaultMessage": "Созданные элементы" + }, + "proxy-host": { + "defaultMessage": "Прокси-хост" + }, + "proxy-host.forward-host": { + "defaultMessage": "Хост / IP перенаправления" + }, + "proxy-hosts": { + "defaultMessage": "Прокси-хосты" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {прокси-хост} few {прокси-хоста} many {прокси-хостов} other {прокси-хоста}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Общедоступный" + }, + "redirection-host": { + "defaultMessage": "Редирект-хост" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Домен перенаправления" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP-код" + }, + "redirection-hosts": { + "defaultMessage": "Редирект-хосты" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {редирект-хост} few {редирект-хоста} many {редирект-хостов} other {редирект-хоста}}" + }, + "role.admin": { + "defaultMessage": "Администратор" + }, + "role.standard-user": { + "defaultMessage": "Обычный пользователь" + }, + "save": { + "defaultMessage": "Сохранить" + }, + "setting": { + "defaultMessage": "Настройка" + }, + "settings": { + "defaultMessage": "Настройки" + }, + "settings.default-site": { + "defaultMessage": "Страница по умолчанию" + }, + "settings.default-site.404": { + "defaultMessage": "404-страница" + }, + "settings.default-site.444": { + "defaultMessage": "Нет ответа (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Страница поздравления" + }, + "settings.default-site.description": { + "defaultMessage": "Что показывать, когда Nginx получает неизвестный хост" + }, + "settings.default-site.html": { + "defaultMessage": "Свой HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Перенаправление" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Начните с создания учётной записи администратора." + }, + "setup.title": { + "defaultMessage": "Добро пожаловать!" + }, + "sign-in": { + "defaultMessage": "Войти" + }, + "ssl-certificate": { + "defaultMessage": "SSL-сертификат" + }, + "stream": { + "defaultMessage": "Поток" + }, + "stream.forward-host": { + "defaultMessage": "Хост перенаправления" + }, + "stream.incoming-port": { + "defaultMessage": "Входящий порт" + }, + "streams": { + "defaultMessage": "Потоки" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {поток} few {потока} many {потоков} other {потока}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Проверить" + }, + "user": { + "defaultMessage": "Пользователь" + }, + "user.change-password": { + "defaultMessage": "Изменить пароль" + }, + "user.confirm-password": { + "defaultMessage": "Повторите пароль" + }, + "user.current-password": { + "defaultMessage": "Текущий пароль" + }, + "user.edit-profile": { + "defaultMessage": "Изменить профиль" + }, + "user.full-name": { + "defaultMessage": "Полное имя" + }, + "user.login-as": { + "defaultMessage": "Войти как {name}" + }, + "user.logout": { + "defaultMessage": "Выйти" + }, + "user.new-password": { + "defaultMessage": "Новый пароль" + }, + "user.nickname": { + "defaultMessage": "Псевдоним" + }, + "user.set-password": { + "defaultMessage": "Задать пароль" + }, + "user.set-permissions": { + "defaultMessage": "Задать разрешения для {name}" + }, + "user.switch-dark": { + "defaultMessage": "Включить тёмную тему" + }, + "user.switch-light": { + "defaultMessage": "Включить светлую тему" + }, + "username": { + "defaultMessage": "Имя пользователя" + }, + "users": { + "defaultMessage": "Пользователи" + } +} diff --git a/frontend/src/locale/src/sk.json b/frontend/src/locale/src/sk.json new file mode 100644 index 0000000000..7f78a22788 --- /dev/null +++ b/frontend/src/locale/src/sk.json @@ -0,0 +1,884 @@ +{ + "2fa.backup-codes-remaining": { + "defaultMessage": "Počet zostávajúcich záložných kódov: {count}" + }, + "2fa.backup-warning": { + "defaultMessage": "Tieto záložné kódy si uložte na bezpečnom mieste. Každý kód je možné použiť len raz." + }, + "2fa.disable": { + "defaultMessage": "Vypnúť dvojfaktorové overovanie" + }, + "2fa.disable-confirm": { + "defaultMessage": "Vypnúť 2FA" + }, + "2fa.disable-warning": { + "defaultMessage": "Vypnutím dvojfaktorového overovania sa zníži bezpečnosť vášho účtu." + }, + "2fa.disabled": { + "defaultMessage": "Vypnuté" + }, + "2fa.done": { + "defaultMessage": "Uložil som si svoje záložné kódy." + }, + "2fa.enable": { + "defaultMessage": "Zapnúť dvojfaktorové overovanie" + }, + "2fa.enabled": { + "defaultMessage": "Zapnuté" + }, + "2fa.enter-code": { + "defaultMessage": "Zadajte overovací kód" + }, + "2fa.enter-code-disable": { + "defaultMessage": "Zadajte overovací kód na vypnutie" + }, + "2fa.regenerate": { + "defaultMessage": "Znova vytvoriť" + }, + "2fa.regenerate-backup": { + "defaultMessage": "Znova vytvoriť záložné kódy" + }, + "2fa.regenerate-instructions": { + "defaultMessage": "Zadajte overovací kód, aby sa vytvorili nové záložné kódy. Vaše staré kódy budú neplatné." + }, + "2fa.secret-key": { + "defaultMessage": "Tajný kľúč" + }, + "2fa.setup-instructions": { + "defaultMessage": "Naskenujte tento QR kód pomocou svojej overovacej aplikácie alebo zadajte tajný kľúč ručne." + }, + "2fa.status": { + "defaultMessage": "Stav" + }, + "2fa.title": { + "defaultMessage": "Dvojfaktorové overenie" + }, + "2fa.verify-enable": { + "defaultMessage": "Overiť a zapnúť" + }, + "access-list": { + "defaultMessage": "zoznam prístupov" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {pravidlo} few {pravidlá} other {pravidiel}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {používateľ} few {používatelia} other {používateľov}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Keď existuje aspoň jedno pravidlo, toto pravidlo „zamietnuť všetko“ bude pridané ako posledné" + }, + "access-list.help.rules-order": { + "defaultMessage": "Upozornenie: pravidlá povoliť a zamietnuť budú uplatňované v poradí, v akom sú definované." + }, + "access-list.pass-auth": { + "defaultMessage": "Odoslať overenie na Upstream" + }, + "access-list.public": { + "defaultMessage": "Verejne prístupné" + }, + "access-list.public.subtitle": { + "defaultMessage": "Nie je potrebné základné overenie" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 alebo 192.168.1.0/24 alebo 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "Splniť ktorékoľvek" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {používateľ} few {používatelia} other {používateľov}}, {rules} {rules, plural, one {pravidlo} few {pravidlá} other {pravidiel}} - Vytvorené: {date}" + }, + "access-lists": { + "defaultMessage": "Zoznamy prístupov" + }, + "action.add": { + "defaultMessage": "Pridať" + }, + "action.add-location": { + "defaultMessage": "Pridať umiestnenie" + }, + "action.allow": { + "defaultMessage": "Povoliť" + }, + "action.close": { + "defaultMessage": "Zavrieť" + }, + "action.delete": { + "defaultMessage": "Vymazať" + }, + "action.deny": { + "defaultMessage": "Zamietnuť" + }, + "action.disable": { + "defaultMessage": "Deaktivovať" + }, + "action.download": { + "defaultMessage": "Stiahnuť" + }, + "action.edit": { + "defaultMessage": "Upraviť" + }, + "action.enable": { + "defaultMessage": "Aktivovať" + }, + "action.permissions": { + "defaultMessage": "Oprávnenia" + }, + "action.renew": { + "defaultMessage": "Obnoviť" + }, + "action.view-details": { + "defaultMessage": "Zobraziť podrobnosti" + }, + "auditlogs": { + "defaultMessage": "Záznamy auditu" + }, + "auto": { + "defaultMessage": "Automaticky" + }, + "cancel": { + "defaultMessage": "Zrušiť" + }, + "certificate": { + "defaultMessage": "certifikát" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certifikát" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Kľúč certifikátu" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Sprostredkovateľský certifikát" + }, + "certificate.in-use": { + "defaultMessage": "Používa sa" + }, + "certificate.none.subtitle": { + "defaultMessage": "Nie je priradený žiadny certifikát" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Tento hostiteľ nebude používať HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Žiadny" + }, + "certificate.not-in-use": { + "defaultMessage": "Nepoužíva sa" + }, + "certificate.renew": { + "defaultMessage": "Obnoviť certifikát" + }, + "certificates": { + "defaultMessage": "Certifikáty" + }, + "certificates.custom": { + "defaultMessage": "Vlastný certifikát" + }, + "certificates.custom.warning": { + "defaultMessage": "Súbory kľúčov chránené heslom nie sú podporované." + }, + "certificates.dns.credentials": { + "defaultMessage": "Obsah súboru s prihlasovacími údajmi" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Tento doplnok vyžaduje konfiguračný súbor obsahujúci API token alebo iné prihlasovacie údaje vášho poskytovateľa" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Tieto údaje budú uložené v databáze a v súbore ako obyčajný text!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Propagácia v sekundách" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Ponechajte prázdne pre predvolenú hodnotu doplnku. Počet sekúnd, počas ktorých sa čaká na propagáciu DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "DNS poskytovateľ" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Vyberte poskytovateľa..." + }, + "certificates.dns.warning": { + "defaultMessage": "Táto sekcia vyžaduje znalosť Certbotu a jeho DNS doplnkov. Prosím, pozrite si dokumentáciu príslušného doplnku." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Na tejto doméne bol nájdený server, ale nezdá sa, že ide o Nginx Proxy Manager. Uistite sa, že vaša doména smeruje na IP, kde beží vaša inštancia NPM." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Nepodarilo sa overiť dostupnosť kvôli chybe komunikácie so službou site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Na tejto doméne nie je dostupný žiadny server. Uistite sa, že doména existuje a smeruje na IP adresu s NPM a ak je to potrebné, port 80 je presmerovaný vo vašom smerovači." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Váš server je dostupný a vytvorenie certifikátu by malo byť možné." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Na tejto doméne bol nájdený server, ale vrátil neočakávaný stavový kód {code}. Je to NPM server? Uistite sa prosím, že doména smeruje na IP, kde beží vaša inštancia NPM." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Na tejto doméne bol nájdený server, ale vrátil neočakávané údaje. Je to NPM server? Uistite sa, že doména smeruje na IP, kde beží vaša inštancia NPM." + }, + "certificates.http.test-results": { + "defaultMessage": "Výsledky testu" + }, + "certificates.http.warning": { + "defaultMessage": "Tieto domény musia byť už nakonfigurované tak, aby smerovali na túto inštaláciu." + }, + "certificates.key-type": { + "defaultMessage": "Typ kľúča" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA je široko kompatibilný, ECDSA je rýchlejší a bezpečnejší, ale nemusí byť podporovaný staršími systémami" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "pomocou Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Vyžiadať nový certifikát" + }, + "column.access": { + "defaultMessage": "Prístup" + }, + "column.authorization": { + "defaultMessage": "Autorizácia" + }, + "column.authorizations": { + "defaultMessage": "Autorizácie" + }, + "column.custom-locations": { + "defaultMessage": "Vlastné umiestnenia" + }, + "column.destination": { + "defaultMessage": "Cieľ" + }, + "column.details": { + "defaultMessage": "Podrobnosti" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.event": { + "defaultMessage": "Udalosť" + }, + "column.expires": { + "defaultMessage": "Platnosť do" + }, + "column.http-code": { + "defaultMessage": "Prístup" + }, + "column.incoming-port": { + "defaultMessage": "Vstupný port" + }, + "column.name": { + "defaultMessage": "Názov" + }, + "column.protocol": { + "defaultMessage": "Protokol" + }, + "column.provider": { + "defaultMessage": "Poskytovateľ" + }, + "column.roles": { + "defaultMessage": "Roly" + }, + "column.rules": { + "defaultMessage": "Pravidlá" + }, + "column.satisfy": { + "defaultMessage": "Splniť" + }, + "column.satisfy-all": { + "defaultMessage": "Všetky" + }, + "column.satisfy-any": { + "defaultMessage": "Ktorékoľvek" + }, + "column.scheme": { + "defaultMessage": "Schéma" + }, + "column.source": { + "defaultMessage": "Zdroj" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Stav" + }, + "created-on": { + "defaultMessage": "Vytvorené: {date}" + }, + "dashboard": { + "defaultMessage": "Panel" + }, + "dead-host": { + "defaultMessage": "404 hostiteľa" + }, + "dead-hosts": { + "defaultMessage": "404 Hostitelia" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {404 hostiteľ} few {404 hostitelia} other {404 hostiteľov}}" + }, + "disabled": { + "defaultMessage": "Deaktivované" + }, + "domain-names": { + "defaultMessage": "Doménové mená" + }, + "domain-names.max": { + "defaultMessage": "Maximálne {count} doménových mien" + }, + "domain-names.placeholder": { + "defaultMessage": "Začnite písať pre pridanie domény..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Wildcards nie sú pre tento typ povolené" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Wildcards nie sú podporované pre túto certifikačnú autoritu" + }, + "domains.force-ssl": { + "defaultMessage": "Vynútiť SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS povolené" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS pre subdomény" + }, + "domains.http2-support": { + "defaultMessage": "Podpora HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Použiť DNS výzvu" + }, + "email-address": { + "defaultMessage": "Emailová adresa" + }, + "empty-search": { + "defaultMessage": "Nenašli sa žiadne výsledky" + }, + "empty-subtitle": { + "defaultMessage": "Prečo nevytvoríte nejaký?" + }, + "enabled": { + "defaultMessage": "Aktivované" + }, + "error.access.at-least-one": { + "defaultMessage": "Je vyžadovaná aspoň jedna autorizácia alebo jedno prístupové pravidlo" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Používateľské mená pre autorizáciu musia byť jedinečné" + }, + "error.invalid-auth": { + "defaultMessage": "Neplatný email alebo heslo" + }, + "error.invalid-domain": { + "defaultMessage": "Neplatná doména: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Neplatná emailová adresa" + }, + "error.max-character-length": { + "defaultMessage": "Maximálna dĺžka je {max} znak{max, plural, one {} few {y} other {ov}}" + }, + "error.max-domains": { + "defaultMessage": "Príliš veľa domén, maximum je {max}" + }, + "error.maximum": { + "defaultMessage": "Maximum je {max}" + }, + "error.min-character-length": { + "defaultMessage": "Minimálna dĺžka je {min} znak{min, plural, one {} few {y} other {ov}}" + }, + "error.minimum": { + "defaultMessage": "Minimum je {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Heslá sa musia zhodovať" + }, + "error.required": { + "defaultMessage": "Toto pole je povinné" + }, + "expires.on": { + "defaultMessage": "Platnosť do: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Forknite ma na GitHube" + }, + "host.flags.block-exploits": { + "defaultMessage": "Blokovať bežné exploity" + }, + "host.flags.cache-assets": { + "defaultMessage": "Uložiť zdroje do vyrovnávacej pamäte" + }, + "host.flags.preserve-path": { + "defaultMessage": "Zachovať cestu" + }, + "host.flags.protocols": { + "defaultMessage": "Protokoly" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Podpora WebSockets" + }, + "host.forward-port": { + "defaultMessage": "Port presmerovania" + }, + "host.forward-scheme": { + "defaultMessage": "Schéma" + }, + "hosts": { + "defaultMessage": "Hostitelia" + }, + "http-only": { + "defaultMessage": "Len HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt cez DNS" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt cez HTTP" + }, + "loading": { + "defaultMessage": "Načítava sa…" + }, + "login.2fa-code": { + "defaultMessage": "Overovací kód" + }, + "login.2fa-code-placeholder": { + "defaultMessage": "Vložiť kód" + }, + "login.2fa-description": { + "defaultMessage": "Vložte kód z vašej overovacej aplikácie" + }, + "login.2fa-title": { + "defaultMessage": "Dvoj-faktorové overenie" + }, + "login.2fa-verify": { + "defaultMessage": "Overiť" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Prihláste sa do svojho účtu" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Vlastná Nginx konfigurácia" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Zadajte vlastnú Nginx konfiguráciu na vlastné riziko!" + }, + "no-permission-error": { + "defaultMessage": "Nemáte oprávnenie na zobrazenie tohto obsahu." + }, + "notfound.action": { + "defaultMessage": "Späť na hlavnú stránku" + }, + "notfound.content": { + "defaultMessage": "Ľutujeme, stránka, ktorú hľadáte, nebola nájdená" + }, + "notfound.title": { + "defaultMessage": "Ups… Našli ste chybovú stránku" + }, + "notification.error": { + "defaultMessage": "Chyba" + }, + "notification.object-deleted": { + "defaultMessage": "{object} bol odstránený" + }, + "notification.object-disabled": { + "defaultMessage": "{object} bol deaktivovaný" + }, + "notification.object-enabled": { + "defaultMessage": "{object} bol aktivovaný" + }, + "notification.object-renewed": { + "defaultMessage": "{object} bol obnovený" + }, + "notification.object-saved": { + "defaultMessage": "{object} bol uložený" + }, + "notification.success": { + "defaultMessage": "Úspech" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Pridať {object}" + }, + "object.delete": { + "defaultMessage": "Vymazať {object}" + }, + "object.delete.content": { + "defaultMessage": "Naozaj chcete vymazať tento {object}?" + }, + "object.edit": { + "defaultMessage": "Upraviť {object}" + }, + "object.empty": { + "defaultMessage": "Nie sú {objects}" + }, + "object.event.created": { + "defaultMessage": "Vytvorený {object}" + }, + "object.event.deleted": { + "defaultMessage": "Vymazaný {object}" + }, + "object.event.disabled": { + "defaultMessage": "Deaktivovaný {object}" + }, + "object.event.enabled": { + "defaultMessage": "Aktivovaný {object}" + }, + "object.event.renewed": { + "defaultMessage": "Obnovený {object}" + }, + "object.event.updated": { + "defaultMessage": "Aktualizovaný {object}" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Možnosti" + }, + "password": { + "defaultMessage": "Heslo" + }, + "password.generate": { + "defaultMessage": "Vygenerovať náhodné heslo" + }, + "password.hide": { + "defaultMessage": "Skryť heslo" + }, + "password.show": { + "defaultMessage": "Zobraziť heslo" + }, + "permissions.hidden": { + "defaultMessage": "Skryté" + }, + "permissions.manage": { + "defaultMessage": "Spravovať" + }, + "permissions.view": { + "defaultMessage": "Len na zobrazenie" + }, + "permissions.visibility.all": { + "defaultMessage": "Všetky položky" + }, + "permissions.visibility.title": { + "defaultMessage": "Viditeľnosť položky" + }, + "permissions.visibility.user": { + "defaultMessage": "Len vytvorené položky" + }, + "proxy-host": { + "defaultMessage": "proxy hostiteľa" + }, + "proxy-host.forward-host": { + "defaultMessage": "Cieľový názov hostiteľa / IP" + }, + "proxy-hosts": { + "defaultMessage": "Proxy hostitelia" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {proxy hostiteľ} few {proxy hostitelia} other {proxy hostiteľov}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Verejné" + }, + "redirection-host": { + "defaultMessage": "presmerovacieho hostiteľa" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Cieľová doména" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP kód" + }, + "redirection-hosts": { + "defaultMessage": "Presmerovací hostitelia" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {presmerovací hostiteľ} few {presmerovací hostitelia} other {presmerovacích hostiteľov}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Viacero možností" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Trvalo presunuté" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Dočasne presunuté" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Pozrieť iné" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Dočasné presmerovanie" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Trvalé presmerovanie" + }, + "role.admin": { + "defaultMessage": "Administrátor" + }, + "role.standard-user": { + "defaultMessage": "Bežný používateľ" + }, + "save": { + "defaultMessage": "Uložiť" + }, + "setting": { + "defaultMessage": "Nastavenie" + }, + "settings": { + "defaultMessage": "Nastavenia" + }, + "settings.default-site": { + "defaultMessage": "Predvolená stránka" + }, + "settings.default-site.404": { + "defaultMessage": "Stránka 404" + }, + "settings.default-site.444": { + "defaultMessage": "Bez odpovede (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Gratulačná stránka" + }, + "settings.default-site.description": { + "defaultMessage": "Čo zobraziť, keď Nginx zachytí neznámeho hostiteľa" + }, + "settings.default-site.html": { + "defaultMessage": "Vlastné HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Presmerovať" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Začnite vytvorením administrátorského účtu." + }, + "setup.title": { + "defaultMessage": "Vitajte!" + }, + "sign-in": { + "defaultMessage": "Prihlásiť sa" + }, + "ssl-certificate": { + "defaultMessage": "SSL certifikát" + }, + "stream": { + "defaultMessage": "stream" + }, + "stream.forward-host": { + "defaultMessage": "Cieľový hostiteľ" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "napriklad.sk alebo 10.0.0.1 alebo 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "Vstupný port" + }, + "streams": { + "defaultMessage": "Streamy" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {stream} few {streamy} other {streamov}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Test" + }, + "update-available": { + "defaultMessage": "Dostupná aktualizácia: {latestVersion}" + }, + "user": { + "defaultMessage": "používateľa" + }, + "user.change-password": { + "defaultMessage": "Zmeniť heslo" + }, + "user.confirm-password": { + "defaultMessage": "Potvrdiť heslo" + }, + "user.current-password": { + "defaultMessage": "Aktuálne heslo" + }, + "user.edit-profile": { + "defaultMessage": "Upraviť profil" + }, + "user.full-name": { + "defaultMessage": "Celé meno" + }, + "user.login-as": { + "defaultMessage": "Prihlásiť sa ako {name}" + }, + "user.logout": { + "defaultMessage": "Odhlásiť sa" + }, + "user.new-password": { + "defaultMessage": "Nové heslo" + }, + "user.nickname": { + "defaultMessage": "Prezývka" + }, + "user.set-password": { + "defaultMessage": "Nastaviť heslo" + }, + "user.set-permissions": { + "defaultMessage": "Nastaviť oprávnenia pre {name}" + }, + "user.switch-dark": { + "defaultMessage": "Prepnúť na tmavý režim" + }, + "user.switch-light": { + "defaultMessage": "Prepnúť na svetlý režim" + }, + "user.two-factor": { + "defaultMessage": "Dvojfakt. overenie" + }, + "username": { + "defaultMessage": "Používateľské meno" + }, + "users": { + "defaultMessage": "Používatelia" + } +} diff --git a/frontend/src/locale/src/tr.json b/frontend/src/locale/src/tr.json new file mode 100644 index 0000000000..5d6241004f --- /dev/null +++ b/frontend/src/locale/src/tr.json @@ -0,0 +1,797 @@ +{ + "access-list": { + "defaultMessage": "Erişim Listesi" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Kural} other {Kural}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {Kullanıcı} other {Kullanıcı}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "En az 1 kural mevcut olduğunda, bu tümünü reddet kuralı en son eklenir" + }, + "access-list.help.rules-order": { + "defaultMessage": "İzin ver ve reddet direktiflerinin tanımlandıkları sırayla uygulanacağını unutmayın." + }, + "access-list.pass-auth": { + "defaultMessage": "Kimlik Doğrulamayı Yukarı Akışa İlet" + }, + "access-list.public": { + "defaultMessage": "Herkese Açık" + }, + "access-list.public.subtitle": { + "defaultMessage": "Temel kimlik doğrulama gerekmez" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 veya 192.168.1.0/24 veya 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "Herhangi Birini Karşıla" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {Kullanıcı} other {Kullanıcı}}, {rules} {rules, plural, one {Kural} other {Kural}} - Oluşturuldu: {date}" + }, + "access-lists": { + "defaultMessage": "Erişim Listeleri" + }, + "action.add": { + "defaultMessage": "Ekle" + }, + "action.add-location": { + "defaultMessage": "Konum Ekle" + }, + "action.allow": { + "defaultMessage": "İzin Ver" + }, + "action.close": { + "defaultMessage": "Kapat" + }, + "action.delete": { + "defaultMessage": "Sil" + }, + "action.deny": { + "defaultMessage": "Reddet" + }, + "action.disable": { + "defaultMessage": "Devre Dışı Bırak" + }, + "action.download": { + "defaultMessage": "İndir" + }, + "action.edit": { + "defaultMessage": "Düzenle" + }, + "action.enable": { + "defaultMessage": "Etkinleştir" + }, + "action.permissions": { + "defaultMessage": "İzinler" + }, + "action.renew": { + "defaultMessage": "Yenile" + }, + "action.view-details": { + "defaultMessage": "Detayları Görüntüle" + }, + "auditlogs": { + "defaultMessage": "Denetim Kayıtları" + }, + "auto": { + "defaultMessage": "Otomatik" + }, + "cancel": { + "defaultMessage": "İptal" + }, + "certificate": { + "defaultMessage": "Sertifika" + }, + "certificate.custom-certificate": { + "defaultMessage": "Sertifika" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Sertifika Anahtarı" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Ara Sertifika" + }, + "certificate.in-use": { + "defaultMessage": "Kullanımda" + }, + "certificate.none.subtitle": { + "defaultMessage": "Sertifika atanmamış" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Bu host HTTPS kullanmayacak" + }, + "certificate.none.title": { + "defaultMessage": "Yok" + }, + "certificate.not-in-use": { + "defaultMessage": "Kullanılmıyor" + }, + "certificate.renew": { + "defaultMessage": "Sertifikayı Yenile" + }, + "certificates": { + "defaultMessage": "Sertifikalar" + }, + "certificates.custom": { + "defaultMessage": "Özel Sertifika" + }, + "certificates.custom.warning": { + "defaultMessage": "Parola ile korumalı anahtar dosyaları desteklenmiyor." + }, + "certificates.dns.credentials": { + "defaultMessage": "Kimlik Bilgileri Dosya İçeriği" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Bu eklenti, sağlayıcınız için bir API token'ı veya diğer kimlik bilgilerini içeren bir yapılandırma dosyası gerektirir" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Bu veriler veritabanında ve bir dosyada düz metin olarak saklanacak!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Yayılma Saniyesi" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Eklentinin varsayılan değerini kullanmak için boş bırakın. DNS yayılması için beklenilecek saniye sayısı." + }, + "certificates.dns.provider": { + "defaultMessage": "DNS Sağlayıcı" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Bir Sağlayıcı Seçin..." + }, + "certificates.dns.warning": { + "defaultMessage": "Bu bölüm Certbot ve DNS eklentileri hakkında bazı bilgiler gerektirir. Lütfen ilgili eklenti dokümantasyonuna bakın." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Bu alan adında bir sunucu bulundu ancak Nginx Proxy Manager gibi görünmüyor. Lütfen alan adınızın NPM örneğinizin çalıştığı IP'ye işaret ettiğinden emin olun." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "site24x7.com ile iletişim hatası nedeniyle erişilebilirlik kontrolü başarısız oldu." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Bu alan adında kullanılabilir bir sunucu yok. Lütfen alan adınızın mevcut olduğundan ve NPM örneğinizin çalıştığı IP'ye işaret ettiğinden ve gerekirse yönlendiricinizde 80 portunun yönlendirildiğinden emin olun." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Sunucunuz erişilebilir ve sertifika oluşturma mümkün olmalı." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Bu alan adında bir sunucu bulundu ancak beklenmeyen bir durum kodu döndürdü {code}. Bu NPM sunucusu mu? Lütfen alan adınızın NPM örneğinizin çalıştığı IP'ye işaret ettiğinden emin olun." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Bu alan adında bir sunucu bulundu ancak beklenmeyen veri döndürdü. Bu NPM sunucusu mu? Lütfen alan adınızın NPM örneğinizin çalıştığı IP'ye işaret ettiğinden emin olun." + }, + "certificates.http.test-results": { + "defaultMessage": "Test Sonuçları" + }, + "certificates.http.warning": { + "defaultMessage": "Bu alan adları zaten bu kuruluma işaret edecek şekilde yapılandırılmış olmalıdır." + }, + "certificates.request.subtitle": { + "defaultMessage": "Let's Encrypt ile" + }, + "certificates.request.title": { + "defaultMessage": "Yeni Sertifika İste" + }, + "column.access": { + "defaultMessage": "Erişim" + }, + "column.authorization": { + "defaultMessage": "Yetkilendirme" + }, + "column.authorizations": { + "defaultMessage": "Yetkilendirmeler" + }, + "column.custom-locations": { + "defaultMessage": "Özel Konumlar" + }, + "column.destination": { + "defaultMessage": "Hedef" + }, + "column.details": { + "defaultMessage": "Detaylar" + }, + "column.email": { + "defaultMessage": "E-posta" + }, + "column.event": { + "defaultMessage": "Olay" + }, + "column.expires": { + "defaultMessage": "Sona Erer" + }, + "column.http-code": { + "defaultMessage": "HTTP Kodu" + }, + "column.incoming-port": { + "defaultMessage": "Gelen Port" + }, + "column.name": { + "defaultMessage": "Ad" + }, + "column.protocol": { + "defaultMessage": "Protokol" + }, + "column.provider": { + "defaultMessage": "Sağlayıcı" + }, + "column.roles": { + "defaultMessage": "Roller" + }, + "column.rules": { + "defaultMessage": "Kurallar" + }, + "column.satisfy": { + "defaultMessage": "Karşıla" + }, + "column.satisfy-all": { + "defaultMessage": "Tümü" + }, + "column.satisfy-any": { + "defaultMessage": "Herhangi Biri" + }, + "column.scheme": { + "defaultMessage": "Şema" + }, + "column.source": { + "defaultMessage": "Kaynak" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Durum" + }, + "created-on": { + "defaultMessage": "Oluşturuldu: {date}" + }, + "dashboard": { + "defaultMessage": "Kontrol Paneli" + }, + "dead-host": { + "defaultMessage": "404 Host" + }, + "dead-hosts": { + "defaultMessage": "404 Host'lar" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {404 Host} other {404 Host}}" + }, + "disabled": { + "defaultMessage": "Devre Dışı" + }, + "domain-names": { + "defaultMessage": "Alan Adları" + }, + "domain-names.max": { + "defaultMessage": "Maksimum {count} alan adı" + }, + "domain-names.placeholder": { + "defaultMessage": "Alan adı eklemek için yazmaya başlayın..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Bu tür için joker karakterler izin verilmez" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Bu CA için joker karakterler desteklenmiyor" + }, + "domains.force-ssl": { + "defaultMessage": "SSL'i Zorla" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS Etkin" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS Alt Alan Adları" + }, + "domains.http2-support": { + "defaultMessage": "HTTP/2 Desteği" + }, + "domains.use-dns": { + "defaultMessage": "DNS Challenge Kullan" + }, + "email-address": { + "defaultMessage": "E-posta adresi" + }, + "empty-search": { + "defaultMessage": "Sonuç bulunamadı" + }, + "empty-subtitle": { + "defaultMessage": "Neden bir tane oluşturmuyorsunuz?" + }, + "enabled": { + "defaultMessage": "Etkin" + }, + "error.access.at-least-one": { + "defaultMessage": "Ya bir Yetkilendirme ya da bir Erişim Kuralı gereklidir" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Yetkilendirme Kullanıcı Adları benzersiz olmalıdır" + }, + "error.invalid-auth": { + "defaultMessage": "Geçersiz e-posta veya şifre" + }, + "error.invalid-domain": { + "defaultMessage": "Geçersiz alan adı: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Geçersiz e-posta adresi" + }, + "error.max-character-length": { + "defaultMessage": "Maksimum uzunluk {max} karakter{max, plural, one {} other {}}" + }, + "error.max-domains": { + "defaultMessage": "Çok fazla alan adı, maksimum {max}" + }, + "error.maximum": { + "defaultMessage": "Maksimum {max}" + }, + "error.min-character-length": { + "defaultMessage": "Minimum uzunluk {min} karakter{min, plural, one {} other {}}" + }, + "error.minimum": { + "defaultMessage": "Minimum {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Şifreler eşleşmelidir" + }, + "error.required": { + "defaultMessage": "Bu gereklidir" + }, + "expires.on": { + "defaultMessage": "Sona Erer: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Github'da Fork Yap" + }, + "host.flags.block-exploits": { + "defaultMessage": "Yaygın Saldırıları Engelle" + }, + "host.flags.cache-assets": { + "defaultMessage": "Varlıkları Önbelleğe Al" + }, + "host.flags.preserve-path": { + "defaultMessage": "Yolu Koru" + }, + "host.flags.protocols": { + "defaultMessage": "Protokoller" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Websockets Desteği" + }, + "host.forward-port": { + "defaultMessage": "İletme Portu" + }, + "host.forward-scheme": { + "defaultMessage": "Şema" + }, + "hosts": { + "defaultMessage": "Host'lar" + }, + "http-only": { + "defaultMessage": "Sadece HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "DNS ile Let's Encrypt" + }, + "lets-encrypt-via-http": { + "defaultMessage": "HTTP ile Let's Encrypt" + }, + "loading": { + "defaultMessage": "Yükleniyor…" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Hesabınıza giriş yapın" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Özel Nginx Yapılandırması" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Kendi riskinizle özel Nginx yapılandırmanızı buraya girin!" + }, + "no-permission-error": { + "defaultMessage": "Bunu görüntüleme erişiminiz yok." + }, + "notfound.action": { + "defaultMessage": "Ana sayfaya götür" + }, + "notfound.content": { + "defaultMessage": "Üzgünüz, aradığınız sayfa bulunamadı" + }, + "notfound.title": { + "defaultMessage": "Hata… Bir hata sayfası buldunuz" + }, + "notification.error": { + "defaultMessage": "Hata" + }, + "notification.object-deleted": { + "defaultMessage": "{object} silindi" + }, + "notification.object-disabled": { + "defaultMessage": "{object} devre dışı bırakıldı" + }, + "notification.object-enabled": { + "defaultMessage": "{object} etkinleştirildi" + }, + "notification.object-renewed": { + "defaultMessage": "{object} yenilendi" + }, + "notification.object-saved": { + "defaultMessage": "{object} kaydedildi" + }, + "notification.success": { + "defaultMessage": "Başarılı" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "{object} Ekle" + }, + "object.delete": { + "defaultMessage": "{object} Sil" + }, + "object.delete.content": { + "defaultMessage": "Bu {object} öğesini silmek istediğinizden emin misiniz?" + }, + "object.edit": { + "defaultMessage": "{object} Düzenle" + }, + "object.empty": { + "defaultMessage": "Hiç {objects} yok" + }, + "object.event.created": { + "defaultMessage": "{object} oluşturuldu" + }, + "object.event.deleted": { + "defaultMessage": "{object} silindi" + }, + "object.event.disabled": { + "defaultMessage": "{object} devre dışı bırakıldı" + }, + "object.event.enabled": { + "defaultMessage": "{object} etkinleştirildi" + }, + "object.event.renewed": { + "defaultMessage": "{object} yenilendi" + }, + "object.event.updated": { + "defaultMessage": "{object} güncellendi" + }, + "offline": { + "defaultMessage": "Çevrimdışı" + }, + "online": { + "defaultMessage": "Çevrimiçi" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Seçenekler" + }, + "password": { + "defaultMessage": "Şifre" + }, + "password.generate": { + "defaultMessage": "Rastgele şifre oluştur" + }, + "password.hide": { + "defaultMessage": "Şifreyi Gizle" + }, + "password.show": { + "defaultMessage": "Şifreyi Göster" + }, + "permissions.hidden": { + "defaultMessage": "Gizli" + }, + "permissions.manage": { + "defaultMessage": "Yönet" + }, + "permissions.view": { + "defaultMessage": "Sadece Görüntüle" + }, + "permissions.visibility.all": { + "defaultMessage": "Tüm Öğeler" + }, + "permissions.visibility.title": { + "defaultMessage": "Öğe Görünürlüğü" + }, + "permissions.visibility.user": { + "defaultMessage": "Sadece Oluşturulan Öğeler" + }, + "proxy-host": { + "defaultMessage": "Proxy Host" + }, + "proxy-host.forward-host": { + "defaultMessage": "İletme Host Adı / IP" + }, + "proxy-hosts": { + "defaultMessage": "Proxy Host'lar" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Proxy Host} other {Proxy Host}}" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Herkese Açık" + }, + "redirection-host": { + "defaultMessage": "Yönlendirme Host'u" + }, + "redirection-host.forward-domain": { + "defaultMessage": "İletme Alan Adı" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP Kodu" + }, + "redirection-hosts": { + "defaultMessage": "Yönlendirme Host'ları" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Yönlendirme Host'u} other {Yönlendirme Host'u}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Çoklu Seçenek" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Kalıcı olarak taşındı" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Geçici olarak taşındı" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Diğerini gör" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Geçici yönlendirme" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Kalıcı yönlendirme" + }, + "role.admin": { + "defaultMessage": "Yönetici" + }, + "role.standard-user": { + "defaultMessage": "Standart Kullanıcı" + }, + "save": { + "defaultMessage": "Kaydet" + }, + "setting": { + "defaultMessage": "Ayar" + }, + "settings": { + "defaultMessage": "Ayarlar" + }, + "settings.default-site": { + "defaultMessage": "Varsayılan Site" + }, + "settings.default-site.404": { + "defaultMessage": "404 Sayfası" + }, + "settings.default-site.444": { + "defaultMessage": "Yanıt Yok (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Tebrikler Sayfası" + }, + "settings.default-site.description": { + "defaultMessage": "Nginx bilinmeyen bir Host ile karşılaştığında ne gösterilecek" + }, + "settings.default-site.html": { + "defaultMessage": "Özel HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Yönlendir" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Yönetici hesabınızı oluşturarak başlayın." + }, + "setup.title": { + "defaultMessage": "Hoş Geldiniz!" + }, + "sign-in": { + "defaultMessage": "Giriş yap" + }, + "ssl-certificate": { + "defaultMessage": "SSL Sertifikası" + }, + "stream": { + "defaultMessage": "Akış" + }, + "stream.forward-host": { + "defaultMessage": "İletme Host'u" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "example.com veya 10.0.0.1 veya 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "Gelen Port" + }, + "streams": { + "defaultMessage": "Akışlar" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Akış} other {Akış}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Test" + }, + "update-available": { + "defaultMessage": "Güncelleme Mevcut: {latestVersion}" + }, + "user": { + "defaultMessage": "Kullanıcı" + }, + "user.change-password": { + "defaultMessage": "Şifreyi Değiştir" + }, + "user.confirm-password": { + "defaultMessage": "Şifreyi Onayla" + }, + "user.current-password": { + "defaultMessage": "Mevcut Şifre" + }, + "user.edit-profile": { + "defaultMessage": "Profili Düzenle" + }, + "user.full-name": { + "defaultMessage": "Ad Soyad" + }, + "user.login-as": { + "defaultMessage": "{name} olarak giriş yap" + }, + "user.logout": { + "defaultMessage": "Çıkış Yap" + }, + "user.new-password": { + "defaultMessage": "Yeni Şifre" + }, + "user.nickname": { + "defaultMessage": "Takma Ad" + }, + "user.set-password": { + "defaultMessage": "Şifre Belirle" + }, + "user.set-permissions": { + "defaultMessage": "{name} için İzinleri Belirle" + }, + "user.switch-dark": { + "defaultMessage": "Karanlık moda geç" + }, + "user.switch-light": { + "defaultMessage": "Açık moda geç" + }, + "username": { + "defaultMessage": "Kullanıcı Adı" + }, + "users": { + "defaultMessage": "Kullanıcılar" + } +} diff --git a/frontend/src/locale/src/vi.json b/frontend/src/locale/src/vi.json new file mode 100644 index 0000000000..4701a7cc70 --- /dev/null +++ b/frontend/src/locale/src/vi.json @@ -0,0 +1,773 @@ +{ + "access-list": { + "defaultMessage": "Danh sách truy cập" + }, + "access-list.access-count": { + "defaultMessage": "{count} quy tắc" + }, + "access-list.auth-count": { + "defaultMessage": "{count} người dùng" + }, + "access-list.help-rules-last": { + "defaultMessage": "Quy tắc từ chối tất cả này sẽ được thêm vào cuối khi tồn tại ít nhất 1 quy tắc" + }, + "access-list.help.rules-order": { + "defaultMessage": "Các quy tắc cho phép và từ chối sẽ được thực thi theo thứ tự được xác định." + }, + "access-list.pass-auth": { + "defaultMessage": "Chuyển xác thực lên thượng nguồn" + }, + "access-list.public": { + "defaultMessage": "Có thể truy cập công khai" + }, + "access-list.public.subtitle": { + "defaultMessage": "Không cần xác thực cơ bản" + }, + "access-list.satisfy-any": { + "defaultMessage": "Thỏa mãn điều kiện bất kỳ" + }, + "access-list.subtitle": { + "defaultMessage": "{users} người dùng, {rules} quy tắc - Tạo lúc: {date}" + }, + "access-lists": { + "defaultMessage": "Danh sách truy cập" + }, + "action.add": { + "defaultMessage": "Thêm" + }, + "action.add-location": { + "defaultMessage": "Thêm Vị trí" + }, + "action.close": { + "defaultMessage": "Đóng" + }, + "action.delete": { + "defaultMessage": "Xóa" + }, + "action.disable": { + "defaultMessage": "Tắt" + }, + "action.download": { + "defaultMessage": "Tải xuống" + }, + "action.edit": { + "defaultMessage": "Chỉnh sửa" + }, + "action.enable": { + "defaultMessage": "Bật" + }, + "action.permissions": { + "defaultMessage": "Quyền" + }, + "action.renew": { + "defaultMessage": "Gia hạn" + }, + "action.view-details": { + "defaultMessage": "Xem Chi tiết" + }, + "auditlogs": { + "defaultMessage": "Nhật ký kiểm tra" + }, + "cancel": { + "defaultMessage": "Hủy" + }, + "certificate": { + "defaultMessage": "Chứng chỉ" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certificate (crt)" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Certificate Key" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Intermediate Certificate" + }, + "certificate.in-use": { + "defaultMessage": "Đang sử dụng" + }, + "certificate.none.subtitle": { + "defaultMessage": "Không có chứng chỉ nào được chỉ định" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Máy chủ này sẽ không sử dụng HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Không có" + }, + "certificate.not-in-use": { + "defaultMessage": "Không được dùng" + }, + "certificate.renew": { + "defaultMessage": "Gia hạn Chứng chỉ" + }, + "certificates": { + "defaultMessage": "Danh sách chứng chỉ" + }, + "certificates.custom": { + "defaultMessage": "Chứng chỉ tùy chỉnh" + }, + "certificates.custom.warning": { + "defaultMessage": "Các tệp chính được bảo vệ bằng cụm mật khẩu không được hỗ trợ." + }, + "certificates.dns.credentials": { + "defaultMessage": "Nội dung tệp thông tin xác thực" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Plugin này yêu cầu tệp cấu hình chứa mã thông báo API hoặc thông tin xác thực khác cho nhà cung cấp của bạn" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Dữ liệu này sẽ được lưu trữ dưới dạng bản rõ trong cơ sở dữ liệu và trong một tệp!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Thời gian lan truyền (Giây)" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Để trống để sử dụng giá trị mặc định của plugin. Số giây chờ truyền DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "Nhà cung cấp DNS" + }, + "certificates.dns.warning": { + "defaultMessage": "Phần này yêu cầu một số kiến thức về Certbot và các plugin DNS của nó. Vui lòng tham khảo tài liệu plugin tương ứng." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Có một máy chủ được tìm thấy ở miền này nhưng có vẻ như nó không phải là NPM. Vui lòng đảm bảo tên miền của bạn trỏ đến IP nơi phiên bản NPM của bạn đang chạy." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Không thể kiểm tra khả năng truy cập do lỗi giao tiếp với site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Không có máy chủ có sẵn tại tên miền này. Vui lòng đảm bảo rằng miền của bạn tồn tại và trỏ đến IP nơi phiên bản NPM của bạn đang chạy và nếu cần, cổng 80 sẽ được chuyển tiếp trong bộ định tuyến của bạn." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Máy chủ của bạn có thể truy cập được và có thể tạo chứng chỉ." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Có một máy chủ được tìm thấy ở miền này nhưng nó trả về mã trạng thái không mong muốn {code}. Đây có phải là máy chủ NPM không? Vui lòng đảm bảo tên miền của bạn trỏ đến IP nơi phiên bản NPM của bạn đang chạy." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Có một máy chủ được tìm thấy ở miền này nhưng nó trả về một dữ liệu không mong muốn. Đây có phải là máy chủ NPM không? Vui lòng đảm bảo tên miền của bạn trỏ đến IP nơi phiên bản NPM của bạn đang chạy." + }, + "certificates.http.test-results": { + "defaultMessage": "Kết quả kiểm tra" + }, + "certificates.http.warning": { + "defaultMessage": "Các miền này phải được cấu hình sẵn để trỏ đến cài đặt này." + }, + "certificates.key-type": { + "defaultMessage": "Loại khóa" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA tương thích rộng rãi, ECDSA nhanh hơn và an toàn hơn nhưng có thể không được hỗ trợ bởi các hệ thống cũ" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "bằng Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Yêu cầu chứng chỉ mới" + }, + "column.access": { + "defaultMessage": "Truy cập" + }, + "column.authorization": { + "defaultMessage": "Ủy quyền" + }, + "column.authorizations": { + "defaultMessage": "Danh sách ủy quyền" + }, + "column.custom-locations": { + "defaultMessage": "Quy tắc đường dẫn tùy chỉnh (Vị trí)" + }, + "column.destination": { + "defaultMessage": "Mục tiêu" + }, + "column.details": { + "defaultMessage": "Chi tiết" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.event": { + "defaultMessage": "Sự kiện" + }, + "column.expires": { + "defaultMessage": "Hết hạn" + }, + "column.http-code": { + "defaultMessage": "HTTP Code" + }, + "column.incoming-port": { + "defaultMessage": "Cổng đến" + }, + "column.name": { + "defaultMessage": "Tên" + }, + "column.protocol": { + "defaultMessage": "Giao thức" + }, + "column.provider": { + "defaultMessage": "Nhà cung cấp" + }, + "column.roles": { + "defaultMessage": "Vai trò" + }, + "column.rules": { + "defaultMessage": "Quy tắc" + }, + "column.satisfy": { + "defaultMessage": "Thỏa mãn" + }, + "column.satisfy-all": { + "defaultMessage": "Tất cả" + }, + "column.satisfy-any": { + "defaultMessage": "Bất kì" + }, + "column.scheme": { + "defaultMessage": "Scheme" + }, + "column.source": { + "defaultMessage": "Nguồn" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Trạng thái" + }, + "created-on": { + "defaultMessage": "Đã tạo: {date}" + }, + "dashboard": { + "defaultMessage": "Bảng điều khiển" + }, + "dead-host": { + "defaultMessage": "Máy chủ 404" + }, + "dead-hosts": { + "defaultMessage": "Máy chủ 404" + }, + "dead-hosts.count": { + "defaultMessage": "Số trang lỗi {count}" + }, + "disabled": { + "defaultMessage": "Đã tắt" + }, + "domain-names": { + "defaultMessage": "Danh sách tên miền" + }, + "domain-names.max": { + "defaultMessage": "Tối đa {count} tên miền" + }, + "domain-names.placeholder": { + "defaultMessage": "Nhập tên miền vào đây..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Ký tự đại diện không được phép cho loại này" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Ký tự đại diện không được hỗ trợ cho CA này" + }, + "domains.force-ssl": { + "defaultMessage": "Bắt buộc SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "Bật HSTS" + }, + "domains.hsts-subdomains": { + "defaultMessage": "Tên miền phụ HSTS" + }, + "domains.http2-support": { + "defaultMessage": "Hỗ trợ HTTP/2" + }, + "domains.use-dns": { + "defaultMessage": "Dùng thử thách DNS" + }, + "email-address": { + "defaultMessage": "Địa chỉ email" + }, + "empty-search": { + "defaultMessage": "Không có kết quả nào" + }, + "empty-subtitle": { + "defaultMessage": "Tại sao bạn không tạo một cái luôn?" + }, + "enabled": { + "defaultMessage": "Đã bật" + }, + "error.access.at-least-one": { + "defaultMessage": "Yêu cầu ít nhất một quy tắc ủy quyền hoặc truy cập" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Tên người dùng được ủy quyền phải là duy nhất" + }, + "error.invalid-auth": { + "defaultMessage": "Email hoặc Mật khẩu không hợp lệ" + }, + "error.invalid-domain": { + "defaultMessage": "Tên miền không hợp lệ: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Địa chỉ email không hợp lệ" + }, + "error.max-character-length": { + "defaultMessage": "Độ dài tối đa là {max} ký tự" + }, + "error.max-domains": { + "defaultMessage": "Quá nhiều tên miền, tối đa là {max}" + }, + "error.maximum": { + "defaultMessage": "Tối đa là {max}" + }, + "error.min-character-length": { + "defaultMessage": "Độ dài tối thiểu là {min} ký tự" + }, + "error.minimum": { + "defaultMessage": "Tối thiểu là {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "Mật khẩu phải khớp" + }, + "error.required": { + "defaultMessage": "Điều này là bắt buộc" + }, + "expires.on": { + "defaultMessage": "Hết hạn: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Fork dự án này trên Github" + }, + "host.flags.block-exploits": { + "defaultMessage": "Chặn các hoạt động khai thác phổ biến" + }, + "host.flags.cache-assets": { + "defaultMessage": "Cache tài nguyên" + }, + "host.flags.preserve-path": { + "defaultMessage": "Bảo toàn đường dẫn" + }, + "host.flags.protocols": { + "defaultMessage": "Giao thức" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Hỗ trợ Websockets" + }, + "host.forward-port": { + "defaultMessage": "Chuyển tiếp cổng" + }, + "host.forward-scheme": { + "defaultMessage": "Scheme" + }, + "hosts": { + "defaultMessage": "Máy chủ" + }, + "http-only": { + "defaultMessage": "HTTP Only" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt qua DNS" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt qua HTTP" + }, + "loading": { + "defaultMessage": "Đang tải..." + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "Đăng nhập vào tài khoản của bạn" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "Cấu hình Nginx tùy chỉnh" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Nhập cấu hình Nginx tùy chỉnh của bạn vào đây và bạn phải tự chịu rủi ro!" + }, + "no-permission-error": { + "defaultMessage": "Bạn không có quyền truy cập trang này." + }, + "notfound.action": { + "defaultMessage": "Về trang chủ" + }, + "notfound.content": { + "defaultMessage": "Chúng tôi xin lỗi nhưng trang bạn đang tìm kiếm không được tìm thấy" + }, + "notfound.title": { + "defaultMessage": "Rất tiếc… Bạn vừa tìm thấy một trang lỗi" + }, + "notification.error": { + "defaultMessage": "Lỗi" + }, + "notification.object-deleted": { + "defaultMessage": "{object} đã được xóa" + }, + "notification.object-disabled": { + "defaultMessage": "{object} đã được tắt" + }, + "notification.object-enabled": { + "defaultMessage": "{object} đã được bật" + }, + "notification.object-renewed": { + "defaultMessage": "{object} đã được làm mới" + }, + "notification.object-saved": { + "defaultMessage": "{object} đã được lưu" + }, + "notification.success": { + "defaultMessage": "Thành công" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Thêm {object}" + }, + "object.delete": { + "defaultMessage": "Xóa {object}" + }, + "object.delete.content": { + "defaultMessage": "Bạn có chắc muốn xóa {object} không?" + }, + "object.edit": { + "defaultMessage": "Chỉnh sửa {object}" + }, + "object.empty": { + "defaultMessage": "Không có {objects}" + }, + "object.event.created": { + "defaultMessage": "Đã tạo {object}" + }, + "object.event.deleted": { + "defaultMessage": "Đã xóa {object}" + }, + "object.event.disabled": { + "defaultMessage": "Đã tắt {object}" + }, + "object.event.enabled": { + "defaultMessage": "Đã bật {object}" + }, + "object.event.renewed": { + "defaultMessage": "Đã gia hạn {object}" + }, + "object.event.updated": { + "defaultMessage": "Đã cập nhật {object}" + }, + "offline": { + "defaultMessage": "Ngoại tuyến" + }, + "online": { + "defaultMessage": "Trực tuyến" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "Tùy chọn" + }, + "password": { + "defaultMessage": "Mật khẩu" + }, + "password.generate": { + "defaultMessage": "Tạo mật khẩu ngẫu nhiên" + }, + "password.hide": { + "defaultMessage": "Ẩn Mật khẩu" + }, + "password.show": { + "defaultMessage": "Hiện Mật khẩu" + }, + "permissions.hidden": { + "defaultMessage": "Ẩn" + }, + "permissions.manage": { + "defaultMessage": "Quản lý" + }, + "permissions.view": { + "defaultMessage": "Chỉ xem" + }, + "permissions.visibility.all": { + "defaultMessage": "Tất cả các mục" + }, + "permissions.visibility.title": { + "defaultMessage": "Khả năng hiển thị mục" + }, + "permissions.visibility.user": { + "defaultMessage": "Chỉ các mục đã tạo" + }, + "proxy-host": { + "defaultMessage": "Máy chủ proxy" + }, + "proxy-host.forward-host": { + "defaultMessage": "Chuyển tiếp Hostname / IP" + }, + "proxy-hosts": { + "defaultMessage": "Máy chủ proxy" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} máy chủ proxy" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "Công khai" + }, + "redirection-host": { + "defaultMessage": "Redirection Host" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Chuyển tiếp Tên miền" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP Code" + }, + "redirection-hosts": { + "defaultMessage": "Redirection Hosts" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} máy chủ chuyển hướng" + }, + "role.admin": { + "defaultMessage": "Quản trị viên" + }, + "role.standard-user": { + "defaultMessage": "Người dùng bình thường" + }, + "save": { + "defaultMessage": "Lưu" + }, + "setting": { + "defaultMessage": "Cài đặt" + }, + "settings": { + "defaultMessage": "Cài đặt" + }, + "settings.default-site": { + "defaultMessage": "Trang web mặc định" + }, + "settings.default-site.404": { + "defaultMessage": "Trang 404" + }, + "settings.default-site.444": { + "defaultMessage": "Không có phản hồi (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Trang chào mừng" + }, + "settings.default-site.description": { + "defaultMessage": "Hiển thị gì khi Nginx gặp phải Máy chủ không xác định" + }, + "settings.default-site.html": { + "defaultMessage": "HTML tùy chỉnh" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Chuyển hướng" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "Bắt đầu bằng cách tạo tài khoản quản trị viên." + }, + "setup.title": { + "defaultMessage": "Chào mừng!" + }, + "sign-in": { + "defaultMessage": "Đăng nhập" + }, + "ssl-certificate": { + "defaultMessage": "Chứng chỉ SSL" + }, + "stream": { + "defaultMessage": "Stream" + }, + "stream.forward-host": { + "defaultMessage": "Chuyển tiếp Host" + }, + "stream.incoming-port": { + "defaultMessage": "Cổng vào" + }, + "streams": { + "defaultMessage": "Danh sách các Stream" + }, + "streams.count": { + "defaultMessage": "Số Stream {count}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Kiểm tra" + }, + "update-available": { + "defaultMessage": "Cập nhật khả dụng: {latestVersion}" + }, + "user": { + "defaultMessage": "Người dùng" + }, + "user.change-password": { + "defaultMessage": "Đổi Mật khẩu" + }, + "user.confirm-password": { + "defaultMessage": "Xác nhận Mật khẩu" + }, + "user.current-password": { + "defaultMessage": "Mật khẩu hiện tại" + }, + "user.edit-profile": { + "defaultMessage": "Chỉnh sửa hồ sơ" + }, + "user.full-name": { + "defaultMessage": "Tên" + }, + "user.login-as": { + "defaultMessage": "Đăng nhập bằng {name}" + }, + "user.logout": { + "defaultMessage": "Đăng xuất" + }, + "user.new-password": { + "defaultMessage": "Mật khẩu mới" + }, + "user.nickname": { + "defaultMessage": "Tên hiển thị" + }, + "user.set-password": { + "defaultMessage": "Đặt Mật khẩu" + }, + "user.set-permissions": { + "defaultMessage": "Đặt quyền cho {name}" + }, + "user.switch-dark": { + "defaultMessage": "Chuyển sang chế độ tối" + }, + "user.switch-light": { + "defaultMessage": "Chuyển sang chế độ sáng" + }, + "username": { + "defaultMessage": "Tên người dùng" + }, + "users": { + "defaultMessage": "Danh sách người dùng" + } +} diff --git a/frontend/src/locale/src/zh.json b/frontend/src/locale/src/zh.json new file mode 100644 index 0000000000..8585abf262 --- /dev/null +++ b/frontend/src/locale/src/zh.json @@ -0,0 +1,776 @@ +{ + "access-list": { + "defaultMessage": "通信规则" + }, + "access-list.access-count": { + "defaultMessage": "{count} 条规则" + }, + "access-list.auth-count": { + "defaultMessage": "{count} 个用户" + }, + "access-list.help-rules-last": { + "defaultMessage": "当至少存在1条规则时,此拒绝所有规则将被添加到最后" + }, + "access-list.help.rules-order": { + "defaultMessage": " 允许 (allow) 和禁止 (deny) 规则将按照它们定义的顺序执行。" + }, + "access-list.pass-auth": { + "defaultMessage": "将认证传递给上游" + }, + "access-list.public": { + "defaultMessage": "公开可访问" + }, + "access-list.public.subtitle": { + "defaultMessage": "无需基本认证" + }, + "access-list.satisfy-any": { + "defaultMessage": "满足任意条件" + }, + "access-list.subtitle": { + "defaultMessage": "{users} 个用户, {rules} 条规则 - 创建时间: {date}" + }, + "access-lists": { + "defaultMessage": "通信规则" + }, + "action.add": { + "defaultMessage": "添加" + }, + "action.add-location": { + "defaultMessage": "添加路径规则(Location)" + }, + "action.close": { + "defaultMessage": "关闭" + }, + "action.delete": { + "defaultMessage": "删除" + }, + "action.disable": { + "defaultMessage": "禁用" + }, + "action.download": { + "defaultMessage": "下载" + }, + "action.edit": { + "defaultMessage": "编辑" + }, + "action.enable": { + "defaultMessage": "启用" + }, + "action.permissions": { + "defaultMessage": "权限" + }, + "action.renew": { + "defaultMessage": "续期" + }, + "action.view-details": { + "defaultMessage": "查看详情" + }, + "auditlogs": { + "defaultMessage": "审计日志" + }, + "cancel": { + "defaultMessage": "取消" + }, + "certificate": { + "defaultMessage": "证书" + }, + "certificate.custom-certificate": { + "defaultMessage": "证书" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "证书密钥" + }, + "certificate.custom-intermediate": { + "defaultMessage": "中间证书" + }, + "certificate.in-use": { + "defaultMessage": "使用中" + }, + "certificate.none.subtitle": { + "defaultMessage": "未分配证书" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "此主机将不使用 HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "无" + }, + "certificate.not-in-use": { + "defaultMessage": "未使用" + }, + "certificate.renew": { + "defaultMessage": "续期证书" + }, + "certificates": { + "defaultMessage": "证书列表" + }, + "certificates.custom": { + "defaultMessage": "自定义证书" + }, + "certificates.custom.warning": { + "defaultMessage": "不支持受密码保护的密钥文件。" + }, + "certificates.dns.credentials": { + "defaultMessage": "凭据文件内容" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "此插件需要一个包含 API 令牌或提供商其他凭证的配置文件" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "此数据将以明文形式存储在数据库和文件中!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "传播时间 (秒)" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "留空以使用插件默认值。等待DNS传播的秒数。" + }, + "certificates.dns.provider": { + "defaultMessage": "DNS 提供商" + }, + "certificates.dns.warning": { + "defaultMessage": "本节需要您具备一些关于 Certbot 及其 DNS 插件的知识,请参阅相应插件的官方文档。" + }, + "certificates.http.reachability-404": { + "defaultMessage": "在此域名下找到了一个服务器,但它似乎不是 Nginx 代理管理器。请确保您的域名指向 NPM 实例运行的 IP 地址。" + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "由于与site24x7.com通信错误,无法检查可达性。" + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "此域名下没有可用的服务器。请确保您的域名存在并指向NPM实例运行的 IP 地址,如有必要,请在路由器中转发 80 端口。" + }, + "certificates.http.reachability-ok": { + "defaultMessage": "您的服务器可以访问,应该可以创建证书。" + }, + "certificates.http.reachability-other": { + "defaultMessage": "在此域名下找到了一个服务器,但它返回了意外的状态码 {code}。它是 NPM 服务器吗?请确保您的域名指向NPM实例运行的 IP 地址。" + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "在此域名下找到了一个服务器,但它返回了意外的数据。它是 NPM 服务器吗?请确保您的域名指向 NPM 实例运行的 IP 地址。" + }, + "certificates.http.test-results": { + "defaultMessage": "测试结果" + }, + "certificates.http.warning": { + "defaultMessage": "这些域名必须配置为指向本设备。" + }, + "certificates.key-type": { + "defaultMessage": "密钥类型" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA 兼容性更好,ECDSA 更快更安全但旧系统可能不支持" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "使用 Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "申请新证书" + }, + "column.access": { + "defaultMessage": "访问" + }, + "column.authorization": { + "defaultMessage": "授权" + }, + "column.authorizations": { + "defaultMessage": "授权列表" + }, + "column.custom-locations": { + "defaultMessage": "自定义路径规则 (Locations)" + }, + "column.destination": { + "defaultMessage": "目标" + }, + "column.details": { + "defaultMessage": "详情" + }, + "column.email": { + "defaultMessage": "邮箱" + }, + "column.event": { + "defaultMessage": "事件" + }, + "column.expires": { + "defaultMessage": "过期时间" + }, + "column.http-code": { + "defaultMessage": "访问" + }, + "column.incoming-port": { + "defaultMessage": "入站端口" + }, + "column.name": { + "defaultMessage": "名称" + }, + "column.protocol": { + "defaultMessage": "协议" + }, + "column.provider": { + "defaultMessage": "提供商" + }, + "column.roles": { + "defaultMessage": "角色" + }, + "column.rules": { + "defaultMessage": "规则" + }, + "column.satisfy": { + "defaultMessage": "满足" + }, + "column.satisfy-all": { + "defaultMessage": "全部" + }, + "column.satisfy-any": { + "defaultMessage": "任意" + }, + "column.scheme": { + "defaultMessage": "协议" + }, + "column.source": { + "defaultMessage": "来源" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "状态" + }, + "created-on": { + "defaultMessage": "创建时间: {date}" + }, + "dashboard": { + "defaultMessage": "仪表板" + }, + "dead-host": { + "defaultMessage": "错误页面" + }, + "dead-hosts": { + "defaultMessage": "错误页面列表" + }, + "dead-hosts.count": { + "defaultMessage": "{count} 个错误页面列表" + }, + "disabled": { + "defaultMessage": "已禁用" + }, + "domain-names": { + "defaultMessage": "域名" + }, + "domain-names.max": { + "defaultMessage": "{count} 个最多域名数量" + }, + "domain-names.placeholder": { + "defaultMessage": "开始输入以添加域名..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "此类型不允许使用通配符" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "此 CA 不支持通配符" + }, + "domains.advanced": { + "defaultMessage": "高级选项" + }, + "domains.force-ssl": { + "defaultMessage": "强制 SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS 已启用" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS 子域名" + }, + "domains.http2-support": { + "defaultMessage": "HTTP/2 支持" + }, + "domains.trust-forwarded-proto": { + "defaultMessage": "信任上游代理传递的协议类型头" + }, + "domains.use-dns": { + "defaultMessage": "使用DNS验证" + }, + "email-address": { + "defaultMessage": "邮箱地址" + }, + "empty-search": { + "defaultMessage": "未找到结果" + }, + "empty-subtitle": { + "defaultMessage": "为什么不由您来创建一个呢?" + }, + "enabled": { + "defaultMessage": "已启用" + }, + "error.access.at-least-one": { + "defaultMessage": "需要至少一个授权或访问规则" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "授权用户名必须唯一" + }, + "error.invalid-auth": { + "defaultMessage": "无效的邮箱或密码" + }, + "error.invalid-domain": { + "defaultMessage": "无效的域名: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "无效的邮箱地址" + }, + "error.max-character-length": { + "defaultMessage": "最大长度为 {max} 个字符" + }, + "error.max-domains": { + "defaultMessage": "域名过多,最多为 {max} 个" + }, + "error.maximum": { + "defaultMessage": "最大值为 {max}" + }, + "error.min-character-length": { + "defaultMessage": "最小长度为 {min} 个字符" + }, + "error.minimum": { + "defaultMessage": "最小值为 {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "密码必须匹配" + }, + "error.required": { + "defaultMessage": "此项为必填项" + }, + "expires.on": { + "defaultMessage": "过期时间: {date}" + }, + "footer.github-fork": { + "defaultMessage": "在 Github 上复刻 (Fork) 本项目" + }, + "host.flags.block-exploits": { + "defaultMessage": "阻止常见攻击" + }, + "host.flags.cache-assets": { + "defaultMessage": "缓存资源" + }, + "host.flags.preserve-path": { + "defaultMessage": "保留路径" + }, + "host.flags.protocols": { + "defaultMessage": "协议" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Websockets 支持" + }, + "host.forward-port": { + "defaultMessage": "转发端口" + }, + "host.forward-scheme": { + "defaultMessage": "协议" + }, + "hosts": { + "defaultMessage": "主机列表" + }, + "http-only": { + "defaultMessage": "仅 HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt DNS 验证" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt HTTP 验证" + }, + "loading": { + "defaultMessage": "加载中···" + }, + "login.protected-by-open-appsec": { + "defaultMessage": "Protected by open-appsec" + }, + "login.title": { + "defaultMessage": "登录您的账户" + }, + "menu.openAppsecLog": { + "defaultMessage": "open-appsec Logs" + }, + "nginx-config.label": { + "defaultMessage": "自定义 Nginx 配置" + }, + "nginx-config.placeholder": { + "defaultMessage": "# 在此输入您的自定义 Nginx 配置,风险自负!" + }, + "no-permission-error": { + "defaultMessage": "您无权查看此内容。" + }, + "notfound.action": { + "defaultMessage": "返回首页" + }, + "notfound.content": { + "defaultMessage": "很抱歉,您要查找的页面未找到" + }, + "notfound.title": { + "defaultMessage": "糟糕...您刚刚找到了一个错误页面" + }, + "notification.error": { + "defaultMessage": "错误" + }, + "notification.object-deleted": { + "defaultMessage": "{object} 已被删除" + }, + "notification.object-disabled": { + "defaultMessage": "{object} 已被禁用" + }, + "notification.object-enabled": { + "defaultMessage": "{object} 已被启用" + }, + "notification.object-renewed": { + "defaultMessage": "{object} 已续期" + }, + "notification.object-saved": { + "defaultMessage": "{object} 已保存" + }, + "notification.success": { + "defaultMessage": "成功" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "添加 {object}" + }, + "object.delete": { + "defaultMessage": "删除 {object}" + }, + "object.delete.content": { + "defaultMessage": "您确定要删除 {object} 吗?" + }, + "object.edit": { + "defaultMessage": "编辑 {object}" + }, + "object.empty": { + "defaultMessage": "没有 {objects}" + }, + "object.event.created": { + "defaultMessage": "已创建 {object}" + }, + "object.event.deleted": { + "defaultMessage": "已删除 {object}" + }, + "object.event.disabled": { + "defaultMessage": "已禁用 {object}" + }, + "object.event.enabled": { + "defaultMessage": "已启用 {object}" + }, + "object.event.renewed": { + "defaultMessage": "已续期 {object}" + }, + "object.event.updated": { + "defaultMessage": "已更新 {object}" + }, + "offline": { + "defaultMessage": "离线" + }, + "online": { + "defaultMessage": "在线" + }, + "openappsec-log.col.action": { + "defaultMessage": "Action" + }, + "openappsec-log.col.details": { + "defaultMessage": "Details" + }, + "openappsec-log.col.event-name": { + "defaultMessage": "Event" + }, + "openappsec-log.col.host": { + "defaultMessage": "Host" + }, + "openappsec-log.col.incident-type": { + "defaultMessage": "Incident Type" + }, + "openappsec-log.col.matched-location": { + "defaultMessage": "Matched Location" + }, + "openappsec-log.col.matched-parameter": { + "defaultMessage": "Matched Parameter" + }, + "openappsec-log.col.matched-sample": { + "defaultMessage": "Matched Sample" + }, + "openappsec-log.col.method": { + "defaultMessage": "Method" + }, + "openappsec-log.col.service": { + "defaultMessage": "Service" + }, + "openappsec-log.col.severity": { + "defaultMessage": "Severity" + }, + "openappsec-log.col.source-ip": { + "defaultMessage": "Source IP" + }, + "openappsec-log.col.time": { + "defaultMessage": "Time" + }, + "openappsec-log.col.uri": { + "defaultMessage": "URI Path" + }, + "openappsec-log.details.raw": { + "defaultMessage": "Raw" + }, + "openappsec-log.details.title": { + "defaultMessage": "Log Entry Details" + }, + "openappsec-log.tab.all": { + "defaultMessage": "All" + }, + "openappsec-log.tab.detailed": { + "defaultMessage": "Detailed" + }, + "openappsec-log.tab.important": { + "defaultMessage": "Important" + }, + "openappsec-log.tab.notifications": { + "defaultMessage": "Notifications" + }, + "openappsec-log.title": { + "defaultMessage": "open-appsec Logs" + }, + "options": { + "defaultMessage": "选项" + }, + "password": { + "defaultMessage": "密码" + }, + "password.generate": { + "defaultMessage": "生成随机密码" + }, + "password.hide": { + "defaultMessage": "隐藏密码" + }, + "password.show": { + "defaultMessage": "显示密码" + }, + "permissions.hidden": { + "defaultMessage": "隐藏" + }, + "permissions.manage": { + "defaultMessage": "管理" + }, + "permissions.view": { + "defaultMessage": "仅查看" + }, + "permissions.visibility.all": { + "defaultMessage": "所有项目" + }, + "permissions.visibility.title": { + "defaultMessage": "项目可见性" + }, + "permissions.visibility.user": { + "defaultMessage": "仅创建的项目" + }, + "proxy-host": { + "defaultMessage": "代理服务" + }, + "proxy-host.forward-host": { + "defaultMessage": "转发主机名 / IP" + }, + "proxy-hosts": { + "defaultMessage": "代理服务列表" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} 个代理服务" + }, + "proxy-hosts.form.openappsec.confidence": { + "defaultMessage": "Minimum Confidence" + }, + "proxy-hosts.form.openappsec.confidence.critical": { + "defaultMessage": "Critical" + }, + "proxy-hosts.form.openappsec.confidence.high": { + "defaultMessage": "High" + }, + "proxy-hosts.form.openappsec.confidence.low": { + "defaultMessage": "Low" + }, + "proxy-hosts.form.openappsec.confidence.medium": { + "defaultMessage": "Medium" + }, + "proxy-hosts.form.openappsec.enable": { + "defaultMessage": "Enable open-appsec Protection" + }, + "proxy-hosts.form.openappsec.mode": { + "defaultMessage": "Mode" + }, + "proxy-hosts.form.openappsec.mode.detect-learn": { + "defaultMessage": "Detect-Learn" + }, + "proxy-hosts.form.openappsec.mode.inactive": { + "defaultMessage": "Inactive" + }, + "proxy-hosts.form.openappsec.mode.prevent-learn": { + "defaultMessage": "Prevent-Learn" + }, + "proxy-hosts.form.openappsec.tab": { + "defaultMessage": "open-appsec" + }, + "public": { + "defaultMessage": "公开" + }, + "redirection-host": { + "defaultMessage": "重定向主机" + }, + "redirection-host.forward-domain": { + "defaultMessage": "转发域名" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP 状态码" + }, + "redirection-hosts": { + "defaultMessage": "重定向主机列表" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} 个重定向主机" + }, + "role.admin": { + "defaultMessage": "管理员" + }, + "role.standard-user": { + "defaultMessage": "标准用户" + }, + "save": { + "defaultMessage": "保存" + }, + "setting": { + "defaultMessage": "设置" + }, + "settings": { + "defaultMessage": "设置列表" + }, + "settings.default-site": { + "defaultMessage": "默认站点" + }, + "settings.default-site.404": { + "defaultMessage": "错误页面" + }, + "settings.default-site.444": { + "defaultMessage": "无响应 (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "欢迎页面" + }, + "settings.default-site.description": { + "defaultMessage": "当 Nginx 遇到未知主机时显示什么" + }, + "settings.default-site.html": { + "defaultMessage": "自定义 HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "重定向" + }, + "settings.openappsec.local-policy": { + "defaultMessage": "Local Policy" + }, + "settings.openappsec.local-policy.description": { + "defaultMessage": "open-appsec YAML-based configuration file. Edits will be written to /ext/appsec/local_policy.yaml." + }, + "settings.openappsec.save": { + "defaultMessage": "Save" + }, + "settings.openappsec.title": { + "defaultMessage": "open-appsec" + }, + "setup.preamble": { + "defaultMessage": "通过创建您的管理员账户开始使用。" + }, + "setup.title": { + "defaultMessage": "欢迎!" + }, + "sign-in": { + "defaultMessage": "登录" + }, + "ssl-certificate": { + "defaultMessage": "SSL 证书" + }, + "stream": { + "defaultMessage": "端口转发" + }, + "stream.forward-host": { + "defaultMessage": "转发主机" + }, + "stream.incoming-port": { + "defaultMessage": "入站端口" + }, + "streams": { + "defaultMessage": "端口转发列表" + }, + "streams.count": { + "defaultMessage": "{count} 个端口转发" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "测试" + }, + "user": { + "defaultMessage": "用户" + }, + "user.change-password": { + "defaultMessage": "修改密码" + }, + "user.confirm-password": { + "defaultMessage": "确认密码" + }, + "user.current-password": { + "defaultMessage": "当前密码" + }, + "user.edit-profile": { + "defaultMessage": "编辑资料" + }, + "user.full-name": { + "defaultMessage": "全名" + }, + "user.login-as": { + "defaultMessage": "登录用户 {name}" + }, + "user.logout": { + "defaultMessage": "退出登录" + }, + "user.new-password": { + "defaultMessage": "新密码" + }, + "user.nickname": { + "defaultMessage": "昵称" + }, + "user.set-password": { + "defaultMessage": "设置密码" + }, + "user.set-permissions": { + "defaultMessage": "为用户 {name} 设置权限" + }, + "user.switch-dark": { + "defaultMessage": "切换到深色模式" + }, + "user.switch-light": { + "defaultMessage": "切换到浅色模式" + }, + "username": { + "defaultMessage": "用户名" + }, + "users": { + "defaultMessage": "用户列表" + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000000..62c1c48103 --- /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 0000000000..79537f5cd5 --- /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 0000000000..48221e2812 --- /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 0000000000..deab1c5231 --- /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 0000000000..f062e24dea --- /dev/null +++ b/frontend/src/modals/DNSCertificateModal.tsx @@ -0,0 +1,117 @@ +import { useQueryClient } from "@tanstack/react-query"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Form, Formik, Field } 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} + +
    +
    + + + {({ field }: any) => ( +
    + + + + + +
    + )} +
    + +
    +
    +
    + + + + +
    + )} +
    +
    + ); +}); + +export { showDNSCertificateModal }; diff --git a/frontend/src/modals/DeadHostModal.tsx b/frontend/src/modals/DeadHostModal.tsx new file mode 100644 index 0000000000..c9dce9a1c4 --- /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 0000000000..fb89f05290 --- /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 0000000000..7cf6848723 --- /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 0000000000..a06df2c62d --- /dev/null +++ b/frontend/src/modals/HTTPCertificateModal.tsx @@ -0,0 +1,218 @@ +import { IconAlertTriangle } from "@tabler/icons-react"; +import { useQueryClient } from "@tanstack/react-query"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Form, Formik, Field } 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); + }} + /> + + {({ field }: any) => ( +
    + + + + + +
    + )} +
    +
    + {testResults ? ( +
    +
    + +
    + {parseTestResults()} +
    + ) : null} +
    +
    + + +
    + + +
    +
    +
    + )} +
    +
    + ); +}); + +export { showHTTPCertificateModal }; diff --git a/frontend/src/modals/HelpModal.tsx b/frontend/src/modals/HelpModal.tsx new file mode 100644 index 0000000000..f058161424 --- /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/OpenAppsecLogDetailsModal.tsx b/frontend/src/modals/OpenAppsecLogDetailsModal.tsx new file mode 100644 index 0000000000..72ee40641e --- /dev/null +++ b/frontend/src/modals/OpenAppsecLogDetailsModal.tsx @@ -0,0 +1,109 @@ +import CodeEditor from "@uiw/react-textarea-code-editor"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import Modal from "react-bootstrap/Modal"; +import type { OpenAppsecLogEntry } from "src/api/backend"; +import { Button, DateFormatter, SeverityFormatter } from "src/components"; +import { T } from "src/locale"; + +const showOpenAppsecLogDetailsModal = (entry: OpenAppsecLogEntry) => { + EasyModal.show(OpenAppsecLogDetailsModal, { entry }); +}; + +interface Props extends InnerModalProps { + entry: OpenAppsecLogEntry; +} + +// Flat list of the wrapper fields we want to surface as a key/value table. +// The full original payload is shown separately as JSON underneath. +const FIELD_KEYS: Array = [ + "eventTime", + "eventSeverity", + "eventPriority", + "eventLevel", + "eventName", + "eventTopic", + "serviceName", + "assetName", + "securityAction", + "waapIncidentType", + "httpSourceId", + "sourceIP", + "proxyIP", + "httpHostName", + "httpMethod", + "httpUriPath", + "matchedLocation", + "matchedParameter", + "matchedSample", + "source", +]; + +const renderValue = (key: keyof OpenAppsecLogEntry, value: unknown): React.ReactNode => { + if (value === null || value === undefined || value === "") { + return ; + } + if (key === "eventTime" && typeof value === "string") { + return ; + } + if (key === "eventSeverity" && typeof value === "string") { + return ; + } + return {String(value)}; +}; + +const OpenAppsecLogDetailsModal = EasyModal.create(({ entry, visible, remove }: Props) => { + return ( + + + + + + + +
    + + + {FIELD_KEYS.map((key) => ( + + + + + ))} + +
    + {key} + {renderValue(key, entry[key])}
    +
    +

    + +

    + +
    + + + +
    + ); +}); + +export { showOpenAppsecLogDetailsModal }; diff --git a/frontend/src/modals/PermissionsModal.module.css b/frontend/src/modals/PermissionsModal.module.css new file mode 100644 index 0000000000..6ef4265ff8 --- /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 0000000000..d363de9ede --- /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 0000000000..493ce60031 --- /dev/null +++ b/frontend/src/modals/ProxyHostModal.tsx @@ -0,0 +1,397 @@ +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, + OpenAppsecFields, + 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 0000000000..1bd7877f4f --- /dev/null +++ b/frontend/src/modals/RedirectionHostModal.tsx @@ -0,0 +1,337 @@ +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} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.forwardHttpCode ? ( +
    + {form.errors.forwardHttpCode && + form.touched.forwardHttpCode + ? form.errors.forwardHttpCode + : null} +
    + ) : null} +
    + )} +
    +
    +

    + +

    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + + + + +
    + )} +
    + )} +
    + ); +}); + +export { showRedirectionHostModal }; diff --git a/frontend/src/modals/RenewCertificateModal.tsx b/frontend/src/modals/RenewCertificateModal.tsx new file mode 100644 index 0000000000..8f7cc63ace --- /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]); + + 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 0000000000..ef6d99bebf --- /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 0000000000..6d55348896 --- /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 { intl, 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/TwoFactorModal.tsx b/frontend/src/modals/TwoFactorModal.tsx new file mode 100644 index 0000000000..b5dd480c30 --- /dev/null +++ b/frontend/src/modals/TwoFactorModal.tsx @@ -0,0 +1,368 @@ +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useCallback, useEffect, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { + disable2FA, + enable2FA, + get2FAStatus, + regenerateBackupCodes, + start2FASetup, +} from "src/api/backend"; +import { Button } from "src/components"; +import { T } from "src/locale"; +import { validateString } from "src/modules/Validations"; + +type Step = "loading" | "status" | "setup" | "verify" | "backup" | "disable"; + +const showTwoFactorModal = (id: number | "me") => { + EasyModal.show(TwoFactorModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number | "me"; +} + +const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => { + const [error, setError] = useState(null); + const [step, setStep] = useState("loading"); + const [isEnabled, setIsEnabled] = useState(false); + const [backupCodesRemaining, setBackupCodesRemaining] = useState(0); + const [setupData, setSetupData] = useState<{ secret: string; otpauthUrl: string } | null>(null); + const [backupCodes, setBackupCodes] = useState([]); + const [isSubmitting, setIsSubmitting] = useState(false); + + const loadStatus = useCallback(async () => { + try { + const status = await get2FAStatus(id); + setIsEnabled(status.enabled); + setBackupCodesRemaining(status.backupCodesRemaining); + setStep("status"); + } catch (err: any) { + setError(err.message || "Failed to load 2FA status"); + setStep("status"); + } + }, [id]); + + useEffect(() => { + loadStatus(); + }, [loadStatus]); + + const handleStartSetup = async () => { + setError(null); + setIsSubmitting(true); + try { + const data = await start2FASetup(id); + setSetupData(data); + setStep("setup"); + } catch (err: any) { + setError(err.message || "Failed to start 2FA setup"); + } + setIsSubmitting(false); + }; + + const handleVerify = async (values: { code: string }) => { + setError(null); + setIsSubmitting(true); + try { + const result = await enable2FA(id, values.code); + setBackupCodes(result.backupCodes); + setStep("backup"); + } catch (err: any) { + setError(err.message || "Failed to enable 2FA"); + } + setIsSubmitting(false); + }; + + const handleDisable = async (values: { code: string }) => { + setError(null); + setIsSubmitting(true); + try { + await disable2FA(id, values.code); + setIsEnabled(false); + setStep("status"); + } catch (err: any) { + setError(err.message || "Failed to disable 2FA"); + } + setIsSubmitting(false); + }; + + const handleRegenerateBackup = async (values: { code: string }) => { + setError(null); + setIsSubmitting(true); + try { + const result = await regenerateBackupCodes(id, values.code); + setBackupCodes(result.backupCodes); + setStep("backup"); + } catch (err: any) { + setError(err.message || "Failed to regenerate backup codes"); + } + setIsSubmitting(false); + }; + + const handleBackupDone = () => { + setIsEnabled(true); + setBackupCodes([]); + loadStatus(); + }; + + const renderContent = () => { + if (step === "loading") { + return ( +
    +
    + Loading... +
    +
    + ); + } + + if (step === "status") { + return ( +
    +
    +
    + + + + + {isEnabled ? : } + +
    + {isEnabled && ( +

    + +

    + )} +
    + {!isEnabled ? ( + + ) : ( +
    + + +
    + )} +
    + ); + } + + if (step === "setup" && setupData) { + return ( +
    +

    + +

    +
    + QR Code +
    + + + {() => ( +
    + + {({ field, form }: any) => ( + + )} + +
    + + +
    +
    + )} +
    +
    + ); + } + + if (step === "backup") { + return ( +
    + + + +
    +
    + {backupCodes.map((code, index) => ( +
    + {code} +
    + ))} +
    +
    + +
    + ); + } + + if (step === "disable") { + return ( +
    + + + + + {() => ( +
    + + {({ field, form }: any) => ( + + )} + +
    + + +
    +
    + )} +
    +
    + ); + } + + if (step === "verify") { + return ( +
    +

    + +

    + + {() => ( +
    + + {({ field, form }: any) => ( + + )} + +
    + + +
    +
    + )} +
    +
    + ); + } + + return null; + }; + + return ( + + + + + + + + setError(null)} dismissible> + {error} + + {renderContent()} + + + ); +}); + +export { showTwoFactorModal }; diff --git a/frontend/src/modals/UserModal.tsx b/frontend/src/modals/UserModal.tsx new file mode 100644 index 0000000000..06bc38cf98 --- /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 0000000000..2af8d11975 --- /dev/null +++ b/frontend/src/modals/index.ts @@ -0,0 +1,18 @@ +export * from "./AccessListModal"; +export * from "./ChangePasswordModal"; +export * from "./CustomCertificateModal"; +export * from "./DeadHostModal"; +export * from "./DeleteConfirmModal"; +export * from "./DNSCertificateModal"; +export * from "./EventDetailsModal"; +export * from "./OpenAppsecLogDetailsModal"; +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 "./TwoFactorModal"; +export * from "./UserModal"; diff --git a/frontend/src/modules/AuthStore.ts b/frontend/src/modules/AuthStore.ts new file mode 100644 index 0000000000..9978aaa2f6 --- /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 0000000000..2d784213e4 --- /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 0000000000..3b2908198e --- /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.-]+$/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 0000000000..24c55921d6 --- /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 0000000000..b68b867172 --- /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 0000000000..07517e49c0 --- /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 0000000000..d4e09d7b43 --- /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 0000000000..433c077b34 --- /dev/null +++ b/frontend/src/pages/Access/Table.tsx @@ -0,0 +1,144 @@ +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) => { + 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) => ( + + ), + }), + 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 0000000000..1e028f4ad5 --- /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 0000000000..9c4624cb08 --- /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 0000000000..f61809a56f --- /dev/null +++ b/frontend/src/pages/AuditLog/Table.tsx @@ -0,0 +1,70 @@ +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, { + 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 0000000000..a7d3967da5 --- /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 0000000000..4bdc2d072e --- /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 0000000000..7eaeb77701 --- /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 0000000000..14dfc417fd --- /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 0000000000..130fb5a97c --- /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 0000000000..5cb64867f0 --- /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 0000000000..f1485ca942 --- /dev/null +++ b/frontend/src/pages/Login/index.module.css @@ -0,0 +1,19 @@ +.logo { + width: 200px; +} + +.appsecBrand { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + margin-top: 1rem; + color: var(--tblr-secondary, #808390); + font-size: 0.75rem; +} + +.appsecLogo { + width: 96px; + height: auto; + opacity: 0.85; +} diff --git a/frontend/src/pages/Login/index.tsx b/frontend/src/pages/Login/index.tsx new file mode 100644 index 0000000000..85224830c2 --- /dev/null +++ b/frontend/src/pages/Login/index.tsx @@ -0,0 +1,214 @@ +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"; + +function TwoFactorForm() { + const codeRef = useRef(null); + const [formErr, setFormErr] = useState(""); + const { verifyTwoFactor, cancelTwoFactor } = useAuthState(); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + setFormErr(""); + try { + await verifyTwoFactor(values.code); + } catch (err) { + if (err instanceof Error) { + setFormErr(err.message); + } + } + setSubmitting(false); + }; + + useEffect(() => { + codeRef.current?.focus(); + }, []); + + return ( + <> +

    + +

    +

    + +

    + {formErr !== "" && {formErr}} + + {({ isSubmitting }) => ( +
    +
    + + {({ field, form }: any) => ( + + )} + +
    +
    + + +
    + + )} +
    + + ); +} + +function LoginForm() { + 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(() => { + emailRef.current?.focus(); + }, []); + + return ( + <> +

    + +

    + {formErr !== "" && {formErr}} + + {({ isSubmitting }) => ( +
    +
    + + {({ field, form }: any) => ( + + )} + +
    +
    + + {({ field, form }: any) => ( + <> + + + )} + +
    +
    + +
    + + )} +
    + + ); +} + +export default function Login() { + const { twoFactorChallenge } = useAuthState(); + 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 +
    + + +
    +
    +
    +
    + {twoFactorChallenge ? : } +
    +
    +
    {getVersion()}
    +
    + open-appsec + + + +
    +
    +
    + ); +} diff --git a/frontend/src/pages/Nginx/DeadHosts/Table.tsx b/frontend/src/pages/Nginx/DeadHosts/Table.tsx new file mode 100644 index 0000000000..f8a1a27542 --- /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 0000000000..07dd1318ef --- /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 0000000000..0464b7a645 --- /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 0000000000..9d58b26acd --- /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.forwardScheme}://${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 0000000000..68af43e10a --- /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 0000000000..d20658e19a --- /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 0000000000..6ac4152348 --- /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 0000000000..382b733698 --- /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 0000000000..32bf244124 --- /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 0000000000..4b9ff7d600 --- /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 0000000000..ec9a8d4090 --- /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 0000000000..31c4f63670 --- /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/OpenAppsecLog/All/index.tsx b/frontend/src/pages/OpenAppsecLog/All/index.tsx new file mode 100644 index 0000000000..12044ba9d1 --- /dev/null +++ b/frontend/src/pages/OpenAppsecLog/All/index.tsx @@ -0,0 +1,20 @@ +import type { OpenAppsecLogEntry } from "src/api/backend"; +import TableWrapper from "../TableWrapper"; + +// Event names that the original fork's `frontend/js/app/openappsec-log/main.js` +// excluded from its "All Events" tab. They are telemetry noise rather than +// security-relevant entries, so we mirror that behaviour here. The Detailed +// tab continues to show everything for power users. +const TELEMETRY_EVENT_NAMES = new Set([ + "waap telemetry", + "waap attack type telemetry", + "ips stats", +]); + +function filterAll(entry: OpenAppsecLogEntry): boolean { + return !TELEMETRY_EVENT_NAMES.has((entry.eventName ?? "").trim().toLowerCase()); +} + +export default function All() { + return ; +} diff --git a/frontend/src/pages/OpenAppsecLog/Detailed/index.tsx b/frontend/src/pages/OpenAppsecLog/Detailed/index.tsx new file mode 100644 index 0000000000..4851ba461c --- /dev/null +++ b/frontend/src/pages/OpenAppsecLog/Detailed/index.tsx @@ -0,0 +1,5 @@ +import TableWrapper from "../TableWrapper"; + +export default function Detailed() { + return ; +} diff --git a/frontend/src/pages/OpenAppsecLog/Important/index.tsx b/frontend/src/pages/OpenAppsecLog/Important/index.tsx new file mode 100644 index 0000000000..663e1ff13e --- /dev/null +++ b/frontend/src/pages/OpenAppsecLog/Important/index.tsx @@ -0,0 +1,14 @@ +import type { OpenAppsecLogEntry } from "src/api/backend"; +import TableWrapper from "../TableWrapper"; + +// Mirrors the original fork's filter: case-insensitive match on +// "critical" or "high" eventSeverity. +const IMPORTANT_SEVERITIES = new Set(["critical", "high"]); + +const isImportant = (entry: OpenAppsecLogEntry): boolean => { + return IMPORTANT_SEVERITIES.has((entry.eventSeverity ?? "").trim().toLowerCase()); +}; + +export default function Important() { + return ; +} diff --git a/frontend/src/pages/OpenAppsecLog/Layout.tsx b/frontend/src/pages/OpenAppsecLog/Layout.tsx new file mode 100644 index 0000000000..d8f228b9c8 --- /dev/null +++ b/frontend/src/pages/OpenAppsecLog/Layout.tsx @@ -0,0 +1,53 @@ +import cn from "classnames"; +import { NavLink, Outlet } from "react-router-dom"; +import { T } from "src/locale"; + +interface Tab { + to: string; + labelKey: string; + end?: boolean; +} + +const tabs: Tab[] = [ + { to: "/open-appsec-log", labelKey: "openappsec-log.tab.all", end: true }, + { to: "/open-appsec-log/detailed", labelKey: "openappsec-log.tab.detailed" }, + { to: "/open-appsec-log/important", labelKey: "openappsec-log.tab.important" }, + { to: "/open-appsec-log/notifications", labelKey: "openappsec-log.tab.notifications" }, +]; + +export default function Layout() { + return ( +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    +
      + {tabs.map((tab) => ( +
    • + + cn("nav-link", { active: isActive }) + } + > + + +
    • + ))} +
    +
    +
    + +
    +
    +
    + ); +} diff --git a/frontend/src/pages/OpenAppsecLog/Notifications/index.tsx b/frontend/src/pages/OpenAppsecLog/Notifications/index.tsx new file mode 100644 index 0000000000..15453572d9 --- /dev/null +++ b/frontend/src/pages/OpenAppsecLog/Notifications/index.tsx @@ -0,0 +1,17 @@ +import type { OpenAppsecLogEntry } from "src/api/backend"; +import TableWrapper from "../TableWrapper"; + +// Mirrors the original fork's filter: case-insensitive match on +// eventLevel === "action item". Falls back to a `source` match so we +// still surface entries from a `*notification*.log` file even if the +// eventLevel field is empty. +const isNotification = (entry: OpenAppsecLogEntry): boolean => { + const level = (entry.eventLevel ?? "").trim().toLowerCase(); + if (level === "action item") return true; + const source = (entry.source ?? "").toLowerCase(); + return source.includes("notification"); +}; + +export default function Notifications() { + return ; +} diff --git a/frontend/src/pages/OpenAppsecLog/Table.tsx b/frontend/src/pages/OpenAppsecLog/Table.tsx new file mode 100644 index 0000000000..ed21b00a06 --- /dev/null +++ b/frontend/src/pages/OpenAppsecLog/Table.tsx @@ -0,0 +1,30 @@ +import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { OpenAppsecLogEntry } from "src/api/backend"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { buildColumns, type ColumnVariant } from "./columns"; + +interface Props { + data: OpenAppsecLogEntry[]; + isFetching?: boolean; + variant: ColumnVariant; + onSelectRow: (entry: OpenAppsecLogEntry) => void; +} + +export default function Table({ data, isFetching, variant, onSelectRow }: Props) { + const columns = useMemo( + () => buildColumns({ variant, onSelectRow }), + [variant, onSelectRow], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { isFetching }, + enableSortingRemoval: false, + }); + + return ; +} diff --git a/frontend/src/pages/OpenAppsecLog/TableWrapper.tsx b/frontend/src/pages/OpenAppsecLog/TableWrapper.tsx new file mode 100644 index 0000000000..dda3d4869d --- /dev/null +++ b/frontend/src/pages/OpenAppsecLog/TableWrapper.tsx @@ -0,0 +1,53 @@ +import { useCallback, useMemo } from "react"; +import Alert from "react-bootstrap/Alert"; +import type { OpenAppsecLogEntry } from "src/api/backend"; +import { LoadingPage } from "src/components"; +import { useOpenAppsecLogs } from "src/hooks"; +import { showOpenAppsecLogDetailsModal } from "src/modals"; +import type { ColumnVariant } from "./columns"; +import Table from "./Table"; + +interface Props { + variant: ColumnVariant; + /** Optional client-side filter applied to the raw log array. */ + filter?: (entry: OpenAppsecLogEntry) => boolean; +} + +export default function TableWrapper({ variant, filter }: Props) { + const { isFetching, isLoading, isError, error, data } = useOpenAppsecLogs(); + + const rows = useMemo(() => { + if (!data) return []; + const filtered = filter ? data.filter(filter) : data; + // Sort by eventTime descending; entries with an unparseable timestamp + // fall to the bottom. + return [...filtered].sort((a, b) => { + const ta = Date.parse(a.eventTime); + const tb = Date.parse(b.eventTime); + const va = Number.isFinite(ta) ? ta : Number.NEGATIVE_INFINITY; + const vb = Number.isFinite(tb) ? tb : Number.NEGATIVE_INFINITY; + return vb - va; + }); + }, [data, filter]); + + const handleSelectRow = useCallback((entry: OpenAppsecLogEntry) => { + showOpenAppsecLogDetailsModal(entry); + }, []); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + return ( +
    + ); +} diff --git a/frontend/src/pages/OpenAppsecLog/columns.tsx b/frontend/src/pages/OpenAppsecLog/columns.tsx new file mode 100644 index 0000000000..014892f4e3 --- /dev/null +++ b/frontend/src/pages/OpenAppsecLog/columns.tsx @@ -0,0 +1,131 @@ +import { type ColumnDef, createColumnHelper } from "@tanstack/react-table"; +import type { OpenAppsecLogEntry } from "src/api/backend"; +import { DateFormatter, SeverityFormatter } from "src/components"; +import { intl, T } from "src/locale"; + +export type ColumnVariant = "summary" | "detailed"; + +interface BuildColumnsOptions { + variant: ColumnVariant; + onSelectRow: (entry: OpenAppsecLogEntry) => void; +} + +const columnHelper = createColumnHelper(); + +const renderText = (value: unknown): React.ReactNode => { + if (value === null || value === undefined || value === "") { + return ; + } + return String(value); +}; + +const detailsButton = ( + entry: OpenAppsecLogEntry, + onSelectRow: (entry: OpenAppsecLogEntry) => void, +) => ( + +); + +export function buildColumns({ + variant, + onSelectRow, +}: BuildColumnsOptions): ColumnDef[] { + const summary: ColumnDef[] = [ + columnHelper.accessor((row) => row.eventTime, { + id: "eventTime", + header: intl.formatMessage({ id: "openappsec-log.col.time" }), + cell: (info) => { + const value = info.getValue() as string | undefined; + return value ? : renderText(value); + }, + meta: { className: "text-nowrap" }, + }), + columnHelper.accessor((row) => row.eventSeverity, { + id: "eventSeverity", + header: intl.formatMessage({ id: "openappsec-log.col.severity" }), + cell: (info) => , + meta: { className: "text-nowrap" }, + }), + columnHelper.accessor((row) => row.serviceName, { + id: "serviceName", + header: intl.formatMessage({ id: "openappsec-log.col.service" }), + cell: (info) => renderText(info.getValue()), + }), + columnHelper.accessor((row) => row.sourceIP, { + id: "sourceIP", + header: intl.formatMessage({ id: "openappsec-log.col.source-ip" }), + cell: (info) => renderText(info.getValue()), + meta: { className: "text-nowrap" }, + }), + columnHelper.accessor((row) => row.httpHostName, { + id: "httpHostName", + header: intl.formatMessage({ id: "openappsec-log.col.host" }), + cell: (info) => renderText(info.getValue()), + }), + columnHelper.accessor((row) => row.httpMethod, { + id: "httpMethod", + header: intl.formatMessage({ id: "openappsec-log.col.method" }), + cell: (info) => renderText(info.getValue()), + meta: { className: "text-nowrap" }, + }), + columnHelper.accessor((row) => row.httpUriPath, { + id: "httpUriPath", + header: intl.formatMessage({ id: "openappsec-log.col.uri" }), + cell: (info) => renderText(info.getValue()), + }), + columnHelper.accessor((row) => row.securityAction, { + id: "securityAction", + header: intl.formatMessage({ id: "openappsec-log.col.action" }), + cell: (info) => renderText(info.getValue()), + meta: { className: "text-nowrap" }, + }), + ]; + + const detailedExtras: ColumnDef[] = [ + columnHelper.accessor((row) => row.eventName, { + id: "eventName", + header: intl.formatMessage({ id: "openappsec-log.col.event-name" }), + cell: (info) => renderText(info.getValue()), + }), + columnHelper.accessor((row) => row.waapIncidentType, { + id: "waapIncidentType", + header: intl.formatMessage({ id: "openappsec-log.col.incident-type" }), + cell: (info) => renderText(info.getValue()), + }), + columnHelper.accessor((row) => row.matchedLocation, { + id: "matchedLocation", + header: intl.formatMessage({ id: "openappsec-log.col.matched-location" }), + cell: (info) => renderText(info.getValue()), + }), + columnHelper.accessor((row) => row.matchedParameter, { + id: "matchedParameter", + header: intl.formatMessage({ id: "openappsec-log.col.matched-parameter" }), + cell: (info) => renderText(info.getValue()), + }), + columnHelper.accessor((row) => row.matchedSample, { + id: "matchedSample", + header: intl.formatMessage({ id: "openappsec-log.col.matched-sample" }), + cell: (info) => renderText(info.getValue()), + }), + ]; + + const detailsAction: ColumnDef = columnHelper.display({ + id: "details", + header: () => null, + cell: (info) => detailsButton(info.row.original, onSelectRow), + meta: { className: "text-end w-1" }, + }); + + return variant === "detailed" + ? [...summary, ...detailedExtras, detailsAction] + : [...summary, detailsAction]; +} diff --git a/frontend/src/pages/OpenAppsecLog/index.tsx b/frontend/src/pages/OpenAppsecLog/index.tsx new file mode 100644 index 0000000000..07f1c3bde6 --- /dev/null +++ b/frontend/src/pages/OpenAppsecLog/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { ADMIN, VIEW } from "src/modules/Permissions"; +import Layout from "./Layout"; + +const OpenAppsecLog = () => { + return ( + + + + ); +}; + +export default OpenAppsecLog; diff --git a/frontend/src/pages/Settings/DefaultSite.tsx b/frontend/src/pages/Settings/DefaultSite.tsx new file mode 100644 index 0000000000..1ee1cb5ef5 --- /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 0000000000..d00e31e424 --- /dev/null +++ b/frontend/src/pages/Settings/Layout.tsx @@ -0,0 +1,70 @@ +import cn from "classnames"; +import { useState } from "react"; +import { T } from "src/locale"; +import DefaultSite from "./DefaultSite"; +import OpenAppsec from "./OpenAppsec"; + +type TabId = "default-site" | "openappsec"; + +interface Tab { + id: TabId; + labelKey: string; +} + +const tabs: Tab[] = [ + { id: "default-site", labelKey: "settings.default-site" }, + { id: "openappsec", labelKey: "settings.openappsec.title" }, +]; + +export default function Layout() { + // Taken from https://preview.tabler.io/settings.html + // Refer to that when updating this content + + const [activeTab, setActiveTab] = useState("default-site"); + + return ( +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + +
    + {activeTab === "default-site" && } + {activeTab === "openappsec" && } +
    +
    +
    +
    + ); +} diff --git a/frontend/src/pages/Settings/OpenAppsec.tsx b/frontend/src/pages/Settings/OpenAppsec.tsx new file mode 100644 index 0000000000..563d5a9dab --- /dev/null +++ b/frontend/src/pages/Settings/OpenAppsec.tsx @@ -0,0 +1,124 @@ +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 { useOpenAppsecSettings, useSetOpenAppsecSettings } from "src/hooks"; +import { T } from "src/locale"; +import { showObjectSuccess } from "src/notifications"; + +interface FormValues { + localPolicy: string; +} + +export default function OpenAppsec() { + const { data, isLoading, error } = useOpenAppsecSettings(); + const { mutate: setOpenAppsec } = useSetOpenAppsecSettings(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: FormValues, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + setOpenAppsec( + { localPolicy: values.localPolicy }, + { + onError: (err: Error) => setErrorMsg(), + onSuccess: () => { + showObjectSuccess("settings.openappsec.title", "saved"); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }, + ); + }; + + if (!isLoading && error) { + return ( +
    +
    + + {error.message} + +
    +
    + ); + } + + if (isLoading) { + return ( +
    +
    + +
    +
    + ); + } + + return ( + + {() => ( +
    +
    + setErrorMsg(null)} + dismissible + > + {errorMsg} + +
    + +

    + +

    + + {({ field }: any) => ( + + )} + +
    +
    +
    +
    + +
    +
    + + )} +
    + ); +} diff --git a/frontend/src/pages/Settings/index.tsx b/frontend/src/pages/Settings/index.tsx new file mode 100644 index 0000000000..7904cbc0e6 --- /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 0000000000..16f8477ca5 --- /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 0000000000..4becf5faec --- /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 0000000000..d07fc9bcff --- /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 0000000000..461ed6ab6a --- /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 0000000000..53b4366407 --- /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 0000000000..11f02fe2a0 --- /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 0000000000..fb86d45b53 --- /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 0000000000..eca66688d2 --- /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 0000000000..1e21d90f0c --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,61 @@ +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"; + +const runLocaleScripts = () => { + 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); + }); + }); +}; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + { + name: 'run-on-start', + configureServer(_server) { + runLocaleScripts(); + }, + }, + { + name: "trigger-on-reload", + configureServer(server) { + server.watcher.on("change", (file) => { + if (file.includes("locale/src")) { + console.log(`File changed: ${file}, running locale scripts...`); + runLocaleScripts(); + } + }); + }, + }, + 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 0000000000..f149f27ae4 --- /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 05350a4755..0000000000 --- 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 a18558e16f..1720bee524 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2,7222 +2,3151 @@ # 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== - 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" +"@adobe/css-tools@^4.4.0": + version "4.4.4" + resolved "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz" + integrity sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg== -"@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== +"@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.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== 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/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" + "@babel/helper-validator-identifier" "^7.28.5" js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.28.6": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + +"@babel/core@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz" + integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ== + dependencies: + "@babel/parser" "^7.28.5" + "@babel/types" "^7.28.5" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/generator@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.0.tgz#4cba5a76b3c71d8be31761b03329d5dc7768447f" + integrity sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ== + dependencies: + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" -"@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/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== -"@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/helper-module-imports@^7.16.7": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" + +"@babel/helper-plugin-utils@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" + integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.27.2", "@babel/parser@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz" + integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== + dependencies: + "@babel/types" "^7.28.5" + +"@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" + integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== + dependencies: + "@babel/types" "^7.29.0" + +"@babel/plugin-transform-react-jsx-self@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz" + 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.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz" + integrity sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@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.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz" + integrity sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA== + +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/template@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse@^7.27.1": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz" + integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.5" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.5" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.5" + debug "^4.3.1" + +"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.28.2", "@babel/types@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz" + integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@babel/types@^7.28.6", "@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@biomejs/biome@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.4.2.tgz#91fea27398106f87fa81118577756d9e29fe8c74" + integrity sha512-vVE/FqLxNLbvYnFDYg3Xfrh1UdFhmPT5i+yPT9GE2nTUgI4rkqo5krw5wK19YHBd7aE7J6r91RRmb8RWwkjy6w== + optionalDependencies: + "@biomejs/cli-darwin-arm64" "2.4.2" + "@biomejs/cli-darwin-x64" "2.4.2" + "@biomejs/cli-linux-arm64" "2.4.2" + "@biomejs/cli-linux-arm64-musl" "2.4.2" + "@biomejs/cli-linux-x64" "2.4.2" + "@biomejs/cli-linux-x64-musl" "2.4.2" + "@biomejs/cli-win32-arm64" "2.4.2" + "@biomejs/cli-win32-x64" "2.4.2" + +"@biomejs/cli-darwin-arm64@2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.2.tgz#a748ec0a0d8f4ea559b4a3834e8981500caaa5fa" + integrity sha512-3pEcKCP/1POKyaZZhXcxFl3+d9njmeAihZ17k8lL/1vk+6e0Cbf0yPzKItFiT+5Yh6TQA4uKvnlqe0oVZwRxCA== -"@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" - debug "^4.1.0" - globals "^11.1.0" +"@biomejs/cli-darwin-x64@2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.2.tgz#b825d247a7e582c00c9782524284bea2033a272c" + integrity sha512-P7hK1jLVny+0R9UwyGcECxO6sjETxfPyBm/1dmFjnDOHgdDPjPqozByunrwh4xPKld8sxOr5eAsSqal5uKgeBg== -"@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== - dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - lodash "^4.17.19" - to-fast-properties "^2.0.0" +"@biomejs/cli-linux-arm64-musl@2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.2.tgz#9a2d15f525928f06d21fbc0eaea2e332be3ed5d9" + integrity sha512-/x04YK9+7erw6tYEcJv9WXoBHcULI/wMOvNdAyE9S3JStZZ9yJyV67sWAI+90UHuDo/BDhq0d96LDqGlSVv7WA== -"@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== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" +"@biomejs/cli-linux-arm64@2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.2.tgz#0f7928a3253cdb10ebf5884db452d81d7a49ba30" + integrity sha512-DI3Mi7GT2zYNgUTDEbSjl3e1KhoP76OjQdm8JpvZYZWtVDRyLd3w8llSr2TWk1z+U3P44kUBWY3X7H9MD1/DGQ== -"@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== +"@biomejs/cli-linux-x64-musl@2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.2.tgz#07c971451e62f6e147ec58b7acae345792d331b1" + integrity sha512-wbBmTkeAoAYbOQ33f6sfKG7pcRSydQiF+dTYOBjJsnXO2mWEOQHllKlC2YVnedqZFERp2WZhFUoO7TNRwnwEHQ== + +"@biomejs/cli-linux-x64@2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.2.tgz#0c7755fe8b5b2745d6afa8e14806591aefef52e3" + integrity sha512-GK2ErnrKpWFigYP68cXiCHK4RTL4IUWhK92AFS3U28X/nuAL5+hTuy6hyobc8JZRSt+upXt1nXChK+tuHHx4mA== + +"@biomejs/cli-win32-arm64@2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.2.tgz#a25007d0eb8ac4bbf81fb0fd010a68ce38fd46d1" + integrity sha512-k2uqwLYrNNxnaoiW3RJxoMGnbKda8FuCmtYG3cOtVljs3CzWxaTR+AoXwKGHscC9thax9R4kOrtWqWN0+KdPTw== -"@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== +"@biomejs/cli-win32-x64@2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.2.tgz#7693a47973bd1c910a7ce3d7eb8133292f779646" + integrity sha512-9ma7C4g8Sq3cBlRJD2yrsHXB1mnnEBdpy7PhvFrylQWQb4PoyCmPucdX7frvsSBQuFtIiKCrolPl/8tCZrKvgQ== + +"@emotion/babel-plugin@^11.13.5": + version "11.13.5" + resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz" + 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.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz" + integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@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" -"@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== +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== -"@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== +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + +"@emotion/react@^11.8.1": + version "11.14.0" + resolved "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz" + 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.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz" + 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.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== -"@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== +"@emotion/unitless@^0.10.0": + version "0.10.0" + resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz" + integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== -"@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== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" +"@emotion/use-insertion-effect-with-fallbacks@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz" + integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== -"@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== +"@emotion/utils@^1.4.2": + version "1.4.2" + resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz" + integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== + +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== + +"@esbuild/aix-ppc64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz#521cbd968dcf362094034947f76fa1b18d2d403c" + integrity sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw== + +"@esbuild/android-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz#61ea550962d8aa12a9b33194394e007657a6df57" + integrity sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA== + +"@esbuild/android-arm@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz#554887821e009dd6d853f972fde6c5143f1de142" + integrity sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA== + +"@esbuild/android-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz#a7ce9d0721825fc578f9292a76d9e53334480ba2" + integrity sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A== + +"@esbuild/darwin-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz#2cb7659bd5d109803c593cfc414450d5430c8256" + integrity sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg== + +"@esbuild/darwin-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz#e741fa6b1abb0cd0364126ba34ca17fd5e7bf509" + integrity sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA== + +"@esbuild/freebsd-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz#2b64e7116865ca172d4ce034114c21f3c93e397c" + integrity sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g== + +"@esbuild/freebsd-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz#e5252551e66f499e4934efb611812f3820e990bb" + integrity sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA== + +"@esbuild/linux-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz#dc4acf235531cd6984f5d6c3b13dbfb7ddb303cb" + integrity sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw== + +"@esbuild/linux-arm@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz#56a900e39240d7d5d1d273bc053daa295c92e322" + integrity sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw== + +"@esbuild/linux-ia32@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz#d4a36d473360f6870efcd19d52bbfff59a2ed1cc" + integrity sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w== + +"@esbuild/linux-loong64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz#fcf0ab8c3eaaf45891d0195d4961cb18b579716a" + integrity sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg== + +"@esbuild/linux-mips64el@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz#598b67d34048bb7ee1901cb12e2a0a434c381c10" + integrity sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw== + +"@esbuild/linux-ppc64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz#3846c5df6b2016dab9bc95dde26c40f11e43b4c0" + integrity sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ== + +"@esbuild/linux-riscv64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz#173d4475b37c8d2c3e1707e068c174bb3f53d07d" + integrity sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA== + +"@esbuild/linux-s390x@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz#f7a4790105edcab8a5a31df26fbfac1aa3dacfab" + integrity sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w== + +"@esbuild/linux-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz#2ecc1284b1904aeb41e54c9ddc7fcd349b18f650" + integrity sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA== + +"@esbuild/netbsd-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz#e2863c2cd1501845995cb11adf26f7fe4be527b0" + integrity sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw== + +"@esbuild/netbsd-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz#93f7609e2885d1c0b5a1417885fba8d1fcc41272" + integrity sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA== + +"@esbuild/openbsd-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz#a1985604a203cdc325fd47542e106fafd698f02e" + integrity sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA== + +"@esbuild/openbsd-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz#8209e46c42f1ffbe6e4ef77a32e1f47d404ad42a" + integrity sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg== + +"@esbuild/openharmony-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz#8fade4441893d9cc44cbd7dcf3776f508ab6fb2f" + integrity sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag== + +"@esbuild/sunos-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz#980d4b9703a16f0f07016632424fc6d9a789dfc2" + integrity sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg== + +"@esbuild/win32-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz#1c09a3633c949ead3d808ba37276883e71f6111a" + integrity sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg== + +"@esbuild/win32-ia32@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz#1b1e3a63ad4bef82200fef4e369e0fff7009eee5" + integrity sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ== + +"@esbuild/win32-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz#9e585ab6086bef994c6e8a5b3a0481219ada862b" + integrity sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ== + +"@floating-ui/core@^1.7.3": + version "1.7.3" + resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz" + 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.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz" + integrity sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA== dependencies: - "@nodelib/fs.stat" "2.0.3" - run-parallel "^1.1.9" + "@floating-ui/core" "^1.7.3" + "@floating-ui/utils" "^0.2.10" -"@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== +"@floating-ui/utils@^0.2.10": + version "0.2.10" + resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz" + integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== -"@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== - dependencies: - "@nodelib/fs.scandir" "2.1.3" - fastq "^1.6.0" +"@formatjs/cli@^6.12.2": + version "6.12.2" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.12.2.tgz#0ca91899249f41860deb2b2921e6ad0a4db55de9" + integrity sha512-y215aarLZXei3u1WDRAiet/VajgvUXzvz4ifPENDSPXfIog0aBJKtFvIU7EWbcFWy7rstbn5GcwwQW4F1G3TJg== -"@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== +"@formatjs/ecma402-abstract@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-3.1.1.tgz#329fa5eed8024ee389e9c82be8c798315631b11d" + integrity sha512-jhZbTwda+2tcNrs4kKvxrPLPjx8QsBCLCUgrrJ/S+G9YrGHWLhAyFMMBHJBnBoOwuLHd7L14FgYudviKaxkO2Q== dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" + "@formatjs/fast-memoize" "3.1.0" + "@formatjs/intl-localematcher" "0.8.1" + decimal.js "^10.6.0" + tslib "^2.8.1" -"@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== +"@formatjs/fast-memoize@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-3.1.0.tgz#f8643d44803df8f579506d40804f4faeba6f5da2" + integrity sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg== dependencies: - "@gar/promisify" "^1.1.3" - semver "^7.3.5" + tslib "^2.8.1" -"@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== +"@formatjs/icu-messageformat-parser@3.5.1": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.1.tgz#a08faadbbb333d9e4fa7276c92a2b66a37788aa1" + integrity sha512-sSDmSvmmoVQ92XqWb499KrIhv/vLisJU8ITFrx7T7NZHUmMY7EL9xgRowAosaljhqnj/5iufG24QrdzB6X3ItA== dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" + "@formatjs/ecma402-abstract" "3.1.1" + "@formatjs/icu-skeleton-parser" "2.1.1" + tslib "^2.8.1" -"@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== +"@formatjs/icu-skeleton-parser@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.1.tgz#a05b45733bd0f277c50f31b32e4a73375569d3c8" + integrity sha512-PSFABlcNefjI6yyk8f7nyX1DC7NHmq6WaCHZLySEXBrXuLOB2f935YsnzuPjlz+ibhb9yWTdPeVX1OVcj24w2Q== dependencies: - 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== + "@formatjs/ecma402-abstract" "3.1.1" + tslib "^2.8.1" -"@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== +"@formatjs/intl-localematcher@0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.8.1.tgz#554fe5f8c746ba52d430674de9a242fae64f67ff" + integrity sha512-xwEuwQFdtSq1UKtQnyTZWC+eHdv7Uygoa+H2k/9uzBVQjDyp9r20LNDNKedWXll7FssT3GRHvqsdJGYSUWqYFA== dependencies: - defer-to-connect "^1.0.1" + "@formatjs/fast-memoize" "3.1.0" + tslib "^2.8.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== +"@formatjs/intl@4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-4.1.2.tgz#4a0d487e6290067462f93d034ef36a8aaf6f671b" + integrity sha512-V60fNY/X/7zqmRffr7qPwscGmVGYDmlKF069mSQ2a/7fE22q602NtIfOQY8vzRA63Gr/O/U6vjRVBHMabrnA9A== + dependencies: + "@formatjs/ecma402-abstract" "3.1.1" + "@formatjs/fast-memoize" "3.1.0" + "@formatjs/icu-messageformat-parser" "3.5.1" + intl-messageformat "11.1.2" + tslib "^2.8.1" -"@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== +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" -"@types/anymatch@*": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" - integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" -"@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== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@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== +"@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.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@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== +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.30" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz" + integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" -"@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== +"@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.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz" + 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.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz" + integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== + dependencies: + 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" + +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + +"@react-aria/ssr@^3.5.0": + version "3.9.10" + resolved "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz" + integrity sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ== + dependencies: + "@swc/helpers" "^0.5.0" + +"@restart/hooks@^0.4.9": + version "0.4.16" + resolved "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz" + integrity sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w== + dependencies: + dequal "^2.0.3" + +"@restart/hooks@^0.5.0": + version "0.5.1" + resolved "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz" + integrity sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q== + dependencies: + dequal "^2.0.3" + +"@restart/ui@^1.9.4": + version "1.9.4" + resolved "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz" + integrity sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA== + dependencies: + "@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" + +"@rolldown/pluginutils@1.0.0-rc.3": + version "1.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz#8a88cc92a0f741befc7bc109cb1a4c6b9408e1c5" + integrity sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q== + +"@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.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz" + 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.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz" + integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== -"@types/minimist@^1.2.0": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" - integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== +"@swc/helpers@^0.5.0": + version "0.5.17" + resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz" + integrity sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A== + dependencies: + tslib "^2.8.0" -"@types/node@*": - version "14.0.27" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" - integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g== +"@tabler/core@^1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@tabler/core/-/core-1.4.0.tgz" + integrity sha512-5BigzOlbOH9N0Is4u0rYNRCiwtnUXWO57K9zwuscygcicAa8UV9MGaS4zTgQsZEtZ9tsNANhN/YD8gCBGKYCiw== + dependencies: + "@popperjs/core" "^2.11.8" + bootstrap "5.3.7" -"@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== +"@tabler/icons-react@^3.36.1": + version "3.36.1" + resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.36.1.tgz#332d4407b5e2a9a783ce0a26567727aaedd5a9a8" + integrity sha512-/8nOXeNeMoze9xY/QyEKG65wuvRhkT3q9aytaur6Gj8bYU2A98YVJyLc9MRmc5nVvpy+bRlrrwK/Ykr8WGyUWg== + dependencies: + "@tabler/icons" "" -"@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== +"@tabler/icons@": + version "3.36.1" + resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.36.1.tgz#a893104b3c56e02458b13342abb6e466d4a81a8d" + integrity sha512-f4Jg3Fof/Vru5ioix/UO4GX+sdDsF9wQo47FbtvG+utIYYVQ/QVAC0QYgcBbAjQGfbdOh2CCf0BgiFOF9Ixtjw== -"@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== +"@tanstack/query-core@5.90.20": + version "5.90.20" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.90.20.tgz#e12128e39210715d4ce4fb299c33498ac297771e" + integrity sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg== -"@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== - dependencies: - source-map "^0.6.1" +"@tanstack/query-devtools@5.93.0": + version "5.93.0" + resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.93.0.tgz#517f61d4e2cfb9af671e34ad5e7e871052bca814" + integrity sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg== -"@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== +"@tanstack/react-query-devtools@^5.91.3": + version "5.91.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.91.3.tgz#0f65340fa3f7e7d5575de928ad70cfa6b5f74ff1" + integrity sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA== dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.7.3" + "@tanstack/query-devtools" "5.93.0" -"@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== +"@tanstack/react-query@^5.90.21": + version "5.90.21" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.90.21.tgz#e0eb40831a76510be438109435b8807ef63ab1b9" + integrity sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg== dependencies: - "@types/anymatch" "*" - "@types/node" "*" - "@types/tapable" "*" - "@types/uglify-js" "*" - "@types/webpack-sources" "*" - source-map "^0.6.0" + "@tanstack/query-core" "5.90.20" -"@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== +"@tanstack/react-table@^8.21.3": + version "8.21.3" + resolved "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz" + integrity sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww== dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - -"@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== + "@tanstack/table-core" "8.21.3" -"@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== +"@tanstack/table-core@8.21.3": + version "8.21.3" + resolved "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz" + integrity sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg== -"@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== +"@testing-library/dom@^10.4.1": + version "10.4.1" + resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz" + 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.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz" + 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" -"@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== +"@testing-library/react@^16.3.2": + version "16.3.2" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.2.tgz#672883b7acb8e775fc0492d9e9d25e06e89786d0" + integrity sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g== dependencies: - "@webassemblyjs/wast-printer" "1.9.0" + "@babel/runtime" "^7.12.5" -"@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/aria-query@^5.0.1": + version "5.0.4" + resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== -"@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/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== dependencies: - "@webassemblyjs/ast" "1.9.0" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" -"@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/babel__generator@*": + version "7.27.0" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" -"@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/babel__template@*": + version "7.4.4" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== 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" + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.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/babel__traverse@*": + version "7.28.0" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== dependencies: - "@xtuc/ieee754" "^1.2.0" + "@babel/types" "^7.28.2" -"@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/chai@^5.2.2": + version "5.2.2" + resolved "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz" + integrity sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg== dependencies: - "@xtuc/long" "4.2.2" + "@types/deep-eql" "*" -"@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== +"@types/country-flag-icons@^1.2.2": + version "1.2.2" + resolved "https://registry.npmjs.org/@types/country-flag-icons/-/country-flag-icons-1.2.2.tgz" + integrity sha512-CefEn/J336TBDp7NX8JqzlDtCBOsm8M3r1Li0gEOt0HOMHF1XemNyrx9lSHjsafcb1yYWybU0N8ZAXuyCaND0w== -"@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== - 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" - -"@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/debug@^4.0.0": + version "4.1.12" + resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== 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" + "@types/ms" "*" -"@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== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== -"@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== +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== 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" + "@types/estree" "*" -"@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== +"@types/estree@*", "@types/estree@1.0.8", "@types/estree@^1.0.0": + version "1.0.8" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/hast@^2.0.0": + version "2.3.10" + resolved "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz" + integrity sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw== 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" + "@types/unist" "^2" -"@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== +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" + "@types/unist" "*" -"@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== +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.7" + resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz" + integrity sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g== + dependencies: + hoist-non-react-statics "^3.3.0" -"@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== +"@types/humps@^2.0.6": + version "2.0.6" + resolved "https://registry.npmjs.org/@types/humps/-/humps-2.0.6.tgz" + integrity sha512-Fagm1/a/1J9gDKzGdtlPmmTN5eSw/aaTzHtj740oSfo+MODsSY2WglxMmhTdOglC8nxqUhGGQ+5HfVtBvxo3Kg== -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== +"@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" -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== +"@types/ms@*": + version "2.1.0" + resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== -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== +"@types/node@*", "@types/node@>=20.0.0": + version "25.0.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.10.tgz#4864459c3c9459376b8b75fd051315071c8213e7" + integrity sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg== dependencies: - acorn "^7.0.0" - acorn-walk "^7.0.0" - xtend "^4.0.2" + undici-types "~7.16.0" -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== +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== -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== +"@types/prismjs@^1.0.0": + version "1.26.5" + resolved "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz" + integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ== -acorn@^6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" - integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== +"@types/prop-types@^15.7.12": + version "15.7.15" + resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz" + integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== -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== +"@types/react-dom@^19.2.3": + version "19.2.3" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz" + integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== -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== +"@types/react-table@^7.7.20": + version "7.7.20" + resolved "https://registry.npmjs.org/@types/react-table/-/react-table-7.7.20.tgz" + integrity sha512-ahMp4pmjVlnExxNwxyaDrFgmKxSbPwU23sGQw2gJK4EhCvnvmib2s/O/+y1dfV57dXOwpr2plfyBol+vEHbi2w== dependencies: - debug "4" + "@types/react" "*" -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== - dependencies: - humanize-ms "^1.2.1" +"@types/react-transition-group@^4.4.0", "@types/react-transition-group@^4.4.6": + version "4.4.12" + resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz" + integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== -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== +"@types/react@*", "@types/react@>=16.9.11", "@types/react@^19.2.14": + version "19.2.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.14.tgz#39604929b5e3957e3a6fa0001dafb17c7af70bad" + integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" + csstype "^3.2.2" -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= +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== -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" +"@types/unist@^2", "@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + +"@types/warning@^3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz" + integrity sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q== -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== +"@types/whatwg-mimetype@^3.0.2": + version "3.0.2" + resolved "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz" + integrity sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA== -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== +"@types/ws@^8.18.1": + version "8.18.1" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" + integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== 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= + "@types/node" "*" -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== +"@uiw/react-textarea-code-editor@^3.1.1": + version "3.1.1" + resolved "https://registry.npmjs.org/@uiw/react-textarea-code-editor/-/react-textarea-code-editor-3.1.1.tgz" + integrity sha512-AERRbp/d85vWR+UPgsB5hEgerNXuyszdmhWl2fV2H2jN63jgOobwEnjIpb76Vwy8SaGa/AdehaoJX2XZgNXtJA== + dependencies: + "@babel/runtime" "^7.18.6" + rehype "~13.0.0" + rehype-prism-plus "2.0.0" -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== +"@ungap/structured-clone@^1.0.0": + version "1.3.0" + resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@vitejs/plugin-react@^5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz#5b477e060bf612a7394c4febacc5de33a219b0e4" + integrity sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA== + dependencies: + "@babel/core" "^7.29.0" + "@babel/plugin-transform-react-jsx-self" "^7.27.1" + "@babel/plugin-transform-react-jsx-source" "^7.27.1" + "@rolldown/pluginutils" "1.0.0-rc.3" + "@types/babel__core" "^7.20.5" + react-refresh "^0.18.0" + +"@vitest/expect@4.0.18": + version "4.0.18" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.0.18.tgz#361510d99fbf20eb814222e4afcb8539d79dc94d" + integrity sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ== + dependencies: + "@standard-schema/spec" "^1.0.0" + "@types/chai" "^5.2.2" + "@vitest/spy" "4.0.18" + "@vitest/utils" "4.0.18" + chai "^6.2.1" + tinyrainbow "^3.0.3" + +"@vitest/mocker@4.0.18": + version "4.0.18" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.0.18.tgz#b9735da114ef65ea95652c5bdf13159c6fab4865" + integrity sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ== + dependencies: + "@vitest/spy" "4.0.18" + estree-walker "^3.0.3" + magic-string "^0.30.21" + +"@vitest/pretty-format@4.0.18": + version "4.0.18" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.0.18.tgz#fbccd4d910774072ec15463553edb8ca5ce53218" + integrity sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw== + dependencies: + tinyrainbow "^3.0.3" + +"@vitest/runner@4.0.18": + version "4.0.18" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.0.18.tgz#c2c0a3ed226ec85e9312f9cc8c43c5b3a893a8b1" + integrity sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw== + dependencies: + "@vitest/utils" "4.0.18" + pathe "^2.0.3" + +"@vitest/snapshot@4.0.18": + version "4.0.18" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.0.18.tgz#bcb40fd6d742679c2ac927ba295b66af1c6c34c5" + integrity sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA== + dependencies: + "@vitest/pretty-format" "4.0.18" + magic-string "^0.30.21" + pathe "^2.0.3" + +"@vitest/spy@4.0.18": + version "4.0.18" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.0.18.tgz#ba0f20503fb6d08baf3309d690b3efabdfa88762" + integrity sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw== + +"@vitest/utils@4.0.18": + version "4.0.18" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.0.18.tgz#9636b16d86a4152ec68a8d6859cff702896433d4" + integrity sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA== + dependencies: + "@vitest/pretty-format" "4.0.18" + 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" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" 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@^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== - dependencies: - color-convert "^1.9.0" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -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== +aria-query@5.3.0, aria-query@^5.0.0: + version "5.3.0" + resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== dependencies: - "@types/color-name" "^1.1.1" - color-convert "^2.0.1" + dequal "^2.0.3" -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== +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" -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" +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== -"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== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +bootstrap@5.3.7: + version "5.3.7" + resolved "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz" + integrity sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw== -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== +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" + fill-range "^7.1.1" -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== +browserslist@^4.24.0: + version "4.25.4" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz" + integrity sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg== dependencies: - sprintf-js "~1.0.2" + caniuse-lite "^1.0.30001737" + electron-to-chromium "^1.5.211" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" -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== +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" -arr-union@^3.1.0: +callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -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" +caniuse-lite@^1.0.30001737: + version "1.0.30001739" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz" + integrity sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA== -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== +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -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= +chai@^6.2.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e" + integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg== -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= +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== -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= +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== -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" +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== -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== +chokidar@^4.0.0, chokidar@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== 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= + readdirp "^4.0.1" -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== +classnames@^2.3.2, classnames@^2.5.1: + version "2.5.1" + resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== -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== +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== -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= +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== -async@~0.2.6: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E= +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== -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== - -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" +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -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" +cookie@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz" + integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== -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== +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== dependencies: - backbone.radio "^2.0.0" + "@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" -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= +country-flag-icons@^1.6.13: + version "1.6.13" + resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.6.13.tgz#744de466baf5d8189942ba08fc57167f74037282" + integrity sha512-mR9GoTXtj3zAXoZXBkb3J4QvyDllFEPtEfZvHb9U23TAHYXfkJyP03pRtZiR0spxo6Ibja3R/hn6a68T4LHBuA== -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" +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== -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== +csstype@^3.0.2, csstype@^3.2.2: + version "3.2.3" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== -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== +date-fns@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz" + integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== -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== +debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" -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== +decimal.js@^10.6.0: + version "10.6.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" + integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== -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== +decode-named-character-reference@^1.0.0: + version "1.2.0" + resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz" + integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q== dependencies: - file-uri-to-path "1.0.0" + character-entities "^2.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== +decode-uri-component@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz" + integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ== -bn.js@^5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0" - integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA== +deepmerge@^2.1.1: + version "2.2.1" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== -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= +dequal@^2.0.0, dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -bootstrap@^4.0.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.1.tgz#f7322c7dd3e6376d430efc0c3f57e4d8005eb5b2" - integrity sha512-bxUooHBSbvefnIZfjD0LE8nfdPKrtiFy2sgrxQwUZ0UpFzpjVbVMUxaGIoo9XWT4B2LG1HX6UQg0UMOakT0prQ== +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== -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== +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== dependencies: - fill-range "^7.0.1" + dequal "^2.0.0" -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= +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== -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" +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== -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== +dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: + version "5.2.1" + resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" -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, browserify-rsa@^4.0.1: - 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-sign@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" - integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== - dependencies: - bn.js "^5.1.1" - browserify-rsa "^4.0.1" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.5.3" - inherits "^2.0.4" - parse-asn1 "^5.1.5" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -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== +electron-to-chromium@^1.5.211: + version "1.5.213" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz" + integrity sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q== -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= +entities@^6.0.0, entities@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== -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== +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" + is-arrayish "^0.2.1" -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" +es-module-lexer@^1.7.0: + version "1.7.0" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== -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== +esbuild@^0.27.0: + version "0.27.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.2.tgz#d83ed2154d5813a5367376bb2292a9296fc83717" + integrity sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.27.2" + "@esbuild/android-arm" "0.27.2" + "@esbuild/android-arm64" "0.27.2" + "@esbuild/android-x64" "0.27.2" + "@esbuild/darwin-arm64" "0.27.2" + "@esbuild/darwin-x64" "0.27.2" + "@esbuild/freebsd-arm64" "0.27.2" + "@esbuild/freebsd-x64" "0.27.2" + "@esbuild/linux-arm" "0.27.2" + "@esbuild/linux-arm64" "0.27.2" + "@esbuild/linux-ia32" "0.27.2" + "@esbuild/linux-loong64" "0.27.2" + "@esbuild/linux-mips64el" "0.27.2" + "@esbuild/linux-ppc64" "0.27.2" + "@esbuild/linux-riscv64" "0.27.2" + "@esbuild/linux-s390x" "0.27.2" + "@esbuild/linux-x64" "0.27.2" + "@esbuild/netbsd-arm64" "0.27.2" + "@esbuild/netbsd-x64" "0.27.2" + "@esbuild/openbsd-arm64" "0.27.2" + "@esbuild/openbsd-x64" "0.27.2" + "@esbuild/openharmony-arm64" "0.27.2" + "@esbuild/sunos-x64" "0.27.2" + "@esbuild/win32-arm64" "0.27.2" + "@esbuild/win32-ia32" "0.27.2" + "@esbuild/win32-x64" "0.27.2" + +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -camel-case@3.0.x: +estree-util-is-identifier-name@^3.0.0: 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" + resolved "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== -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== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== dependencies: - pascal-case "^3.1.1" - tslib "^1.10.0" + "@types/estree" "^1.0.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" +expect-type@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz" + integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA== -camelcase@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -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== +ez-modal-react@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/ez-modal-react/-/ez-modal-react-1.0.5.tgz" + integrity sha512-/A8yLK54tpmWCMkW8Pwqc2xxspmimGOOw/m+1Y+tNtUIheuDHhLynHP1Q0utciJEGDAK849aQcd+6DrJ88hggQ== -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== +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -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" +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== -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: - 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" - -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= - -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== - -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.1.2, 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-hmac@^1.1.0, create-hmac@^1.1.4, 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: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -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-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: - 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" - -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: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== - -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== - dependencies: - domelementtype "1" - -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - -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== - dependencies: - dom-serializer "0" - domelementtype "1" - -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== - dependencies: - no-case "^3.0.3" - tslib "^1.10.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" - -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: - version "6.6.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.0.tgz#5919ec723286c1edf28685aa89261d4761afa210" - integrity sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA== - 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: - version "2.2.1" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - -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: - 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== - -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== - dependencies: - prr "~1.0.1" - -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== - 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-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.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== - -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: - version "3.2.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" - integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== - -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== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -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" - -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: - 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" - -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" - -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: - 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" - -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== - 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== - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.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-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== - -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-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" - -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-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-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== - -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= - 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@^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" - -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= - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -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= - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.1" - -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== - dependencies: - parse-passwd "^1.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== - dependencies: - lru-cache "^6.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== - 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" - -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== - 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" - -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== - 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" - -htmlparser2@^3.3.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== - 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" - -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== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -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== - dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" - -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== - dependencies: - agent-base "6" - debug "4" - -humanize-ms@^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" - -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" - -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== - 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= - -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= - dependencies: - once "^1.3.0" - wrappy "1" - -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: - 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-address@^9.0.5: - version "9.0.5" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" - integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== - dependencies: - jsbn "1.1.0" - sprintf-js "^1.1.3" - -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= - 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-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= - -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= - dependencies: - binary-extensions "^1.0.0" - -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-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-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: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -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== - 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-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-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= - -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== - -"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" - -jsbn@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" - integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== - -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== - -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" - -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= - -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== - -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" - -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: - 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= - -loose-envify@^1.0.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" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - 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== - dependencies: - yallist "^4.0.0" - -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== - dependencies: - pify "^4.0.1" - semver "^5.6.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" - -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: - 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== - dependencies: - loader-utils "^1.2.3" - messageformat-convert "^0.3.1" - yaml "^1.6.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== - dependencies: - make-plural "^4.3.0" - messageformat-formatters "^2.0.1" - messageformat-parser "^4.1.2" - -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== - 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" - -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== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - -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== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -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== - -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: - 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== - -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== - -nan@^2.17.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0" - integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== - -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== - 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" - -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: - 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" - -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-asn1@^5.0.0, parse-asn1@^5.1.5: - version "5.1.5" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" - integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== - 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-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - 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= - -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== - dependencies: - no-case "^3.0.3" - tslib "^1.10.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: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -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: - 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.1" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" - integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -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= - -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== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== 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== + to-regex-range "^5.0.1" -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" +filter-obj@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz" + integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng== -prelude-ls@~1.1.2: +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +formik@^2.4.9: + version "2.4.9" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.9.tgz#7e5b81e9c9e215d0ce2ac8fed808cf7fba0cd204" + integrity sha512-5nI94BMnlFDdQRBY4Sz39WkhxajZJ57Fzs8wVbtsQlm5ScKIR1QLYqv/ultBnobObtlUyxpxoLodpixrsf36Og== + dependencies: + "@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" + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^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= + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -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= +generate-password-browser@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/generate-password-browser/-/generate-password-browser-1.1.0.tgz" + integrity sha512-qsQve0rVbCqGqAfKgZwjxKUfI1d1nyd22dz+kE8gn1iw1LxGkR+Slsl79XXfm2wxuK27IkopTs5KXcOEQnhg0w== 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: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + buffer "^6.0.3" + randombytes "^2.0.5" -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= +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -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== +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + +happy-dom@^20.6.1: + version "20.6.1" + resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-20.6.1.tgz#af9cf1722871621334e0451b01548d551c91b515" + integrity sha512-+0vhESXXhFwkdjZnJ5DlmJIfUYGgIEEjzIjB+aKJbFuqlvvKyOi+XkI1fYbgYR9QCxG5T08koxsQ6HrQfa5gCQ== + dependencies: + "@types/node" ">=20.0.0" + "@types/whatwg-mimetype" "^3.0.2" + "@types/ws" "^8.18.1" + entities "^6.0.1" + whatwg-mimetype "^3.0.0" + ws "^8.18.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: - err-code "^2.0.2" - retry "^0.12.0" + function-bind "^1.1.2" -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== +hast-util-from-html@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz" + integrity sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw== + dependencies: + "@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" + +hast-util-from-parse5@^8.0.0: + version "8.0.3" + resolved "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz" + integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg== + dependencies: + "@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" + +hast-util-parse-selector@^3.0.0: + version "3.1.1" + resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz" + integrity sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA== dependencies: - asap "~2.0.3" - -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= + "@types/hast" "^2.0.0" -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: - version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== 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" + "@types/hast" "^3.0.0" -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== +hast-util-to-html@^9.0.0: + version "9.0.5" + resolved "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz" + integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw== + dependencies: + "@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" + +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.6" + resolved "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz" + integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== + dependencies: + "@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" + +hast-util-to-string@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz" + integrity sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A== dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" + "@types/hast" "^3.0.0" -pump@^3.0.0: +hast-util-whitespace@^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== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - -punycode@^1.2.4: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -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" - -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= - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.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: - 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== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -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@^0.14.0: - version "0.14.9" - resolved "https://registry.yarnpkg.com/react/-/react-0.14.9.tgz#9110a6497c49d44ba1c0edd317aec29c2e0d91d1" - integrity sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE= + resolved "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== dependencies: - envify "^3.0.0" - fbjs "^0.6.1" + "@types/hast" "^3.0.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= - -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== +hastscript@^7.0.0: + version "7.2.0" + resolved "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz" + integrity sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw== dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" + "@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" -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" - -"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== - 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" - -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== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -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== +hastscript@^9.0.0: + version "9.0.1" + resolved "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz" + integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w== dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" + "@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" -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== +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.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: - picomatch "^2.2.1" + react-is "^16.7.0" -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" +html-url-attributes@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz" + integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== -redent@^3.0.0: +html-void-elements@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" + resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== -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== +humps@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz" + integrity sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g== -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== +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -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== - dependencies: - babel-runtime "^6.18.0" - babel-types "^6.19.0" - private "^0.1.6" +immutable@^5.0.2: + version "5.1.3" + resolved "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz" + integrity sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg== -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== +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" + parent-module "^1.0.0" + resolve-from "^4.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== +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -regexpu-core@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" - integrity sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA= - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" +inline-style-parser@0.2.4: + version "0.2.4" + resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz" + integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q== -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== +intl-messageformat@11.1.2: + version "11.1.2" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-11.1.2.tgz#c72879165d15633f38b092479822c5d831c21ac5" + integrity sha512-ucSrQmZGAxfiBHfBRXW/k7UC8MaGFlEj4Ry1tKiDcmgwQm1y3EDl40u+4VNHYomxJQMJi9NEI3riDRlth96jKg== dependencies: - rc "^1.2.8" + "@formatjs/ecma402-abstract" "3.1.1" + "@formatjs/fast-memoize" "3.1.0" + "@formatjs/icu-messageformat-parser" "3.5.1" + tslib "^2.8.1" -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== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== dependencies: - rc "^1.2.8" + loose-envify "^1.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= +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== -regjsparser@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" - integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== dependencies: - jsesc "~0.5.0" + is-alphabetical "^2.0.0" + is-decimal "^2.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= +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -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== +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== 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= + hasown "^2.0.2" -repeating@^2.0.0: +is-decimal@^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" + resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== -require-directory@^2.1.1: +is-extglob@^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" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -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= +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 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= + is-extglob "^2.1.1" -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== +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== -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= +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -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== - dependencies: - path-parse "^1.0.6" +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== -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" +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -restore-cursor@^3.1.0: +jsesc@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -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.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== + resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== -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== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -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" +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -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" +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -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.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== +lodash-es@^4.17.21: + version "4.17.23" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.23.tgz#58c4360fd1b5d33afc6c0bbd3d1149349b1138e0" + integrity sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg== -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== +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -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= - dependencies: - ret "~0.1.10" +lodash@^4.17.21: + version "4.17.23" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" + integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -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" +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== -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== +loose-envify@^1.0.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" + js-tokens "^3.0.0 || ^4.0.0" -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== +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" + yallist "^3.0.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" +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== -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== +magic-string@^0.30.21: + 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: - 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: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + "@jridgewell/sourcemap-codec" "^1.5.5" -semver@^7.3.2, semver@^7.3.4: - 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== +mdast-util-from-markdown@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz" + integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== + dependencies: + "@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" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== dependencies: - lru-cache "^6.0.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" -semver@^7.3.5: - version "7.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== +mdast-util-mdx-jsx@^3.0.0: + version "3.2.0" + resolved "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + dependencies: + "@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" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== dependencies: - lru-cache "^6.0.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" -serialize-javascript@^2.1.2: +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-hast@^13.0.0: + version "13.2.0" + resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz" + integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== + dependencies: + "@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" + +mdast-util-to-markdown@^2.0.0: 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== + resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz" + 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.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== dependencies: - randombytes "^2.1.0" + "@types/mdast" "^4.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= +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== -set-value@^2.0.0, set-value@^2.0.1: +micromark-core-commonmark@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz" + 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/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== 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= + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -sha.js@^2.4.0, 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== +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -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= +micromark-factory-space@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== dependencies: - shebang-regex "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-types "^2.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== +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== 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== + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -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== +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== 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== + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -snapdragon-node@^2.0.1: +micromark-util-character@^2.0.0: 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: - version "2.8.3" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" - integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== - dependencies: - ip-address "^9.0.5" - smart-buffer "^4.2.0" - -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= + resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== dependencies: - is-plain-obj "^1.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -source-list-map@^2.0.0: +micromark-util-chunked@^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-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: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -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== + resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" + micromark-util-symbol "^2.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== +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz" + 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" -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== +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.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== +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" -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== +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== dependencies: - extend-shallow "^3.0.0" + 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" -sprintf-js@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== -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= +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== -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== +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== dependencies: - figgy-pudding "^3.5.1" + micromark-util-symbol "^2.0.0" -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== +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== dependencies: - minipass "^3.1.1" + micromark-util-types "^2.0.0" -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== +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== dependencies: - minipass "^3.1.1" + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" -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= +micromark-util-subtokenize@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.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" +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== -stream-browserify@^2.0.1: +micromark-util-types@^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== - 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" + resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== -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" +micromark@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz" + 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.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" -stream-shift@^1.0.0: +min-indent@^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== + resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -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= +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -"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" +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== -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" +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== -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" +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== -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== +npm-run-path@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz" + integrity sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA== dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" + path-key "^4.0.0" + unicorn-magic "^0.3.0" -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" +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -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" +obug@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/obug/-/obug-2.1.1.tgz#2cba74ff241beb77d63055ddf4cd1e9f90b538be" + integrity sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ== -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== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: - safe-buffer "~5.1.0" + callsites "^3.0.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= +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== dependencies: - ansi-regex "^2.0.0" + "@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" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: +parse-json@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: - ansi-regex "^4.1.0" + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" -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" +parse-numeric-range@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz" + integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== -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== +parse5@^7.0.0: + version "7.3.0" + resolved "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== dependencies: - ansi-regex "^5.0.1" + entities "^6.0.0" -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== -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== +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -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= +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -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== - dependencies: - loader-utils "^2.0.0" - schema-utils "^2.6.6" +pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== -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= +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -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== - dependencies: - has-flag "^3.0.0" +picocolors@1.1.1, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -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" +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -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" +picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== -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" +postcss-simple-vars@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz" + integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A== -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== +postcss@^8.5.6: + version "8.5.6" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" -"tabler-ui@git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813": - version "0.0.31" - resolved "git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813" +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== dependencies: - bootstrap "^4.0.0" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" -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== +prop-types-extra@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz" + integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== dependencies: - setimmediate "^1.0.4" + react-is "^16.3.2" + warning "^4.0.0" -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== 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= + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" -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= +property-information@^6.0.0: + version "6.5.0" + resolved "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz" + integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== -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= +property-information@^7.0.0: + version "7.1.0" + resolved "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz" + integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== -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= +query-string@^9.3.1: + version "9.3.1" + resolved "https://registry.npmjs.org/query-string/-/query-string-9.3.1.tgz" + integrity sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw== dependencies: - kind-of "^3.0.2" + decode-uri-component "^0.4.1" + filter-obj "^5.1.0" + split-on-first "^3.0.0" -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@^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= +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" + performance-now "^2.1.0" -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" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== +randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: - is-number "^7.0.0" + safe-buffer "^5.1.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" +react-bootstrap@^2.10.10: + version "2.10.10" + resolved "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz" + integrity sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ== + dependencies: + "@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" + +react-dom@^19.2.4: + version "19.2.4" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.4.tgz#6fac6bd96f7db477d966c7ec17c1a2b1ad8e6591" + integrity sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ== + dependencies: + scheduler "^0.27.0" + +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + +react-intl@^8.1.3: + version "8.1.3" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-8.1.3.tgz#216e0a916c40a535c590995c3103fe9e2525d2e4" + integrity sha512-eL1/d+uQdnapirynOGAriW0K9uAoyarjRGL3V9LaTRuohNSvPgCfJX06EZl5M52h/Hu7Gz7A1sD7dNHcos1lNg== + dependencies: + "@formatjs/ecma402-abstract" "3.1.1" + "@formatjs/icu-messageformat-parser" "3.5.1" + "@formatjs/intl" "4.1.2" + "@types/hoist-non-react-statics" "^3.3.1" + hoist-non-react-statics "^3.3.2" + intl-messageformat "11.1.2" + tslib "^2.8.1" + +react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-markdown@^10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz" + integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ== + dependencies: + "@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" + +react-refresh@^0.18.0: + version "0.18.0" + resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz" + integrity sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw== + +react-router-dom@^7.13.0: + version "7.13.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.13.0.tgz#8b5f7204fadca680f0e94f207c163f0dcd1cfdf5" + integrity sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g== + dependencies: + react-router "7.13.0" + +react-router@7.13.0: + version "7.13.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.13.0.tgz#de9484aee764f4f65b93275836ff5944d7f5bd3b" + integrity sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw== + dependencies: + cookie "^1.0.1" + set-cookie-parser "^2.6.0" + +react-select@^5.10.2: + version "5.10.2" + resolved "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz" + integrity sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ== + dependencies: + "@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" + +react-toastify@^11.0.5: + version "11.0.5" + resolved "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz" + integrity sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA== + dependencies: + clsx "^2.1.1" + +react-transition-group@^4.3.0, react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react@^19.2.4: + version "19.2.4" + resolved "https://registry.yarnpkg.com/react/-/react-19.2.4.tgz#438e57baa19b77cb23aab516cf635cd0579ee09a" + integrity sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ== + +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== -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== +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== dependencies: - nopt "~1.0.10" - -trim-newlines@^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== - -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= + indent-string "^4.0.0" + strip-indent "^3.0.0" -"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== - -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== - -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= - -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= - dependencies: - prelude-ls "~1.1.2" - -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== - -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== - -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== - -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== +refractor@^4.8.0: + version "4.9.0" + resolved "https://registry.npmjs.org/refractor/-/refractor-4.9.0.tgz" + integrity sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og== + dependencies: + "@types/hast" "^2.0.0" + "@types/prismjs" "^1.0.0" + hastscript "^7.0.0" + parse-entities "^4.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== +rehype-parse@^9.0.0: + version "9.0.1" + resolved "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz" + integrity sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag== dependencies: - is-typedarray "^1.0.0" + "@types/hast" "^3.0.0" + hast-util-from-html "^2.0.0" + unified "^11.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= +rehype-prism-plus@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-2.0.0.tgz" + integrity sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ== + dependencies: + 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" + +rehype-stringify@^10.0.0: + version "10.0.1" + resolved "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz" + integrity sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA== + dependencies: + "@types/hast" "^3.0.0" + hast-util-to-html "^9.0.0" + unified "^11.0.0" + +rehype@~13.0.0: + version "13.0.2" + resolved "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz" + integrity sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A== + dependencies: + "@types/hast" "^3.0.0" + rehype-parse "^9.0.0" + rehype-stringify "^10.0.0" + unified "^11.0.0" + +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-rehype@^11.0.0: + version "11.1.2" + resolved "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz" + integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== + dependencies: + "@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" -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== +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -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== +resolve@^1.19.0: + version "1.22.10" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== dependencies: - commander "~2.19.0" - source-map "~0.6.1" + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.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= +rollup@^4.43.0: + version "4.50.0" + resolved "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz" + integrity sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw== dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.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= + "@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" + +rooks@^9.5.0: + version "9.5.0" + resolved "https://registry.yarnpkg.com/rooks/-/rooks-9.5.0.tgz#bbe15fdf64fff44b9a1750cc913de3401572f012" + integrity sha512-AtmaX8yjQkJAW7EXW+UU481bpGwuk455hjD/aEUuy7N7VjvXlNmO8BErQ+jEUQp1DRA/PTWonv+Dq1nEkJdgkw== + dependencies: + fast-deep-equal "^3.1.3" + lodash.debounce "^4.0.8" + raf "^3.4.1" + use-sync-external-store "^1.4.0" + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -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== +sass@^1.97.3: + version "1.97.3" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.97.3.tgz#9cb59339514fa7e2aec592b9700953ac6e331ab2" + integrity sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg== dependencies: - debug "^2.2.0" + chokidar "^4.0.0" + immutable "^5.0.2" + source-map-js ">=0.6.2 <2.0.0" + optionalDependencies: + "@parcel/watcher" "^2.4.1" -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== +scheduler@^0.27.0: + version "0.27.0" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz" + integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== -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" +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= +set-cookie-parser@^2.6.0: + version "2.7.1" + resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz" + integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== -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" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== -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" +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== -unique-slug@^2.0.0: +space-separated-tokens@^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" + resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== -unique-slug@^3.0.0: +split-on-first@^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" + resolved "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz" + integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== -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== +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== -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= +std-env@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b" + integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== -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== +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== 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= + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" -url-parse-lax@^3.0.0: +strip-indent@^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= + resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== dependencies: - prepend-http "^2.0.0" + min-indent "^1.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= +style-to-js@^1.0.0: + version "1.1.18" + resolved "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.18.tgz" + integrity sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg== dependencies: - punycode "1.3.2" - querystring "0.2.0" + style-to-object "1.0.11" -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== +style-to-object@1.0.11: + version "1.0.11" + resolved "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.11.tgz" + integrity sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow== + dependencies: + inline-style-parser "0.2.4" -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= +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== -util.promisify@1.0.0: +supports-preserve-symlinks-flag@^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" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -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" +tiny-invariant@^1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== -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= +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -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== +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== -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== +tinyexec@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.2.tgz#bdd2737fe2ba40bd6f918ae26642f264b99ca251" + integrity sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg== -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== +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" + fdir "^6.5.0" + picomatch "^4.0.3" -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== +tinyrainbow@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz" + integrity sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q== -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" +tmp@^0.2.5: + version "0.2.5" + resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz" + integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== -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== +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 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= + is-number "^7.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= +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== -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== +trough@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz" + integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== + +tsconfck@^3.0.3: + version "3.1.6" + resolved "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz" + integrity sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w== + +tslib@^2.0.0, tslib@^2.8.0, tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +typescript@5.9.3: + version "5.9.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +uncontrollable@^7.2.1: + version "7.2.1" + resolved "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz" + integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== + dependencies: + "@babel/runtime" "^7.6.3" + "@types/react" ">=16.9.11" + invariant "^2.2.4" + react-lifecycles-compat "^3.0.4" + +uncontrollable@^8.0.4: + version "8.0.4" + resolved "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz" + integrity sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ== + +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + +unicorn-magic@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz" + integrity sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA== + +unified@^11.0.0: + version "11.0.5" + resolved "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + dependencies: + "@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" + +unist-util-filter@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-5.0.1.tgz" + integrity sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw== dependencies: - isexe "^2.0.0" + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.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== +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== dependencies: - isexe "^2.0.0" + "@types/unist" "^3.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== +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== dependencies: - string-width "^1.0.2 || 2 || 3 || 4" + "@types/unist" "^3.0.0" -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== +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== 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= + "@types/unist" "^3.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== - -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== +unist-util-visit-parents@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== dependencies: - errno "~0.1.7" + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" -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== +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.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== +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" + escalade "^3.2.0" + picocolors "^1.1.1" -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" +use-isomorphic-layout-effect@^1.2.0: + version "1.2.1" + resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz" + integrity sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA== -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +use-sync-external-store@^1.4.0: + version "1.5.0" + resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz" + integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== -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== +vfile-location@^5.0.0: + version "5.0.3" + resolved "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz" + integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" + "@types/unist" "^3.0.0" + vfile "^6.0.0" -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== +vfile-message@^4.0.0: + version "4.0.3" + resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz" + integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== dependencies: - mkdirp "^0.5.1" + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" -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== +vfile@^6.0.0: + version "6.0.3" + resolved "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.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== +vite-plugin-checker@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.12.0.tgz#1e9688a5a10f5de1fd833bc1351618eed54db3bc" + integrity sha512-CmdZdDOGss7kdQwv73UyVgLPv0FVYe5czAgnmRX2oKljgEvSrODGuClaV3PDR2+3ou7N/OKGauDDBjy2MB07Rg== + 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.15" + vscode-uri "^3.1.0" + +vite-tsconfig-paths@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.1.tgz#d5c28cba79c89ebf76489ef1040024b21df6da3a" + integrity sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg== + dependencies: + debug "^4.1.1" + globrex "^0.1.2" + tsconfck "^3.0.3" + +"vite@^6.0.0 || ^7.0.0", vite@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-7.3.1.tgz#7f6cfe8fb9074138605e822a75d9d30b814d6507" + integrity sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA== + dependencies: + esbuild "^0.27.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.18: + version "4.0.18" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.0.18.tgz#56f966353eca0b50f4df7540cd4350ca6d454a05" + integrity sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ== + dependencies: + "@vitest/expect" "4.0.18" + "@vitest/mocker" "4.0.18" + "@vitest/pretty-format" "4.0.18" + "@vitest/runner" "4.0.18" + "@vitest/snapshot" "4.0.18" + "@vitest/spy" "4.0.18" + "@vitest/utils" "4.0.18" + es-module-lexer "^1.7.0" + expect-type "^1.2.2" + magic-string "^0.30.21" + obug "^2.1.1" + pathe "^2.0.3" + picomatch "^4.0.3" + std-env "^3.10.0" + tinybench "^2.9.0" + tinyexec "^1.0.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.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz" + integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ== -y18n@^4.0.0: +warning@^4.0.0, warning@^4.0.3: 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.npmjs.org/warning/-/warning-4.0.3.tgz" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +web-namespaces@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz" + integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== -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== +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + +ws@^8.18.3: + version "8.19.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" + integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== yallist@^3.0.2: version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" 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.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" +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +zwitch@^2.0.0, zwitch@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== diff --git a/scripts/.common.sh b/scripts/.common.sh index c89dbf4f2d..2b59e6ed5a 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 4da6c16740..8838290af4 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 @@ -23,7 +22,7 @@ docker buildx build \ --build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \ --build-arg GOPROXY="${GOPROXY:-}" \ --build-arg GOPRIVATE="${GOPRIVATE:-}" \ - --platform linux/amd64,linux/arm64,linux/arm/7 \ + --platform linux/amd64,linux/arm64 \ --progress plain \ --pull \ -f docker/Dockerfile \ diff --git a/scripts/ci/frontend-build b/scripts/ci/frontend-build index ef1e75de6a..8b66a13f9f 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 locale-compile && yarn vitest run --no-color && 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 c7255ffb2a..54fa0528d6 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 a09ac9befe..a0de34140a 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 a0c64ad969..f3749f8dc6 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 1309aad45b..b3ead38d23 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 3715588c53..bc166f64d2 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 b972ab805a..c561ac9adf 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 3d27551fee..283b45b61d 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/Dockerfile b/test/cypress/Dockerfile index 9b835fe0ed..a92e6aadb2 100644 --- a/test/cypress/Dockerfile +++ b/test/cypress/Dockerfile @@ -1,4 +1,4 @@ -FROM cypress/included:14.0.1 +FROM cypress/included:15.9.0 # Disable Cypress CLI colors ENV FORCE_COLOR=0 diff --git a/test/cypress/config/ci.js b/test/cypress/config/ci.js deleted file mode 100644 index dc968dbd37..0000000000 --- a/test/cypress/config/ci.js +++ /dev/null @@ -1,22 +0,0 @@ -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://fullstack:81', - } -}); diff --git a/test/cypress/config/ci.mjs b/test/cypress/config/ci.mjs new file mode 100644 index 0000000000..2597c194ce --- /dev/null +++ b/test/cypress/config/ci.mjs @@ -0,0 +1,23 @@ +import { defineConfig } from 'cypress'; +import pluginSetup from '../plugins/index.mjs'; + +export default 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 pluginSetup(on, config); + }, + env: { + swaggerBase: `{{baseUrl}}/api/schema?ts=${Date.now()}`, + }, + baseUrl: "http://fullstack:81", + } +}); diff --git a/test/cypress/config/dev.mjs b/test/cypress/config/dev.mjs new file mode 100644 index 0000000000..f32d973c85 --- /dev/null +++ b/test/cypress/config/dev.mjs @@ -0,0 +1,23 @@ +import { defineConfig } from 'cypress'; +import pluginSetup from '../plugins/index.mjs'; + +export default 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 pluginSetup(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 1e8a6fed44..00da38b224 100644 --- a/test/cypress/e2e/api/Certificates.cy.js +++ b/test/cypress/e2e/api/Certificates.cy.js @@ -5,12 +5,13 @@ describe('Certificates endpoints', () => { let certID; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); }); - it('Validate custom certificate', function() { + it('Validate custom certificate', () => { cy.task('backendApiPostFiles', { token: token, path: '/api/nginx/certificates/validate', @@ -25,7 +26,7 @@ describe('Certificates endpoints', () => { }); }); - it('Custom certificate lifecycle', function() { + it('Custom certificate lifecycle', () => { // Create custom cert cy.task('backendApiPost', { token: token, @@ -73,16 +74,14 @@ describe('Certificates endpoints', () => { }); }); - it('Request Certificate - CVE-2024-46256/CVE-2024-46257', function() { + it('Request Certificate - CVE-2024-46256/CVE-2024-46257', () => { cy.task('backendApiPost', { token: token, path: '/api/nginx/certificates', 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 new file mode 100644 index 0000000000..8da332ab41 --- /dev/null +++ b/test/cypress/e2e/api/Dashboard.cy.js @@ -0,0 +1,26 @@ +/// + +describe('Dashboard endpoints', () => { + let token; + + before(() => { + cy.resetUsers(); + cy.getToken().then((tok) => { + token = tok; + }); + }); + + it('Should be able to get host counts', () => { + cy.task('backendApiGet', { + token: token, + path: '/api/reports/hosts' + }).then((data) => { + cy.validateSwaggerSchema('get', 200, '/reports/hosts', data); + expect(data).to.have.property('dead'); + expect(data).to.have.property('proxy'); + expect(data).to.have.property('redirection'); + expect(data).to.have.property('stream'); + }); + }); + +}); diff --git a/test/cypress/e2e/api/FullCertProvision.cy.js b/test/cypress/e2e/api/FullCertProvision.cy.js index 5ca5692cdf..2c7d9ffd62 100644 --- a/test/cypress/e2e/api/FullCertProvision.cy.js +++ b/test/cypress/e2e/api/FullCertProvision.cy.js @@ -4,12 +4,13 @@ describe('Full Certificate Provisions', () => { let token; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); }); - it('Should be able to create new http certificate', function() { + it('Should be able to create new http certificate', () => { cy.task('backendApiPost', { token: token, path: '/api/nginx/certificates', @@ -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' @@ -32,7 +31,7 @@ describe('Full Certificate Provisions', () => { }); }); - it('Should be able to create new DNS certificate with Powerdns', function() { + it('Should be able to create new DNS certificate with Powerdns', () => { cy.task('backendApiPost', { token: token, path: '/api/nginx/certificates', @@ -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/Health.cy.js b/test/cypress/e2e/api/Health.cy.js index 49881e97b4..0469c70ae6 100644 --- a/test/cypress/e2e/api/Health.cy.js +++ b/test/cypress/e2e/api/Health.cy.js @@ -1,7 +1,7 @@ /// describe('Basic API checks', () => { - it('Should return a valid health payload', function () { + it('Should return a valid health payload', () => { cy.task('backendApiGet', { path: '/api/', }).then((data) => { @@ -10,11 +10,19 @@ describe('Basic API checks', () => { }); }); - it('Should return a valid schema payload', function () { + it('Should return a valid schema payload', () => { cy.task('backendApiGet', { - path: '/api/schema?ts=' + Date.now(), + path: `/api/schema?ts=${Date.now()}`, }).then((data) => { expect(data.openapi).to.be.equal('3.1.0'); }); }); + + it('Should return a valid payload for version check', () => { + cy.task('backendApiGet', { + path: `/api/version/check?ts=${Date.now()}`, + }).then((data) => { + cy.validateSwaggerSchema('get', 200, '/version/check', data); + }); + }); }); diff --git a/test/cypress/e2e/api/Ldap.cy.js b/test/cypress/e2e/api/Ldap.cy.js index 6b7e5f76c6..64e177303d 100644 --- a/test/cypress/e2e/api/Ldap.cy.js +++ b/test/cypress/e2e/api/Ldap.cy.js @@ -1,12 +1,13 @@ /// describe('LDAP with Authentik', () => { - let token; + let _token; if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') { before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { - token = tok; + _token = tok; // cy.task('backendApiPut', { // token: token, @@ -45,7 +46,7 @@ describe('LDAP with Authentik', () => { }); }); - it.skip('Should log in with LDAP', function() { + it.skip('Should log in with LDAP', () => { // cy.task('backendApiPost', { // token: token, // path: '/api/auth', diff --git a/test/cypress/e2e/api/OAuth.cy.js b/test/cypress/e2e/api/OAuth.cy.js index 044bb27573..c5c819f9ba 100644 --- a/test/cypress/e2e/api/OAuth.cy.js +++ b/test/cypress/e2e/api/OAuth.cy.js @@ -1,12 +1,12 @@ /// describe('OAuth with Authentik', () => { - let token; + let _token; if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') { before(() => { cy.getToken().then((tok) => { - token = tok; + _token = tok; // cy.task('backendApiPut', { // token: token, @@ -47,7 +47,7 @@ describe('OAuth with Authentik', () => { }); }); - it.skip('Should log in with OAuth', function() { + it.skip('Should log in with OAuth', () => { // cy.task('backendApiGet', { // path: '/oauth/login?redirect_base=' + encodeURI(Cypress.config('baseUrl')), // }).then((data) => { diff --git a/test/cypress/e2e/api/ProxyHosts.cy.js b/test/cypress/e2e/api/ProxyHosts.cy.js index 5bc6458005..5f437cf950 100644 --- a/test/cypress/e2e/api/ProxyHosts.cy.js +++ b/test/cypress/e2e/api/ProxyHosts.cy.js @@ -4,12 +4,13 @@ describe('Proxy Hosts endpoints', () => { let token; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); }); - it('Should be able to create a http host', function() { + it('Should be able to create a http host', () => { cy.task('backendApiPost', { token: token, path: '/api/nginx/proxy-hosts', @@ -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 6942760c77..a925b2d374 100644 --- a/test/cypress/e2e/api/Settings.cy.js +++ b/test/cypress/e2e/api/Settings.cy.js @@ -4,12 +4,13 @@ describe('Settings endpoints', () => { let token; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); }); - it('Get all settings', function() { + it('Get all settings', () => { cy.task('backendApiGet', { token: token, path: '/api/settings', @@ -19,7 +20,7 @@ describe('Settings endpoints', () => { }); }); - it('Get default-site setting', function() { + it('Get default-site setting', () => { cy.task('backendApiGet', { token: token, path: '/api/settings/default-site', @@ -30,7 +31,7 @@ describe('Settings endpoints', () => { }); }); - it('Default Site congratulations', function() { + it('Default Site congratulations', () => { cy.task('backendApiPut', { token: token, path: '/api/settings/default-site', @@ -46,7 +47,7 @@ describe('Settings endpoints', () => { }); }); - it('Default Site 404', function() { + it('Default Site 404', () => { cy.task('backendApiPut', { token: token, path: '/api/settings/default-site', @@ -62,7 +63,7 @@ describe('Settings endpoints', () => { }); }); - it('Default Site 444', function() { + it('Default Site 444', () => { cy.task('backendApiPut', { token: token, path: '/api/settings/default-site', @@ -78,7 +79,7 @@ describe('Settings endpoints', () => { }); }); - it('Default Site redirect', function() { + it('Default Site redirect', () => { cy.task('backendApiPut', { token: token, path: '/api/settings/default-site', @@ -100,7 +101,7 @@ describe('Settings endpoints', () => { }); }); - it('Default Site html', function() { + it('Default Site html', () => { cy.task('backendApiPut', { token: token, path: '/api/settings/default-site', diff --git a/test/cypress/e2e/api/Streams.cy.js b/test/cypress/e2e/api/Streams.cy.js index 9de43157eb..10809f8948 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 @@ -23,17 +24,17 @@ describe('Streams', () => { // Create a custom cert pair cy.exec('mkcert -cert-file=/test/cypress/fixtures/website1.pem -key-file=/test/cypress/fixtures/website1.key.pem website1.example.com').then((result) => { - expect(result.code).to.eq(0); + expect(result.exitCode).to.eq(0); // Install CA cy.exec('mkcert -install').then((result) => { - expect(result.code).to.eq(0); + expect(result.exitCode).to.eq(0); }); }); cy.exec('rm -f /test/results/testssl.json'); }); - it('Should be able to create TCP Stream', function() { + it('Should be able to create TCP Stream', () => { cy.task('backendApiPost', { token: token, path: '/api/nginx/streams', @@ -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 } @@ -59,13 +56,13 @@ describe('Streams', () => { expect(data).to.have.property('udp_forwarding', false); cy.exec('curl --noproxy -- http://website1.example.com:1500').then((result) => { - expect(result.code).to.eq(0); + expect(result.exitCode).to.eq(0); expect(result.stdout).to.contain('yay it works'); }); }); }); - it('Should be able to create UDP Stream', function() { + it('Should be able to create UDP Stream', () => { cy.task('backendApiPost', { token: token, path: '/api/nginx/streams', @@ -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 } @@ -92,7 +85,7 @@ describe('Streams', () => { }); }); - it('Should be able to create TCP/UDP Stream', function() { + it('Should be able to create TCP/UDP Stream', () => { cy.task('backendApiPost', { token: token, path: '/api/nginx/streams', @@ -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 } @@ -118,13 +107,13 @@ describe('Streams', () => { expect(data).to.have.property('udp_forwarding', true); cy.exec('curl --noproxy -- http://website1.example.com:1502').then((result) => { - expect(result.code).to.eq(0); + expect(result.exitCode).to.eq(0); expect(result.stdout).to.contain('yay it works'); }); }); }); - it('Should be able to create SSL TCP Stream', function() { + it('Should be able to create SSL TCP Stream', () => { let certID = 0; // Create custom cert @@ -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 } @@ -184,13 +169,14 @@ describe('Streams', () => { cy.exec('/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503', { timeout: 120000, // 2 minutes }).then((result) => { - cy.task('log', '[testssl.sh] ' + result.stdout); + cy.task('log', `[testssl.sh] ${result.stdout}`); const allowedSeverities = ["INFO", "OK", "LOW", "MEDIUM"]; const ignoredIDs = [ 'cert_chain_of_trust', 'cert_extlifeSpan', 'cert_revocation', + 'engine_problem', 'overall_grade', ]; @@ -210,4 +196,16 @@ describe('Streams', () => { }); }); + it('Should be able to List Streams', () => { + cy.task('backendApiGet', { + token: token, + path: '/api/nginx/streams?expand=owner,certificate', + }).then((data) => { + cy.validateSwaggerSchema('get', 200, '/nginx/streams', data); + expect(data.length).to.be.greaterThan(0); + expect(data[0]).to.have.property('id'); + expect(data[0]).to.have.property('enabled'); + }); + }); + }); diff --git a/test/cypress/e2e/api/SwaggerSchema.cy.js b/test/cypress/e2e/api/SwaggerSchema.cy.js new file mode 100644 index 0000000000..ba6fc7834e --- /dev/null +++ b/test/cypress/e2e/api/SwaggerSchema.cy.js @@ -0,0 +1,7 @@ +/// + +describe('Swagger Schema Linting', () => { + it('Should be a completely valid schema', () => { + cy.validateSwaggerFile('/api/schema', 'results/swagger-schema.json'); + }); +}); diff --git a/test/cypress/e2e/api/Users.cy.js b/test/cypress/e2e/api/Users.cy.js index 06b1831760..044dcdee9c 100644 --- a/test/cypress/e2e/api/Users.cy.js +++ b/test/cypress/e2e/api/Users.cy.js @@ -4,12 +4,13 @@ describe('Users endpoints', () => { let token; before(() => { + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; }); }); - it('Should be able to get yourself', function() { + it('Should be able to get yourself', () => { cy.task('backendApiGet', { token: token, path: '/api/users/me' @@ -20,7 +21,7 @@ describe('Users endpoints', () => { }); }); - it('Should be able to get all users', function() { + it('Should be able to get all users', () => { cy.task('backendApiGet', { token: token, path: '/api/users' @@ -30,7 +31,7 @@ describe('Users endpoints', () => { }); }); - it('Should be able to update yourself', function() { + it('Should be able to update yourself', () => { cy.task('backendApiPut', { token: token, path: '/api/users/me', diff --git a/test/cypress/plugins/backendApi/client.js b/test/cypress/plugins/backendApi/client.mjs similarity index 59% rename from test/cypress/plugins/backendApi/client.js rename to test/cypress/plugins/backendApi/client.mjs index 6f5f7661ea..78618abb26 100644 --- a/test/cypress/plugins/backendApi/client.js +++ b/test/cypress/plugins/backendApi/client.mjs @@ -1,9 +1,9 @@ -const logger = require('./logger'); -const axios = require('axios').default; +import axios from "axios"; +import logger from "./logger.mjs"; -const BackendApi = function(config, token) { +const BackendApi = function (config, token) { this.config = config; - this.token = token; + this.token = token; this.axios = axios.create({ baseURL: config.baseUrl, @@ -14,26 +14,24 @@ const BackendApi = function(config, token) { /** * @param {string} token */ -BackendApi.prototype.setToken = function(token) { +BackendApi.prototype.setToken = function (token) { this.token = token; }; /** * @param {bool} returnOnError */ -BackendApi.prototype._prepareOptions = function(returnOnError) { - let options = { +BackendApi.prototype._prepareOptions = function (returnOnError) { + const options = { headers: { - Accept: 'application/json' - } - } + Accept: "application/json", + }, + }; if (this.token) { - options.headers.Authorization = 'Bearer ' + this.token; + options.headers.Authorization = `Bearer ${this.token}`; } if (returnOnError) { - options.validateStatus = function () { - return true; - } + options.validateStatus = () => true; } return options; }; @@ -44,13 +42,30 @@ BackendApi.prototype._prepareOptions = function(returnOnError) { * @param {function} reject * @param {bool} returnOnError */ -BackendApi.prototype._handleResponse = function(response, resolve, reject, returnOnError) { - logger('Response data:', response.data); - if (!returnOnError && typeof response.data === 'object' && typeof response.data.error === 'object') { - if (typeof response.data === 'object' && typeof response.data.error === 'object' && typeof response.data.error.message !== 'undefined') { - reject(new Error(response.data.error.code + ': ' + response.data.error.message)); +BackendApi.prototype._handleResponse = ( + response, + resolve, + reject, + returnOnError, +) => { + logger("Response data:", response.data); + if ( + !returnOnError && + typeof response.data === "object" && + typeof response.data.error === "object" + ) { + if ( + typeof response.data === "object" && + typeof response.data.error === "object" && + typeof response.data.error.message !== "undefined" + ) { + reject( + new Error( + `${response.data.error.code}: ${response.data.error.message}`, + ), + ); } else { - reject(new Error('Error ' + response.status)); + reject(new Error(`Error ${response.status}`)); } } else { resolve(response.data); @@ -63,10 +78,10 @@ BackendApi.prototype._handleResponse = function(response, resolve, reject, retur * @param {function} reject * @param {bool} returnOnError */ -BackendApi.prototype._handleError = function(err, resolve, reject, returnOnError) { - logger('Axios Error:', err); +BackendApi.prototype._handleError = (err, resolve, reject, returnOnError) => { + logger("Axios Error:", err); if (returnOnError) { - resolve(typeof err.response.data !== 'undefined' ? err.response.data : err); + resolve(typeof err.response.data !== "undefined" ? err.response.data : err); } else { reject(err); } @@ -84,11 +99,11 @@ BackendApi.prototype.request = function (method, path, returnOnError, data) { const options = this._prepareOptions(returnOnError); return new Promise((resolve, reject) => { - let opts = { + const opts = { method: method, url: path, - ...options - } + ...options, + }; if (data !== undefined && data !== null) { opts.data = data; } @@ -110,16 +125,17 @@ BackendApi.prototype.request = function (method, path, returnOnError, data) { * @returns {Promise} */ BackendApi.prototype.postForm = function (path, form, returnOnError) { - logger('POST', this.config.baseUrl + path); + logger("POST", this.config.baseUrl + path); const options = this._prepareOptions(returnOnError); return new Promise((resolve, reject) => { const opts = { ...options, ...form.getHeaders(), - } + }; - this.axios.post(path, form, opts) + this.axios + .post(path, form, opts) .then((response) => { this._handleResponse(response, resolve, reject, returnOnError); }) @@ -129,4 +145,4 @@ BackendApi.prototype.postForm = function (path, form, returnOnError) { }); }; -module.exports = BackendApi; +export default BackendApi; diff --git a/test/cypress/plugins/backendApi/logger.js b/test/cypress/plugins/backendApi/logger.js deleted file mode 100644 index 8920b86929..0000000000 --- a/test/cypress/plugins/backendApi/logger.js +++ /dev/null @@ -1,7 +0,0 @@ -const _ = require("lodash"); - -module.exports = function() { - let arr = _.values(arguments); - arr.unshift('[Backend API]'); - console.log.apply(null, arr); -}; diff --git a/test/cypress/plugins/backendApi/logger.mjs b/test/cypress/plugins/backendApi/logger.mjs new file mode 100644 index 0000000000..613bc478c6 --- /dev/null +++ b/test/cypress/plugins/backendApi/logger.mjs @@ -0,0 +1,7 @@ +const log = (...args) => { + const arr = args; + arr.unshift("[Backend API]"); + console.log(...arr); +}; + +export default log; diff --git a/test/cypress/plugins/backendApi/task.js b/test/cypress/plugins/backendApi/task.mjs similarity index 74% rename from test/cypress/plugins/backendApi/task.js rename to test/cypress/plugins/backendApi/task.mjs index ab9704f4a6..edc5015c69 100644 --- a/test/cypress/plugins/backendApi/task.js +++ b/test/cypress/plugins/backendApi/task.mjs @@ -1,13 +1,12 @@ -const fs = require('fs'); -const FormData = require('form-data'); -const logger = require('./logger'); -const Client = require('./client'); +import fs from "node:fs"; +import FormData from "form-data"; +import Client from "./client.mjs"; +import logger from "./logger.mjs"; -module.exports = function (config) { - logger('Client Ready using', config.baseUrl); +export default (config) => { + logger("Client Ready using", config.baseUrl); return { - /** * @param {object} options * @param {string} options.path API path @@ -18,7 +17,7 @@ module.exports = function (config) { backendApiGet: (options) => { const api = new Client(config); api.setToken(options.token); - return api.request('get', options.path, options.returnOnError || false); + return api.request("get", options.path, options.returnOnError || false); }, /** @@ -32,7 +31,12 @@ module.exports = function (config) { backendApiPost: (options) => { const api = new Client(config); api.setToken(options.token); - return api.request('post', options.path, options.returnOnError || false, options.data); + return api.request( + "post", + options.path, + options.returnOnError || false, + options.data, + ); }, /** @@ -48,8 +52,11 @@ module.exports = function (config) { api.setToken(options.token); const form = new FormData(); - for (let [key, value] of Object.entries(options.files)) { - form.append(key, fs.createReadStream(config.fixturesFolder + '/' + value)); + for (const [key, value] of Object.entries(options.files)) { + form.append( + key, + fs.createReadStream(`${config.fixturesFolder}/${value}`), + ); } return api.postForm(options.path, form, options.returnOnError || false); }, @@ -65,7 +72,12 @@ module.exports = function (config) { backendApiPut: (options) => { const api = new Client(config); api.setToken(options.token); - return api.request('put', options.path, options.returnOnError || false, options.data); + return api.request( + "put", + options.path, + options.returnOnError || false, + options.data, + ); }, /** @@ -78,7 +90,11 @@ module.exports = function (config) { backendApiDelete: (options) => { const api = new Client(config); api.setToken(options.token); - return api.request('delete', options.path, options.returnOnError || false); - } + return api.request( + "delete", + options.path, + options.returnOnError || false, + ); + }, }; }; diff --git a/test/cypress/plugins/index.js b/test/cypress/plugins/index.js deleted file mode 100644 index 2f3e3c5af3..0000000000 --- a/test/cypress/plugins/index.js +++ /dev/null @@ -1,20 +0,0 @@ -const { SwaggerValidation } = require('@jc21/cypress-swagger-validation'); - -module.exports = (on, config) => { - // Replace swaggerBase config var wildcard - if (typeof config.env.swaggerBase !== 'undefined') { - config.env.swaggerBase = config.env.swaggerBase.replace('{{baseUrl}}', config.baseUrl); - } - - // Plugin Events - on('task', SwaggerValidation(config)); - on('task', require('./backendApi/task')(config)); - on('task', { - log(message) { - console.log(message); - return null; - } - }); - - return config; -}; diff --git a/test/cypress/plugins/index.mjs b/test/cypress/plugins/index.mjs new file mode 100644 index 0000000000..1058d63381 --- /dev/null +++ b/test/cypress/plugins/index.mjs @@ -0,0 +1,27 @@ +import { SwaggerValidation } from "@jc21/cypress-swagger-validation"; +import chalk from "chalk"; +import backendTask from "./backendApi/task.mjs"; + +export default (on, config) => { + // Replace swaggerBase config var wildcard + if (typeof config.env.swaggerBase !== "undefined") { + config.env.swaggerBase = config.env.swaggerBase.replace( + "{{baseUrl}}", + config.baseUrl, + ); + } + + // Plugin Events + on("task", SwaggerValidation(config)); + on("task", backendTask(config)); + on("task", { + log(message) { + console.log( + `${chalk.cyan.bold("[")}${chalk.blue.bold("LOG")}${chalk.cyan.bold("]")} ${chalk.red.bold(message)}`, + ); + return null; + }, + }); + + return config; +}; diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js deleted file mode 100644 index 7d602ecb0b..0000000000 --- a/test/cypress/support/commands.js +++ /dev/null @@ -1,70 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// - -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++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; -}); - -/** - * Check the swagger schema: - * - * @param {string} method API Method in swagger doc, "get", "put", "post", "delete" - * @param {integer} code Swagger doc endpoint response code, exactly as defined in swagger doc - * @param {string} path Swagger doc endpoint path, exactly as defined in swagger doc - * @param {*} data The API response data to check against the swagger schema - */ -Cypress.Commands.add('validateSwaggerSchema', (method, code, path, data) => { - cy.task('validateSwaggerSchema', { - file: Cypress.env('swaggerBase'), - endpoint: path, - method: method, - statusCode: code, - responseSchema: data, - verbose: true - }).should('equal', null); -}); - -Cypress.Commands.add('getToken', () => { - // login with existing user - cy.task('backendApiPost', { - path: '/api/tokens', - data: { - identity: 'admin@example.com', - secret: 'changeme' - } - }).then(res => { - cy.wrap(res.token); - }); -}); - -// TODO: copied from v3, is this usable? -Cypress.Commands.add('waitForCertificateStatus', (token, certID, expected, timeout = 60) => { - cy.log(`Waiting for certificate (${certID}) status (${expected}) timeout (${timeout})`); - - cy.waitUntil(() => cy.task('backendApiGet', { - token: token, - path: `/api/certificates/${certID}` - }).then((data) => { - return data.result.status === expected; - }), { - errorMsg: 'Waiting for certificate status failed', - timeout: timeout * 1000, - interval: 5000 - }); -}); diff --git a/test/cypress/support/commands.mjs b/test/cypress/support/commands.mjs new file mode 100644 index 0000000000..7d9224d09b --- /dev/null +++ b/test/cypress/support/commands.mjs @@ -0,0 +1,153 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// + +import 'cypress-wait-until'; + +Cypress.Commands.add('randomString', (length) => { + 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; +}); + +/** + * Check the swagger schema file: + * + * @param {string} url + * @param {string} savePath + */ +Cypress.Commands.add("validateSwaggerFile", (url, savePath) => { + cy.task('log', `validateSwaggerFile: ${url} -- ${savePath}`) + .then(() => { + return cy + .request(url) + .then((response) => cy.writeFile(savePath, response.body, { log: false })) + .then(() => cy.exec(`yarn swagger-lint '${savePath}'`, { failOnNonZeroExit: false })) + .then((result) => cy.task('log', `Swagger Vacuum Results:\n${result.stdout || ''}`) + .then(() => expect(result.exitCode).to.eq(0))); + }); +}); + +/** + * Check the swagger schema for a specific endpoint: + * + * @param {string} method API Method in swagger doc, "get", "put", "post", "delete" + * @param {integer} code Swagger doc endpoint response code, exactly as defined in swagger doc + * @param {string} path Swagger doc endpoint path, exactly as defined in swagger doc + * @param {*} data The API response data to check against the swagger schema + */ +Cypress.Commands.add('validateSwaggerSchema', (method, code, path, data) => { + cy.task('validateSwaggerSchema', { + file: Cypress.env('swaggerBase'), + endpoint: path, + method: method, + statusCode: code, + responseSchema: data, + verbose: true + }).should('equal', null); +}); + +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 = {}; + } + 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); + }); +}); + +// TODO: copied from v3, is this usable? +Cypress.Commands.add('waitForCertificateStatus', (token, certID, expected, timeout = 60) => { + cy.log(`Waiting for certificate (${certID}) status (${expected}) timeout (${timeout})`); + + cy.waitUntil(() => cy.task('backendApiGet', { + token: token, + path: `/api/certificates/${certID}` + }).then((data) => { + return data.result.status === expected; + }), { + errorMsg: 'Waiting for certificate status failed', + timeout: timeout * 1000, + interval: 5000 + }); +}); diff --git a/test/cypress/support/e2e.js b/test/cypress/support/e2e.js index 9f724bf419..2d8a13a76b 100644 --- a/test/cypress/support/e2e.js +++ b/test/cypress/support/e2e.js @@ -1,4 +1,4 @@ -import './commands'; +import './commands.mjs'; Cypress.on('uncaught:exception', (/*err, runnable*/) => { // returning false here prevents Cypress from diff --git a/test/jsconfig.json b/test/jsconfig.json index e7ebe3026d..9caeeb9149 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -1,6 +1,13 @@ { "include": [ "./node_modules/cypress", - "cypress/**/*.js" + "cypress/**/*.js", + "cypress/config/dev.mjs", + "cypress/config/ci.mjs", + "cypress/plugins/index.mjs", + "cypress/plugins/backendApi/task.mjs", + "cypress/plugins/backendApi/logger.mjs", + "cypress/plugins/backendApi/client.mjs", + "cypress/support/commands.mjs" ] } diff --git a/test/package.json b/test/package.json index a3a3cad6db..2a4213dde1 100644 --- a/test/package.json +++ b/test/package.json @@ -5,22 +5,26 @@ "main": "index.js", "dependencies": { "@jc21/cypress-swagger-validation": "^0.3.2", - "axios": "^1.7.9", - "cypress": "^14.0.1", + "@quobix/vacuum": "^0.23.4", + "axios": "^1.13.1", + "chalk": "^5.6.2", + "cypress": "^15.9.0", "cypress-multi-reporters": "^2.0.5", "cypress-wait-until": "^3.0.2", - "eslint": "^9.19.0", + "eslint": "^10.0.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": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress open --config-file=cypress/config/ci.mjs", + "cypress:headless": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress run --config-file=cypress/config/ci.mjs", + "cypress:dev": "cypress run --config-file=cypress/config/dev.mjs", + "swagger-lint": "vacuum lint -b -q -d -a --no-clip -n=warn" }, "author": "", "license": "ISC" diff --git a/test/vacuum-rules.yaml b/test/vacuum-rules.yaml new file mode 100644 index 0000000000..fc129b7279 --- /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 `