diff --git a/.version b/.version index 94f15e9c..a1a4224d 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.13.1 +2.13.3 diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index fc249ab4..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,285 +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 $(docke rcompose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1' - - junit 'test/results/junit/*' - sh 'docker compose down --remove-orphans --volumes -t 30 || true' - } - 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): -``` -nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER} -``` - -> [!NOTE] -> Ensure you backup your NPM instance before testing this image! Especially if there are database changes. -> This is a different docker image namespace than the official image. - -> [!WARNING] -> Changes and additions to DNS Providers require verification by at least 2 members of the community! -""", 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/README.md b/README.md index 669be713..6b3d1a8b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@



- + diff --git a/backend/certbot/dns-plugins.json b/backend/certbot/dns-plugins.json index 6238f466..daf2d0a8 100644 --- a/backend/certbot/dns-plugins.json +++ b/backend/certbot/dns-plugins.json @@ -370,7 +370,7 @@ "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" @@ -399,6 +399,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", diff --git a/backend/db.js b/backend/db.js index bd74e518..bf540f8a 100644 --- a/backend/db.js +++ b/backend/db.js @@ -1,6 +1,8 @@ import knex from "knex"; import {configGet, configHas} from "./lib/config.js"; +let instance = null; + const generateDbConfig = () => { if (!configHas("database")) { throw new Error( @@ -30,4 +32,11 @@ const generateDbConfig = () => { }; }; -export default knex(generateDbConfig()); +const getInstance = () => { + if (!instance) { + instance = knex(generateDbConfig()); + } + return instance; +} + +export default getInstance; diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 83d2c3f0..fe84607f 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -216,6 +216,11 @@ const internalNginx = { } } + // 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); diff --git a/backend/migrate.js b/backend/migrate.js index dd3f1b61..4c99cab6 100644 --- a/backend/migrate.js +++ b/backend/migrate.js @@ -2,9 +2,9 @@ import db from "./db.js"; import { migrate as logger } from "./logger.js"; const migrateUp = async () => { - const version = await db.migrate.currentVersion(); + const version = await db().migrate.currentVersion(); logger.info("Current database version:", version); - return await db.migrate.latest({ + return await db().migrate.latest({ tableName: "migrations", directory: "migrations", }); diff --git a/backend/migrations/20251111090000_redirect_auto_scheme.js b/backend/migrations/20251111090000_redirect_auto_scheme.js new file mode 100644 index 00000000..9f5f9d06 --- /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/models/access_list.js b/backend/models/access_list.js index 98016a17..427d447d 100644 --- a/backend/models/access_list.js +++ b/backend/models/access_list.js @@ -10,7 +10,7 @@ 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"]; diff --git a/backend/models/access_list_auth.js b/backend/models/access_list_auth.js index a4fd85a5..75bf4352 100644 --- a/backend/models/access_list_auth.js +++ b/backend/models/access_list_auth.js @@ -6,7 +6,7 @@ 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() { diff --git a/backend/models/access_list_client.js b/backend/models/access_list_client.js index 4b63aec9..91165fe1 100644 --- a/backend/models/access_list_client.js +++ b/backend/models/access_list_client.js @@ -6,7 +6,7 @@ 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() { diff --git a/backend/models/audit-log.js b/backend/models/audit-log.js index a9b2d563..6e2d398f 100644 --- a/backend/models/audit-log.js +++ b/backend/models/audit-log.js @@ -6,7 +6,7 @@ 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() { diff --git a/backend/models/auth.js b/backend/models/auth.js index 4ba50b41..e8af582d 100644 --- a/backend/models/auth.js +++ b/backend/models/auth.js @@ -8,7 +8,7 @@ import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.j import now from "./now_helper.js"; import User from "./user.js"; -Model.knex(db); +Model.knex(db()); const boolFields = ["is_deleted"]; diff --git a/backend/models/certificate.js b/backend/models/certificate.js index 9ad03c89..ad6e0a65 100644 --- a/backend/models/certificate.js +++ b/backend/models/certificate.js @@ -11,7 +11,7 @@ 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"]; diff --git a/backend/models/dead_host.js b/backend/models/dead_host.js index 56807012..0acf7ca7 100644 --- a/backend/models/dead_host.js +++ b/backend/models/dead_host.js @@ -8,7 +8,7 @@ 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", "http2_support", "enabled", "hsts_enabled", "hsts_subdomains"]; diff --git a/backend/models/now_helper.js b/backend/models/now_helper.js index 4dc71cea..293dcc72 100644 --- a/backend/models/now_helper.js +++ b/backend/models/now_helper.js @@ -2,7 +2,7 @@ import { Model } from "objection"; import db from "../db.js"; import { isSqlite } from "../lib/config.js"; -Model.knex(db); +Model.knex(db()); export default () => { if (isSqlite()) { diff --git a/backend/models/proxy_host.js b/backend/models/proxy_host.js index 119fe2b7..b6ce6361 100644 --- a/backend/models/proxy_host.js +++ b/backend/models/proxy_host.js @@ -9,7 +9,7 @@ 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", diff --git a/backend/models/redirection_host.js b/backend/models/redirection_host.js index bb397baa..46c73017 100644 --- a/backend/models/redirection_host.js +++ b/backend/models/redirection_host.js @@ -8,7 +8,7 @@ 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", diff --git a/backend/models/setting.js b/backend/models/setting.js index 0e0d6f4f..56f7dc5a 100644 --- a/backend/models/setting.js +++ b/backend/models/setting.js @@ -4,7 +4,7 @@ import { Model } from "objection"; import db from "../db.js"; -Model.knex(db); +Model.knex(db()); class Setting extends Model { $beforeInsert () { diff --git a/backend/models/stream.js b/backend/models/stream.js index 92d335ff..5f61945a 100644 --- a/backend/models/stream.js +++ b/backend/models/stream.js @@ -5,7 +5,7 @@ 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", "tcp_forwarding", "udp_forwarding"]; diff --git a/backend/models/user.js b/backend/models/user.js index 64aed05d..68a31446 100644 --- a/backend/models/user.js +++ b/backend/models/user.js @@ -7,7 +7,7 @@ import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.j import now from "./now_helper.js"; import UserPermission from "./user_permission.js"; -Model.knex(db); +Model.knex(db()); const boolFields = ["is_deleted", "is_disabled"]; diff --git a/backend/models/user_permission.js b/backend/models/user_permission.js index 49ea2d90..d8784717 100644 --- a/backend/models/user_permission.js +++ b/backend/models/user_permission.js @@ -5,7 +5,7 @@ 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 () { diff --git a/backend/setup.js b/backend/setup.js index b2c0dcb7..84f42793 100644 --- a/backend/setup.js +++ b/backend/setup.js @@ -37,7 +37,7 @@ const setupDefaultUser = async () => { const data = { is_deleted: 0, - email: email, + email: initialAdminEmail, name: "Administrator", nickname: "Admin", avatar: "", @@ -53,7 +53,7 @@ const setupDefaultUser = async () => { .insert({ user_id: user.id, type: "password", - secret: password, + secret: initialAdminPassword, meta: {}, }); diff --git a/backend/templates/_access.conf b/backend/templates/_access.conf index f5926377..4f388545 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/docker/Dockerfile b/docker/Dockerfile index 913f79d5..88ce95ed 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 @@ -46,7 +45,6 @@ RUN yarn install \ # 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 # Remove frontend service not required for prod, dev nginx config as well diff --git a/docker/ci.env b/docker/ci.env index 7128295d..11e2b2f8 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 45d97a32..f0f5ec60 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 " @@ -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 ea937905..00000000 --- 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.postgres.yml b/docker/docker-compose.ci.postgres.yml index e9eb4bc2..b8c42446 100644 --- a/docker/docker-compose.ci.postgres.yml +++ b/docker/docker-compose.ci.postgres.yml @@ -6,7 +6,7 @@ services: fullstack: environment: - DB_POSTGRES_HOST: "db-postgres" + DB_POSTGRES_HOST: "pgdb.internal" DB_POSTGRES_PORT: "5432" DB_POSTGRES_USER: "npm" DB_POSTGRES_PASSWORD: "npmpass" @@ -27,7 +27,9 @@ services: - psql_vol:/var/lib/postgresql/data - ./ci/postgres:/docker-entrypoint-initdb.d networks: - - fulltest + fulltest: + aliases: + - pgdb.internal authentik-redis: image: "redis:alpine" @@ -41,6 +43,8 @@ services: timeout: 3s volumes: - redis_vol:/data + networks: + - fulltest authentik: image: ghcr.io/goauthentik/server:2024.10.1 @@ -51,6 +55,8 @@ services: depends_on: - authentik-redis - db-postgres + networks: + - fulltest authentik-worker: image: ghcr.io/goauthentik/server:2024.10.1 @@ -61,6 +67,8 @@ services: depends_on: - authentik-redis - db-postgres + networks: + - fulltest authentik-ldap: image: ghcr.io/goauthentik/ldap:2024.10.1 @@ -71,6 +79,8 @@ services: 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 8070aa42..ce8913fe 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -3,31 +3,30 @@ # 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: TZ: "${TZ:-Australia/Brisbane}" - DEBUG: 'true' - CI: 'true' + 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-81/tcp" + - "443/tcp" + - "1500-1503/tcp" networks: fulltest: aliases: @@ -38,8 +37,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: @@ -48,18 +47,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 @@ -76,14 +75,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 @@ -100,12 +99,12 @@ 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' + - "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 networks: - fulltest @@ -113,9 +112,9 @@ services: 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 57c30e07..01577716 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -32,7 +32,7 @@ services: # DB_MYSQL_PASSWORD: 'npm' # DB_MYSQL_NAME: 'npm' # db-postgres: - DB_POSTGRES_HOST: "db-postgres" + DB_POSTGRES_HOST: "pgdb.internal" DB_POSTGRES_PORT: "5432" DB_POSTGRES_USER: "npm" DB_POSTGRES_PASSWORD: "npmpass" @@ -81,8 +81,6 @@ services: db-postgres: image: postgres:17 container_name: npm2dev.db-postgres - networks: - - nginx_proxy_manager environment: POSTGRES_USER: "npm" POSTGRES_PASSWORD: "npmpass" @@ -90,6 +88,10 @@ services: volumes: - psql_data:/var/lib/postgresql/data - ./ci/postgres:/docker-entrypoint-initdb.d + networks: + nginx_proxy_manager: + aliases: + - pgdb.internal stepca: image: jc21/testca diff --git a/frontend/check-locales.cjs b/frontend/check-locales.cjs index 03a8be14..3a6ecff4 100755 --- a/frontend/check-locales.cjs +++ b/frontend/check-locales.cjs @@ -8,7 +8,8 @@ const allLocales = [ ["en", "en-US"], - ["fa", "fa-IR"], + ["es", "es-ES"], + ["de", "de-DE"], ]; const ignoreUnused = [ diff --git a/frontend/src/components/Form/AccessField.tsx b/frontend/src/components/Form/AccessField.tsx index c72ef588..16059ac3 100644 --- a/frontend/src/components/Form/AccessField.tsx +++ b/frontend/src/components/Form/AccessField.tsx @@ -4,7 +4,7 @@ import type { ReactNode } from "react"; import Select, { type ActionMeta, components, type OptionProps } from "react-select"; import type { AccessList } from "src/api/backend"; import { useAccessLists } from "src/hooks"; -import { DateTimeFormat, intl, T } from "src/locale"; +import { formatDateTime, intl, T } from "src/locale"; interface AccessOption { readonly value: number; @@ -48,7 +48,7 @@ export function AccessField({ name = "accessListId", label = "access-list", id = { users: item?.items?.length, rules: item?.clients?.length, - date: item?.createdOn ? DateTimeFormat(item?.createdOn) : "N/A", + date: item?.createdOn ? formatDateTime(item?.createdOn) : "N/A", }, ), icon: , diff --git a/frontend/src/components/Form/SSLCertificateField.tsx b/frontend/src/components/Form/SSLCertificateField.tsx index c4767509..0e7ce337 100644 --- a/frontend/src/components/Form/SSLCertificateField.tsx +++ b/frontend/src/components/Form/SSLCertificateField.tsx @@ -3,7 +3,7 @@ import { Field, useFormikContext } from "formik"; import Select, { type ActionMeta, components, type OptionProps } from "react-select"; import type { Certificate } from "src/api/backend"; import { useCertificates } from "src/hooks"; -import { DateTimeFormat, intl, T } from "src/locale"; +import { formatDateTime, intl, T } from "src/locale"; interface CertOption { readonly value: number | "new"; @@ -75,7 +75,7 @@ export function SSLCertificateField({ data?.map((cert: Certificate) => ({ value: cert.id, label: cert.niceName, - subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A" })}`, + subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? formatDateTime(cert.expiresOn) : "N/A" })}`, icon: , })) || []; diff --git a/frontend/src/components/SiteMenu.tsx b/frontend/src/components/SiteMenu.tsx index 3a0b0065..34012147 100644 --- a/frontend/src/components/SiteMenu.tsx +++ b/frontend/src/components/SiteMenu.tsx @@ -190,7 +190,7 @@ export function SiteMenu() { return (

-
+ {createdOn ? (
- +
) : null}
diff --git a/frontend/src/locale/DateTimeFormat.ts b/frontend/src/locale/DateTimeFormat.ts deleted file mode 100644 index fb8e66c8..00000000 --- a/frontend/src/locale/DateTimeFormat.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { intlFormat, parseISO } from "date-fns"; - -const DateTimeFormat = (isoDate: string) => - intlFormat(parseISO(isoDate), { - weekday: "long", - year: "numeric", - month: "numeric", - day: "numeric", - hour: "numeric", - minute: "numeric", - second: "numeric", - hour12: true, - }); - -export { DateTimeFormat }; diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index af027024..60501cdd 100644 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -1,5 +1,7 @@ import { createIntl, createIntlCache } from "react-intl"; import langEn from "./lang/en.json"; +import langDe from "./lang/de.json"; +import langEs from "./lang/es.json"; import langJa from "./lang/ja.json"; import langList from "./lang/lang-list.json"; @@ -8,14 +10,20 @@ import langList from "./lang/lang-list.json"; // Remember when adding to this list, also update check-locales.js script const localeOptions = [ ["en", "en-US"], - ["ja", "ja-JP"] + ["de", "de-DE"], + ["es", "es-ES"], + ["ja", "ja-JP"], ]; const loadMessages = (locale?: string): typeof langList & typeof langEn => { const thisLocale = locale || "en"; switch (thisLocale.slice(0, 2)) { + case "de": + return Object.assign({}, langList, langEn, langDe); + case "es": + return Object.assign({}, langList, langEn, langEs); case "ja": - return Object.assign({}, langList, langJa); + return Object.assign({}, langList, langEn, langJa); default: return Object.assign({}, langList, langEn); } @@ -23,6 +31,12 @@ const loadMessages = (locale?: string): typeof langList & typeof langEn => { const getFlagCodeForLocale = (locale?: string) => { switch (locale) { + case "es-ES": + case "es": + return "ES"; + case "de-DE": + case "de": + return "DE"; case "ja-JP": case "ja": return "JP"; @@ -39,6 +53,10 @@ const getLocale = (short = false) => { if (short) { return loc.slice(0, 2); } + // finally, fallback + if (!loc) { + loc = "en"; + } return loc; }; diff --git a/frontend/src/locale/Utils.test.tsx b/frontend/src/locale/Utils.test.tsx new file mode 100644 index 00000000..fb262501 --- /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("Monday, 01/01/2024, 12:00:00 am"); + }); + + it("format date from unix timestamp number", () => { + const value = 1762476112; + const text = formatDateTime(value); + expect(text).toBe("Friday, 07/11/2025, 12:41:52 am"); + }); + + it("format date from unix timestamp string", () => { + const value = "1762476112"; + const text = formatDateTime(value); + expect(text).toBe("Friday, 07/11/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 00000000..018efd0b --- /dev/null +++ b/frontend/src/locale/Utils.ts @@ -0,0 +1,42 @@ +import { fromUnixTime, 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): string => { + const d = parseDate(value); + if (!d) return `${value}`; + try { + return intlFormat(d, { + weekday: "long", + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric", + hour12: true, + }); + } catch { + return `${value}`; + } +}; + +export { formatDateTime, parseDate, isUnixTimestamp }; diff --git a/frontend/src/locale/index.ts b/frontend/src/locale/index.ts index 6d9ac03c..bdd1343e 100644 --- a/frontend/src/locale/index.ts +++ b/frontend/src/locale/index.ts @@ -1,2 +1,2 @@ -export * from "./DateTimeFormat"; export * from "./IntlProvider"; +export * from "./Utils"; diff --git a/frontend/src/locale/lang/de.json b/frontend/src/locale/lang/de.json new file mode 100644 index 00000000..8e308f0b --- /dev/null +++ b/frontend/src/locale/lang/de.json @@ -0,0 +1,216 @@ +{ + "access-list": "Zugriffsliste", + "access-list.access-count": "{count} {count, plural, one {Regel} other {Regeln}}", + "access-list.auth-count": "{count} {count, plural, one {User} other {Users}}", + "access-list.help-rules-last": "Wenn mindestens eine Regel vorhanden ist, wird diese Regel zum Ablehnen aller Anfragen als letzte hinzugefügt.", + "access-list.help.rules-order": "Beachten Sie, dass die Anweisungen „Erlauben“ und „Verbieten“ in der Reihenfolge ihrer Definition angewendet werden.", + "access-list.pass-auth": "Authentifizierung an Upstream weiterleiten", + "access-list.public": "Öffentlich", + "access-list.public.subtitle": "Keine Authentifizierung erforderlich", + "access-list.satisfy-any": "Satisfy Any", + "access-list.subtitle": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Regel} other {Regeln}} - Erstellt: {date}", + "access-lists": "Zugrifflisten", + "action.add": "Hinzufügen", + "action.add-location": "Pfad Hinzufügen", + "action.close": "Schließen", + "action.delete": "Löschen", + "action.disable": "Deaktivieren", + "action.download": "Herunterladen", + "action.edit": "Bearbeiten", + "action.enable": "Aktivieren", + "action.permissions": "Berechtigungen", + "action.renew": "Erneuert", + "action.view-details": "Details", + "auditlogs": "Protokoll", + "cancel": "Abbrechen", + "certificate": "Zertifikat", + "certificate.custom-certificate": "Zertifikat", + "certificate.custom-certificate-key": "Privater Schlüssel", + "certificate.custom-intermediate": "Zwischen Zertifikat", + "certificate.in-use": "In Benutzung", + "certificate.none.subtitle": "Kein Zertifikat zugewiesen", + "certificate.none.subtitle.for-http": "Dieser Host verwendet kein HTTPS.", + "certificate.none.title": "Kein", + "certificate.not-in-use": "Nicht in Benutzung", + "certificate.renew": "Zertifikat erneuern", + "certificates": "Zertifikate", + "certificates.custom": "Benutzerdefiniertes Zertifikat", + "certificates.custom.warning": "Mit einem Passwort geschützte Schlüsseldateien werden nicht unterstützt.", + "certificates.dns.credentials": "Inhalt der Anmeldedaten-Datei", + "certificates.dns.credentials-note": "Dieses Plugin erfordert eine Konfigurationsdatei, die einen API-Token oder andere Anmeldedaten für Ihren Anbieter enthält.", + "certificates.dns.credentials-warning": "Diese Daten werden als Klartext in der Datenbank und in einer Datei gespeichert!", + "certificates.dns.propagation-seconds": "Wartzeit in Sekunden", + "certificates.dns.propagation-seconds-note": "Leer lassen um die Standardwartezeit des Plugins zu nutzen", + "certificates.dns.provider": "DNS Provider", + "certificates.dns.warning": "Dieser Abschnitt erfordert einige Kenntnisse über Certbot und seine DNS-Plugins. Bitte konsultieren Sie die jeweilige Plugin-Dokumentation.", + "certificates.http.reachability-404": "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": "Die Erreichbarkeit konnte aufgrund eines Kommunikationsfehlers mit site24x7.com nicht überprüft werden.", + "certificates.http.reachability-not-resolved": "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": "Ihr Server ist erreichbar und die Erstellung von Zertifikaten sollte möglich sein.", + "certificates.http.reachability-other": "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": "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": "Test Ergeniss", + "certificates.http.warning": "Diese Domänen müssen bereits so konfiguriert sein, dass sie auf diese Installation verweisen.", + "certificates.request.subtitle": "Über Let's Encrypt", + "certificates.request.title": "Anfordern eines neuen Zertifikates", + "column.access": "Zugriff", + "column.authorization": "Genehmigung", + "column.authorizations": "Genehmigungen", + "column.custom-locations": "Benutzerdefinierte Pfad", + "column.destination": "Ziel", + "column.details": "Details", + "column.email": "Email", + "column.event": "Ereignis", + "column.expires": "Verfällt am", + "column.http-code": "HTTP Code", + "column.incoming-port": "Eingehender Port", + "column.name": "Name", + "column.protocol": "Protokoll", + "column.provider": "Provider", + "column.roles": "Rollen", + "column.rules": "Regeln", + "column.satisfy": "Satisfy", + "column.satisfy-all": "Alle", + "column.satisfy-any": "Jeder", + "column.scheme": "Schema", + "column.source": "Quelle", + "column.ssl": "SSL", + "column.status": "Status", + "created-on": "Erstelldatum: {date}", + "dashboard": "Dashboard", + "dead-host": "404 Host", + "dead-hosts": "404 Hosts", + "dead-hosts.count": "{count} {count, plural, one {404 Host} other {404 Hosts}}", + "disabled": "Deaktiviert", + "domain-names": "Domain Names", + "domain-names.max": "{count} Maximale Anzahl von Domainnamen", + "domain-names.placeholder": "Eintragen der Domain...", + "domain-names.wildcards-not-permitted": "Wildcards sind für diesen Typ nicht zulässig.", + "domain-names.wildcards-not-supported": "Wildcards werden für diese Zertifizierungsstelle nicht unterstützt.", + "domains.force-ssl": "Erzwinge SSL", + "domains.hsts-enabled": "HSTS aktiviert", + "domains.hsts-subdomains": "HSTS Sub-domains", + "domains.http2-support": "HTTP/2 Support", + "domains.use-dns": "Nutze DNS Challenge", + "email-address": "Email Addresse", + "empty-search": "Keine Ergebnisse gefunden", + "empty-subtitle": "Warum erstellen Sie nicht eine?", + "enabled": "aktiviert", + "error.access.at-least-one": "Entweder eine Genehmigung oder eine Zugriffsregel ist erforderlich.", + "error.access.duplicate-usernames": "Autorisierung Benutzernamen müssen eindeutig sein", + "error.invalid-auth": "Ungültige E-Mail-Adresse oder Passwort", + "error.invalid-domain": "Ungültige Domain: {domain}", + "error.invalid-email": "Ungültige E-Mail-Adresse", + "error.max-character-length": "Die maximale Länge beträgt {max} Zeichen{max, plural, one {} other {s}}", + "error.max-domains": "Zu viele Domains, maximal sind {max}", + "error.maximum": "Maximum ist {max}", + "error.min-character-length": "Die minimale Länge beträgt {min} Zeichen{min, plural, one {} other {s}}", + "error.minimum": "Minimum ist {min}", + "error.passwords-must-match": "Passwörter müssen übereinstimmen", + "error.required": "Dies ist erforderlich.", + "expires.on": "Ablauf am: {date}", + "footer.github-fork": "Fork me on Github", + "host.flags.block-exploits": "Gängige Exploits blockieren", + "host.flags.cache-assets": "Cache Assets", + "host.flags.preserve-path": "Pfad beibehalten", + "host.flags.protocols": "Protokole", + "host.flags.websockets-upgrade": "Websockets Support", + "host.forward-port": "Forward Port", + "host.forward-scheme": "Schema", + "hosts": "Hosts", + "http-only": "HTTP Only", + "lets-encrypt": "Let's Encrypt", + "lets-encrypt-via-dns": "Let's Encrypt via DNS", + "lets-encrypt-via-http": "Let's Encrypt via HTTP", + "loading": "Laden…", + "login.title": "Account Login", + "nginx-config.label": "Benutzerdefinierte Nginx Konfiguration", + "nginx-config.placeholder": "# Geben Sie hier Ihre benutzerdefinierte Nginx-Konfiguration auf eigene Gefahr ein!", + "no-permission-error": "Sie haben keinen Zugriff, um dies anzuzeigen.", + "notfound.action": "Take me home", + "notfound.content": "We are sorry but the page you are looking for was not found", + "notfound.title": "Oops… You just found an error page", + "notification.error": "Error", + "notification.object-deleted": "{object} wurde gelöscht", + "notification.object-disabled": "{object} wurde deaktiviert", + "notification.object-enabled": "{object} wurde aktiviert", + "notification.object-renewed": "{object} wurde erneuert", + "notification.object-saved": "{object} wurde gespeichert", + "notification.success": "Erfolgreich", + "object.actions-title": "{object} #{id}", + "object.add": "{object} hinzufügen", + "object.delete": "{object} löschen", + "object.delete.content": "Bist du dir sicher das du diese(n) {object} löschen möchtest?", + "object.edit": "{object} bearbeiten", + "object.empty": "Keine {objects} vorhanden", + "object.event.created": "{object} erstellt", + "object.event.deleted": "{object} gelöscht", + "object.event.disabled": "{object} deaktiviert", + "object.event.enabled": "{object} aktiviert", + "object.event.renewed": "{object} erneuert", + "object.event.updated": "{object} aktualisiert", + "offline": "Offline", + "online": "Online", + "options": "Optionen", + "password": "Passwort", + "password.generate": "Zufälliges Passwort generieren", + "password.hide": "Passwort verstecken", + "password.show": "Passwort anzeigen", + "permissions.hidden": "Versteckt", + "permissions.manage": "Verwalten", + "permissions.view": "Nur anzeigen", + "permissions.visibility.all": "Alle Elemente", + "permissions.visibility.title": "Objekt Sichtbarkeit", + "permissions.visibility.user": "Nur erstellte Elemente", + "proxy-host": "Proxy Host", + "proxy-host.forward-host": "Forward Hostname / IP", + "proxy-hosts": "Proxy Hosts", + "proxy-hosts.count": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}", + "public": "Öffentlich", + "redirection-host": "Redirection Host", + "redirection-host.forward-domain": "Forward Domain", + "redirection-host.forward-http-code": "HTTP Code", + "redirection-hosts": "Redirection Hosts", + "redirection-hosts.count": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}", + "role.admin": "Administrator", + "role.standard-user": "Standard User", + "save": "Speichern", + "setting": "Einstellung", + "settings": "Einstellungen", + "settings.default-site": "Standard Seite", + "settings.default-site.404": "404 Page", + "settings.default-site.444": "No Response (444)", + "settings.default-site.congratulations": "Willkommensseite", + "settings.default-site.description": "Was angezeigt wird, wenn der Nginx eine unbekannte Webseitenanfrage bekommt", + "settings.default-site.html": "Benutzerdefinierte HTML", + "settings.default-site.html.placeholder": "", + "settings.default-site.redirect": "Weiterleitung", + "setup.preamble": "Beginnen Sie mit der Erstellung Ihres Administratorkontos.", + "setup.title": "Willkommen!", + "sign-in": "Login", + "ssl-certificate": "SSL Zertifikate", + "stream": "Stream", + "stream.forward-host": "Forward Host", + "stream.incoming-port": "Incoming Port", + "streams": "Streams", + "streams.count": "{count} {count, plural, one {Stream} other {Streams}}", + "streams.tcp": "TCP", + "streams.udp": "UDP", + "test": "Test", + "user": "User", + "user.change-password": "Passwort ändern", + "user.confirm-password": "Passwort wiederholen", + "user.current-password": "Aktuelles Passwort", + "user.edit-profile": "Profil bearbeiten", + "user.full-name": "Name", + "user.login-as": "Einloggen als {name}", + "user.logout": "Ausloggen", + "user.new-password": "Neues Password", + "user.nickname": "Nickname", + "user.set-password": "Passwort setzen", + "user.set-permissions": "Berechtigungen für {name} setzen", + "user.switch-dark": "Zum Dark Mode wechseln", + "user.switch-light": "Zum Light Mode wechslen", + "username": "Benutzername", + "users": "Benutzer" +} \ No newline at end of file diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json index bda50f47..b77104ff 100644 --- a/frontend/src/locale/lang/en.json +++ b/frontend/src/locale/lang/en.json @@ -62,7 +62,7 @@ "column.email": "Email", "column.event": "Event", "column.expires": "Expires", - "column.http-code": "Access", + "column.http-code": "HTTP Code", "column.incoming-port": "Incoming Port", "column.name": "Name", "column.protocol": "Protocol", @@ -169,6 +169,7 @@ "public": "Public", "redirection-host": "Redirection Host", "redirection-host.forward-domain": "Forward Domain", + "redirection-host.forward-http-code": "HTTP Code", "redirection-hosts": "Redirection Hosts", "redirection-hosts.count": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}", "role.admin": "Administrator", diff --git a/frontend/src/locale/lang/es.json b/frontend/src/locale/lang/es.json new file mode 100644 index 00000000..ddca4559 --- /dev/null +++ b/frontend/src/locale/lang/es.json @@ -0,0 +1,216 @@ +{ + "access-list": "Lista de Acceso", + "access-list.access-count": "{count} {count, plural, one {Regla} other {Reglas}}", + "access-list.auth-count": "{count} {count, plural, one {Usuario} other {Usuarios}}", + "access-list.help-rules-last": "Cuando exista al menos 1 regla, esta regla de denegar todo se añadirá al final", + "access-list.help.rules-order": "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": "Pasar Autenticación al Upstream", + "access-list.public": "Accesible Públicamente", + "access-list.public.subtitle": "No se requiere autenticación básica", + "access-list.satisfy-any": "Satisfacer Cualquiera", + "access-list.subtitle": "{users} {users, plural, one {Usuario} other {Usuarios}}, {rules} {rules, plural, one {Regla} other {Reglas}} - Creado: {date}", + "access-lists": "Listas de Acceso", + "action.add": "Añadir", + "action.add-location": "Añadir Ubicación", + "action.close": "Cerrar", + "action.delete": "Eliminar", + "action.disable": "Deshabilitar", + "action.download": "Descargar", + "action.edit": "Editar", + "action.enable": "Habilitar", + "action.permissions": "Permisos", + "action.renew": "Renovar", + "action.view-details": "Ver Detalles", + "auditlogs": "Registros de Auditoría", + "cancel": "Cancelar", + "certificate": "Certificado", + "certificate.custom-certificate": "Certificado", + "certificate.custom-certificate-key": "Clave del Certificado", + "certificate.custom-intermediate": "Certificado Intermedio", + "certificate.in-use": "En Uso", + "certificate.none.subtitle": "Sin certificado asignado", + "certificate.none.subtitle.for-http": "Este host no usará HTTPS", + "certificate.none.title": "Ninguno", + "certificate.not-in-use": "Sin Usar", + "certificate.renew": "Renovar Certificado", + "certificates": "Certificados", + "certificates.custom": "Certificado Personalizado", + "certificates.custom.warning": "No se admiten archivos de claves protegidos con contraseña.", + "certificates.dns.credentials": "Contenido del Archivo de Credenciales", + "certificates.dns.credentials-note": "Este plugin requiere un archivo de configuración que contenga un token de API u otras credenciales para tu proveedor", + "certificates.dns.credentials-warning": "¡Estos datos se almacenarán como texto plano en la base de datos y en un archivo!", + "certificates.dns.propagation-seconds": "Segundos de Propagación", + "certificates.dns.propagation-seconds-note": "Dejar vacío para usar el valor predeterminado del plugin. Número de segundos a esperar para la propagación DNS.", + "certificates.dns.provider": "Proveedor DNS", + "certificates.dns.warning": "Esta sección requiere algunos conocimientos sobre Certbot y sus plugins DNS. Consulta la documentación de los plugins respectivos.", + "certificates.http.reachability-404": "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": "No se pudo verificar la accesibilidad debido a un error de comunicación con site24x7.com.", + "certificates.http.reachability-not-resolved": "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": "Tu servidor es accesible y debería ser posible crear certificados.", + "certificates.http.reachability-other": "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": "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": "Resultados de la Prueba", + "certificates.http.warning": "Estos dominios ya deben estar configurados para apuntar a esta instalación.", + "certificates.request.subtitle": "con Let's Encrypt", + "certificates.request.title": "Solicitar un nuevo Certificado", + "column.access": "Acceso", + "column.authorization": "Autorización", + "column.authorizations": "Autorizaciones", + "column.custom-locations": "Ubicaciones Personalizadas", + "column.destination": "Destino", + "column.details": "Detalles", + "column.email": "Correo Electrónico", + "column.event": "Evento", + "column.expires": "Expira", + "column.http-code": "Código HTTP", + "column.incoming-port": "Puerto de Entrada", + "column.name": "Nombre", + "column.protocol": "Protocolo", + "column.provider": "Proveedor", + "column.roles": "Roles", + "column.rules": "Reglas", + "column.satisfy": "Satisfacer", + "column.satisfy-all": "Todo", + "column.satisfy-any": "Cualquiera", + "column.scheme": "Esquema", + "column.source": "Origen", + "column.ssl": "SSL", + "column.status": "Estado", + "created-on": "Creado: {date}", + "dashboard": "Panel de Control", + "dead-host": "Host 404", + "dead-hosts": "Hosts 404", + "dead-hosts.count": "{count} {count, plural, one {Host 404} other {Hosts 404}}", + "disabled": "Deshabilitado", + "domain-names": "Nombres de Dominio", + "domain-names.max": "{count} nombres de dominio como máximo", + "domain-names.placeholder": "Comienza a escribir para añadir dominio...", + "domain-names.wildcards-not-permitted": "No se permiten comodines para este tipo", + "domain-names.wildcards-not-supported": "No se admiten comodines para esta CA", + "domains.force-ssl": "Forzar SSL", + "domains.hsts-enabled": "HSTS Habilitado", + "domains.hsts-subdomains": "HSTS en Subdominios", + "domains.http2-support": "Soporte HTTP/2", + "domains.use-dns": "Usar Desafío DNS", + "email-address": "Dirección de correo electrónico", + "empty-search": "No se encontraron resultados", + "empty-subtitle": "¿Por qué no creas uno?", + "enabled": "Habilitado", + "error.access.at-least-one": "Se requiere al menos una Autorización o una Regla de Acceso", + "error.access.duplicate-usernames": "Los nombres de usuario de autorización deben ser únicos", + "error.invalid-auth": "Correo electrónico o contraseña no válidos", + "error.invalid-domain": "Dominio no válido: {domain}", + "error.invalid-email": "Dirección de correo electrónico no válida", + "error.max-character-length": "La longitud máxima es {max} caracter{max, plural, one {} other {es}}", + "error.max-domains": "Demasiados dominios, el máximo es {max}", + "error.maximum": "El máximo es {max}", + "error.min-character-length": "La longitud mínima es {min} caracter{min, plural, one {} other {es}}", + "error.minimum": "El mínimo es {min}", + "error.passwords-must-match": "Las contraseñas deben coincidir", + "error.required": "Este campo es obligatorio", + "expires.on": "Expira: {date}", + "footer.github-fork": "Bifúrcame en Github", + "host.flags.block-exploits": "Bloquear Exploits Comunes", + "host.flags.cache-assets": "Cachear Recursos", + "host.flags.preserve-path": "Preservar Ruta", + "host.flags.protocols": "Protocolos", + "host.flags.websockets-upgrade": "Soporte de Websockets", + "host.forward-port": "Puerto de Reenvío", + "host.forward-scheme": "Esquema", + "hosts": "Hosts", + "http-only": "Solo HTTP", + "lets-encrypt": "Let's Encrypt", + "lets-encrypt-via-dns": "Let's Encrypt vía DNS", + "lets-encrypt-via-http": "Let's Encrypt vía HTTP", + "loading": "Cargando…", + "login.title": "Inicia sesión en tu cuenta", + "nginx-config.label": "Configuración Personalizada de Nginx", + "nginx-config.placeholder": "# ¡Introduce aquí tu configuración personalizada de Nginx bajo tu propio riesgo!", + "no-permission-error": "No tienes acceso para ver esto.", + "notfound.action": "Llévame al inicio", + "notfound.content": "Lo sentimos, pero la página que buscas no fue encontrada", + "notfound.title": "Ups… Has encontrado una página de error", + "notification.error": "Error", + "notification.object-deleted": "{object} ha sido eliminado", + "notification.object-disabled": "{object} ha sido deshabilitado", + "notification.object-enabled": "{object} ha sido habilitado", + "notification.object-renewed": "{object} ha sido renovado", + "notification.object-saved": "{object} ha sido guardado", + "notification.success": "Éxito", + "object.actions-title": "{object} #{id}", + "object.add": "Añadir {object}", + "object.delete": "Eliminar {object}", + "object.delete.content": "¿Estás seguro de que quieres eliminar este {object}?", + "object.edit": "Editar {object}", + "object.empty": "No hay {objects}", + "object.event.created": "{object} Creado", + "object.event.deleted": "{object} Eliminado", + "object.event.disabled": "{object} Deshabilitado", + "object.event.enabled": "{object} Habilitado", + "object.event.renewed": "{object} Renovado", + "object.event.updated": "{object} Actualizado", + "offline": "Desconectado", + "online": "Conectado", + "options": "Opciones", + "password": "Contraseña", + "password.generate": "Generar contraseña aleatoria", + "password.hide": "Ocultar Contraseña", + "password.show": "Mostrar Contraseña", + "permissions.hidden": "Oculto", + "permissions.manage": "Gestionar", + "permissions.view": "Solo Ver", + "permissions.visibility.all": "Todos los Elementos", + "permissions.visibility.title": "Visibilidad de Elementos", + "permissions.visibility.user": "Solo Elementos Creados", + "proxy-host": "Host Proxy", + "proxy-host.forward-host": "Nombre de Host / IP de Reenvío", + "proxy-hosts": "Hosts Proxy", + "proxy-hosts.count": "{count} {count, plural, one {Host Proxy} other {Hosts Proxy}}", + "public": "Público", + "redirection-host": "Host de Redirección", + "redirection-host.forward-domain": "Dominio de Reenvío", + "redirection-host.forward-http-code": "Código HTTP", + "redirection-hosts": "Hosts de Redirección", + "redirection-hosts.count": "{count} {count, plural, one {Host de Redirección} other {Hosts de Redirección}}", + "role.admin": "Administrador", + "role.standard-user": "Usuario Estándar", + "save": "Guardar", + "setting": "Configuración", + "settings": "Configuración", + "settings.default-site": "Sitio Predeterminado", + "settings.default-site.404": "Página 404", + "settings.default-site.444": "Sin Respuesta (444)", + "settings.default-site.congratulations": "Página de Felicitaciones", + "settings.default-site.description": "Qué mostrar cuando Nginx recibe un Host desconocido", + "settings.default-site.html": "HTML Personalizado", + "settings.default-site.html.placeholder": "", + "settings.default-site.redirect": "Redirigir", + "setup.preamble": "Comienza creando tu cuenta de administrador.", + "setup.title": "¡Bienvenido!", + "sign-in": "Iniciar Sesión", + "ssl-certificate": "Certificado SSL", + "stream": "Stream", + "stream.forward-host": "Host de Reenvío", + "stream.incoming-port": "Puerto de Entrada", + "streams": "Streams", + "streams.count": "{count} {count, plural, one {Stream} other {Streams}}", + "streams.tcp": "TCP", + "streams.udp": "UDP", + "test": "Probar", + "user": "Usuario", + "user.change-password": "Cambiar Contraseña", + "user.confirm-password": "Confirmar Contraseña", + "user.current-password": "Contraseña Actual", + "user.edit-profile": "Editar Perfil", + "user.full-name": "Nombre Completo", + "user.login-as": "Iniciar sesión como {name}", + "user.logout": "Cerrar Sesión", + "user.new-password": "Nueva Contraseña", + "user.nickname": "Apodo", + "user.set-password": "Establecer Contraseña", + "user.set-permissions": "Establecer Permisos para {name}", + "user.switch-dark": "Cambiar a modo Oscuro", + "user.switch-light": "Cambiar a modo Claro", + "username": "Nombre de Usuario", + "users": "Usuarios" +} \ No newline at end of file diff --git a/frontend/src/locale/lang/lang-list.json b/frontend/src/locale/lang/lang-list.json index 962787be..65462ef0 100644 --- a/frontend/src/locale/lang/lang-list.json +++ b/frontend/src/locale/lang/lang-list.json @@ -1,4 +1,6 @@ { "locale-en-US": "English", + "locale-de-DE": "German", + "locale-es-ES": "Español", "locale-ja-JP": "日本語" } \ No newline at end of file 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 00000000..315406a7 --- /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 00000000..be15ffa6 --- /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 00000000..a73992c2 --- /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 00000000..f116b857 --- /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 00000000..3e06919b --- /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 00000000..35cd539d --- /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 00000000..a9bb46ba --- /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/es/AccessLists.md b/frontend/src/locale/src/HelpDoc/es/AccessLists.md new file mode 100644 index 00000000..f0a089f6 --- /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 00000000..5907147f --- /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 00000000..bc775684 --- /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 00000000..57994b7c --- /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 00000000..7a05a301 --- /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 00000000..f085352f --- /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 00000000..a9bb46ba --- /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/index.ts b/frontend/src/locale/src/HelpDoc/index.ts index 1b8a5422..5ba9396b 100644 --- a/frontend/src/locale/src/HelpDoc/index.ts +++ b/frontend/src/locale/src/HelpDoc/index.ts @@ -1,18 +1,23 @@ -// import * as de from "./de/index"; -// import * as fa from "./fa/index"; import * as en from "./en/index"; +import * as de from "./de/index"; import * as ja from "./ja/index"; -const items: any = { en, ja }; +const items: any = { en, de, ja }; const fallbackLang = "en"; export const getHelpFile = (lang: string, section: string): string => { - if (typeof items[lang] !== "undefined" && typeof items[lang][section] !== "undefined") { + 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") { + 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}`); diff --git a/frontend/src/locale/src/de.json b/frontend/src/locale/src/de.json new file mode 100644 index 00000000..561ef82b --- /dev/null +++ b/frontend/src/locale/src/de.json @@ -0,0 +1,644 @@ +{ + "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": "Zugrifflisten" + }, + "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": "Erneuert" + }, + "action.view-details": { + "defaultMessage": "Details" + }, + "auditlogs": { + "defaultMessage": "Protokoll" + }, + "cancel": { + "defaultMessage": "Abbrechen" + }, + "certificate": { + "defaultMessage": "Zertifikat" + }, + "certificate.custom-certificate": { + "defaultMessage": "Zertifikat" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Privater Schlüssel" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Zwischen Zertifikat" + }, + "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": "Wartzeit 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": "Test Ergeniss" + }, + "certificates.http.warning": { + "defaultMessage": "Diese Domänen müssen bereits so konfiguriert sein, dass sie auf diese Installation verweisen." + }, + "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 Pfad" + }, + "column.destination": { + "defaultMessage": "Ziel" + }, + "column.details": { + "defaultMessage": "Details" + }, + "column.email": { + "defaultMessage": "Email" + }, + "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": "Email Addresse" + }, + "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.title": { + "defaultMessage": "Account Login" + }, + "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": "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} 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": "Bist du dir sicher das du diese(n) {object} löschen möchtest?" + }, + "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" + }, + "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": "Objekt Sichtbarkeit" + }, + "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}}" + }, + "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": "Standard User" + }, + "save": { + "defaultMessage": "Speichern" + }, + "setting": { + "defaultMessage": "Einstellung" + }, + "settings": { + "defaultMessage": "Einstellungen" + }, + "settings.default-site": { + "defaultMessage": "Standard Seite" + }, + "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" + }, + "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 wechslen" + }, + "username": { + "defaultMessage": "Benutzername" + }, + "users": { + "defaultMessage": "Benutzer" + } +} diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index 3a730e2f..36ee14d9 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -189,7 +189,7 @@ "defaultMessage": "Expires" }, "column.http-code": { - "defaultMessage": "Access" + "defaultMessage": "HTTP Code" }, "column.incoming-port": { "defaultMessage": "Incoming Port" @@ -509,6 +509,9 @@ "redirection-host.forward-domain": { "defaultMessage": "Forward Domain" }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP Code" + }, "redirection-hosts": { "defaultMessage": "Redirection Hosts" }, diff --git a/frontend/src/locale/src/es.json b/frontend/src/locale/src/es.json new file mode 100644 index 00000000..bd65f4de --- /dev/null +++ b/frontend/src/locale/src/es.json @@ -0,0 +1,644 @@ +{ + "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.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.close": { + "defaultMessage": "Cerrar" + }, + "action.delete": { + "defaultMessage": "Eliminar" + }, + "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" + }, + "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.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.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 de Reenvío" + }, + "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.title": { + "defaultMessage": "Inicia sesión en tu cuenta" + }, + "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" + }, + "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}}" + }, + "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}}" + }, + "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" + }, + "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.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/lang-list.json b/frontend/src/locale/src/lang-list.json index e5e8ea76..a068a5d1 100644 --- a/frontend/src/locale/src/lang-list.json +++ b/frontend/src/locale/src/lang-list.json @@ -2,6 +2,12 @@ "locale-en-US": { "defaultMessage": "English" }, + "locale-es-ES": { + "defaultMessage": "Español" + }, + "locale-de-DE": { + "defaultMessage": "German" + }, "locale-ja-JP": { "defaultMessage": "日本語" } diff --git a/frontend/src/modals/RedirectionHostModal.tsx b/frontend/src/modals/RedirectionHostModal.tsx index d2a40b56..ea6762bc 100644 --- a/frontend/src/modals/RedirectionHostModal.tsx +++ b/frontend/src/modals/RedirectionHostModal.tsx @@ -162,7 +162,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = required {...field} > - + @@ -212,6 +212,36 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) = + + {({ field, form }: any) => ( +
+ + + {form.errors.forwardHttpCode ? ( +
+ {form.errors.forwardHttpCode && + form.touched.forwardHttpCode + ? form.errors.forwardHttpCode + : null} +
+ ) : null} +
+ )} +

diff --git a/frontend/src/modules/Validations.tsx b/frontend/src/modules/Validations.tsx index facb875b..3b290819 100644 --- a/frontend/src/modules/Validations.tsx +++ b/frontend/src/modules/Validations.tsx @@ -44,7 +44,7 @@ const validateEmail = () => { if (!value.length) { return intl.formatMessage({ id: "error.required" }); } - if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) { + if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+$/i.test(value)) { return intl.formatMessage({ id: "error.invalid-email" }); } }; diff --git a/scripts/ci/frontend-build b/scripts/ci/frontend-build index 64e26e50..1cfc5116 100755 --- a/scripts/ci/frontend-build +++ b/scripts/ci/frontend-build @@ -16,7 +16,7 @@ if hash docker 2>/dev/null; then -e NODE_OPTIONS=--openssl-legacy-provider \ -v "$(pwd)/frontend:/app/frontend" \ -w /app/frontend "${DOCKER_IMAGE}" \ - sh -c "yarn install && yarn lint && yarn build && chown -R $(id -u):$(id -g) /app/frontend" + sh -c "yarn install && yarn lint && yarn vitest run && yarn build && chown -R $(id -u):$(id -g) /app/frontend" echo -e "${BLUE}❯ ${GREEN}Building Frontend Complete${RESET}" else