From b748b21fbd1406f82a0f9c4e35667ff5c23b93e5 Mon Sep 17 00:00:00 2001 From: GenticFlow Labs Date: Tue, 17 Mar 2026 21:46:02 +0000 Subject: [PATCH 01/17] Introduced support for load balancing through upstream hosts and customizable Real IP header source --- backend/internal/ip_ranges.js | 28 +- backend/internal/nginx.js | 2 + backend/internal/proxy-host.js | 6 +- backend/internal/report.js | 3 + backend/internal/setting.js | 5 + backend/internal/upstream-host.js | 320 ++++++++++ backend/internal/user.js | 1 + backend/lib/access.js | 1 + backend/lib/access/upstream_hosts-create.json | 23 + backend/lib/access/upstream_hosts-delete.json | 23 + backend/lib/access/upstream_hosts-get.json | 23 + backend/lib/access/upstream_hosts-list.json | 23 + backend/lib/access/upstream_hosts-update.json | 23 + backend/logger.js | 3 +- .../20260315120000_upstream_hosts.js | 96 +++ backend/models/proxy_host.js | 16 +- backend/models/upstream_host.js | 99 +++ backend/models/upstream_host_server.js | 53 ++ backend/routes/main.js | 2 + backend/routes/nginx/upstream_hosts.js | 145 +++++ .../schema/components/proxy-host-object.json | 14 + .../schema/components/upstream-host-list.json | 7 + .../components/upstream-host-object.json | 41 ++ .../paths/nginx/proxy-hosts/hostID/put.json | 3 + .../schema/paths/nginx/proxy-hosts/post.json | 3 + .../paths/nginx/upstream-hosts/get.json | 26 + .../paths/nginx/upstream-hosts/post.json | 74 +++ .../upstream-hosts/upstreamID/delete.json | 27 + .../nginx/upstream-hosts/upstreamID/get.json | 33 + .../nginx/upstream-hosts/upstreamID/put.json | 83 +++ .../schema/paths/settings/settingID/put.json | 7 +- backend/schema/swagger.json | 23 + backend/scripts/regenerate-config | 10 +- backend/setup.js | 86 ++- backend/templates/_assets.conf | 39 +- backend/templates/_location.conf | 4 + backend/templates/ip_ranges.conf | 3 +- backend/templates/proxy_host.conf | 5 + backend/templates/upstream_host.conf | 14 + .../etc/nginx/conf.d/include/assets.conf | 1 + .../etc/nginx/conf.d/include/proxy.conf | 1 - docker/rootfs/etc/nginx/nginx.conf | 3 +- .../s6-overlay/s6-rc.d/prepare/20-paths.sh | 1 + frontend/src/Router.tsx | 2 + .../src/api/backend/createUpstreamHost.ts | 9 + .../src/api/backend/deleteUpstreamHost.ts | 7 + frontend/src/api/backend/expansions.ts | 3 +- frontend/src/api/backend/getUpstreamHost.ts | 13 + frontend/src/api/backend/getUpstreamHosts.ts | 13 + frontend/src/api/backend/index.ts | 5 + frontend/src/api/backend/models.ts | 39 ++ .../src/api/backend/updateUpstreamHost.ts | 12 + .../components/Form/LoadBalancingFields.tsx | 155 +++++ .../src/components/Form/LocationsFields.tsx | 101 ++- .../src/components/Form/UpstreamHostField.tsx | 92 +++ .../components/Form/UpstreamHostSelect.tsx | 70 +++ frontend/src/components/Form/index.ts | 3 + frontend/src/components/SiteMenu.tsx | 9 + frontend/src/hooks/index.ts | 2 + frontend/src/hooks/useProxyHost.ts | 1 + frontend/src/hooks/useUpstreamHost.ts | 61 ++ frontend/src/hooks/useUpstreamHosts.ts | 17 + frontend/src/locale/src/bg.json | 174 +++++- frontend/src/locale/src/cs.json | 125 +++- frontend/src/locale/src/de.json | 201 ++++++ frontend/src/locale/src/en.json | 81 +++ frontend/src/locale/src/es.json | 171 ++++- frontend/src/locale/src/et.json | 583 ++++++++++-------- frontend/src/locale/src/fr.json | 212 ++++++- frontend/src/locale/src/ga.json | 174 ++++++ frontend/src/locale/src/hu.json | 93 ++- frontend/src/locale/src/id.json | 182 +++++- frontend/src/locale/src/it.json | 200 +++++- frontend/src/locale/src/ja.json | 204 ++++++ frontend/src/locale/src/ko.json | 176 +++++- frontend/src/locale/src/nl.json | 200 +++++- frontend/src/locale/src/no.json | 89 ++- frontend/src/locale/src/pl.json | 197 +++++- frontend/src/locale/src/pt.json | 190 +++++- frontend/src/locale/src/ru.json | 201 ++++++ frontend/src/locale/src/sk.json | 155 ++++- frontend/src/locale/src/tr.json | 184 +++++- frontend/src/locale/src/vi.json | 200 +++++- frontend/src/locale/src/zh.json | 199 +++++- frontend/src/modals/PermissionsModal.tsx | 9 + frontend/src/modals/ProxyHostModal.tsx | 255 +++++--- frontend/src/modals/UpstreamHostModal.tsx | 215 +++++++ frontend/src/modals/index.ts | 1 + frontend/src/modules/Permissions.ts | 4 +- frontend/src/pages/Nginx/ProxyHosts/Table.tsx | 15 + .../pages/Nginx/ProxyHosts/TableWrapper.tsx | 2 +- frontend/src/pages/Settings/Layout.tsx | 24 +- frontend/src/pages/Settings/RealIpHeader.tsx | 163 +++++ frontend/src/pages/UpstreamHosts/Table.tsx | 150 +++++ .../src/pages/UpstreamHosts/TableWrapper.tsx | 100 +++ frontend/src/pages/UpstreamHosts/index.tsx | 13 + 96 files changed, 6681 insertions(+), 478 deletions(-) create mode 100644 backend/internal/upstream-host.js create mode 100644 backend/lib/access/upstream_hosts-create.json create mode 100644 backend/lib/access/upstream_hosts-delete.json create mode 100644 backend/lib/access/upstream_hosts-get.json create mode 100644 backend/lib/access/upstream_hosts-list.json create mode 100644 backend/lib/access/upstream_hosts-update.json create mode 100644 backend/migrations/20260315120000_upstream_hosts.js create mode 100644 backend/models/upstream_host.js create mode 100644 backend/models/upstream_host_server.js create mode 100644 backend/routes/nginx/upstream_hosts.js create mode 100644 backend/schema/components/upstream-host-list.json create mode 100644 backend/schema/components/upstream-host-object.json create mode 100644 backend/schema/paths/nginx/upstream-hosts/get.json create mode 100644 backend/schema/paths/nginx/upstream-hosts/post.json create mode 100644 backend/schema/paths/nginx/upstream-hosts/upstreamID/delete.json create mode 100644 backend/schema/paths/nginx/upstream-hosts/upstreamID/get.json create mode 100644 backend/schema/paths/nginx/upstream-hosts/upstreamID/put.json create mode 100644 backend/templates/upstream_host.conf create mode 100644 frontend/src/api/backend/createUpstreamHost.ts create mode 100644 frontend/src/api/backend/deleteUpstreamHost.ts create mode 100644 frontend/src/api/backend/getUpstreamHost.ts create mode 100644 frontend/src/api/backend/getUpstreamHosts.ts create mode 100644 frontend/src/api/backend/updateUpstreamHost.ts create mode 100644 frontend/src/components/Form/LoadBalancingFields.tsx create mode 100644 frontend/src/components/Form/UpstreamHostField.tsx create mode 100644 frontend/src/components/Form/UpstreamHostSelect.tsx create mode 100644 frontend/src/hooks/useUpstreamHost.ts create mode 100644 frontend/src/hooks/useUpstreamHosts.ts create mode 100644 frontend/src/modals/UpstreamHostModal.tsx create mode 100644 frontend/src/pages/Settings/RealIpHeader.tsx create mode 100644 frontend/src/pages/UpstreamHosts/Table.tsx create mode 100644 frontend/src/pages/UpstreamHosts/TableWrapper.tsx create mode 100644 frontend/src/pages/UpstreamHosts/index.tsx diff --git a/backend/internal/ip_ranges.js b/backend/internal/ip_ranges.js index 6aa2b88a98..c7a47ae09d 100644 --- a/backend/internal/ip_ranges.js +++ b/backend/internal/ip_ranges.js @@ -6,6 +6,7 @@ import { ProxyAgent } from "proxy-agent"; import errs from "../lib/error.js"; import utils from "../lib/utils.js"; import { ipRanges as logger } from "../logger.js"; +import settingModel from "../models/setting.js"; import internalNginx from "./nginx.js"; const __filename = fileURLToPath(import.meta.url); @@ -23,6 +24,7 @@ const internalIpRanges = { interval: null, interval_processing: false, iteration_count: 0, + last_ip_ranges: [], initTimer: () => { logger.info("IP Ranges Renewal Timer initialized"); @@ -107,6 +109,8 @@ const internalIpRanges = { return true; }); + internalIpRanges.last_ip_ranges = clean_ip_ranges; + return internalIpRanges.generateConfig(clean_ip_ranges).then(() => { if (internalIpRanges.iteration_count) { // Reload nginx @@ -129,7 +133,17 @@ const internalIpRanges = { * @param {Array} ip_ranges * @returns {Promise} */ - generateConfig: (ip_ranges) => { + generateConfig: async (ip_ranges) => { + let realIpHeader = "X-Real-IP"; + try { + const setting = await settingModel.query().where("id", "real-ip-header").first(); + if (setting?.value) { + realIpHeader = setting.value === "custom" && setting.meta?.custom + ? setting.meta.custom + : setting.value; + } + } catch (_) {} + const renderEngine = utils.getRenderEngine(); return new Promise((resolve, reject) => { let template = null; @@ -142,7 +156,7 @@ const internalIpRanges = { } renderEngine - .parseAndRender(template, { ip_ranges: ip_ranges }) + .parseAndRender(template, { ip_ranges: ip_ranges, real_ip_header: realIpHeader }) .then((config_text) => { fs.writeFileSync(filename, config_text, { encoding: "utf8" }); resolve(true); @@ -153,6 +167,16 @@ const internalIpRanges = { }); }); }, + + /** + * Regenerate ip_ranges.conf with cached ranges and reload nginx. + * Called when the real-ip-header setting changes. + * @returns {Promise} + */ + regenerate: async () => { + await internalIpRanges.generateConfig(internalIpRanges.last_ip_ranges); + await internalNginx.reload(); + }, }; export default internalIpRanges; diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index fe84607f96..5ece76e14a 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -162,6 +162,8 @@ const internalNginx = { { hsts_subdomains: host.hsts_subdomains }, { access_list: host.access_list }, { certificate: host.certificate }, + { upstream_host_id: 0 }, + { upstream_host_forward_scheme: "http" }, host.locations[i], ); diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 34475c99f7..7d8b570a90 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -80,7 +80,7 @@ const internalProxyHost = { // re-fetch with cert return internalProxyHost.get(access, { id: row.id, - expand: ["certificate", "owner", "access_list.[clients,items]"], + expand: ["certificate", "owner", "access_list.[clients,items]", "upstream_host.[servers]"], }); }) .then((row) => { @@ -206,7 +206,7 @@ const internalProxyHost = { return internalProxyHost .get(access, { id: thisData.id, - expand: ["owner", "certificate", "access_list.[clients,items]"], + expand: ["owner", "certificate", "access_list.[clients,items]", "upstream_host.[servers]"], }) .then((row) => { if (!row.enabled) { @@ -323,7 +323,7 @@ const internalProxyHost = { .then(() => { return internalProxyHost.get(access, { id: data.id, - expand: ["certificate", "owner", "access_list"], + expand: ["certificate", "owner", "access_list", "upstream_host.[servers]"], }); }) .then((row) => { diff --git a/backend/internal/report.js b/backend/internal/report.js index 59f13fe695..e9fbfd34a1 100644 --- a/backend/internal/report.js +++ b/backend/internal/report.js @@ -2,6 +2,7 @@ import internalDeadHost from "./dead-host.js"; import internalProxyHost from "./proxy-host.js"; import internalRedirectionHost from "./redirection-host.js"; import internalStream from "./stream.js"; +import internalUpstreamHost from "./upstream-host.js"; const internalReport = { /** @@ -19,6 +20,7 @@ const internalReport = { internalRedirectionHost.getCount(userId, access_data.permission_visibility), internalStream.getCount(userId, access_data.permission_visibility), internalDeadHost.getCount(userId, access_data.permission_visibility), + internalUpstreamHost.getCount(userId, access_data.permission_visibility), ]; return Promise.all(promises); @@ -29,6 +31,7 @@ const internalReport = { redirection: counts.shift(), stream: counts.shift(), dead: counts.shift(), + upstream: counts.shift(), }; }); }, diff --git a/backend/internal/setting.js b/backend/internal/setting.js index f8fc711454..63a1ceca99 100644 --- a/backend/internal/setting.js +++ b/backend/internal/setting.js @@ -1,6 +1,7 @@ import fs from "node:fs"; import errs from "../lib/error.js"; import settingModel from "../models/setting.js"; +import internalIpRanges from "./ip_ranges.js"; import internalNginx from "./nginx.js"; const internalSetting = { @@ -32,6 +33,10 @@ const internalSetting = { }); }) .then((row) => { + if (row.id === "real-ip-header") { + return internalIpRanges.regenerate().then(() => row); + } + if (row.id === "default-site") { // write the html if we need to if (row.value === "html") { diff --git a/backend/internal/upstream-host.js b/backend/internal/upstream-host.js new file mode 100644 index 0000000000..9920d5026e --- /dev/null +++ b/backend/internal/upstream-host.js @@ -0,0 +1,320 @@ +import _ from "lodash"; +import errs from "../lib/error.js"; +import utils from "../lib/utils.js"; +import { upstreamHosts as logger } from "../logger.js"; +import proxyHostModel from "../models/proxy_host.js"; +import upstreamHostModel from "../models/upstream_host.js"; +import upstreamHostServerModel from "../models/upstream_host_server.js"; +import internalAuditLog from "./audit-log.js"; +import internalNginx from "./nginx.js"; + +const omissions = () => { + return ["is_deleted"]; +}; + +const internalUpstreamHost = { + /** + * @param {Access} access + * @param {Object} data + * @returns {Promise} + */ + create: async (access, data) => { + await access.can("upstream_hosts:create", data); + const row = await upstreamHostModel + .query() + .insertAndFetch({ + name: data.name, + forward_scheme: data.forward_scheme || "http", + method: data.method || "round_robin", + owner_user_id: access.token.getUserId(1), + }) + .then(utils.omitRow(omissions())); + + data.id = row.id; + + // Insert servers + const promises = []; + if (data.servers && data.servers.length) { + data.servers.map((server) => { + promises.push( + upstreamHostServerModel.query().insert({ + upstream_host_id: row.id, + host: server.host, + port: server.port, + weight: server.weight || null, + }), + ); + return true; + }); + } + + await Promise.all(promises); + + // re-fetch with expansions + const freshRow = await internalUpstreamHost.get(access, { + id: data.id, + expand: ["owner", "servers"], + }); + + // Configure nginx + await internalNginx.configure(upstreamHostModel, "upstream_host", freshRow); + + // Add to audit log + await internalAuditLog.add(access, { + action: "created", + object_type: "upstream-host", + object_id: freshRow.id, + meta: data, + }); + + return freshRow; + }, + + /** + * @param {Access} access + * @param {Object} data + * @param {Integer} data.id + * @return {Promise} + */ + update: async (access, data) => { + await access.can("upstream_hosts:update", data.id); + const row = await internalUpstreamHost.get(access, { id: data.id }); + if (row.id !== data.id) { + throw new errs.InternalValidationError( + `Upstream Host could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); + } + + // Patch fields + const patchData = {}; + if (typeof data.name !== "undefined") patchData.name = data.name; + if (typeof data.forward_scheme !== "undefined") patchData.forward_scheme = data.forward_scheme; + if (typeof data.method !== "undefined") patchData.method = data.method; + + if (Object.keys(patchData).length) { + await upstreamHostModel.query().where({ id: data.id }).patch(patchData); + } + + // Handle servers: delete + re-insert + if (typeof data.servers !== "undefined" && data.servers) { + await upstreamHostServerModel.query().delete().where("upstream_host_id", data.id); + + const serverPromises = []; + data.servers.map((server) => { + if (server.host) { + serverPromises.push( + upstreamHostServerModel.query().insert({ + upstream_host_id: data.id, + host: server.host, + port: server.port, + weight: server.weight || null, + }), + ); + } + return true; + }); + + if (serverPromises.length) { + await Promise.all(serverPromises); + } + } + + // Add to audit log + await internalAuditLog.add(access, { + action: "updated", + object_type: "upstream-host", + object_id: data.id, + meta: data, + }); + + // re-fetch with expansions + const freshRow = await internalUpstreamHost.get(access, { + id: data.id, + expand: ["owner", "servers", "proxy_hosts.[certificate,access_list.[clients,items],upstream_host.[servers]]"], + }); + + // Regenerate upstream config + await internalNginx.configure(upstreamHostModel, "upstream_host", freshRow); + + // Bulk regenerate all referencing proxy host configs + if (Number.parseInt(freshRow.proxy_host_count, 10)) { + await internalNginx.bulkGenerateConfigs("proxy_host", freshRow.proxy_hosts); + } + await internalNginx.reload(); + + return freshRow; + }, + + /** + * @param {Access} access + * @param {Object} data + * @param {Integer} data.id + * @param {Array} [data.expand] + * @param {Array} [data.omit] + * @return {Promise} + */ + get: async (access, data) => { + const thisData = data || {}; + const accessData = await access.can("upstream_hosts:get", thisData.id); + + const query = upstreamHostModel + .query() + .select("upstream_host.*", upstreamHostModel.raw("COUNT(proxy_host.id) as proxy_host_count")) + .leftJoin("proxy_host", function () { + this.on("proxy_host.upstream_host_id", "=", "upstream_host.id").andOn( + "proxy_host.is_deleted", + "=", + 0, + ); + }) + .where("upstream_host.is_deleted", 0) + .andWhere("upstream_host.id", thisData.id) + .groupBy("upstream_host.id") + .allowGraph("[owner,servers,proxy_hosts.[certificate,access_list.[clients,items],upstream_host.[servers]]]") + .first(); + + if (accessData.permission_visibility !== "all") { + query.andWhere("upstream_host.owner_user_id", access.token.getUserId(1)); + } + + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); + } + + const row = await query.then(utils.omitRow(omissions())); + + if (!row || !row.id) { + throw new errs.ItemNotFoundError(thisData.id); + } + + // Custom omissions + if (typeof data.omit !== "undefined" && data.omit !== null) { + return _.omit(row, data.omit); + } + + return row; + }, + + /** + * @param {Access} access + * @param {Object} data + * @param {Integer} data.id + * @returns {Promise} + */ + delete: async (access, data) => { + await access.can("upstream_hosts:delete", data.id); + const row = await internalUpstreamHost.get(access, { + id: data.id, + expand: ["proxy_hosts.[certificate,access_list.[clients,items]]", "servers"], + }); + + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } + + // 1. soft-delete the upstream host + await upstreamHostModel + .query() + .where("id", row.id) + .patch({ + is_deleted: 1, + }); + + // 2. clear upstream_host_id on referencing proxy hosts + if (row.proxy_hosts) { + await proxyHostModel + .query() + .where("upstream_host_id", "=", row.id) + .patch({ upstream_host_id: 0 }); + + // update in-memory so config regen uses 0 + row.proxy_hosts.map((_val, idx) => { + row.proxy_hosts[idx].upstream_host_id = 0; + return true; + }); + + await internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); + } + + // 3. delete upstream config file + await internalNginx.deleteConfig("upstream_host", row, true); + await internalNginx.reload(); + + // 4. audit log + await internalAuditLog.add(access, { + action: "deleted", + object_type: "upstream-host", + object_id: row.id, + meta: _.omit(row, ["is_deleted", "proxy_hosts"]), + }); + + return true; + }, + + /** + * All upstream hosts + * + * @param {Access} access + * @param {Array} [expand] + * @param {String} [searchQuery] + * @returns {Promise} + */ + getAll: async (access, expand, searchQuery) => { + const accessData = await access.can("upstream_hosts:list"); + + const query = upstreamHostModel + .query() + .select("upstream_host.*", upstreamHostModel.raw("COUNT(proxy_host.id) as proxy_host_count")) + .leftJoin("proxy_host", function () { + this.on("proxy_host.upstream_host_id", "=", "upstream_host.id").andOn( + "proxy_host.is_deleted", + "=", + 0, + ); + }) + .where("upstream_host.is_deleted", 0) + .groupBy("upstream_host.id") + .allowGraph("[owner,servers]") + .orderBy("upstream_host.name", "ASC"); + + if (accessData.permission_visibility !== "all") { + query.andWhere("upstream_host.owner_user_id", access.token.getUserId(1)); + } + + // Query is used for searching + if (typeof searchQuery === "string") { + query.where(function () { + this.where("upstream_host.name", "like", `%${searchQuery}%`); + }); + } + + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + return query.then(utils.omitRows(omissions())); + }, + + /** + * Count is used in reports + * + * @param {Integer} userId + * @param {String} visibility + * @returns {Promise} + */ + getCount: async (userId, visibility) => { + const query = upstreamHostModel + .query() + .count("id as count") + .where("is_deleted", 0); + + if (visibility !== "all") { + query.andWhere("owner_user_id", userId); + } + + const row = await query.first(); + return Number.parseInt(row.count, 10); + }, +}; + +export default internalUpstreamHost; diff --git a/backend/internal/user.js b/backend/internal/user.js index d13931d54a..b8de0ff895 100644 --- a/backend/internal/user.js +++ b/backend/internal/user.js @@ -59,6 +59,7 @@ const internalUser = { streams: "manage", access_lists: "manage", certificates: "manage", + upstream_hosts: "manage", }); user = await internalUser.get(access, { id: user.id, expand: ["permissions"] }); diff --git a/backend/lib/access.js b/backend/lib/access.js index a4dec5c4dd..8fafa35c49 100644 --- a/backend/lib/access.js +++ b/backend/lib/access.js @@ -241,6 +241,7 @@ export default function (tokenString) { permission_streams: permissions.streams, permission_access_lists: permissions.access_lists, permission_certificates: permissions.certificates, + permission_upstream_hosts: permissions.upstream_hosts, }, }; diff --git a/backend/lib/access/upstream_hosts-create.json b/backend/lib/access/upstream_hosts-create.json new file mode 100644 index 0000000000..1c153758c9 --- /dev/null +++ b/backend/lib/access/upstream_hosts-create.json @@ -0,0 +1,23 @@ +{ + "anyOf": [ + { + "$ref": "roles#/definitions/admin" + }, + { + "type": "object", + "required": ["permission_upstream_hosts", "roles"], + "properties": { + "permission_upstream_hosts": { + "$ref": "perms#/definitions/manage" + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": ["user"] + } + } + } + } + ] +} diff --git a/backend/lib/access/upstream_hosts-delete.json b/backend/lib/access/upstream_hosts-delete.json new file mode 100644 index 0000000000..1c153758c9 --- /dev/null +++ b/backend/lib/access/upstream_hosts-delete.json @@ -0,0 +1,23 @@ +{ + "anyOf": [ + { + "$ref": "roles#/definitions/admin" + }, + { + "type": "object", + "required": ["permission_upstream_hosts", "roles"], + "properties": { + "permission_upstream_hosts": { + "$ref": "perms#/definitions/manage" + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": ["user"] + } + } + } + } + ] +} diff --git a/backend/lib/access/upstream_hosts-get.json b/backend/lib/access/upstream_hosts-get.json new file mode 100644 index 0000000000..a9378778e6 --- /dev/null +++ b/backend/lib/access/upstream_hosts-get.json @@ -0,0 +1,23 @@ +{ + "anyOf": [ + { + "$ref": "roles#/definitions/admin" + }, + { + "type": "object", + "required": ["permission_upstream_hosts", "roles"], + "properties": { + "permission_upstream_hosts": { + "$ref": "perms#/definitions/view" + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": ["user"] + } + } + } + } + ] +} diff --git a/backend/lib/access/upstream_hosts-list.json b/backend/lib/access/upstream_hosts-list.json new file mode 100644 index 0000000000..a9378778e6 --- /dev/null +++ b/backend/lib/access/upstream_hosts-list.json @@ -0,0 +1,23 @@ +{ + "anyOf": [ + { + "$ref": "roles#/definitions/admin" + }, + { + "type": "object", + "required": ["permission_upstream_hosts", "roles"], + "properties": { + "permission_upstream_hosts": { + "$ref": "perms#/definitions/view" + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": ["user"] + } + } + } + } + ] +} diff --git a/backend/lib/access/upstream_hosts-update.json b/backend/lib/access/upstream_hosts-update.json new file mode 100644 index 0000000000..1c153758c9 --- /dev/null +++ b/backend/lib/access/upstream_hosts-update.json @@ -0,0 +1,23 @@ +{ + "anyOf": [ + { + "$ref": "roles#/definitions/admin" + }, + { + "type": "object", + "required": ["permission_upstream_hosts", "roles"], + "properties": { + "permission_upstream_hosts": { + "$ref": "perms#/definitions/manage" + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": ["user"] + } + } + } + } + ] +} diff --git a/backend/logger.js b/backend/logger.js index 2b60dbff7b..889c9b3a10 100644 --- a/backend/logger.js +++ b/backend/logger.js @@ -16,6 +16,7 @@ const importer = new signale.Signale({ scope: "Importer ", ...opts }); const setup = new signale.Signale({ scope: "Setup ", ...opts }); const ipRanges = new signale.Signale({ scope: "IP Ranges", ...opts }); const remoteVersion = new signale.Signale({ scope: "Remote Version", ...opts }); +const upstreamHosts = new signale.Signale({ scope: "Upstream ", ...opts }); const debug = (logger, ...args) => { if (isDebugMode()) { @@ -23,4 +24,4 @@ const debug = (logger, ...args) => { } }; -export { debug, global, migrate, express, access, nginx, ssl, certbot, importer, setup, ipRanges, remoteVersion }; +export { debug, global, migrate, express, access, nginx, ssl, certbot, importer, setup, ipRanges, remoteVersion, upstreamHosts }; diff --git a/backend/migrations/20260315120000_upstream_hosts.js b/backend/migrations/20260315120000_upstream_hosts.js new file mode 100644 index 0000000000..6d2b15aece --- /dev/null +++ b/backend/migrations/20260315120000_upstream_hosts.js @@ -0,0 +1,96 @@ +import { migrate as logger } from "../logger.js"; + +const migrateName = "upstream_hosts"; + +/** + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @returns {Promise} + */ +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); + + return knex.schema + .createTable("upstream_host", (table) => { + table.increments().primary(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.string("name", 255).notNull(); + table.string("forward_scheme", 10).notNull().defaultTo("http"); + table.string("method", 32).notNull().defaultTo("round_robin"); + table.json("meta").notNull(); + }) + .then(() => { + logger.info(`[${migrateName}] upstream_host Table created`); + + return knex.schema.createTable("upstream_host_server", (table) => { + table.increments().primary(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("upstream_host_id").notNull().unsigned(); + table.string("host", 255).notNull(); + table.integer("port").notNull().unsigned(); + table.integer("weight").nullable().unsigned(); + table.json("meta").notNull(); + }); + }) + .then(() => { + logger.info(`[${migrateName}] upstream_host_server Table created`); + + return knex.schema.table("proxy_host", (proxy_host) => { + proxy_host.integer("upstream_host_id").notNull().unsigned().defaultTo(0); + }); + }) + .then(() => { + logger.info(`[${migrateName}] proxy_host Table altered`); + + return knex.schema.table("user_permission", (user_permission) => { + user_permission.string("upstream_hosts").notNull().defaultTo("manage"); + }); + }) + .then(() => { + logger.info(`[${migrateName}] user_permission Table altered`); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); + + return knex.schema + .dropTable("upstream_host_server") + .then(() => { + logger.info(`[${migrateName}] upstream_host_server Table dropped`); + + return knex.schema.dropTable("upstream_host"); + }) + .then(() => { + logger.info(`[${migrateName}] upstream_host Table dropped`); + + return knex.schema.table("proxy_host", (proxy_host) => { + proxy_host.dropColumn("upstream_host_id"); + }); + }) + .then(() => { + logger.info(`[${migrateName}] proxy_host Table reverted`); + + return knex.schema.table("user_permission", (user_permission) => { + user_permission.dropColumn("upstream_hosts"); + }); + }) + .then(() => { + logger.info(`[${migrateName}] user_permission Table reverted`); + }); +}; + +export { up, down }; diff --git a/backend/models/proxy_host.js b/backend/models/proxy_host.js index acb8da9358..1bc3a44c74 100644 --- a/backend/models/proxy_host.js +++ b/backend/models/proxy_host.js @@ -8,6 +8,7 @@ import AccessList from "./access_list.js"; import Certificate from "./certificate.js"; import now from "./now_helper.js"; import User from "./user.js"; +import UpstreamHost from "./upstream_host.js"; Model.knex(db()); @@ -74,11 +75,11 @@ class ProxyHost extends Model { } static get defaultAllowGraph() { - return "[owner,access_list.[clients,items],certificate]"; + return "[owner,access_list.[clients,items],certificate,upstream_host.[servers]]"; } static get defaultExpand() { - return ["owner", "certificate", "access_list.[clients,items]"]; + return ["owner", "certificate", "access_list.[clients,items]", "upstream_host.[servers]"]; } static get defaultOrder() { @@ -120,6 +121,17 @@ class ProxyHost extends Model { qb.where("certificate.is_deleted", 0); }, }, + upstream_host: { + relation: Model.HasOneRelation, + modelClass: UpstreamHost, + join: { + from: "proxy_host.upstream_host_id", + to: "upstream_host.id", + }, + modify: (qb) => { + qb.where("upstream_host.is_deleted", 0); + }, + }, }; } } diff --git a/backend/models/upstream_host.js b/backend/models/upstream_host.js new file mode 100644 index 0000000000..0a074cc999 --- /dev/null +++ b/backend/models/upstream_host.js @@ -0,0 +1,99 @@ +// Objection Docs: +// http://vincit.github.io/objection.js/ + +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import now from "./now_helper.js"; +import UpstreamHostServer from "./upstream_host_server.js"; +import ProxyHostModel from "./proxy_host.js"; +import User from "./user.js"; + +Model.knex(db()); + +const boolFields = ["is_deleted"]; + +class UpstreamHost extends Model { + $beforeInsert() { + this.created_on = now(); + this.modified_on = now(); + if (typeof this.meta === "undefined") { + this.meta = {}; + } + } + + $beforeUpdate() { + this.modified_on = now(); + } + + $parseDatabaseJson(json) { + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); + } + + $formatDatabaseJson(json) { + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); + } + + static get name() { + return "UpstreamHost"; + } + + static get tableName() { + return "upstream_host"; + } + + static get jsonAttributes() { + return ["meta"]; + } + + static get defaultAllowGraph() { + return "[owner,servers,proxy_hosts]"; + } + + static get defaultExpand() { + return ["owner", "servers"]; + } + + static get defaultOrder() { + return ["name", "ASC"]; + } + + static get relationMappings() { + return { + owner: { + relation: Model.HasOneRelation, + modelClass: User, + join: { + from: "upstream_host.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); + }, + }, + servers: { + relation: Model.HasManyRelation, + modelClass: UpstreamHostServer, + join: { + from: "upstream_host.id", + to: "upstream_host_server.upstream_host_id", + }, + }, + proxy_hosts: { + relation: Model.HasManyRelation, + modelClass: ProxyHostModel, + join: { + from: "upstream_host.id", + to: "proxy_host.upstream_host_id", + }, + modify: (qb) => { + qb.where("proxy_host.is_deleted", 0); + }, + }, + }; + } +} + +export default UpstreamHost; diff --git a/backend/models/upstream_host_server.js b/backend/models/upstream_host_server.js new file mode 100644 index 0000000000..5a90bf1c45 --- /dev/null +++ b/backend/models/upstream_host_server.js @@ -0,0 +1,53 @@ +// Objection Docs: +// http://vincit.github.io/objection.js/ + +import { Model } from "objection"; +import db from "../db.js"; +import upstreamHostModel from "./upstream_host.js"; +import now from "./now_helper.js"; + +Model.knex(db()); + +class UpstreamHostServer extends Model { + $beforeInsert() { + this.created_on = now(); + this.modified_on = now(); + if (typeof this.meta === "undefined") { + this.meta = {}; + } + } + + $beforeUpdate() { + this.modified_on = now(); + } + + static get name() { + return "UpstreamHostServer"; + } + + static get tableName() { + return "upstream_host_server"; + } + + static get jsonAttributes() { + return ["meta"]; + } + + static get relationMappings() { + return { + upstream_host: { + relation: Model.HasOneRelation, + modelClass: upstreamHostModel, + join: { + from: "upstream_host_server.upstream_host_id", + to: "upstream_host.id", + }, + modify: (qb) => { + qb.where("upstream_host.is_deleted", 0); + }, + }, + }; + } +} + +export default UpstreamHostServer; diff --git a/backend/routes/main.js b/backend/routes/main.js index 94682cfba4..db878e9960 100644 --- a/backend/routes/main.js +++ b/backend/routes/main.js @@ -9,6 +9,7 @@ import deadHostsRoutes from "./nginx/dead_hosts.js"; import proxyHostsRoutes from "./nginx/proxy_hosts.js"; import redirectionHostsRoutes from "./nginx/redirection_hosts.js"; import streamsRoutes from "./nginx/streams.js"; +import upstreamHostsRoutes from "./nginx/upstream_hosts.js"; import reportsRoutes from "./reports.js"; import schemaRoutes from "./schema.js"; import settingsRoutes from "./settings.js"; @@ -53,6 +54,7 @@ router.use("/nginx/redirection-hosts", redirectionHostsRoutes); router.use("/nginx/dead-hosts", deadHostsRoutes); router.use("/nginx/streams", streamsRoutes); router.use("/nginx/access-lists", accessListsRoutes); +router.use("/nginx/upstream-hosts", upstreamHostsRoutes); router.use("/nginx/certificates", certificatesHostsRoutes); /** diff --git a/backend/routes/nginx/upstream_hosts.js b/backend/routes/nginx/upstream_hosts.js new file mode 100644 index 0000000000..19e69cd6b5 --- /dev/null +++ b/backend/routes/nginx/upstream_hosts.js @@ -0,0 +1,145 @@ +import express from "express"; +import internalUpstreamHost from "../../internal/upstream-host.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { debug, express as logger } from "../../logger.js"; +import { getValidationSchema } from "../../schema/index.js"; + +const router = express.Router({ + caseSensitive: true, + strict: true, + mergeParams: true, +}); + +/** + * /api/nginx/upstream-hosts + */ +router + .route("/") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * GET /api/nginx/upstream-hosts + */ + .get(async (req, res, next) => { + try { + const data = await validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, + }, + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ); + const rows = await internalUpstreamHost.getAll(res.locals.access, data.expand, data.query); + res.status(200).send(rows); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }) + + /** + * POST /api/nginx/upstream-hosts + */ + .post(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/upstream-hosts", "post"), req.body); + const result = await internalUpstreamHost.create(res.locals.access, payload); + res.status(201).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +/** + * Specific upstream host + * + * /api/nginx/upstream-hosts/123 + */ +router + .route("/:upstream_id") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * GET /api/nginx/upstream-hosts/123 + */ + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["upstream_id"], + additionalProperties: false, + properties: { + upstream_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, + }, + }, + { + upstream_id: req.params.upstream_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ); + const row = await internalUpstreamHost.get(res.locals.access, { + id: Number.parseInt(data.upstream_id, 10), + expand: data.expand, + }); + res.status(200).send(row); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }) + + /** + * PUT /api/nginx/upstream-hosts/123 + */ + .put(async (req, res, next) => { + try { + const payload = await apiValidator(getValidationSchema("/nginx/upstream-hosts/{upstreamID}", "put"), req.body); + payload.id = Number.parseInt(req.params.upstream_id, 10); + const result = await internalUpstreamHost.update(res.locals.access, payload); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }) + + /** + * DELETE /api/nginx/upstream-hosts/123 + */ + .delete(async (req, res, next) => { + try { + const result = await internalUpstreamHost.delete(res.locals.access, { + id: Number.parseInt(req.params.upstream_id, 10), + }); + res.status(200).send(result); + } catch (err) { + debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + +export default router; diff --git a/backend/schema/components/proxy-host-object.json b/backend/schema/components/proxy-host-object.json index 3ac6462136..e9b3f0663d 100644 --- a/backend/schema/components/proxy-host-object.json +++ b/backend/schema/components/proxy-host-object.json @@ -124,6 +124,14 @@ }, "advanced_config": { "type": "string" + }, + "upstream_host_id": { + "type": "integer", + "minimum": 0 + }, + "upstream_host_forward_scheme": { + "type": "string", + "enum": ["http", "https"] } } }, @@ -142,6 +150,12 @@ "hsts_subdomains": { "$ref": "../common.json#/properties/hsts_subdomains" }, + "upstream_host_id": { + "description": "Upstream Host ID", + "type": "integer", + "minimum": 0, + "example": 0 + }, "trust_forwarded_proto":{ "type": "boolean", "description": "Trust the forwarded headers", diff --git a/backend/schema/components/upstream-host-list.json b/backend/schema/components/upstream-host-list.json new file mode 100644 index 0000000000..dbc66cebff --- /dev/null +++ b/backend/schema/components/upstream-host-list.json @@ -0,0 +1,7 @@ +{ + "type": "array", + "description": "Upstream Hosts list", + "items": { + "$ref": "./upstream-host-object.json" + } +} diff --git a/backend/schema/components/upstream-host-object.json b/backend/schema/components/upstream-host-object.json new file mode 100644 index 0000000000..84ea1ccf30 --- /dev/null +++ b/backend/schema/components/upstream-host-object.json @@ -0,0 +1,41 @@ +{ + "type": "object", + "description": "Upstream Host object", + "required": ["id", "created_on", "modified_on", "owner_user_id", "name", "forward_scheme", "method", "meta"], + "additionalProperties": false, + "properties": { + "id": { + "type": "integer", + "minimum": 1 + }, + "created_on": { + "type": "string" + }, + "modified_on": { + "type": "string" + }, + "owner_user_id": { + "type": "integer", + "minimum": 1 + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "forward_scheme": { + "type": "string", + "enum": ["http", "https"] + }, + "method": { + "type": "string", + "enum": ["round_robin", "least_conn", "ip_hash"] + }, + "meta": { + "type": "object" + }, + "proxy_host_count": { + "type": "integer" + } + } +} diff --git a/backend/schema/paths/nginx/proxy-hosts/hostID/put.json b/backend/schema/paths/nginx/proxy-hosts/hostID/put.json index fc3198456b..969f8850c2 100644 --- a/backend/schema/paths/nginx/proxy-hosts/hostID/put.json +++ b/backend/schema/paths/nginx/proxy-hosts/hostID/put.json @@ -83,6 +83,9 @@ "meta": { "$ref": "../../../../components/proxy-host-object.json#/properties/meta" }, + "upstream_host_id": { + "$ref": "../../../../components/proxy-host-object.json#/properties/upstream_host_id" + }, "locations": { "$ref": "../../../../components/proxy-host-object.json#/properties/locations" } diff --git a/backend/schema/paths/nginx/proxy-hosts/post.json b/backend/schema/paths/nginx/proxy-hosts/post.json index 28ddad8fc2..c8c76677f8 100644 --- a/backend/schema/paths/nginx/proxy-hosts/post.json +++ b/backend/schema/paths/nginx/proxy-hosts/post.json @@ -75,6 +75,9 @@ "meta": { "$ref": "../../../components/proxy-host-object.json#/properties/meta" }, + "upstream_host_id": { + "$ref": "../../../components/proxy-host-object.json#/properties/upstream_host_id" + }, "locations": { "$ref": "../../../components/proxy-host-object.json#/properties/locations" } diff --git a/backend/schema/paths/nginx/upstream-hosts/get.json b/backend/schema/paths/nginx/upstream-hosts/get.json new file mode 100644 index 0000000000..8b74310d56 --- /dev/null +++ b/backend/schema/paths/nginx/upstream-hosts/get.json @@ -0,0 +1,26 @@ +{ + "operationId": "getUpstreamHosts", + "summary": "Get all upstream hosts", + "tags": ["upstream-hosts"], + "security": [{ "bearerAuth": [] }], + "parameters": [ + { + "in": "query", + "name": "expand", + "description": "Expansions", + "schema": { "type": "string", "enum": ["owner", "servers"] } + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "$ref": "../../../components/upstream-host-list.json" + } + } + } + } + } +} diff --git a/backend/schema/paths/nginx/upstream-hosts/post.json b/backend/schema/paths/nginx/upstream-hosts/post.json new file mode 100644 index 0000000000..bd0bc68ca9 --- /dev/null +++ b/backend/schema/paths/nginx/upstream-hosts/post.json @@ -0,0 +1,74 @@ +{ + "operationId": "createUpstreamHost", + "summary": "Create an Upstream Host", + "tags": ["upstream-hosts"], + "security": [{ "bearerAuth": [] }], + "requestBody": { + "description": "Upstream Host Payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["name", "servers"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "forward_scheme": { + "type": "string", + "enum": ["http", "https"] + }, + "method": { + "type": "string", + "enum": ["round_robin", "least_conn", "ip_hash"] + }, + "servers": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["host", "port"], + "properties": { + "host": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "weight": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + } + }, + "meta": { + "type": "object" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "201 response", + "content": { + "application/json": { + "schema": { + "$ref": "../../../components/upstream-host-object.json" + } + } + } + } + } +} diff --git a/backend/schema/paths/nginx/upstream-hosts/upstreamID/delete.json b/backend/schema/paths/nginx/upstream-hosts/upstreamID/delete.json new file mode 100644 index 0000000000..a3083489e6 --- /dev/null +++ b/backend/schema/paths/nginx/upstream-hosts/upstreamID/delete.json @@ -0,0 +1,27 @@ +{ + "operationId": "deleteUpstreamHost", + "summary": "Delete an Upstream Host", + "tags": ["upstream-hosts"], + "security": [{ "bearerAuth": [] }], + "parameters": [ + { + "in": "path", + "name": "upstreamID", + "schema": { "type": "integer", "minimum": 1 }, + "required": true, + "example": 1 + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } +} diff --git a/backend/schema/paths/nginx/upstream-hosts/upstreamID/get.json b/backend/schema/paths/nginx/upstream-hosts/upstreamID/get.json new file mode 100644 index 0000000000..d318f59c23 --- /dev/null +++ b/backend/schema/paths/nginx/upstream-hosts/upstreamID/get.json @@ -0,0 +1,33 @@ +{ + "operationId": "getUpstreamHost", + "summary": "Get an Upstream Host", + "tags": ["upstream-hosts"], + "security": [{ "bearerAuth": [] }], + "parameters": [ + { + "in": "path", + "name": "upstreamID", + "schema": { "type": "integer", "minimum": 1 }, + "required": true, + "example": 1 + }, + { + "in": "query", + "name": "expand", + "description": "Expansions", + "schema": { "type": "string", "enum": ["owner", "servers"] } + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "$ref": "../../../../components/upstream-host-object.json" + } + } + } + } + } +} diff --git a/backend/schema/paths/nginx/upstream-hosts/upstreamID/put.json b/backend/schema/paths/nginx/upstream-hosts/upstreamID/put.json new file mode 100644 index 0000000000..f9efc0e831 --- /dev/null +++ b/backend/schema/paths/nginx/upstream-hosts/upstreamID/put.json @@ -0,0 +1,83 @@ +{ + "operationId": "updateUpstreamHost", + "summary": "Update an Upstream Host", + "tags": ["upstream-hosts"], + "security": [{ "bearerAuth": [] }], + "parameters": [ + { + "in": "path", + "name": "upstreamID", + "schema": { "type": "integer", "minimum": 1 }, + "required": true, + "example": 1 + } + ], + "requestBody": { + "description": "Upstream Host Payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "minProperties": 1, + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "forward_scheme": { + "type": "string", + "enum": ["http", "https"] + }, + "method": { + "type": "string", + "enum": ["round_robin", "least_conn", "ip_hash"] + }, + "servers": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["host", "port"], + "properties": { + "host": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "weight": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + } + }, + "meta": { + "type": "object" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "$ref": "../../../../components/upstream-host-object.json" + } + } + } + } + } +} diff --git a/backend/schema/paths/settings/settingID/put.json b/backend/schema/paths/settings/settingID/put.json index 050ad44125..0b81f15965 100644 --- a/backend/schema/paths/settings/settingID/put.json +++ b/backend/schema/paths/settings/settingID/put.json @@ -14,7 +14,7 @@ "schema": { "type": "string", "minLength": 1, - "enum": ["default-site"] + "enum": ["default-site", "real-ip-header"] }, "required": true, "description": "Setting ID", @@ -34,7 +34,7 @@ "value": { "type": "string", "minLength": 1, - "enum": ["congratulations", "404", "444", "redirect", "html"], + "enum": ["congratulations", "404", "444", "redirect", "html", "X-Real-IP", "CF-Connecting-IP", "X-Forwarded-For", "custom"], "example": "html" }, "meta": { @@ -46,6 +46,9 @@ }, "html": { "type": "string" + }, + "custom": { + "type": "string" } }, "example": { diff --git a/backend/schema/swagger.json b/backend/schema/swagger.json index 4222f19ddd..dd134f8613 100644 --- a/backend/schema/swagger.json +++ b/backend/schema/swagger.json @@ -48,6 +48,10 @@ "name": "streams", "description": "Endpoints related to Streams" }, + { + "name": "upstream-hosts", + "description": "Endpoints related to Upstream Hosts" + }, { "name": "reports", "description": "Endpoints for viewing reports" @@ -262,6 +266,25 @@ "$ref": "./paths/nginx/streams/streamID/disable/post.json" } }, + "/nginx/upstream-hosts": { + "get": { + "$ref": "./paths/nginx/upstream-hosts/get.json" + }, + "post": { + "$ref": "./paths/nginx/upstream-hosts/post.json" + } + }, + "/nginx/upstream-hosts/{upstreamID}": { + "get": { + "$ref": "./paths/nginx/upstream-hosts/upstreamID/get.json" + }, + "put": { + "$ref": "./paths/nginx/upstream-hosts/upstreamID/put.json" + }, + "delete": { + "$ref": "./paths/nginx/upstream-hosts/upstreamID/delete.json" + } + }, "/reports/hosts": { "get": { "$ref": "./paths/reports/hosts/get.json" diff --git a/backend/scripts/regenerate-config b/backend/scripts/regenerate-config index 00f8411310..2c84100819 100755 --- a/backend/scripts/regenerate-config +++ b/backend/scripts/regenerate-config @@ -60,17 +60,17 @@ const processItems = async (model, type) => { for (const row of rows) { if (!DRY_RUN) { logIt(`[${type}] Regenerating config #${row.id}: ${row.domain_names ? row.domain_names.join(", ") : 'port ' + row.incoming_port}`); - await internalNginx.configure(proxyHostModel, "proxy_host", row); + await internalNginx.configure(model, type, row); } else { logIt(`[${type}] Skipping generation of config #${row.id}: ${row.domain_names ? row.domain_names.join(", ") : 'port ' + row.incoming_port}`); } } }; -await processItems(proxyHostModel, "Proxy Host"); -await processItems(redirectionHostModel, "Redirection Host"); -await processItems(deadHostModel, "404 Host"); -await processItems(streamModel, "Stream"); +await processItems(proxyHostModel, "proxy_host"); +await processItems(redirectionHostModel, "redirection_host"); +await processItems(deadHostModel, "dead_host"); +await processItems(streamModel, "stream"); logIt("Completed", "success"); process.exit(0); diff --git a/backend/setup.js b/backend/setup.js index 84f42793ea..6062ce25cd 100644 --- a/backend/setup.js +++ b/backend/setup.js @@ -1,9 +1,16 @@ +import fs from "node:fs"; import { installPlugins } from "./lib/certbot.js"; import utils from "./lib/utils.js"; +import internalNginx from "./internal/nginx.js"; import { setup as logger } from "./logger.js"; import authModel from "./models/auth.js"; import certificateModel from "./models/certificate.js"; +import deadHostModel from "./models/dead_host.js"; +import proxyHostModel from "./models/proxy_host.js"; +import redirectionHostModel from "./models/redirection_host.js"; import settingModel from "./models/setting.js"; +import streamModel from "./models/stream.js"; +import upstreamHostModel from "./models/upstream_host.js"; import userModel from "./models/user.js"; import userPermissionModel from "./models/user_permission.js"; @@ -66,6 +73,7 @@ const setupDefaultUser = async () => { streams: "manage", access_lists: "manage", certificates: "manage", + upstream_hosts: "manage", }); logger.info("Initial admin setup completed"); } @@ -95,6 +103,18 @@ const setupDefaultSettings = async () => { }); logger.info("Default settings added"); } + + const ipRow = await settingModel.query().select("id").where({ id: "real-ip-header" }).first(); + if (!ipRow?.id) { + await settingModel.query().insert({ + id: "real-ip-header", + name: "Real IP Header", + description: "HTTP header used to determine the real client IP address", + value: "X-Real-IP", + meta: {}, + }); + logger.info("Default real-ip-header setting added"); + } }; /** @@ -141,6 +161,70 @@ const setupCertbotPlugins = async () => { } }; +/** + * Deletes all generated nginx host config files and regenerates them + * from current templates. This ensures configs on disk always match + * the current template version after an upgrade. + * + * @returns {Promise} + */ +const setupNginxConfigs = async () => { + const hostTypes = [ + { model: upstreamHostModel, type: "upstream_host", noEnabledFilter: true }, + { model: proxyHostModel, type: "proxy_host" }, + { model: redirectionHostModel, type: "redirection_host" }, + { model: deadHostModel, type: "dead_host" }, + { model: streamModel, type: "stream" }, + ]; + + // Delete all existing host config files so stale configs don't + // block nginx from starting (e.g. after a template change) + for (const { type } of hostTypes) { + const dir = `/data/nginx/${type}`; + if (fs.existsSync(dir)) { + for (const file of fs.readdirSync(dir)) { + if (file.endsWith(".conf") || file.endsWith(".conf.err")) { + fs.unlinkSync(`${dir}/${file}`); + } + } + } + } + + // Regenerate configs for all enabled hosts + for (const { model, type, noEnabledFilter } of hostTypes) { + const query = model + .query() + .where("is_deleted", 0); + if (!noEnabledFilter) { + query.andWhere("enabled", 1); + } + const rows = await query + .groupBy("id") + .allowGraph(model.defaultAllowGraph) + .withGraphFetched(`[${model.defaultExpand.join(", ")}]`) + .orderBy(...model.defaultOrder); + + for (const row of rows) { + try { + await internalNginx.generateConfig(type, row); + } catch (err) { + logger.warn(`Failed to generate ${type} config #${row.id}: ${err.message}`); + } + } + + if (rows.length) { + logger.info(`Regenerated ${rows.length} ${type} config(s)`); + } + } + + // Reload nginx to pick up the fresh configs + try { + await internalNginx.reload(); + } catch (err) { + logger.warn(`Nginx reload after config regeneration failed: ${err.message}`); + } +}; + /** * Starts a timer to call run the logrotation binary every two days * @returns {Promise} @@ -163,4 +247,4 @@ const setupLogrotation = () => { return runLogrotate(); }; -export default () => setupDefaultUser().then(setupDefaultSettings).then(setupCertbotPlugins).then(setupLogrotation); +export default () => setupDefaultUser().then(setupDefaultSettings).then(setupCertbotPlugins).then(setupNginxConfigs).then(setupLogrotation); diff --git a/backend/templates/_assets.conf b/backend/templates/_assets.conf index dcb183c555..76ed745e4e 100644 --- a/backend/templates/_assets.conf +++ b/backend/templates/_assets.conf @@ -1,4 +1,37 @@ -{% if caching_enabled == 1 or caching_enabled == true -%} +{% if caching_enabled == 1 or caching_enabled == true %} +{% unless path %} # Asset Caching - include conf.d/include/assets.conf; -{% endif %} \ No newline at end of file + location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|woff2|eot|ttf|svg|ico|css\.map|js\.map)$ { + if_modified_since off; + + # use the public cache + proxy_cache public-cache; + proxy_cache_key $host$request_uri; + + # ignore these headers for media + proxy_ignore_headers Set-Cookie Cache-Control Expires X-Accel-Expires; + + # cache 200s and also 404s (not ideal but there are a few 404 images for some reason) + proxy_cache_valid any 30m; + proxy_cache_valid 404 1m; + + # strip this header to avoid If-Modified-Since requests + proxy_hide_header Last-Modified; + proxy_hide_header Cache-Control; + proxy_hide_header Vary; + + proxy_cache_bypass 0; + proxy_no_cache 0; + + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_404; + proxy_connect_timeout 5s; + proxy_read_timeout 45s; + + expires @30m; + access_log off; + + include conf.d/include/proxy.conf; + proxy_pass $forward_scheme://$server:$port; + } +{% endunless %} +{% endif %} diff --git a/backend/templates/_location.conf b/backend/templates/_location.conf index a2ecb166d6..fdd3221fbb 100644 --- a/backend/templates/_location.conf +++ b/backend/templates/_location.conf @@ -7,7 +7,11 @@ proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; + {% if upstream_host_id > 0 %} + proxy_pass {{ upstream_host_forward_scheme }}://upstream_host_{{ upstream_host_id }}; + {% else %} proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }}; + {% endif %} {% include "_access.conf" %} {% include "_assets.conf" %} diff --git a/backend/templates/ip_ranges.conf b/backend/templates/ip_ranges.conf index 8ede2bd992..2c8598133a 100644 --- a/backend/templates/ip_ranges.conf +++ b/backend/templates/ip_ranges.conf @@ -1,3 +1,4 @@ {% for range in ip_ranges %} set_real_ip_from {{ range }}; -{% endfor %} \ No newline at end of file +{% endfor %} +real_ip_header {{ real_ip_header }}; diff --git a/backend/templates/proxy_host.conf b/backend/templates/proxy_host.conf index d23ca46fa2..ba9dc2a860 100644 --- a/backend/templates/proxy_host.conf +++ b/backend/templates/proxy_host.conf @@ -44,6 +44,11 @@ proxy_http_version 1.1; # Proxy! include conf.d/include/proxy.conf; + {% if upstream_host_id > 0 %} + proxy_pass {{ upstream_host.forward_scheme }}://upstream_host_{{ upstream_host_id }}; + {% else %} + proxy_pass $forward_scheme://$server:$port; + {% endif %} } {% endif %} diff --git a/backend/templates/upstream_host.conf b/backend/templates/upstream_host.conf new file mode 100644 index 0000000000..b65b424a45 --- /dev/null +++ b/backend/templates/upstream_host.conf @@ -0,0 +1,14 @@ +# ------------------------------------------------------------ +# {{ name }} +# ------------------------------------------------------------ + +upstream upstream_host_{{ id }} { +{% if method == "least_conn" %} + least_conn; +{% elsif method == "ip_hash" %} + ip_hash; +{% endif %} +{% for server in servers %} + server {{ server.host }}:{{ server.port }}{% if server.weight %} weight={{ server.weight }}{% endif %}; +{% endfor %} +} diff --git a/docker/rootfs/etc/nginx/conf.d/include/assets.conf b/docker/rootfs/etc/nginx/conf.d/include/assets.conf index 5a90beb8ae..c1bc8c215b 100644 --- a/docker/rootfs/etc/nginx/conf.d/include/assets.conf +++ b/docker/rootfs/etc/nginx/conf.d/include/assets.conf @@ -28,4 +28,5 @@ location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|woff2|eot|ttf|svg|ico|css\.map| access_log off; include conf.d/include/proxy.conf; + proxy_pass $forward_scheme://$server:$port$request_uri; } diff --git a/docker/rootfs/etc/nginx/conf.d/include/proxy.conf b/docker/rootfs/etc/nginx/conf.d/include/proxy.conf index fe2c2f2132..99d8a7d216 100644 --- a/docker/rootfs/etc/nginx/conf.d/include/proxy.conf +++ b/docker/rootfs/etc/nginx/conf.d/include/proxy.conf @@ -4,5 +4,4 @@ proxy_set_header X-Forwarded-Scheme $x_forwarded_scheme; proxy_set_header X-Forwarded-Proto $x_forwarded_proto; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; -proxy_pass $forward_scheme://$server:$port$request_uri; diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf index bdba3b3055..4c5ff12b73 100644 --- a/docker/rootfs/etc/nginx/nginx.conf +++ b/docker/rootfs/etc/nginx/nginx.conf @@ -78,7 +78,7 @@ http { # NPM generated CDN ip ranges: include conf.d/include/ip_ranges[.]conf; # always put the following 2 lines after ip subnets: - real_ip_header X-Real-IP; + # real_ip_header is now set dynamically in ip_ranges.conf real_ip_recursive on; # Custom @@ -87,6 +87,7 @@ http { # Files generated by NPM include /etc/nginx/conf.d/*.conf; include /data/nginx/default_host/*.conf; + include /data/nginx/upstream_host/*.conf; include /data/nginx/proxy_host/*.conf; include /data/nginx/redirection_host/*.conf; include /data/nginx/dead_host/*.conf; diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh index 2f59ef41ac..ea24437760 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh @@ -23,6 +23,7 @@ mkdir -p \ /data/nginx/default_host \ /data/nginx/default_www \ /data/nginx/proxy_host \ + /data/nginx/upstream_host \ /data/nginx/redirection_host \ /data/nginx/stream \ /data/nginx/dead_host \ diff --git a/frontend/src/Router.tsx b/frontend/src/Router.tsx index 6aa8f0894f..14ae5c8bc4 100644 --- a/frontend/src/Router.tsx +++ b/frontend/src/Router.tsx @@ -25,6 +25,7 @@ const ProxyHosts = lazy(() => import("src/pages/Nginx/ProxyHosts")); const RedirectionHosts = lazy(() => import("src/pages/Nginx/RedirectionHosts")); const DeadHosts = lazy(() => import("src/pages/Nginx/DeadHosts")); const Streams = lazy(() => import("src/pages/Nginx/Streams")); +const UpstreamHosts = lazy(() => import("src/pages/UpstreamHosts")); function Router() { const health = useHealth(); @@ -70,6 +71,7 @@ function Router() { } /> } /> } /> + } /> } /> diff --git a/frontend/src/api/backend/createUpstreamHost.ts b/frontend/src/api/backend/createUpstreamHost.ts new file mode 100644 index 0000000000..43bd7c28ba --- /dev/null +++ b/frontend/src/api/backend/createUpstreamHost.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { UpstreamHost } from "./models"; + +export async function createUpstreamHost(item: UpstreamHost): Promise { + return await api.post({ + url: "/nginx/upstream-hosts", + data: item, + }); +} diff --git a/frontend/src/api/backend/deleteUpstreamHost.ts b/frontend/src/api/backend/deleteUpstreamHost.ts new file mode 100644 index 0000000000..628c4d426c --- /dev/null +++ b/frontend/src/api/backend/deleteUpstreamHost.ts @@ -0,0 +1,7 @@ +import * as api from "./base"; + +export async function deleteUpstreamHost(id: number): Promise { + return await api.del({ + url: `/nginx/upstream-hosts/${id}`, + }); +} diff --git a/frontend/src/api/backend/expansions.ts b/frontend/src/api/backend/expansions.ts index e098a49000..e0bf7aaec1 100644 --- a/frontend/src/api/backend/expansions.ts +++ b/frontend/src/api/backend/expansions.ts @@ -2,5 +2,6 @@ export type AccessListExpansion = "owner" | "items" | "clients"; export type AuditLogExpansion = "user"; export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts" | "streams"; export type HostExpansion = "owner" | "certificate"; -export type ProxyHostExpansion = "owner" | "access_list" | "certificate"; +export type ProxyHostExpansion = "owner" | "access_list" | "certificate" | "upstream_host"; +export type UpstreamHostExpansion = "owner" | "servers"; export type UserExpansion = "permissions"; diff --git a/frontend/src/api/backend/getUpstreamHost.ts b/frontend/src/api/backend/getUpstreamHost.ts new file mode 100644 index 0000000000..546ac63a02 --- /dev/null +++ b/frontend/src/api/backend/getUpstreamHost.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { UpstreamHostExpansion } from "./expansions"; +import type { UpstreamHost } from "./models"; + +export async function getUpstreamHost(id: number, expand?: UpstreamHostExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/upstream-hosts/${id}`, + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getUpstreamHosts.ts b/frontend/src/api/backend/getUpstreamHosts.ts new file mode 100644 index 0000000000..89ba10cf69 --- /dev/null +++ b/frontend/src/api/backend/getUpstreamHosts.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { UpstreamHostExpansion } from "./expansions"; +import type { UpstreamHost } from "./models"; + +export async function getUpstreamHosts(expand?: UpstreamHostExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/upstream-hosts", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/index.ts b/frontend/src/api/backend/index.ts index 40cb4142fc..c11ea5bcb1 100644 --- a/frontend/src/api/backend/index.ts +++ b/frontend/src/api/backend/index.ts @@ -6,6 +6,7 @@ export * from "./createProxyHost"; export * from "./createRedirectionHost"; export * from "./createStream"; export * from "./createUser"; +export * from "./createUpstreamHost"; export * from "./deleteAccessList"; export * from "./deleteCertificate"; export * from "./deleteDeadHost"; @@ -13,6 +14,7 @@ export * from "./deleteProxyHost"; export * from "./deleteRedirectionHost"; export * from "./deleteStream"; export * from "./deleteUser"; +export * from "./deleteUpstreamHost"; export * from "./downloadCertificate"; export * from "./expansions"; export * from "./getAccessList"; @@ -37,6 +39,8 @@ export * from "./getStreams"; export * from "./getToken"; export * from "./getUser"; export * from "./getUsers"; +export * from "./getUpstreamHost"; +export * from "./getUpstreamHosts"; export * from "./helpers"; export * from "./loginAsUser"; export * from "./models"; @@ -58,6 +62,7 @@ export * from "./updateRedirectionHost"; export * from "./updateSetting"; export * from "./updateStream"; export * from "./updateUser"; +export * from "./updateUpstreamHost"; export * from "./uploadCertificate"; export * from "./validateCertificate"; export * from "./twoFactor"; diff --git a/frontend/src/api/backend/models.ts b/frontend/src/api/backend/models.ts index 2ae0b08348..c0b19cec37 100644 --- a/frontend/src/api/backend/models.ts +++ b/frontend/src/api/backend/models.ts @@ -16,6 +16,7 @@ export interface UserPermissions { streams: string; accessLists: string; certificates: string; + upstreamHosts: string; } export interface User { @@ -97,14 +98,50 @@ export interface Certificate { redirectionHosts?: RedirectionHost[]; } +export interface UpstreamHostServer { + id?: number; + createdOn?: string; + modifiedOn?: string; + upstreamHostId?: number; + host: string; + port: number; + weight?: number; + meta?: Record; +} + +export interface UpstreamHost { + id?: number; + createdOn?: string; + modifiedOn?: string; + ownerUserId: number; + name: string; + forwardScheme: string; + method: LoadBalancingMethod; + meta: Record; + proxyHostCount?: number; + // Expansions: + owner?: User; + servers?: UpstreamHostServer[]; +} + export interface ProxyLocation { path: string; advancedConfig: string; forwardScheme: string; forwardHost: string; forwardPort: number; + upstreamHostId?: number; + upstreamHostForwardScheme?: string; } +export interface LoadBalancingServer { + host: string; + port: number; + weight?: number; +} + +export type LoadBalancingMethod = "round_robin" | "least_conn" | "ip_hash"; + export interface ProxyHost { id: number; createdOn: string; @@ -128,10 +165,12 @@ export interface ProxyHost { hstsEnabled: boolean; hstsSubdomains: boolean; trustForwardedProto: boolean; + upstreamHostId: number; // Expansions: owner?: User; accessList?: AccessList; certificate?: Certificate; + upstreamHost?: UpstreamHost; } export interface DeadHost { diff --git a/frontend/src/api/backend/updateUpstreamHost.ts b/frontend/src/api/backend/updateUpstreamHost.ts new file mode 100644 index 0000000000..ce2c87826f --- /dev/null +++ b/frontend/src/api/backend/updateUpstreamHost.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { UpstreamHost } from "./models"; + +export async function updateUpstreamHost(item: UpstreamHost): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put({ + url: `/nginx/upstream-hosts/${id}`, + data: data, + }); +} diff --git a/frontend/src/components/Form/LoadBalancingFields.tsx b/frontend/src/components/Form/LoadBalancingFields.tsx new file mode 100644 index 0000000000..0aee260b83 --- /dev/null +++ b/frontend/src/components/Form/LoadBalancingFields.tsx @@ -0,0 +1,155 @@ +import { IconX } from "@tabler/icons-react"; +import { useFormikContext } from "formik"; +import { useState } from "react"; +import type { LoadBalancingServer } from "src/api/backend"; +import { T } from "src/locale"; + +interface Props { + initialValues: LoadBalancingServer[]; + name?: string; +} + +export function LoadBalancingFields({ initialValues, name = "loadBalancingServers" }: Props) { + const [values, setValues] = useState(initialValues || []); + const { setFieldValue } = useFormikContext(); + + const blankItem: LoadBalancingServer = { + host: "", + port: 80, + }; + + if (values.length === 0) { + setValues([blankItem]); + } + + const setFormField = (newValues: LoadBalancingServer[]) => { + const filtered = newValues + .map((item) => { + const host = typeof item.host === "string" ? item.host.trim() : ""; + const port = Number.parseInt(String(item.port || ""), 10); + const weight = Number.parseInt(String(item.weight || ""), 10); + const normalized: LoadBalancingServer = { + host, + port: Number.isFinite(port) ? port : 0, + }; + + if (Number.isFinite(weight) && weight > 0) { + normalized.weight = weight; + } + + return normalized; + }) + .filter((item) => item.host !== "" && item.port > 0); + + setFieldValue(name, filtered); + }; + + const handleAdd = () => { + setValues([...values, blankItem]); + }; + + const handleRemove = (idx: number) => { + const newValues = values.filter((_: LoadBalancingServer, i: number) => i !== idx); + if (newValues.length === 0) { + newValues.push(blankItem); + } + setValues(newValues); + setFormField(newValues); + }; + + const handleChange = (idx: number, field: string, fieldValue: string) => { + const newValues = values.map((value: LoadBalancingServer, i: number) => { + if (i !== idx) { + return value; + } + + if (field === "port" || field === "weight") { + const parsed = Number.parseInt(fieldValue, 10); + return { + ...value, + [field]: Number.isFinite(parsed) ? parsed : 0, + }; + } + + return { ...value, [field]: fieldValue }; + }); + + setValues(newValues); + setFormField(newValues); + }; + + return ( + <> + {values.map((item: LoadBalancingServer, idx: number) => ( +
+
+
+ + handleChange(idx, "host", e.target.value)} + /> +
+
+
+
+ + handleChange(idx, "port", e.target.value)} + /> +
+
+
+
+ + handleChange(idx, "weight", e.target.value)} + /> +
+
+ +
+ ))} +
+ +
+ + ); +} diff --git a/frontend/src/components/Form/LocationsFields.tsx b/frontend/src/components/Form/LocationsFields.tsx index 4240b1f986..f8713860a6 100644 --- a/frontend/src/components/Form/LocationsFields.tsx +++ b/frontend/src/components/Form/LocationsFields.tsx @@ -6,6 +6,7 @@ import { useState } from "react"; import type { ProxyLocation } from "src/api/backend"; import { intl, T } from "src/locale"; import styles from "./LocationsFields.module.css"; +import { UpstreamHostSelect } from "./UpstreamHostSelect"; interface Props { initialValues: ProxyLocation[]; @@ -38,12 +39,39 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) { setFormField(newValues); }; - const handleChange = (idx: number, field: string, fieldValue: string) => { + const handleChange = (idx: number, field: string, fieldValue: string | number) => { const newValues = values.map((v: ProxyLocation, i: number) => (i === idx ? { ...v, [field]: fieldValue } : v)); setValues(newValues); setFormField(newValues); }; + const handleUpstreamChange = (idx: number, upstreamHostId: number) => { + const newValues = values.map((v: ProxyLocation, i: number) => + i === idx + ? { + ...v, + upstreamHostId, + upstreamHostForwardScheme: v.upstreamHostForwardScheme || "http", + } + : v, + ); + setValues(newValues); + setFormField(newValues); + }; + + const handleTargetTypeChange = (idx: number, targetType: "direct" | "upstream") => { + const newValues = values.map((v: ProxyLocation, i: number) => + i === idx + ? { + ...v, + upstreamHostId: targetType === "direct" ? 0 : (v.upstreamHostId || 0), + } + : v, + ); + setValues(newValues); + setFormField(newValues); + }; + const setFormField = (newValues: ProxyLocation[]) => { const filtered = newValues.filter((v: ProxyLocation) => v?.path?.trim() !== ""); setFieldValue(name, filtered); @@ -61,7 +89,9 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) { return ( <> - {values.map((item: ProxyLocation, idx: number) => ( + {values.map((item: ProxyLocation, idx: number) => { + const targetType = item.upstreamHostId && item.upstreamHostId > 0 ? "upstream" : "direct"; + return (
@@ -89,14 +119,65 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) {
+
+ + +
+ {targetType === "upstream" && ( +
+ handleUpstreamChange(idx, id)} + /> +
+ )} + {targetType === "direct" && (
-
- ))} + ); + })}
- - )} + + ); + }} )} diff --git a/frontend/src/modals/UpstreamHostModal.tsx b/frontend/src/modals/UpstreamHostModal.tsx new file mode 100644 index 0000000000..0edeb5e653 --- /dev/null +++ b/frontend/src/modals/UpstreamHostModal.tsx @@ -0,0 +1,215 @@ +import EasyModal, { type InnerModalProps } from "ez-modal-react"; +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { Button, LoadBalancingFields, Loading } from "src/components"; +import { useSetUpstreamHost, useUpstreamHost } from "src/hooks"; +import { T } from "src/locale"; +import { validateString } from "src/modules/Validations"; +import { showObjectSuccess } from "src/notifications"; + +const showUpstreamHostModal = (id: number | "new") => { + EasyModal.show(UpstreamHostModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number | "new"; +} +const UpstreamHostModal = EasyModal.create(({ id, visible, remove }: Props) => { + const { data, isLoading, error } = useUpstreamHost(id, ["servers"]); + const { mutate: setUpstreamHost } = useSetUpstreamHost(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + const payload = { + id: id === "new" ? undefined : id, + ...values, + }; + + setUpstreamHost(payload, { + onError: (err: any) => setErrorMsg(), + onSuccess: () => { + showObjectSuccess("upstream-host", "saved"); + remove(); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }); + }; + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + {!isLoading && data && ( + + {() => ( +
+ + + + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
+
+ +
+
+
+
+ + {({ field, form }: any) => ( +
+ + + {form.errors.name ? ( +
+ {form.errors.name && form.touched.name + ? form.errors.name + : null} +
+ ) : null} +
+ )} +
+
+
+ + {({ field }: any) => ( +
+ + +
+ )} +
+
+
+ + {({ field }: any) => ( +
+ + +
+ )} +
+
+
+
+
+ +
+
+
+
+
+ + + + +
+ )} +
+ )} +
+ ); +}); + +export { showUpstreamHostModal }; diff --git a/frontend/src/modals/index.ts b/frontend/src/modals/index.ts index a06a0c0d71..a9e5bf5292 100644 --- a/frontend/src/modals/index.ts +++ b/frontend/src/modals/index.ts @@ -14,4 +14,5 @@ export * from "./RenewCertificateModal"; export * from "./SetPasswordModal"; export * from "./StreamModal"; export * from "./TwoFactorModal"; +export * from "./UpstreamHostModal"; export * from "./UserModal"; diff --git a/frontend/src/modules/Permissions.ts b/frontend/src/modules/Permissions.ts index 2d784213e4..0fdd825517 100644 --- a/frontend/src/modules/Permissions.ts +++ b/frontend/src/modules/Permissions.ts @@ -8,6 +8,7 @@ export const DEAD_HOSTS = "deadHosts"; export const STREAMS = "streams"; export const CERTIFICATES = "certificates"; export const ACCESS_LISTS = "accessLists"; +export const UPSTREAM_HOSTS = "upstreamHosts"; export const MANAGE = "manage"; export const VIEW = "view"; @@ -24,7 +25,8 @@ export type Section = | typeof DEAD_HOSTS | typeof STREAMS | typeof CERTIFICATES - | typeof ACCESS_LISTS; + | typeof ACCESS_LISTS + | typeof UPSTREAM_HOSTS; export type Permission = typeof MANAGE | typeof VIEW; diff --git a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx index 9d58b26acd..d9003ac01a 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx @@ -13,6 +13,7 @@ import { } from "src/components"; import { TableLayout } from "src/components/Table/TableLayout"; import { intl, T } from "src/locale"; +import { showUpstreamHostModal } from "src/modals"; import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions"; interface Props { @@ -51,6 +52,20 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog header: intl.formatMessage({ id: "column.destination" }), cell: (info: any) => { const value = info.getValue(); + if (value.upstreamHostId > 0 && value.upstreamHost) { + return ( + + ); + } return `${value.forwardScheme}://${value.forwardHost}:${value.forwardPort}`; }, }), diff --git a/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx index 68af43e10a..759c1f9b9b 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx @@ -14,7 +14,7 @@ import Table from "./Table"; export default function TableWrapper() { const queryClient = useQueryClient(); const [search, setSearch] = useState(""); - const { isFetching, isLoading, isError, error, data } = useProxyHosts(["owner", "access_list", "certificate"]); + const { isFetching, isLoading, isError, error, data } = useProxyHosts(["owner", "access_list", "certificate", "upstream_host"]); if (isLoading) { return ; diff --git a/frontend/src/pages/Settings/Layout.tsx b/frontend/src/pages/Settings/Layout.tsx index a0a77db29e..68688f3c20 100644 --- a/frontend/src/pages/Settings/Layout.tsx +++ b/frontend/src/pages/Settings/Layout.tsx @@ -1,10 +1,14 @@ +import { useState } from "react"; import { T } from "src/locale"; import DefaultSite from "./DefaultSite"; +import RealIpHeader from "./RealIpHeader"; export default function Layout() { // Taken from https://preview.tabler.io/settings.html // Refer to that when updating this content + const [activeTab, setActiveTab] = useState<"default-site" | "real-ip-header">("default-site"); + return (
- + {activeTab === "default-site" && } + {activeTab === "real-ip-header" && }
diff --git a/frontend/src/pages/Settings/RealIpHeader.tsx b/frontend/src/pages/Settings/RealIpHeader.tsx new file mode 100644 index 0000000000..3df8277721 --- /dev/null +++ b/frontend/src/pages/Settings/RealIpHeader.tsx @@ -0,0 +1,163 @@ +import { Field, Form, Formik } from "formik"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import { Button, Loading } from "src/components"; +import { useSetSetting, useSetting } from "src/hooks"; +import { intl, T } from "src/locale"; +import { showObjectSuccess } from "src/notifications"; + +const HEADER_OPTIONS = [ + { value: "X-Real-IP", localeId: "settings.real-ip-header.x-real-ip", descId: "settings.real-ip-header.x-real-ip.description" }, + { value: "CF-Connecting-IP", localeId: "settings.real-ip-header.cf-connecting-ip", descId: "settings.real-ip-header.cf-connecting-ip.description" }, + { value: "X-Forwarded-For", localeId: "settings.real-ip-header.x-forwarded-for", descId: "settings.real-ip-header.x-forwarded-for.description" }, + { value: "custom", localeId: "settings.real-ip-header.custom", descId: null }, +]; + +export default function RealIpHeader() { + const { data, isLoading, error } = useSetting("real-ip-header"); + const { mutate: setSetting } = useSetSetting(); + const [errorMsg, setErrorMsg] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (isSubmitting) return; + setIsSubmitting(true); + setErrorMsg(null); + + const payload = { + id: "real-ip-header", + value: values.value, + meta: { + custom: values.custom, + }, + }; + + setSetting(payload, { + onError: (err: any) => setErrorMsg(), + onSuccess: () => { + showObjectSuccess("setting", "saved"); + }, + onSettled: () => { + setIsSubmitting(false); + setSubmitting(false); + }, + }); + }; + + if (!isLoading && error) { + return ( +
+
+ + {error.message} + +
+
+ ); + } + + if (isLoading) { + return ( +
+
+ +
+
+ ); + } + + return ( + + {({ values }) => ( +
+
+ setErrorMsg(null)} dismissible> + {errorMsg} + + + {({ field, form }: any) => ( +
+ +
+ {HEADER_OPTIONS.map((opt) => ( + + ))} +
+
+ )} +
+ {values.value === "custom" && ( + + {({ field }: any) => ( +
+ +
+ +
+
+ )} +
+ )} +
+
+
+ +
+
+
+ )} +
+ ); +} diff --git a/frontend/src/pages/UpstreamHosts/Table.tsx b/frontend/src/pages/UpstreamHosts/Table.tsx new file mode 100644 index 0000000000..6971aa48f6 --- /dev/null +++ b/frontend/src/pages/UpstreamHosts/Table.tsx @@ -0,0 +1,150 @@ +import { IconDotsVertical, IconEdit, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { UpstreamHost } from "src/api/backend"; +import { EmptyData, GravatarFormatter, HasPermission, ValueWithDateFormatter } from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl, T } from "src/locale"; +import { MANAGE, UPSTREAM_HOSTS } from "src/modules/Permissions"; + +interface Props { + data: UpstreamHost[]; + isFiltered?: boolean; + isFetching?: boolean; + onEdit?: (id: number) => void; + onDelete?: (id: number) => void; + onNew?: () => void; +} +export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, onNew }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "name", + header: intl.formatMessage({ id: "column.name" }), + cell: (info: any) => ( + + ), + }), + columnHelper.accessor((row: any) => row.forwardScheme, { + id: "forwardScheme", + header: intl.formatMessage({ id: "column.scheme" }), + cell: (info: any) => info.getValue(), + }), + columnHelper.accessor((row: any) => row.method, { + id: "method", + header: intl.formatMessage({ id: "upstream-host.method" }), + cell: (info: any) => { + const method = info.getValue(); + return method?.replace(/_/g, " ") || ""; + }, + }), + columnHelper.accessor((row: any) => row.servers, { + id: "servers", + header: intl.formatMessage({ id: "upstream-host.servers" }), + cell: (info: any) => { + const servers = info.getValue(); + return servers?.length || 0; + }, + }), + columnHelper.accessor((row: any) => row.proxyHostCount, { + id: "proxyHostCount", + header: intl.formatMessage({ id: "proxy-hosts" }), + cell: (info: any) => , + }), + columnHelper.display({ + id: "id", + cell: (info: any) => { + return ( + + +
+ + + + { + e.preventDefault(); + onEdit?.(info.row.original.id); + }} + > + + + + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper, onEdit, onDelete], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return ( + + } + /> + ); +} diff --git a/frontend/src/pages/UpstreamHosts/TableWrapper.tsx b/frontend/src/pages/UpstreamHosts/TableWrapper.tsx new file mode 100644 index 0000000000..0bb41d2111 --- /dev/null +++ b/frontend/src/pages/UpstreamHosts/TableWrapper.tsx @@ -0,0 +1,100 @@ +import { IconSearch } from "@tabler/icons-react"; +import { useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { deleteUpstreamHost } from "src/api/backend"; +import { Button, HasPermission, LoadingPage } from "src/components"; +import { useUpstreamHosts } from "src/hooks"; +import { T } from "src/locale"; +import { showDeleteConfirmModal, showUpstreamHostModal } from "src/modals"; +import { MANAGE, UPSTREAM_HOSTS } from "src/modules/Permissions"; +import { showObjectSuccess } from "src/notifications"; +import Table from "./Table"; + +export default function TableWrapper() { + const [search, setSearch] = useState(""); + const { isFetching, isLoading, isError, error, data } = useUpstreamHosts(["owner", "servers"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + const handleDelete = async (id: number) => { + await deleteUpstreamHost(id); + showObjectSuccess("upstream-host", "deleted"); + }; + + let filtered = null; + if (search && data) { + filtered = data?.filter((item) => { + return item.name.toLowerCase().includes(search); + }); + } else if (search !== "") { + setSearch(""); + } + + return ( +
+
+
+
+
+
+

+ +

+
+ +
+
+ {data?.length ? ( +
+ + + + setSearch(e.target.value.toLowerCase().trim())} + /> +
+ ) : null} + + {data?.length ? ( + + ) : null} + +
+
+
+
+ showUpstreamHostModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: , + onConfirm: () => handleDelete(id), + invalidations: [["upstream-hosts"], ["upstream-host", id]], + children: , + }) + } + onNew={() => showUpstreamHostModal("new")} + /> + + + ); +} diff --git a/frontend/src/pages/UpstreamHosts/index.tsx b/frontend/src/pages/UpstreamHosts/index.tsx new file mode 100644 index 0000000000..1d7439c29e --- /dev/null +++ b/frontend/src/pages/UpstreamHosts/index.tsx @@ -0,0 +1,13 @@ +import { HasPermission } from "src/components"; +import { UPSTREAM_HOSTS, VIEW } from "src/modules/Permissions"; +import TableWrapper from "./TableWrapper"; + +const UpstreamHosts = () => { + return ( + + + + ); +}; + +export default UpstreamHosts; From f42e35df0f692f6e3599d1cee1a2ced94ee501fb Mon Sep 17 00:00:00 2001 From: GenticFlow Labs Date: Tue, 17 Mar 2026 22:02:16 +0000 Subject: [PATCH 02/17] Fixed lint errors on RealIpHeader --- frontend/src/pages/Settings/RealIpHeader.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/Settings/RealIpHeader.tsx b/frontend/src/pages/Settings/RealIpHeader.tsx index 3df8277721..2dcabb2226 100644 --- a/frontend/src/pages/Settings/RealIpHeader.tsx +++ b/frontend/src/pages/Settings/RealIpHeader.tsx @@ -85,10 +85,10 @@ export default function RealIpHeader() { {({ field, form }: any) => (
-
); })} diff --git a/frontend/src/hooks/useProxyHost.ts b/frontend/src/hooks/useProxyHost.ts index 6394ee07a6..b6f68ad914 100644 --- a/frontend/src/hooks/useProxyHost.ts +++ b/frontend/src/hooks/useProxyHost.ts @@ -20,6 +20,14 @@ const fetchProxyHost = (id: number | "new") => { meta: {}, allowWebsocketUpgrade: false, http2Support: false, + rateLimitEnabled: false, + rateLimit: 0, + rateLimitBurst: 0, + rateLimitPeriod: "minute", + rateLimitDelay: false, + loadBalancingEnabled: false, + loadBalancingMethod: "round_robin", + loadBalancingServers: [], forwardScheme: "", enabled: true, hstsEnabled: false, diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index dabe81b5e5..bcdfe2eeba 100755 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -13,6 +13,7 @@ import langJa from "./lang/ja.json"; import langKo from "./lang/ko.json"; import langNl from "./lang/nl.json"; import langPl from "./lang/pl.json"; +import langPtBr from "./lang/pt_br.json"; import langRu from "./lang/ru.json"; import langSk from "./lang/sk.json"; import langCs from "./lang/cs.json"; @@ -26,12 +27,16 @@ import langList from "./lang/lang-list.json"; // first item of each array should be the language code, // not the country code // Remember when adding to this list, also update check-locales.js script -const localeOptions = [ +type LocaleMessages = Record; +type LocaleOption = [string, string, LocaleMessages]; + +const localeOptions: LocaleOption[] = [ ["en", "en-US", langEn], ["de", "de-DE", langDe], ["es", "es-ES", langEs], ["et", "et-EE", langEt], ["pt", "pt-PT", langPt], + ["pt-BR", "pt-BR", langPtBr], ["fr", "fr-FR", langFr], ["ga", "ga-IE", langGa], ["ja", "ja-JP", langJa], @@ -51,8 +56,20 @@ const localeOptions = [ ["no", "no-NO", langNo], ]; -const loadMessages = (locale?: string): typeof langList & typeof langEn => { - const thisLocale = (locale || "en").slice(0, 2); +const normalizeLocale = (locale?: string) => (locale || "en").replace("_", "-").toLowerCase(); + +const findLocaleOption = (locale?: string) => { + const normalizedLocale = normalizeLocale(locale); + return localeOptions.find(([code]) => code.toLowerCase() === normalizedLocale); +}; + +const loadMessages = (locale?: string): typeof langList & LocaleMessages => { + const exactMatch = findLocaleOption(locale); + if (exactMatch) { + return Object.assign({}, langList, langEn, exactMatch[2]); + } + + const thisLocale = normalizeLocale(locale).slice(0, 2); // ensure this lang exists in localeOptions above, otherwise fallback to en if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) { @@ -63,7 +80,13 @@ const loadMessages = (locale?: string): typeof langList & typeof langEn => { }; const getFlagCodeForLocale = (locale?: string) => { - const thisLocale = (locale || "en").slice(0, 2); + const normalizedLocale = normalizeLocale(locale); + const [, region] = normalizedLocale.split("-"); + if (region) { + return region.toUpperCase(); + } + + const thisLocale = normalizedLocale.slice(0, 2); // only add to this if your flag is different from the locale code const specialCases: Record = { @@ -72,6 +95,7 @@ const getFlagCodeForLocale = (locale?: string) => { vi: "vn", // Vietnam ko: "kr", // Korea cs: "cz", // Czechia + ga: "ie", // Ireland (Irish) }; if (specialCases[thisLocale]) { diff --git a/frontend/src/locale/Utils.test.tsx b/frontend/src/locale/Utils.test.tsx index e998dc319c..a49460fee0 100644 --- a/frontend/src/locale/Utils.test.tsx +++ b/frontend/src/locale/Utils.test.tsx @@ -1,4 +1,4 @@ -import { formatDateTime } from "src/locale"; +import { formatDateTime, getFlagCodeForLocale } from "src/locale"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; describe("DateFormatter", () => { @@ -72,3 +72,28 @@ describe("DateFormatter", () => { expect(text).toBe("-100"); }); }); + +describe("getFlagCodeForLocale", () => { + it("returns correct flag code for standard locales", () => { + expect(getFlagCodeForLocale("en-US")).toBe("EN"); + expect(getFlagCodeForLocale("de-DE")).toBe("DE"); + expect(getFlagCodeForLocale("fr-FR")).toBe("FR"); + }); + + it("returns correct flag code for special-case locales", () => { + expect(getFlagCodeForLocale("ja-JP")).toBe("JP"); + expect(getFlagCodeForLocale("zh-CN")).toBe("CN"); + expect(getFlagCodeForLocale("vi-VN")).toBe("VN"); + expect(getFlagCodeForLocale("ko-KR")).toBe("KR"); + expect(getFlagCodeForLocale("cs-CZ")).toBe("CZ"); + }); + + it("returns IE (Ireland) for Irish locale, not GA (Gabon)", () => { + expect(getFlagCodeForLocale("ga-IE")).toBe("IE"); + }); + + it("falls back to EN when no locale is provided", () => { + expect(getFlagCodeForLocale()).toBe("EN"); + expect(getFlagCodeForLocale(undefined)).toBe("EN"); + }); +}); diff --git a/frontend/src/locale/src/HelpDoc/index.ts b/frontend/src/locale/src/HelpDoc/index.ts index 7d4af5f5dd..8660ac4546 100644 --- a/frontend/src/locale/src/HelpDoc/index.ts +++ b/frontend/src/locale/src/HelpDoc/index.ts @@ -12,6 +12,7 @@ import * as ja from "./ja/index"; import * as ko from "./ko/index"; import * as nl from "./nl/index"; import * as pl from "./pl/index"; +import * as ptBr from "./pt_br/index"; import * as ru from "./ru/index"; import * as sk from "./sk/index"; import * as cs from "./cs/index"; @@ -20,8 +21,7 @@ import * as zh from "./zh/index"; import * as tr from "./tr/index"; import * as hu from "./hu/index"; -const items: any = { en, de, pt, es, et, ja, sk, cs, zh, pl, ru, it, vi, nl, bg, ko, ga, id, fr, tr, hu }; - +const items: any = { en, de, pt, pt_br: ptBr, es, et, ja, sk, cs, zh, pl, ru, it, vi, nl, bg, ko, ga, id, fr, tr, hu }; const fallbackLang = "en"; diff --git a/frontend/src/locale/src/HelpDoc/pt_br/AccessLists.md b/frontend/src/locale/src/HelpDoc/pt_br/AccessLists.md new file mode 100644 index 0000000000..2462a503f6 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt_br/AccessLists.md @@ -0,0 +1,7 @@ +## O que é uma Lista de Acesso? + +As Listas de Acesso fornecem uma lista negra ou lista branca de endereços IP de clientes específicos, junto com autenticação para os Hosts Proxy via Autenticação Básica HTTP. + +Você pode configurar múltiplas regras de cliente, nomes de usuário e senhas para uma única Lista de Acesso e então aplicá-la a um ou mais _Hosts Proxy_. + +Isso é muito útil para serviços web encaminhados que não possuem mecanismos de autenticação integrados ou quando você deseja proteger contra clientes desconhecidos. diff --git a/frontend/src/locale/src/HelpDoc/pt_br/Certificates.md b/frontend/src/locale/src/HelpDoc/pt_br/Certificates.md new file mode 100644 index 0000000000..167b062e81 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt_br/Certificates.md @@ -0,0 +1,32 @@ +## Ajuda sobre Certificados + +### Certificado HTTP + +Um certificado validado por HTTP significa que os servidores do Let's Encrypt irão +tentar acessar seus domínios via HTTP (não HTTPS!) e, se bem-sucedido, +emitirão seu certificado. + +Para este método, você precisará ter um _Host Proxy_ criado para seu(s) domínio(s) que +seja acessível via HTTP e apontando para esta instalação do Nginx. Após o certificado +ser concedido, você pode modificar o _Host Proxy_ para também usar este certificado para conexões +HTTPS. No entanto, o _Host Proxy_ ainda precisará estar configurado para acesso HTTP +para que o certificado possa ser renovado. + +Este processo _não_ suporta domínios curinga. + +### Certificado DNS + +Um certificado validado por DNS requer que você use um plugin de Provedor DNS. Este Provedor +DNS será usado para criar registros temporários no seu domínio e então o Let's Encrypt +consultará esses registros para ter certeza de que você é o proprietário e, se bem-sucedido, +emitirão seu certificado. + +Você não precisa ter um _Host Proxy_ criado antes de solicitar este tipo de +certificado. Nem precisa ter seu _Host Proxy_ configurado para acesso HTTP. + +Este processo _suporta_ domínios curinga. + +### Certificado Personalizado + +Use esta opção para enviar seu próprio Certificado SSL, conforme fornecido pela sua própria +Autoridade Certificadora. diff --git a/frontend/src/locale/src/HelpDoc/pt_br/DeadHosts.md b/frontend/src/locale/src/HelpDoc/pt_br/DeadHosts.md new file mode 100644 index 0000000000..174167a540 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt_br/DeadHosts.md @@ -0,0 +1,10 @@ +## O que é um Host 404? + +Um Host 404 é simplesmente uma configuração de host que exibe uma página 404. + +Isso pode ser útil quando seu domínio está listado em mecanismos de busca e você deseja +fornecer uma página de erro mais amigável ou especificamente informar aos indexadores de busca que +as páginas do domínio não existem mais. + +Outro benefício de ter este host é rastrear os logs de acessos a ele e +visualizar os referenciadores. diff --git a/frontend/src/locale/src/HelpDoc/pt_br/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/pt_br/ProxyHosts.md new file mode 100644 index 0000000000..9959a292c3 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt_br/ProxyHosts.md @@ -0,0 +1,7 @@ +## O que é um Host Proxy? + +Um Host Proxy é o ponto de entrada para um serviço web que você deseja encaminhar. + +Ele fornece terminação SSL opcional para seu serviço que pode não ter suporte SSL integrado. + +Os Hosts Proxy são o uso mais comum do Nginx Proxy Manager. diff --git a/frontend/src/locale/src/HelpDoc/pt_br/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/pt_br/RedirectionHosts.md new file mode 100644 index 0000000000..6126393c0c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt_br/RedirectionHosts.md @@ -0,0 +1,7 @@ +## O que é um Host de Redirecionamento? + +Um Host de Redirecionamento irá redirecionar requisições do domínio de entrada e enviar o +visitante para outro domínio. + +O motivo mais comum para usar este tipo de host é quando seu site muda +de domínio, mas você ainda tem links de mecanismos de busca ou referenciadores apontando para o domínio antigo. diff --git a/frontend/src/locale/src/HelpDoc/pt_br/Streams.md b/frontend/src/locale/src/HelpDoc/pt_br/Streams.md new file mode 100644 index 0000000000..c9bb95672a --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt_br/Streams.md @@ -0,0 +1,6 @@ +## O que é um Stream? + +Um recurso relativamente novo do Nginx, um Stream serve para encaminhar tráfego TCP/UDP +diretamente para outro computador na rede. + +Se você está executando servidores de jogos, servidores FTP ou SSH, isso pode ser muito útil. diff --git a/frontend/src/locale/src/HelpDoc/pt_br/index.ts b/frontend/src/locale/src/HelpDoc/pt_br/index.ts new file mode 100644 index 0000000000..a9bb46ba7c --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/pt_br/index.ts @@ -0,0 +1,6 @@ +export * as AccessLists from "./AccessLists.md"; +export * as Certificates from "./Certificates.md"; +export * as DeadHosts from "./DeadHosts.md"; +export * as ProxyHosts from "./ProxyHosts.md"; +export * as RedirectionHosts from "./RedirectionHosts.md"; +export * as Streams from "./Streams.md"; diff --git a/frontend/src/locale/src/bg.json b/frontend/src/locale/src/bg.json index 7f6aaf9ff4..b40a2429d8 100644 --- a/frontend/src/locale/src/bg.json +++ b/frontend/src/locale/src/bg.json @@ -459,16 +459,16 @@ "defaultMessage": "Let's Encrypt чрез HTTP" }, "load-balancing.add-server": { - "defaultMessage": "???????? ?? ??????" + "defaultMessage": "Добавяне на сървър" }, "load-balancing.host": { - "defaultMessage": "????" + "defaultMessage": "Хост" }, "load-balancing.port": { - "defaultMessage": "????" + "defaultMessage": "Порт" }, "load-balancing.weight": { - "defaultMessage": "?????" + "defaultMessage": "Тегло" }, "loading": { "defaultMessage": "Зареждане…" diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index eb278e8903..65234e4d28 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -626,6 +626,21 @@ "proxy-host.forward-host": { "defaultMessage": "Forward Hostname / IP" }, + "proxy-host.forward-target-type": { + "defaultMessage": "Forward Target Type" + }, + "proxy-host.forward-target-type.direct": { + "defaultMessage": "Direct" + }, + "proxy-host.forward-target-type.direct.description": { + "defaultMessage": "Forward to a single host and port" + }, + "proxy-host.forward-target-type.upstream": { + "defaultMessage": "Upstream Host" + }, + "proxy-host.forward-target-type.upstream.description": { + "defaultMessage": "Use an upstream host for load balancing across multiple servers" + }, "proxy-hosts": { "defaultMessage": "Proxy Hosts" }, @@ -737,6 +752,36 @@ "settings.default-site.redirect": { "defaultMessage": "Redirect" }, + "settings.real-ip-header": { + "defaultMessage": "Real IP Header" + }, + "settings.real-ip-header.cf-connecting-ip": { + "defaultMessage": "CF-Connecting-IP" + }, + "settings.real-ip-header.cf-connecting-ip.description": { + "defaultMessage": "Use when behind Cloudflare" + }, + "settings.real-ip-header.custom": { + "defaultMessage": "Custom" + }, + "settings.real-ip-header.custom.placeholder": { + "defaultMessage": "Enter custom header name" + }, + "settings.real-ip-header.description": { + "defaultMessage": "HTTP header used to determine the real client IP address" + }, + "settings.real-ip-header.x-forwarded-for": { + "defaultMessage": "X-Forwarded-For" + }, + "settings.real-ip-header.x-forwarded-for.description": { + "defaultMessage": "Common alternative header" + }, + "settings.real-ip-header.x-real-ip": { + "defaultMessage": "X-Real-IP" + }, + "settings.real-ip-header.x-real-ip.description": { + "defaultMessage": "Standard header, works with most reverse proxies" + }, "setup.preamble": { "defaultMessage": "Get started by creating your admin account." }, @@ -803,6 +848,33 @@ "update-available": { "defaultMessage": "Update Available: {latestVersion}" }, + "upstream-host": { + "defaultMessage": "Upstream Host" + }, + "upstream-host.help": { + "defaultMessage": "Optionally select a global upstream host to use instead of the direct forward or inline load balancing." + }, + "upstream-host.method": { + "defaultMessage": "Method" + }, + "upstream-host.none": { + "defaultMessage": "None (Direct)" + }, + "upstream-host.none.subtitle": { + "defaultMessage": "No upstream host selected" + }, + "upstream-host.servers": { + "defaultMessage": "Servers" + }, + "upstream-host.servers-required": { + "defaultMessage": "At least one upstream server is required" + }, + "upstream-hosts": { + "defaultMessage": "Upstream Hosts" + }, + "upstream-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Upstream Host} other {Upstream Hosts}}" + }, "user": { "defaultMessage": "User" }, diff --git a/frontend/src/locale/src/fr.json b/frontend/src/locale/src/fr.json index f3f435bbc3..0911eedc39 100644 --- a/frontend/src/locale/src/fr.json +++ b/frontend/src/locale/src/fr.json @@ -15,7 +15,7 @@ "defaultMessage": "La désactivation de l'authentification à deux facteurs rendra votre compte moins sécurisé." }, "2fa.disabled": { - "defaultMessage": "Désactivé" + "defaultMessage": "Désactivée" }, "2fa.done": { "defaultMessage": "J'ai sauvegardé mes codes de secours" @@ -24,7 +24,7 @@ "defaultMessage": "Activer l'authentification à deux facteurs" }, "2fa.enabled": { - "defaultMessage": "Activé" + "defaultMessage": "Activée" }, "2fa.enter-code": { "defaultMessage": "Entrez le code de vérification" @@ -45,7 +45,7 @@ "defaultMessage": "Clé secrète" }, "2fa.setup-instructions": { - "defaultMessage": "Scannez ce code QR avec votre application d'authentification ou entrez le secret manuellement." + "defaultMessage": "Scannez ce QR code avec votre application d'authentification, ou entrez la clé secrète manuellement." }, "2fa.status": { "defaultMessage": "Statut" @@ -81,7 +81,7 @@ "defaultMessage": "Aucune authentification de base requise" }, "access-list.rule-source.placeholder": { - "defaultMessage": "192.168.1.100 or 192.168.1.0/24 or 2001:0db8::/32" + "defaultMessage": "192.168.1.100 ou 192.168.1.0/24 ou 2001:0db8::/32" }, "access-list.satisfy-any": { "defaultMessage": "Valide n'importe quelle règle" @@ -135,7 +135,7 @@ "defaultMessage": "Journaux d'audit" }, "auto": { - "defaultMessage": "Automatique" + "defaultMessage": "Auto" }, "cancel": { "defaultMessage": "Annuler" @@ -228,10 +228,10 @@ "defaultMessage": "Ces domaines doivent déjà être configurés pour pointer vers cette installation." }, "certificates.key-type": { - "defaultMessage": "Key Type" + "defaultMessage": "Type de clé" }, "certificates.key-type-description": { - "defaultMessage": "RSA is widely compatible, ECDSA is faster and more secure but may not be supported by older systems" + "defaultMessage": "RSA est largement répandu, ECDSA est plus rapide et plus sécurisé, mais peut ne pas être supporté par les systèmes plus anciens" }, "certificates.key-type-ecdsa": { "defaultMessage": "ECDSA 256" @@ -264,7 +264,7 @@ "defaultMessage": "Détails" }, "column.email": { - "defaultMessage": "eMail" + "defaultMessage": "e-mail" }, "column.event": { "defaultMessage": "Évènement" @@ -369,7 +369,7 @@ "defaultMessage": "Utiliser le challenge DNS" }, "email-address": { - "defaultMessage": "Adresse eMail" + "defaultMessage": "Adresse e-mail" }, "empty-search": { "defaultMessage": "Aucun résultat trouvé" @@ -387,13 +387,13 @@ "defaultMessage": "Les noms d'utilisateurs autorisés doivent être uniques" }, "error.invalid-auth": { - "defaultMessage": "Adresse eMail ou mot de passe invalide" + "defaultMessage": "Adresse e-mail ou mot de passe invalide" }, "error.invalid-domain": { "defaultMessage": "Domaine invalide : {domain}" }, "error.invalid-email": { - "defaultMessage": "Adresse eMail invalide" + "defaultMessage": "Adresse e-mail invalide" }, "error.max-character-length": { "defaultMessage": "La longueur maximale est {max} caractère{max, plural, one {} other {s}}" @@ -458,18 +458,6 @@ "lets-encrypt-via-http": { "defaultMessage": "Let's Encrypt via HTTP" }, - "load-balancing.add-server": { - "defaultMessage": "Ajouter un serveur" - }, - "load-balancing.host": { - "defaultMessage": "H?te" - }, - "load-balancing.port": { - "defaultMessage": "Port" - }, - "load-balancing.weight": { - "defaultMessage": "Poids" - }, "loading": { "defaultMessage": "Chargement…" }, @@ -480,7 +468,7 @@ "defaultMessage": "Entrez le code" }, "login.2fa-description": { - "defaultMessage": "Entrez le code de votre application d'authentification" + "defaultMessage": "Entrez le code depuis votre application d'authentification" }, "login.2fa-title": { "defaultMessage": "Authentification à deux facteurs" @@ -611,21 +599,6 @@ "proxy-host.forward-host": { "defaultMessage": "Nom d'hôte de redirection / IP" }, - "proxy-host.forward-target-type": { - "defaultMessage": "Type de destination" - }, - "proxy-host.forward-target-type.direct": { - "defaultMessage": "Direct" - }, - "proxy-host.forward-target-type.direct.description": { - "defaultMessage": "Transférer vers un seul hôte et port" - }, - "proxy-host.forward-target-type.upstream": { - "defaultMessage": "Hôte upstream" - }, - "proxy-host.forward-target-type.upstream.description": { - "defaultMessage": "Utiliser un hôte upstream pour la répartition de charge sur plusieurs serveurs" - }, "proxy-hosts": { "defaultMessage": "Hôtes proxy" }, @@ -654,7 +627,7 @@ "defaultMessage": "300 Choix multiples" }, "redirection-hosts.http-code.301": { - "defaultMessage": "301 Déplacé de façon permanente" + "defaultMessage": "301 Déplacé définitivement" }, "redirection-hosts.http-code.302": { "defaultMessage": "302 Déplacé temporairement" @@ -707,36 +680,6 @@ "settings.default-site.redirect": { "defaultMessage": "Redirection" }, - "settings.real-ip-header": { - "defaultMessage": "En-tête IP réelle" - }, - "settings.real-ip-header.cf-connecting-ip": { - "defaultMessage": "CF-Connecting-IP" - }, - "settings.real-ip-header.cf-connecting-ip.description": { - "defaultMessage": "Utiliser derrière Cloudflare" - }, - "settings.real-ip-header.custom": { - "defaultMessage": "Personnalisé" - }, - "settings.real-ip-header.custom.placeholder": { - "defaultMessage": "Entrez le nom de l'en-tête personnalisé" - }, - "settings.real-ip-header.description": { - "defaultMessage": "En-tête HTTP utilisé pour déterminer l'adresse IP réelle du client" - }, - "settings.real-ip-header.x-forwarded-for": { - "defaultMessage": "X-Forwarded-For" - }, - "settings.real-ip-header.x-forwarded-for.description": { - "defaultMessage": "En-tête alternatif courant" - }, - "settings.real-ip-header.x-real-ip": { - "defaultMessage": "X-Real-IP" - }, - "settings.real-ip-header.x-real-ip.description": { - "defaultMessage": "En-tête standard, fonctionne avec la plupart des proxys inverses" - }, "setup.preamble": { "defaultMessage": "Commencez par créer votre compte administrateur." }, @@ -756,7 +699,7 @@ "defaultMessage": "Hôte destinataire" }, "stream.forward-host.placeholder": { - "defaultMessage": "example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" + "defaultMessage": "example.com ou 10.0.0.1 ou 2001:db8:3333:4444:5555:6666:7777:8888" }, "stream.incoming-port": { "defaultMessage": "Port d'entrée" @@ -777,31 +720,7 @@ "defaultMessage": "Test" }, "update-available": { - "defaultMessage": "Mise à jour disponible : {latestVersion}" - }, - "upstream-host": { - "defaultMessage": "Hôte upstream" - }, - "upstream-host.help": { - "defaultMessage": "Sélectionnez un hôte upstream au lieu du transfert direct." - }, - "upstream-host.method": { - "defaultMessage": "Méthode" - }, - "upstream-host.none": { - "defaultMessage": "Aucun (Direct)" - }, - "upstream-host.none.subtitle": { - "defaultMessage": "Aucun hôte upstream sélectionné" - }, - "upstream-host.servers": { - "defaultMessage": "Serveurs" - }, - "upstream-hosts": { - "defaultMessage": "Hôtes upstream" - }, - "upstream-hosts.count": { - "defaultMessage": "{count} {count, plural, one {Hôte upstream} other {Hôtes upstream}}" + "defaultMessage": "Mise à jour disponible : {latestVersion}" }, "user": { "defaultMessage": "Utilisateur" @@ -846,7 +765,7 @@ "defaultMessage": "Passer au mode Lumineux" }, "user.two-factor": { - "defaultMessage": "Auth. deux facteurs" + "defaultMessage": "Auth. à deux facteurs" }, "username": { "defaultMessage": "Nom d'utilisateur" diff --git a/frontend/src/locale/src/ko.json b/frontend/src/locale/src/ko.json index 987cdd4407..6ce91ad876 100644 --- a/frontend/src/locale/src/ko.json +++ b/frontend/src/locale/src/ko.json @@ -459,16 +459,16 @@ "defaultMessage": "Let's Encrypt (HTTP 방식)" }, "load-balancing.add-server": { - "defaultMessage": "?? ??" + "defaultMessage": "서버 추가" }, "load-balancing.host": { - "defaultMessage": "???" + "defaultMessage": "호스트" }, "load-balancing.port": { - "defaultMessage": "??" + "defaultMessage": "포트" }, "load-balancing.weight": { - "defaultMessage": "???" + "defaultMessage": "가중치" }, "loading": { "defaultMessage": "불러오는 중…" diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json index 79dabe22b5..8804d97598 100644 --- a/frontend/src/locale/src/lang-list.json +++ b/frontend/src/locale/src/lang-list.json @@ -8,7 +8,7 @@ "locale-et-EE": { "defaultMessage": "Eesti" }, - "locale-ie-GA": { + "locale-ga-IE": { "defaultMessage": "Gaeilge" }, "locale-de-DE": { @@ -41,6 +41,9 @@ "locale-pl-PL": { "defaultMessage": "Polski" }, + "locale-pt-BR": { + "defaultMessage": "Português (Brasil)" + }, "locale-it-IT": { "defaultMessage": "Italiano" }, diff --git a/frontend/src/locale/src/nl.json b/frontend/src/locale/src/nl.json index a4bb7047ed..86d49d95e2 100644 --- a/frontend/src/locale/src/nl.json +++ b/frontend/src/locale/src/nl.json @@ -3,7 +3,7 @@ "defaultMessage": "Resterende back-upcodes: {count}" }, "2fa.backup-warning": { - "defaultMessage": "Bewaar deze back-upcodes op een veilige plek. Elke code kan slechts één keer worden gebruikt." + "defaultMessage": "Bewaar deze back-upcodes op een veilige plaats. Elke code kan slechts één keer worden gebruikt." }, "2fa.disable": { "defaultMessage": "Tweefactorauthenticatie uitschakelen" @@ -12,7 +12,7 @@ "defaultMessage": "2FA uitschakelen" }, "2fa.disable-warning": { - "defaultMessage": "Het uitschakelen van tweefactorauthenticatie maakt uw account minder veilig." + "defaultMessage": "Als je tweefactorauthenticatie uitschakelt, wordt je account minder beveiligd." }, "2fa.disabled": { "defaultMessage": "Uitgeschakeld" @@ -27,10 +27,10 @@ "defaultMessage": "Ingeschakeld" }, "2fa.enter-code": { - "defaultMessage": "Voer verificatiecode in" + "defaultMessage": "Verificatiecode invoeren" }, "2fa.enter-code-disable": { - "defaultMessage": "Voer verificatiecode in om uit te schakelen" + "defaultMessage": "Verificatiecode invoeren om uit te schakelen" }, "2fa.regenerate": { "defaultMessage": "Opnieuw genereren" @@ -39,13 +39,13 @@ "defaultMessage": "Back-upcodes opnieuw genereren" }, "2fa.regenerate-instructions": { - "defaultMessage": "Voer een verificatiecode in om nieuwe back-upcodes te genereren. Uw oude codes worden ongeldig." + "defaultMessage": "Voer een verificatiecode in om nieuwe back-upcodes te genereren. Oude codes worden ongeldig gemaakt." }, "2fa.secret-key": { "defaultMessage": "Geheime sleutel" }, "2fa.setup-instructions": { - "defaultMessage": "Scan deze QR-code met uw authenticatie-app of voer het geheim handmatig in." + "defaultMessage": "Scan deze QR-code met je authenticator-app, of voer de code handmatig in." }, "2fa.status": { "defaultMessage": "Status" @@ -69,10 +69,10 @@ "defaultMessage": "Als er minimaal 1 regel bestaat, wordt deze regel als laatste toegevoegd" }, "access-list.help.rules-order": { - "defaultMessage": "Onthoud dat de regels van boven naar beneden worden toegevoegd." + "defaultMessage": "Houd er rekening mee dat de regels worden toegepast in de volgorde waarin ze zijn gedefinieerd." }, "access-list.pass-auth": { - "defaultMessage": "Pass Auth to Upstream" + "defaultMessage": "Authenticatie naar upstream doorgeven" }, "access-list.public": { "defaultMessage": "Publiekelijk toegankelijk" @@ -81,7 +81,7 @@ "defaultMessage": "Geen basisautentificatie vereist" }, "access-list.rule-source.placeholder": { - "defaultMessage": "192.168.1.100 or 192.168.1.0/24 or 2001:0db8::/32" + "defaultMessage": "192.168.1.100 of 192.168.1.0/24 of 2001:0db8::/32" }, "access-list.satisfy-any": { "defaultMessage": "Voldoe aan elke" @@ -96,7 +96,7 @@ "defaultMessage": "Toevoegen" }, "action.add-location": { - "defaultMessage": "Locatie Toevoegen" + "defaultMessage": "Locatie toevoegen" }, "action.allow": { "defaultMessage": "Toestaan" @@ -108,34 +108,34 @@ "defaultMessage": "Verwijderen" }, "action.deny": { - "defaultMessage": "Weigeren" + "defaultMessage": "Afwijzen" }, "action.disable": { - "defaultMessage": "Uitzetten" + "defaultMessage": "Uitschakelen" }, "action.download": { - "defaultMessage": "Download" + "defaultMessage": "Downloaden" }, "action.edit": { "defaultMessage": "Bewerken" }, "action.enable": { - "defaultMessage": "Aanzetten" + "defaultMessage": "Inschakelen" }, "action.permissions": { - "defaultMessage": "Rechten" + "defaultMessage": "Machtigingen" }, "action.renew": { "defaultMessage": "Vernieuwen" }, "action.view-details": { - "defaultMessage": "Bekijk Details" + "defaultMessage": "Details weergeven" }, "auditlogs": { "defaultMessage": "Logboeken" }, "auto": { - "defaultMessage": "Automatisch" + "defaultMessage": "Autom." }, "cancel": { "defaultMessage": "Annuleren" @@ -147,13 +147,13 @@ "defaultMessage": "Certificaat" }, "certificate.custom-certificate-key": { - "defaultMessage": "Certificaat Sleutel" + "defaultMessage": "Certificaatsleutel" }, "certificate.custom-intermediate": { - "defaultMessage": "Intermediate Certificaat" + "defaultMessage": "Intermediate certificaat" }, "certificate.in-use": { - "defaultMessage": "In Gebruik" + "defaultMessage": "In gebruik" }, "certificate.none.subtitle": { "defaultMessage": "Geen certificaat toegewezen" @@ -165,61 +165,61 @@ "defaultMessage": "Geen" }, "certificate.not-in-use": { - "defaultMessage": "Niet Gebruikt" + "defaultMessage": "Niet gebruikt" }, "certificate.renew": { - "defaultMessage": "Certificaat Vernieuwen" + "defaultMessage": "Certificaat vernieuwen" }, "certificates": { "defaultMessage": "Certificaten" }, "certificates.custom": { - "defaultMessage": "Aangepast Certificaat" + "defaultMessage": "Aangepast certificaat" }, "certificates.custom.warning": { - "defaultMessage": "Sleutels met een wachtzin zijn niet ondersteund." + "defaultMessage": "Sleutels met een wachtwoordzin zijn niet ondersteund." }, "certificates.dns.credentials": { - "defaultMessage": "Credentials File Content" + "defaultMessage": "Inloggegevens bestandsinhoud" }, "certificates.dns.credentials-note": { - "defaultMessage": "Deze plugin vereist een configuratiebestand met een API token of andere gegevens van de provider." + "defaultMessage": "Deze plugin vereist een configuratiebestand met een API-token of andere gegevens van je aanbieder." }, "certificates.dns.credentials-warning": { - "defaultMessage": "Deze data zal worden opgeslagen als plaintext in de database en in een bestand!" + "defaultMessage": "Deze gegevens worden opgeslagen als plaintext in de database en in een bestand!" }, "certificates.dns.propagation-seconds": { "defaultMessage": "Verwerkingstijd (seconden)" }, "certificates.dns.propagation-seconds-note": { - "defaultMessage": "Laat leeg om de standaardwaarde van de plugin te gebruiken. Aantal seconden om te wachten op DNS propagatie." + "defaultMessage": "Laat leeg om de standaardwaarde van de plugin te gebruiken. Aantal seconden om te wachten op DNS-propagatie." }, "certificates.dns.provider": { - "defaultMessage": "DNS Provider" + "defaultMessage": "DNS-aanbieder" }, "certificates.dns.provider.placeholder": { - "defaultMessage": "Selecteer een provider..." + "defaultMessage": "Kies een aanbieder..." }, "certificates.dns.warning": { "defaultMessage": "Deze sectie vereist wat informatie over Certbot en zijn DNS plugins. Gebruik de documentatie van de bijbehorende plugins." }, "certificates.http.reachability-404": { - "defaultMessage": "Er is een server gevonden op deze domeinnaam, maar dat lijkt niet Nginx Proxy Manager te zijn. Zorg ervoor dat je domein naar het IP waar je NPM instance draait wijst." + "defaultMessage": "Er is een server gevonden op deze domeinnaam, maar dat lijkt niet Nginx Proxy Manager te zijn. Zorg ervoor dat je domein verwijst naar het IP waar je NPM-instantie draait." }, "certificates.http.reachability-failed-to-check": { "defaultMessage": "Bereikbaarheid kan niet worden bepaald door een communicatiefout met site24x7.com." }, "certificates.http.reachability-not-resolved": { - "defaultMessage": "Er is geen server beschikbaar op dit domein. Zorg ervoor dat je domein bestaat en naar het IP waar je NPM instance draait wijst en eventueel port 80 wordt doorgegeven in je router." + "defaultMessage": "Er is geen server beschikbaar op dit domein. Zorg ervoor dat je domein bestaat en verwijst naar het IP waar je NPM-instantie draait en eventueel port 80 wordt doorgestuurd in je router." }, "certificates.http.reachability-ok": { - "defaultMessage": "Jouw server is bereikbaar en certificaten kunnen worden aangemaakt." + "defaultMessage": "Je server is bereikbaar en certificaten kunnen worden aangemaakt." }, "certificates.http.reachability-other": { - "defaultMessage": "Er is een server gevonden op deze domeinnaam, maar heeft een onverwachte statuscode ({code}) teruggegeven. Is dat de NPM server? Zorg ervoor dat je domein naar het IP waar je NPM instance draait wijst." + "defaultMessage": "Er is een server gevonden op deze domeinnaam, maar heeft een onverwachte statuscode ({code}) teruggegeven. Is dat de NPM-server? Zorg ervoor dat je domein verwijst naar het IP waar jouw NPM-instantie draait." }, "certificates.http.reachability-wrong-data": { - "defaultMessage": "Er is een server gevonden op deze domeinnaam, maar heeft een onverwachte gegevens teruggegeven. Is dat de NPM server? Zorg ervoor dat je domein naar het IP waar je NPM instance draait wijst." + "defaultMessage": "Er is een server gevonden op deze domeinnaam, maar heeft een onverwachte gegevens teruggegeven. Is dat de NPM-server? Zorg ervoor dat je domein verwijst naar het IP waar jouw NPM-instantie draait." }, "certificates.http.test-results": { "defaultMessage": "Testresultaten" @@ -231,7 +231,7 @@ "defaultMessage": "Sleuteltype" }, "certificates.key-type-description": { - "defaultMessage": "RSA is breed compatibel, ECDSA is sneller en veiliger maar wordt mogelijk niet ondersteund door oudere systemen" + "defaultMessage": "RSA is algemeen toepasbaar, ECDSA is sneller en veiliger maar wordt mogelijk niet ondersteund door oudere systemen" }, "certificates.key-type-ecdsa": { "defaultMessage": "ECDSA 256" @@ -243,19 +243,19 @@ "defaultMessage": "met Let's Encrypt" }, "certificates.request.title": { - "defaultMessage": "Vraag een nieuwe Certificaat aan" + "defaultMessage": "Vraag een nieuw certificaat aan" }, "column.access": { "defaultMessage": "Toegang" }, "column.authorization": { - "defaultMessage": "Authorizatie" + "defaultMessage": "Authorisatie" }, "column.authorizations": { - "defaultMessage": "Authorizaties" + "defaultMessage": "Authorisaties" }, "column.custom-locations": { - "defaultMessage": "Aangepaste Locaties" + "defaultMessage": "Aangepaste locaties" }, "column.destination": { "defaultMessage": "Doel" @@ -264,7 +264,7 @@ "defaultMessage": "Details" }, "column.email": { - "defaultMessage": "Email" + "defaultMessage": "E-mail" }, "column.event": { "defaultMessage": "Gebeurtenis" @@ -273,10 +273,10 @@ "defaultMessage": "Verloopt" }, "column.http-code": { - "defaultMessage": "HTTP Code" + "defaultMessage": "HTTP-code" }, "column.incoming-port": { - "defaultMessage": "Inkomende Poort" + "defaultMessage": "Inkomende poort" }, "column.name": { "defaultMessage": "Naam" @@ -285,7 +285,7 @@ "defaultMessage": "Protocol" }, "column.provider": { - "defaultMessage": "Provider" + "defaultMessage": "Aanbieder" }, "column.roles": { "defaultMessage": "Rollen" @@ -294,7 +294,7 @@ "defaultMessage": "Regels" }, "column.satisfy": { - "defaultMessage": "Vervul" + "defaultMessage": "Vervullen" }, "column.satisfy-all": { "defaultMessage": "Alle" @@ -321,16 +321,16 @@ "defaultMessage": "Dashboard" }, "dead-host": { - "defaultMessage": "404 Host" + "defaultMessage": "404-Host" }, "dead-hosts": { - "defaultMessage": "404 Hosts" + "defaultMessage": "404-Hosts" }, "dead-hosts.count": { - "defaultMessage": "{count} {count, plural, one {404 Host} other {404 Hosts}}" + "defaultMessage": "{count} {count, plural, one {404-Host} other {404-Hosts}}" }, "disabled": { - "defaultMessage": "Uitgezet" + "defaultMessage": "Uitgeschakeld" }, "domain-names": { "defaultMessage": "Domeinnamen" @@ -351,22 +351,22 @@ "defaultMessage": "Geavanceerd" }, "domains.force-ssl": { - "defaultMessage": "Forceer SSL" + "defaultMessage": "SSL forceren" }, "domains.hsts-enabled": { - "defaultMessage": "HSTS Aangezet" + "defaultMessage": "HSTS ingeschakeld" }, "domains.hsts-subdomains": { - "defaultMessage": "HSTS Subdomein" + "defaultMessage": "HSTS-subdomein" }, "domains.http2-support": { - "defaultMessage": "HTTP/2 Ondersteuning" + "defaultMessage": "HTTP/2-ondersteuning" }, "domains.trust-forwarded-proto": { - "defaultMessage": "Vertrouw upstream Forwarded Proto-headers" + "defaultMessage": "Forwarded-Proto-headers van upstream vertrouwen" }, "domains.use-dns": { - "defaultMessage": "Gebruik DNS Challenge" + "defaultMessage": "DNS-challenge gebruiken" }, "email-address": { "defaultMessage": "E-mailadres" @@ -375,19 +375,19 @@ "defaultMessage": "Geen resultaten gevonden" }, "empty-subtitle": { - "defaultMessage": "Waarom niet een maken?" + "defaultMessage": "Waarom maak je er geen aan?" }, "enabled": { "defaultMessage": "Aangezet" }, "error.access.at-least-one": { - "defaultMessage": "Minimaal één authorizatie- of één toegangsregel is vereist" + "defaultMessage": "Minstens één authorisatie- of één toegangsregel is vereist" }, "error.access.duplicate-usernames": { "defaultMessage": "Gebruikersnamen moeten uniek zijn" }, "error.invalid-auth": { - "defaultMessage": "Ongeldige email of wachtwoord" + "defaultMessage": "Ongeldig e-mail of wachtwoord" }, "error.invalid-domain": { "defaultMessage": "Ongeldige domeinnaam: {domain}" @@ -396,19 +396,19 @@ "defaultMessage": "Ongeldig e-mailadres" }, "error.max-character-length": { - "defaultMessage": "Maximale lengte is {max} karakter{max, plural, one {} other {s}}" + "defaultMessage": "Maximale lengte is {max} teken{max, plural, one {} other {s}}" }, "error.max-domains": { "defaultMessage": "Te veel domeinnamen, max is {max}" }, "error.maximum": { - "defaultMessage": "Maximale is {max}" + "defaultMessage": "Maximum is {max}" }, "error.min-character-length": { - "defaultMessage": "Minimale lengte is {min} karakter{min, plural, one {} other {s}}" + "defaultMessage": "Minimale lengte is {min} teken{min, plural, one {} other {s}}" }, "error.minimum": { - "defaultMessage": "Minimale is {min}" + "defaultMessage": "Minimum is {min}" }, "error.passwords-must-match": { "defaultMessage": "Wachtwoorden moeten overeenkomen" @@ -423,22 +423,22 @@ "defaultMessage": "Maak een Fork op Github" }, "host.flags.block-exploits": { - "defaultMessage": "Blokkeer Veelvoorkomende Kwetsbaarheden" + "defaultMessage": "Veelvoorkomende kwetsbaarheden blokkeren" }, "host.flags.cache-assets": { - "defaultMessage": "Cache Assets" + "defaultMessage": "Assets opslaan" }, "host.flags.preserve-path": { - "defaultMessage": "Pad Behouden" + "defaultMessage": "Pad behouden" }, "host.flags.protocols": { "defaultMessage": "Protocollen" }, "host.flags.websockets-upgrade": { - "defaultMessage": "Websockets Ondersteuning" + "defaultMessage": "Websockets-ondersteuning" }, "host.forward-port": { - "defaultMessage": "Poort Doorsturen" + "defaultMessage": "Poort doorsturen" }, "host.forward-scheme": { "defaultMessage": "Schema" @@ -458,18 +458,6 @@ "lets-encrypt-via-http": { "defaultMessage": "Let's Encrypt via HTTP" }, - "load-balancing.add-server": { - "defaultMessage": "Server toevoegen" - }, - "load-balancing.host": { - "defaultMessage": "Host" - }, - "load-balancing.port": { - "defaultMessage": "Poort" - }, - "load-balancing.weight": { - "defaultMessage": "Gewicht" - }, "loading": { "defaultMessage": "Laden…" }, @@ -477,10 +465,10 @@ "defaultMessage": "Verificatiecode" }, "login.2fa-code-placeholder": { - "defaultMessage": "Voer code in" + "defaultMessage": "Code invoeren" }, "login.2fa-description": { - "defaultMessage": "Voer de code van uw authenticatie-app in" + "defaultMessage": "Voer de code in vanuit je Authenticator-app" }, "login.2fa-title": { "defaultMessage": "Tweefactorauthenticatie" @@ -489,16 +477,16 @@ "defaultMessage": "Verifiëren" }, "login.title": { - "defaultMessage": "Inloggen" + "defaultMessage": "Log in op je account" }, "nginx-config.label": { - "defaultMessage": "Aangepaste Nginx Configuratie" + "defaultMessage": "Aangepaste Nginx-configuratie" }, "nginx-config.placeholder": { - "defaultMessage": "# Voeg jouw aangepaste Nginx configuratie hier op eigen risico toe!" + "defaultMessage": "# Voer hier je aangepaste Nginx-configuratie in op eigen risico!" }, "no-permission-error": { - "defaultMessage": "Jij hebt geen toegang om dit te bekijken." + "defaultMessage": "Je hebt geen toegang om dit te bekijken." }, "notfound.action": { "defaultMessage": "Thuis" @@ -516,10 +504,10 @@ "defaultMessage": "{object} is verwijderd" }, "notification.object-disabled": { - "defaultMessage": "{object} is uitgezet" + "defaultMessage": "{object} is uitgeschakeld" }, "notification.object-enabled": { - "defaultMessage": "{object} is aangezet" + "defaultMessage": "{object} is ingeschakeld" }, "notification.object-renewed": { "defaultMessage": "{object} is vernieuwd" @@ -534,16 +522,16 @@ "defaultMessage": "{object} #{id}" }, "object.add": { - "defaultMessage": "Voeg {object} toe" + "defaultMessage": "{object} toevoegen" }, "object.delete": { - "defaultMessage": "Verwijder {object}" + "defaultMessage": "{object} verwijderen" }, "object.delete.content": { "defaultMessage": "Weet je zeker dat je {object} wilt verwijderen?" }, "object.edit": { - "defaultMessage": "Bewerk {object}" + "defaultMessage": "{object} bewerken" }, "object.empty": { "defaultMessage": "Er zijn geen {objects}" @@ -555,10 +543,10 @@ "defaultMessage": "{object} is verwijderd" }, "object.event.disabled": { - "defaultMessage": "{object} is uitgezet" + "defaultMessage": "{object} is uitgeschakeld" }, "object.event.enabled": { - "defaultMessage": "{object} is aangezet" + "defaultMessage": "{object} is ingeschakeld" }, "object.event.renewed": { "defaultMessage": "{object} is vernieuwd" @@ -582,52 +570,37 @@ "defaultMessage": "Willekeurig wachtwoord genereren" }, "password.hide": { - "defaultMessage": "Wachtwoord Verbergen" + "defaultMessage": "Wachtwoord verbergen" }, "password.show": { - "defaultMessage": "Toon Wachtwoord" + "defaultMessage": "Wachtwoord weergeven" }, "permissions.hidden": { "defaultMessage": "Verborgen" }, "permissions.manage": { - "defaultMessage": "Beheer" + "defaultMessage": "Beheren" }, "permissions.view": { - "defaultMessage": "Alleen Bekijken" + "defaultMessage": "Alleen weergeven" }, "permissions.visibility.all": { - "defaultMessage": "Alle Items" + "defaultMessage": "Alle items" }, "permissions.visibility.title": { - "defaultMessage": "Item Zichtbaarheid" + "defaultMessage": "Zichtbaarheid" }, "permissions.visibility.user": { - "defaultMessage": "Alleen Aangemaakte Items" + "defaultMessage": "Alleen aangemaakte items" }, "proxy-host": { - "defaultMessage": "Proxy Host" + "defaultMessage": "Proxyhost" }, "proxy-host.forward-host": { "defaultMessage": "Hostname / IP Doorsturen" }, - "proxy-host.forward-target-type": { - "defaultMessage": "Type doorstuurbestemming" - }, - "proxy-host.forward-target-type.direct": { - "defaultMessage": "Direct" - }, - "proxy-host.forward-target-type.direct.description": { - "defaultMessage": "Doorsturen naar één host en poort" - }, - "proxy-host.forward-target-type.upstream": { - "defaultMessage": "Upstream-host" - }, - "proxy-host.forward-target-type.upstream.description": { - "defaultMessage": "Gebruik een upstream-host voor load balancing over meerdere servers" - }, "proxy-hosts": { - "defaultMessage": "Proxy Hosts" + "defaultMessage": "Proxyhosts" }, "proxy-hosts.count": { "defaultMessage": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}" @@ -636,43 +609,43 @@ "defaultMessage": "Openbaar" }, "redirection-host": { - "defaultMessage": "Redirection Host" + "defaultMessage": "Omleidingshost" }, "redirection-host.forward-domain": { "defaultMessage": "Doorgestuurd Domein" }, "redirection-host.forward-http-code": { - "defaultMessage": "HTTP Code" + "defaultMessage": "HTTP-code" }, "redirection-hosts": { - "defaultMessage": "Redirection Hosts" + "defaultMessage": "Omgeleide Hosts" }, "redirection-hosts.count": { - "defaultMessage": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}" + "defaultMessage": "{count} {count, plural, one {Omgeleide Host} other {Omgeleide Hosts}}" }, "redirection-hosts.http-code.300": { - "defaultMessage": "300 Meerdere keuzes" + "defaultMessage": "300 Meerkeuze" }, "redirection-hosts.http-code.301": { - "defaultMessage": "301 Permanent verplaatst" + "defaultMessage": "301 Definitief verplaatst" }, "redirection-hosts.http-code.302": { "defaultMessage": "302 Tijdelijk verplaatst" }, "redirection-hosts.http-code.303": { - "defaultMessage": "303 Zie ander" + "defaultMessage": "303 Zie andere" }, "redirection-hosts.http-code.307": { "defaultMessage": "307 Tijdelijke omleiding" }, "redirection-hosts.http-code.308": { - "defaultMessage": "308 Permanente omleiding" + "defaultMessage": "308 Definitieve omleiding" }, "role.admin": { "defaultMessage": "Beheerder" }, "role.standard-user": { - "defaultMessage": "Standaard Gebruiker" + "defaultMessage": "Standaardgebruiker" }, "save": { "defaultMessage": "Opslaan" @@ -684,61 +657,31 @@ "defaultMessage": "Instellingen" }, "settings.default-site": { - "defaultMessage": "Standaard Site" + "defaultMessage": "Standaardbestemming" }, "settings.default-site.404": { - "defaultMessage": "404 Pagina" + "defaultMessage": "404-pagina" }, "settings.default-site.444": { - "defaultMessage": "Geen Antwoord (444)" + "defaultMessage": "Geen reactie (444)" }, "settings.default-site.congratulations": { "defaultMessage": "Felicitatiepagina" }, "settings.default-site.description": { - "defaultMessage": "Wat te tonen als Nginx een onbekende Host ontvangt" + "defaultMessage": "Wat als Nginx een onbekende Host ontvangt?" }, "settings.default-site.html": { "defaultMessage": "Aangepaste HTML" }, "settings.default-site.html.placeholder": { - "defaultMessage": "" + "defaultMessage": "" }, "settings.default-site.redirect": { - "defaultMessage": "Omleiding" - }, - "settings.real-ip-header": { - "defaultMessage": "Echte IP-header" - }, - "settings.real-ip-header.cf-connecting-ip": { - "defaultMessage": "CF-Connecting-IP" - }, - "settings.real-ip-header.cf-connecting-ip.description": { - "defaultMessage": "Gebruik achter Cloudflare" - }, - "settings.real-ip-header.custom": { - "defaultMessage": "Aangepast" - }, - "settings.real-ip-header.custom.placeholder": { - "defaultMessage": "Voer aangepaste headernaam in" - }, - "settings.real-ip-header.description": { - "defaultMessage": "HTTP-header om het echte IP-adres van de client te bepalen" - }, - "settings.real-ip-header.x-forwarded-for": { - "defaultMessage": "X-Forwarded-For" - }, - "settings.real-ip-header.x-forwarded-for.description": { - "defaultMessage": "Veelgebruikt alternatief header" - }, - "settings.real-ip-header.x-real-ip": { - "defaultMessage": "X-Real-IP" - }, - "settings.real-ip-header.x-real-ip.description": { - "defaultMessage": "Standaard header, werkt met de meeste reverse proxies" + "defaultMessage": "Omleiden" }, "setup.preamble": { - "defaultMessage": "Begin met het aanmaken van je beheerder account." + "defaultMessage": "Begin met het aanmaken van jouw beheerderaccount." }, "setup.title": { "defaultMessage": "Welkom!" @@ -747,19 +690,19 @@ "defaultMessage": "Inloggen" }, "ssl-certificate": { - "defaultMessage": "SSL Certificaten" + "defaultMessage": "SSL-certificaat" }, "stream": { "defaultMessage": "Stream" }, "stream.forward-host": { - "defaultMessage": "Doorgestuurde Host" + "defaultMessage": "Doelhost" }, "stream.forward-host.placeholder": { - "defaultMessage": "example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" + "defaultMessage": "voorbeeld.nl of 10.0.0.1 of 2001:db8:3333:4444:5555:6666:7777:8888" }, "stream.incoming-port": { - "defaultMessage": "Inkomende Poort" + "defaultMessage": "Inkomende poort" }, "streams": { "defaultMessage": "Streams" @@ -774,52 +717,28 @@ "defaultMessage": "UDP" }, "test": { - "defaultMessage": "Test" + "defaultMessage": "Testen" }, "update-available": { "defaultMessage": "Update beschikbaar: {latestVersion}" }, - "upstream-host": { - "defaultMessage": "Upstream-host" - }, - "upstream-host.help": { - "defaultMessage": "Selecteer een upstream-host in plaats van direct doorsturen." - }, - "upstream-host.method": { - "defaultMessage": "Methode" - }, - "upstream-host.none": { - "defaultMessage": "Geen (Direct)" - }, - "upstream-host.none.subtitle": { - "defaultMessage": "Geen upstream-host geselecteerd" - }, - "upstream-host.servers": { - "defaultMessage": "Servers" - }, - "upstream-hosts": { - "defaultMessage": "Upstream-hosts" - }, - "upstream-hosts.count": { - "defaultMessage": "{count} {count, plural, one {Upstream-host} other {Upstream-hosts}}" - }, "user": { "defaultMessage": "Gebruiker" }, "user.change-password": { - "defaultMessage": "Verander Wachtwoord" + "defaultMessage": "Wachtwoord wijzigen" }, "user.confirm-password": { - "defaultMessage": "Bevestig Wachtwoord" + "defaultMessage": "Wachtwoord bevestigen" }, "user.current-password": { - "defaultMessage": "Huidig Wachtwoord" + "defaultMessage": "Huidig wachtwoord" }, "user.edit-profile": { - "defaultMessage": "Profiel Bewerken" + "defaultMessage": "Profiel bewerken" }, "user.full-name": { - "defaultMessage": "Volledige Naam" + "defaultMessage": "Volledige naam" }, "user.login-as": { "defaultMessage": "Inloggen als {name}" @@ -828,25 +747,25 @@ "defaultMessage": "Uitloggen" }, "user.new-password": { - "defaultMessage": "Nieuw Wachtwoord" + "defaultMessage": "Nieuw wachtwoord" }, "user.nickname": { "defaultMessage": "Bijnaam" }, "user.set-password": { - "defaultMessage": "Zet Wachtwoord" + "defaultMessage": "Wachtwoord instellen" }, "user.set-permissions": { - "defaultMessage": "Zet machtigingen voor {name}" + "defaultMessage": "Machtigingen instellen voor {name}" }, "user.switch-dark": { - "defaultMessage": "Verander naar donkere modus" + "defaultMessage": "Donkere modus inschakelen" }, "user.switch-light": { - "defaultMessage": "Verander naar lichte modus" + "defaultMessage": "Lichte modus inschakelen" }, "user.two-factor": { - "defaultMessage": "Tweefactor-auth." + "defaultMessage": "Tweefactorauthenticatie" }, "username": { "defaultMessage": "Gebruikersnaam" diff --git a/frontend/src/locale/src/pt_br.json b/frontend/src/locale/src/pt_br.json new file mode 100644 index 0000000000..5947d44c8a --- /dev/null +++ b/frontend/src/locale/src/pt_br.json @@ -0,0 +1,857 @@ +{ + "2fa.backup-codes-remaining": { + "defaultMessage": "Códigos de backup restantes: {count}" + }, + "2fa.backup-warning": { + "defaultMessage": "Salve estes códigos de backup em um lugar seguro. Cada código só pode ser usado uma vez." + }, + "2fa.disable": { + "defaultMessage": "Desativar autenticação de dois fatores" + }, + "2fa.disable-confirm": { + "defaultMessage": "Desativar 2FA" + }, + "2fa.disable-warning": { + "defaultMessage": "Desativar a autenticação de dois fatores tornará sua conta menos segura." + }, + "2fa.disabled": { + "defaultMessage": "Desativado" + }, + "2fa.done": { + "defaultMessage": "Salvei meus códigos de backup" + }, + "2fa.enable": { + "defaultMessage": "Ativar autenticação de dois fatores" + }, + "2fa.enabled": { + "defaultMessage": "Ativado" + }, + "2fa.enter-code": { + "defaultMessage": "Digite o código de verificação" + }, + "2fa.enter-code-disable": { + "defaultMessage": "Digite o código de verificação para desativar" + }, + "2fa.regenerate": { + "defaultMessage": "Regenerar" + }, + "2fa.regenerate-backup": { + "defaultMessage": "Regenerar códigos de backup" + }, + "2fa.regenerate-instructions": { + "defaultMessage": "Digite um código de verificação para gerar novos códigos de backup. Seus códigos antigos serão invalidados." + }, + "2fa.secret-key": { + "defaultMessage": "Chave secreta" + }, + "2fa.setup-instructions": { + "defaultMessage": "Escaneie este código QR com seu aplicativo de autenticação ou insira o segredo manualmente." + }, + "2fa.status": { + "defaultMessage": "Status" + }, + "2fa.title": { + "defaultMessage": "Autenticação de dois fatores" + }, + "2fa.verify-enable": { + "defaultMessage": "Verificar e ativar" + }, + "access-list": { + "defaultMessage": "Lista de Acesso" + }, + "access-list.access-count": { + "defaultMessage": "{count} {count, plural, one {Regra} other {Regras}}" + }, + "access-list.auth-count": { + "defaultMessage": "{count} {count, plural, one {Usuário} other {Usuários}}" + }, + "access-list.help-rules-last": { + "defaultMessage": "Quando existir pelo menos 1 regra, esta regra de negar tudo será adicionada por último" + }, + "access-list.help.rules-order": { + "defaultMessage": "Note que as diretivas de permitir e negar serão aplicadas na ordem em que foram definidas." + }, + "access-list.pass-auth": { + "defaultMessage": "Passar Autenticação para Upstream" + }, + "access-list.public": { + "defaultMessage": "Acessível Publicamente" + }, + "access-list.public.subtitle": { + "defaultMessage": "Autenticação básica não necessária" + }, + "access-list.rule-source.placeholder": { + "defaultMessage": "192.168.1.100 ou 192.168.1.0/24 ou 2001:0db8::/32" + }, + "access-list.satisfy-any": { + "defaultMessage": "Satisfazer Qualquer" + }, + "access-list.subtitle": { + "defaultMessage": "{users} {users, plural, one {Usuário} other {Usuários}}, {rules} {rules, plural, one {Regra} other {Regras}} - Criado: {date}" + }, + "access-lists": { + "defaultMessage": "Listas de Acesso" + }, + "action.add": { + "defaultMessage": "Adicionar" + }, + "action.add-location": { + "defaultMessage": "Adicionar Localização" + }, + "action.allow": { + "defaultMessage": "Permitir" + }, + "action.close": { + "defaultMessage": "Fechar" + }, + "action.delete": { + "defaultMessage": "Excluir" + }, + "action.deny": { + "defaultMessage": "Negar" + }, + "action.disable": { + "defaultMessage": "Desativar" + }, + "action.download": { + "defaultMessage": "Baixar" + }, + "action.edit": { + "defaultMessage": "Editar" + }, + "action.enable": { + "defaultMessage": "Ativar" + }, + "action.permissions": { + "defaultMessage": "Permissões" + }, + "action.renew": { + "defaultMessage": "Renovar" + }, + "action.view-details": { + "defaultMessage": "Ver Detalhes" + }, + "auditlogs": { + "defaultMessage": "Logs de Auditoria" + }, + "auto": { + "defaultMessage": "Automático" + }, + "cancel": { + "defaultMessage": "Cancelar" + }, + "certificate": { + "defaultMessage": "Certificado" + }, + "certificate.custom-certificate": { + "defaultMessage": "Certificado" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Chave do Certificado" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Certificado Intermediário" + }, + "certificate.in-use": { + "defaultMessage": "Em Uso" + }, + "certificate.none.subtitle": { + "defaultMessage": "Nenhum certificado atribuído" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "Este host não usará HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "Nenhum" + }, + "certificate.not-in-use": { + "defaultMessage": "Não Usado" + }, + "certificate.renew": { + "defaultMessage": "Renovar Certificado" + }, + "certificates": { + "defaultMessage": "Certificados" + }, + "certificates.custom": { + "defaultMessage": "Certificado Personalizado" + }, + "certificates.custom.warning": { + "defaultMessage": "Arquivos de chave protegidos com senha não são suportados." + }, + "certificates.dns.credentials": { + "defaultMessage": "Conteúdo do Arquivo de Credenciais" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "Este plugin requer um arquivo de configuração contendo um token de API ou outras credenciais do seu provedor" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "Estes dados serão armazenados como texto simples no banco de dados e em um arquivo!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "Segundos de Propagação" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "Deixe vazio para usar o valor padrão do plugin. Número de segundos para aguardar a propagação DNS." + }, + "certificates.dns.provider": { + "defaultMessage": "Provedor DNS" + }, + "certificates.dns.provider.placeholder": { + "defaultMessage": "Selecionar um provedor..." + }, + "certificates.dns.warning": { + "defaultMessage": "Esta seção requer algum conhecimento sobre Certbot e seus plugins DNS. Por favor, consulte a documentação dos respectivos plugins." + }, + "certificates.http.reachability-404": { + "defaultMessage": "Há um servidor neste domínio, mas não parece ser o Nginx Proxy Manager. Certifique-se de que seu domínio aponta para o IP onde sua instância do NPM está rodando." + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "Falha ao verificar a acessibilidade devido a um erro de comunicação com site24x7.com." + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "Não há servidor disponível neste domínio. Certifique-se de que seu domínio existe e aponta para o IP onde sua instância do NPM está rodando e, se necessário, a porta 80 está encaminhada no seu roteador." + }, + "certificates.http.reachability-ok": { + "defaultMessage": "Seu servidor está acessível e a criação de certificados deve ser possível." + }, + "certificates.http.reachability-other": { + "defaultMessage": "Há um servidor neste domínio, mas retornou um código de status inesperado {code}. É o servidor NPM? Certifique-se de que seu domínio aponta para o IP onde sua instância do NPM está rodando." + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "Há um servidor neste domínio, mas retornou dados inesperados. É o servidor NPM? Certifique-se de que seu domínio aponta para o IP onde sua instância do NPM está rodando." + }, + "certificates.http.test-results": { + "defaultMessage": "Resultados do Teste" + }, + "certificates.http.warning": { + "defaultMessage": "Estes domínios já devem estar configurados para apontar para esta instalação." + }, + "certificates.key-type": { + "defaultMessage": "Tipo de Chave" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA é amplamente compatível, ECDSA é mais rápido e seguro mas pode não ser suportado por sistemas mais antigos" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "certificates.request.subtitle": { + "defaultMessage": "com Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "Solicitar um novo Certificado" + }, + "column.access": { + "defaultMessage": "Acesso" + }, + "column.authorization": { + "defaultMessage": "Autorização" + }, + "column.authorizations": { + "defaultMessage": "Autorizações" + }, + "column.custom-locations": { + "defaultMessage": "Localizações Personalizadas" + }, + "column.destination": { + "defaultMessage": "Destino" + }, + "column.details": { + "defaultMessage": "Detalhes" + }, + "column.email": { + "defaultMessage": "E-mail" + }, + "column.event": { + "defaultMessage": "Evento" + }, + "column.expires": { + "defaultMessage": "Expira" + }, + "column.http-code": { + "defaultMessage": "Código HTTP" + }, + "column.incoming-port": { + "defaultMessage": "Porta de Entrada" + }, + "column.name": { + "defaultMessage": "Nome" + }, + "column.protocol": { + "defaultMessage": "Protocolo" + }, + "column.provider": { + "defaultMessage": "Provedor" + }, + "column.roles": { + "defaultMessage": "Funções" + }, + "column.rules": { + "defaultMessage": "Regras" + }, + "column.satisfy": { + "defaultMessage": "Satisfazer" + }, + "column.satisfy-all": { + "defaultMessage": "Todos" + }, + "column.satisfy-any": { + "defaultMessage": "Qualquer" + }, + "column.scheme": { + "defaultMessage": "Esquema" + }, + "column.source": { + "defaultMessage": "Origem" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "Status" + }, + "created-on": { + "defaultMessage": "Criado: {date}" + }, + "dashboard": { + "defaultMessage": "Painel" + }, + "dead-host": { + "defaultMessage": "Host 404" + }, + "dead-hosts": { + "defaultMessage": "Hosts 404" + }, + "dead-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host 404} other {Hosts 404}}" + }, + "disabled": { + "defaultMessage": "Desativado" + }, + "domain-names": { + "defaultMessage": "Nomes de Domínio" + }, + "domain-names.max": { + "defaultMessage": "Máximo de {count} nomes de domínio" + }, + "domain-names.placeholder": { + "defaultMessage": "Comece a digitar para adicionar domínio..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "Curingas não são permitidos para este tipo" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "Curingas não são suportados para esta CA" + }, + "domains.advanced": { + "defaultMessage": "Avançado" + }, + "domains.force-ssl": { + "defaultMessage": "Forçar SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS Ativado" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS em Subdomínios" + }, + "domains.http2-support": { + "defaultMessage": "Suporte HTTP/2" + }, + "domains.trust-forwarded-proto": { + "defaultMessage": "Confiar nos cabeçalhos Forwarded Proto do upstream" + }, + "domains.use-dns": { + "defaultMessage": "Usar Desafio DNS" + }, + "email-address": { + "defaultMessage": "Endereço de e-mail" + }, + "empty-search": { + "defaultMessage": "Nenhum resultado encontrado" + }, + "empty-subtitle": { + "defaultMessage": "Por que não criar um?" + }, + "enabled": { + "defaultMessage": "Ativado" + }, + "error.access.at-least-one": { + "defaultMessage": "É necessário pelo menos uma Autorização ou uma Regra de Acesso" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "Os nomes de usuário de Autorização devem ser únicos" + }, + "error.invalid-auth": { + "defaultMessage": "E-mail ou senha inválidos" + }, + "error.invalid-domain": { + "defaultMessage": "Domínio inválido: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "Endereço de e-mail inválido" + }, + "error.max-character-length": { + "defaultMessage": "O comprimento máximo é de {max} {max, plural, one {caractere} other {caracteres}}" + }, + "error.max-domains": { + "defaultMessage": "Muitos domínios, o máximo é {max}" + }, + "error.maximum": { + "defaultMessage": "O máximo é {max}" + }, + "error.min-character-length": { + "defaultMessage": "O comprimento mínimo é de {min} {min, plural, one {caractere} other {caracteres}}" + }, + "error.minimum": { + "defaultMessage": "O mínimo é {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "As senhas devem ser iguais" + }, + "error.required": { + "defaultMessage": "Este campo é obrigatório" + }, + "expires.on": { + "defaultMessage": "Expira: {date}" + }, + "footer.github-fork": { + "defaultMessage": "Faça um fork no Github" + }, + "host.flags.block-exploits": { + "defaultMessage": "Bloquear Exploits Comuns" + }, + "host.flags.cache-assets": { + "defaultMessage": "Cachear Recursos" + }, + "host.flags.preserve-path": { + "defaultMessage": "Preservar Caminho" + }, + "host.flags.protocols": { + "defaultMessage": "Protocolos" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Suporte a Websockets" + }, + "host.forward-port": { + "defaultMessage": "Porta" + }, + "host.forward-scheme": { + "defaultMessage": "Esquema" + }, + "hosts": { + "defaultMessage": "Hosts" + }, + "http-only": { + "defaultMessage": "Somente HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt via DNS" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt via HTTP" + }, + "load-balancing.add-server": { + "defaultMessage": "Adicionar Servidor" + }, + "load-balancing.host": { + "defaultMessage": "Host" + }, + "load-balancing.port": { + "defaultMessage": "Porta" + }, + "load-balancing.weight": { + "defaultMessage": "Peso" + }, + "loading": { + "defaultMessage": "Carregando…" + }, + "login.2fa-code": { + "defaultMessage": "Código de verificação" + }, + "login.2fa-code-placeholder": { + "defaultMessage": "Digite o código" + }, + "login.2fa-description": { + "defaultMessage": "Digite o código do seu aplicativo de autenticação" + }, + "login.2fa-title": { + "defaultMessage": "Autenticação de dois fatores" + }, + "login.2fa-verify": { + "defaultMessage": "Verificar" + }, + "login.title": { + "defaultMessage": "Entre na sua conta" + }, + "nginx-config.label": { + "defaultMessage": "Configuração Nginx Personalizada" + }, + "nginx-config.placeholder": { + "defaultMessage": "# Digite sua configuração Nginx personalizada aqui por sua conta e risco!" + }, + "no-permission-error": { + "defaultMessage": "Você não tem permissão para visualizar isto." + }, + "notfound.action": { + "defaultMessage": "Voltar ao início" + }, + "notfound.content": { + "defaultMessage": "Desculpe, mas a página que você está procurando não foi encontrada" + }, + "notfound.title": { + "defaultMessage": "Ops… Você encontrou uma página de erro" + }, + "notification.error": { + "defaultMessage": "Erro" + }, + "notification.object-deleted": { + "defaultMessage": "{object} foi excluído" + }, + "notification.object-disabled": { + "defaultMessage": "{object} foi desativado" + }, + "notification.object-enabled": { + "defaultMessage": "{object} foi ativado" + }, + "notification.object-renewed": { + "defaultMessage": "{object} foi renovado" + }, + "notification.object-saved": { + "defaultMessage": "{object} foi salvo" + }, + "notification.success": { + "defaultMessage": "Sucesso" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "Adicionar {object}" + }, + "object.delete": { + "defaultMessage": "Excluir {object}" + }, + "object.delete.content": { + "defaultMessage": "Tem certeza de que deseja excluir este {object}?" + }, + "object.edit": { + "defaultMessage": "Editar {object}" + }, + "object.empty": { + "defaultMessage": "Não há {objects}" + }, + "object.event.created": { + "defaultMessage": "{object} criado" + }, + "object.event.deleted": { + "defaultMessage": "{object} excluído" + }, + "object.event.disabled": { + "defaultMessage": "{object} desativado" + }, + "object.event.enabled": { + "defaultMessage": "{object} ativado" + }, + "object.event.renewed": { + "defaultMessage": "{object} renovado" + }, + "object.event.updated": { + "defaultMessage": "{object} atualizado" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "options": { + "defaultMessage": "Opções" + }, + "password": { + "defaultMessage": "Senha" + }, + "password.generate": { + "defaultMessage": "Gerar senha aleatória" + }, + "password.hide": { + "defaultMessage": "Ocultar Senha" + }, + "password.show": { + "defaultMessage": "Mostrar Senha" + }, + "permissions.hidden": { + "defaultMessage": "Oculto" + }, + "permissions.manage": { + "defaultMessage": "Gerenciar" + }, + "permissions.view": { + "defaultMessage": "Somente Visualização" + }, + "permissions.visibility.all": { + "defaultMessage": "Todos os Itens" + }, + "permissions.visibility.title": { + "defaultMessage": "Visibilidade dos Itens" + }, + "permissions.visibility.user": { + "defaultMessage": "Somente Itens Criados" + }, + "proxy-host": { + "defaultMessage": "Host Proxy" + }, + "proxy-host.forward-host": { + "defaultMessage": "Hostname / IP de Encaminhamento" + }, + "proxy-host.forward-target-type": { + "defaultMessage": "Tipo de destino" + }, + "proxy-host.forward-target-type.direct": { + "defaultMessage": "Direto" + }, + "proxy-host.forward-target-type.direct.description": { + "defaultMessage": "Encaminhar para um único host e porta" + }, + "proxy-host.forward-target-type.upstream": { + "defaultMessage": "Host upstream" + }, + "proxy-host.forward-target-type.upstream.description": { + "defaultMessage": "Usar um host upstream para balanceamento de carga entre múltiplos servidores" + }, + "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 Redirecionamento" + }, + "redirection-host.forward-domain": { + "defaultMessage": "Domínio de Destino" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "Código HTTP" + }, + "redirection-hosts": { + "defaultMessage": "Hosts de Redirecionamento" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host de Redirecionamento} other {Hosts de Redirecionamento}}" + }, + "redirection-hosts.http-code.300": { + "defaultMessage": "300 Múltiplas escolhas" + }, + "redirection-hosts.http-code.301": { + "defaultMessage": "301 Movido permanentemente" + }, + "redirection-hosts.http-code.302": { + "defaultMessage": "302 Movido temporariamente" + }, + "redirection-hosts.http-code.303": { + "defaultMessage": "303 Ver outro" + }, + "redirection-hosts.http-code.307": { + "defaultMessage": "307 Redirecionamento temporário" + }, + "redirection-hosts.http-code.308": { + "defaultMessage": "308 Redirecionamento permanente" + }, + "role.admin": { + "defaultMessage": "Administrador" + }, + "role.standard-user": { + "defaultMessage": "Usuário Padrão" + }, + "save": { + "defaultMessage": "Salvar" + }, + "setting": { + "defaultMessage": "Configuração" + }, + "settings": { + "defaultMessage": "Configurações" + }, + "settings.default-site": { + "defaultMessage": "Site Padrão" + }, + "settings.default-site.404": { + "defaultMessage": "Página 404" + }, + "settings.default-site.444": { + "defaultMessage": "Sem Resposta (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "Página de Parabéns" + }, + "settings.default-site.description": { + "defaultMessage": "O que mostrar quando o Nginx recebe um Host desconhecido" + }, + "settings.default-site.html": { + "defaultMessage": "HTML Personalizado" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "Redirecionar" + }, + "settings.real-ip-header": { + "defaultMessage": "Cabeçalho de IP real" + }, + "settings.real-ip-header.cf-connecting-ip": { + "defaultMessage": "CF-Connecting-IP" + }, + "settings.real-ip-header.cf-connecting-ip.description": { + "defaultMessage": "Usar quando atrás do Cloudflare" + }, + "settings.real-ip-header.custom": { + "defaultMessage": "Personalizado" + }, + "settings.real-ip-header.custom.placeholder": { + "defaultMessage": "Digite o nome do cabeçalho personalizado" + }, + "settings.real-ip-header.description": { + "defaultMessage": "Cabeçalho HTTP usado para determinar o endereço IP real do cliente" + }, + "settings.real-ip-header.x-forwarded-for": { + "defaultMessage": "X-Forwarded-For" + }, + "settings.real-ip-header.x-forwarded-for.description": { + "defaultMessage": "Cabeçalho alternativo comum" + }, + "settings.real-ip-header.x-real-ip": { + "defaultMessage": "X-Real-IP" + }, + "settings.real-ip-header.x-real-ip.description": { + "defaultMessage": "Cabeçalho padrão, funciona com a maioria dos proxies reversos" + }, + "setup.preamble": { + "defaultMessage": "Comece criando sua conta de administrador." + }, + "setup.title": { + "defaultMessage": "Bem-vindo!" + }, + "sign-in": { + "defaultMessage": "Entrar" + }, + "ssl-certificate": { + "defaultMessage": "Certificado SSL" + }, + "stream": { + "defaultMessage": "Stream" + }, + "stream.forward-host": { + "defaultMessage": "Host de Encaminhamento" + }, + "stream.forward-host.placeholder": { + "defaultMessage": "exemplo.com ou 10.0.0.1 ou 2001:db8:3333:4444:5555:6666:7777:8888" + }, + "stream.incoming-port": { + "defaultMessage": "Porta de Entrada" + }, + "streams": { + "defaultMessage": "Streams" + }, + "streams.count": { + "defaultMessage": "{count} {count, plural, one {Stream} other {Streams}}" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "Testar" + }, + "update-available": { + "defaultMessage": "Atualização disponível: {latestVersion}" + }, + "upstream-host": { + "defaultMessage": "Host upstream" + }, + "upstream-host.help": { + "defaultMessage": "Selecione um host upstream ao invés do encaminhamento direto." + }, + "upstream-host.method": { + "defaultMessage": "Método" + }, + "upstream-host.none": { + "defaultMessage": "Nenhum (Direto)" + }, + "upstream-host.none.subtitle": { + "defaultMessage": "Nenhum host upstream selecionado" + }, + "upstream-host.servers": { + "defaultMessage": "Servidores" + }, + "upstream-hosts": { + "defaultMessage": "Hosts upstream" + }, + "upstream-hosts.count": { + "defaultMessage": "{count} {count, plural, one {Host upstream} other {Hosts upstream}}" + }, + "user": { + "defaultMessage": "Usuário" + }, + "user.change-password": { + "defaultMessage": "Alterar Senha" + }, + "user.confirm-password": { + "defaultMessage": "Confirmar Senha" + }, + "user.current-password": { + "defaultMessage": "Senha Atual" + }, + "user.edit-profile": { + "defaultMessage": "Editar Perfil" + }, + "user.full-name": { + "defaultMessage": "Nome Completo" + }, + "user.login-as": { + "defaultMessage": "Entrar como {name}" + }, + "user.logout": { + "defaultMessage": "Sair" + }, + "user.new-password": { + "defaultMessage": "Nova Senha" + }, + "user.nickname": { + "defaultMessage": "Apelido" + }, + "user.set-password": { + "defaultMessage": "Definir Senha" + }, + "user.set-permissions": { + "defaultMessage": "Definir Permissões para {name}" + }, + "user.switch-dark": { + "defaultMessage": "Mudar para modo Escuro" + }, + "user.switch-light": { + "defaultMessage": "Mudar para modo Claro" + }, + "user.two-factor": { + "defaultMessage": "Auth. dois fatores" + }, + "username": { + "defaultMessage": "Nome de usuário" + }, + "users": { + "defaultMessage": "Usuários" + } +} diff --git a/frontend/src/locale/src/ru.json b/frontend/src/locale/src/ru.json index bab87a0865..c18be998f9 100644 --- a/frontend/src/locale/src/ru.json +++ b/frontend/src/locale/src/ru.json @@ -1,51 +1,51 @@ { "2fa.backup-codes-remaining": { - "defaultMessage": "Оставшиеся резервные коды: {count}" + "defaultMessage": "Осталось резервных кодов: {count}" }, "2fa.backup-warning": { - "defaultMessage": "Сохраните эти резервные коды в надёжном месте. Каждый код можно использовать только один раз." + "defaultMessage": "Сохраните коды в надежном месте. Они одноразовые." }, "2fa.disable": { - "defaultMessage": "Отключить двухфакторную аутентификацию" + "defaultMessage": "Выключить двухфакторную аутентификацию" }, "2fa.disable-confirm": { - "defaultMessage": "Отключить 2FA" + "defaultMessage": "Выключить 2FA" }, "2fa.disable-warning": { - "defaultMessage": "Отключение двухфакторной аутентификации сделает вашу учётную запись менее защищённой." + "defaultMessage": "Выключение двухфакторной аутентификации снизит защиту вашего аккаунта." }, "2fa.disabled": { - "defaultMessage": "Отключено" + "defaultMessage": "Выключена" }, "2fa.done": { - "defaultMessage": "Я сохранил свои резервные коды" + "defaultMessage": "Я сохранил резервные коды" }, "2fa.enable": { "defaultMessage": "Включить двухфакторную аутентификацию" }, "2fa.enabled": { - "defaultMessage": "Включено" + "defaultMessage": "Включена" }, "2fa.enter-code": { - "defaultMessage": "Введите код подтверждения" + "defaultMessage": "Введите код проверки" }, "2fa.enter-code-disable": { - "defaultMessage": "Введите код подтверждения для отключения" + "defaultMessage": "Введите код проверки для выключения" }, "2fa.regenerate": { - "defaultMessage": "Перегенерировать" + "defaultMessage": "Перевыпустить" }, "2fa.regenerate-backup": { - "defaultMessage": "Перегенерировать резервные коды" + "defaultMessage": "Перевыпустить резервные коды" }, "2fa.regenerate-instructions": { - "defaultMessage": "Введите код подтверждения для генерации новых резервных кодов. Старые коды будут аннулированы." + "defaultMessage": "Введите код проверки, чтобы создать новые резервные коды. Прежние перестанут работать." }, "2fa.secret-key": { "defaultMessage": "Секретный ключ" }, "2fa.setup-instructions": { - "defaultMessage": "Отсканируйте этот QR-код приложением для аутентификации или введите секрет вручную." + "defaultMessage": "Отсканируйте QR-код приложением или введите ключ настройки вручную." }, "2fa.status": { "defaultMessage": "Статус" @@ -54,7 +54,7 @@ "defaultMessage": "Двухфакторная аутентификация" }, "2fa.verify-enable": { - "defaultMessage": "Подтвердить и включить" + "defaultMessage": "Проверить и включить" }, "access-list": { "defaultMessage": "Список доступа" @@ -72,16 +72,16 @@ "defaultMessage": "Обратите внимание: разрешающие и запрещающие директивы применяются в порядке их определения." }, "access-list.pass-auth": { - "defaultMessage": "Передавать авторизацию на upstream-сервер" + "defaultMessage": "Проксировать авторизацию" }, "access-list.public": { - "defaultMessage": "Общедоступный" + "defaultMessage": "Публичный" }, "access-list.public.subtitle": { "defaultMessage": "Без аутентификации" }, "access-list.rule-source.placeholder": { - "defaultMessage": "192.168.1.100 or 192.168.1.0/24 or 2001:0db8::/32" + "defaultMessage": "192.168.1.100 или 192.168.1.0/24 или 2001:0db8::/32" }, "access-list.satisfy-any": { "defaultMessage": "Любое совпадение" @@ -348,7 +348,7 @@ "defaultMessage": "Подстановочные домены не поддерживаются этим центром сертификации" }, "domains.advanced": { - "defaultMessage": "Дополнительно" + "defaultMessage": "Расширенные" }, "domains.force-ssl": { "defaultMessage": "Всегда SSL" @@ -363,7 +363,7 @@ "defaultMessage": "Поддержка HTTP/2" }, "domains.trust-forwarded-proto": { - "defaultMessage": "Доверять заголовкам Forwarded Proto от upstream" + "defaultMessage": "Доверять X-Forwarded-Proto" }, "domains.use-dns": { "defaultMessage": "Проверка через DNS" @@ -438,7 +438,7 @@ "defaultMessage": "Поддержка WebSocket" }, "host.forward-port": { - "defaultMessage": "Порт перенаправления" + "defaultMessage": "Целевой порт" }, "host.forward-scheme": { "defaultMessage": "Схема" @@ -458,23 +458,11 @@ "lets-encrypt-via-http": { "defaultMessage": "Let's Encrypt через HTTP" }, - "load-balancing.add-server": { - "defaultMessage": "???????? ??????" - }, - "load-balancing.host": { - "defaultMessage": "????" - }, - "load-balancing.port": { - "defaultMessage": "????" - }, - "load-balancing.weight": { - "defaultMessage": "???" - }, "loading": { "defaultMessage": "Загрузка…" }, "login.2fa-code": { - "defaultMessage": "Код подтверждения" + "defaultMessage": "Код проверки" }, "login.2fa-code-placeholder": { "defaultMessage": "Введите код" @@ -486,7 +474,7 @@ "defaultMessage": "Двухфакторная аутентификация" }, "login.2fa-verify": { - "defaultMessage": "Подтвердить" + "defaultMessage": "Проверить" }, "login.title": { "defaultMessage": "Авторизация" @@ -609,22 +597,7 @@ "defaultMessage": "Прокси-хост" }, "proxy-host.forward-host": { - "defaultMessage": "Хост / IP перенаправления" - }, - "proxy-host.forward-target-type": { - "defaultMessage": "Тип цели перенаправления" - }, - "proxy-host.forward-target-type.direct": { - "defaultMessage": "Прямое" - }, - "proxy-host.forward-target-type.direct.description": { - "defaultMessage": "Перенаправление на один хост и порт" - }, - "proxy-host.forward-target-type.upstream": { - "defaultMessage": "Upstream-хост" - }, - "proxy-host.forward-target-type.upstream.description": { - "defaultMessage": "Использовать upstream-хост для балансировки нагрузки между несколькими серверами" + "defaultMessage": "Целевой хост" }, "proxy-hosts": { "defaultMessage": "Прокси-хосты" @@ -633,13 +606,13 @@ "defaultMessage": "{count} {count, plural, one {прокси-хост} few {прокси-хоста} many {прокси-хостов} other {прокси-хоста}}" }, "public": { - "defaultMessage": "Общедоступный" + "defaultMessage": "Публичный" }, "redirection-host": { "defaultMessage": "Редирект-хост" }, "redirection-host.forward-domain": { - "defaultMessage": "Домен перенаправления" + "defaultMessage": "Целевой домен" }, "redirection-host.forward-http-code": { "defaultMessage": "HTTP-код" @@ -651,22 +624,22 @@ "defaultMessage": "{count} {count, plural, one {редирект-хост} few {редирект-хоста} many {редирект-хостов} other {редирект-хоста}}" }, "redirection-hosts.http-code.300": { - "defaultMessage": "300 Множество выборов" + "defaultMessage": "300 Множественный выбор" }, "redirection-hosts.http-code.301": { - "defaultMessage": "301 Перемещён навсегда" + "defaultMessage": "301 Ресурс перемещен навсегда" }, "redirection-hosts.http-code.302": { - "defaultMessage": "302 Перемещён временно" + "defaultMessage": "302 Ресурс временно перемещен" }, "redirection-hosts.http-code.303": { - "defaultMessage": "303 Смотреть другое" + "defaultMessage": "303 Смотрите другой ресурс" }, "redirection-hosts.http-code.307": { "defaultMessage": "307 Временное перенаправление" }, "redirection-hosts.http-code.308": { - "defaultMessage": "308 Постоянное перенаправление" + "defaultMessage": "308 Ресурс перемещен навсегда" }, "role.admin": { "defaultMessage": "Администратор" @@ -707,36 +680,6 @@ "settings.default-site.redirect": { "defaultMessage": "Перенаправление" }, - "settings.real-ip-header": { - "defaultMessage": "Заголовок реального IP" - }, - "settings.real-ip-header.cf-connecting-ip": { - "defaultMessage": "CF-Connecting-IP" - }, - "settings.real-ip-header.cf-connecting-ip.description": { - "defaultMessage": "Использовать за Cloudflare" - }, - "settings.real-ip-header.custom": { - "defaultMessage": "Пользовательский" - }, - "settings.real-ip-header.custom.placeholder": { - "defaultMessage": "Введите имя пользовательского заголовка" - }, - "settings.real-ip-header.description": { - "defaultMessage": "HTTP-заголовок для определения реального IP-адреса клиента" - }, - "settings.real-ip-header.x-forwarded-for": { - "defaultMessage": "X-Forwarded-For" - }, - "settings.real-ip-header.x-forwarded-for.description": { - "defaultMessage": "Распространённый альтернативный заголовок" - }, - "settings.real-ip-header.x-real-ip": { - "defaultMessage": "X-Real-IP" - }, - "settings.real-ip-header.x-real-ip.description": { - "defaultMessage": "Стандартный заголовок, работает с большинством обратных прокси" - }, "setup.preamble": { "defaultMessage": "Начните с создания учётной записи администратора." }, @@ -753,10 +696,10 @@ "defaultMessage": "Поток" }, "stream.forward-host": { - "defaultMessage": "Хост перенаправления" + "defaultMessage": "Целевой хост" }, "stream.forward-host.placeholder": { - "defaultMessage": "example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" + "defaultMessage": "example.com или 10.0.0.1 или 2001:db8:3333:4444:5555:6666:7777:8888" }, "stream.incoming-port": { "defaultMessage": "Входящий порт" @@ -779,30 +722,6 @@ "update-available": { "defaultMessage": "Доступно обновление: {latestVersion}" }, - "upstream-host": { - "defaultMessage": "Upstream-хост" - }, - "upstream-host.help": { - "defaultMessage": "Выберите upstream-хост вместо прямого перенаправления." - }, - "upstream-host.method": { - "defaultMessage": "Метод" - }, - "upstream-host.none": { - "defaultMessage": "Нет (Прямое)" - }, - "upstream-host.none.subtitle": { - "defaultMessage": "Upstream-хост не выбран" - }, - "upstream-host.servers": { - "defaultMessage": "Серверы" - }, - "upstream-hosts": { - "defaultMessage": "Upstream-хосты" - }, - "upstream-hosts.count": { - "defaultMessage": "{count} {count, plural, one {Upstream-хост} few {Upstream-хоста} other {Upstream-хостов}}" - }, "user": { "defaultMessage": "Пользователь" }, @@ -846,7 +765,7 @@ "defaultMessage": "Включить светлую тему" }, "user.two-factor": { - "defaultMessage": "Двухфакторная авт." + "defaultMessage": "Настроить 2FA" }, "username": { "defaultMessage": "Имя пользователя" diff --git a/frontend/src/locale/src/sk.json b/frontend/src/locale/src/sk.json index 49586e6ca4..a813339751 100644 --- a/frontend/src/locale/src/sk.json +++ b/frontend/src/locale/src/sk.json @@ -459,16 +459,16 @@ "defaultMessage": "Let's Encrypt cez HTTP" }, "load-balancing.add-server": { - "defaultMessage": "Prida? server" + "defaultMessage": "Pridať server" }, "load-balancing.host": { - "defaultMessage": "Host" + "defaultMessage": "Hostiteľ" }, "load-balancing.port": { "defaultMessage": "Port" }, "load-balancing.weight": { - "defaultMessage": "V?ha" + "defaultMessage": "Váha" }, "loading": { "defaultMessage": "Načítava sa…" @@ -780,28 +780,28 @@ "defaultMessage": "Dostupná aktualizace: {latestVersion}" }, "upstream-host": { - "defaultMessage": "Upstream hostitel" + "defaultMessage": "Upstream hostiteľ" }, "upstream-host.help": { - "defaultMessage": "Vyberte upstream hostitel místo přímého přesměrování." + "defaultMessage": "Vyberte upstream hostiteľa namiesto priameho presmerovania." }, "upstream-host.method": { - "defaultMessage": "Metoda" + "defaultMessage": "Metóda" }, "upstream-host.none": { - "defaultMessage": "Žádný (přímo)" + "defaultMessage": "Žiadny (priamo)" }, "upstream-host.none.subtitle": { - "defaultMessage": "Nebyl vybrán upstream hostitel" + "defaultMessage": "Nebol vybraný žiadny upstream hostiteľ" }, "upstream-host.servers": { "defaultMessage": "Servery" }, "upstream-hosts": { - "defaultMessage": "Upstream hostitelé" + "defaultMessage": "Upstream hostitelia" }, "upstream-hosts.count": { - "defaultMessage": "{count} {count, plural, one {Upstream hostitel} few {Upstream hostitelé} other {Upstream hostitelů}}" + "defaultMessage": "{count} {count, plural, one {Upstream hostiteľ} few {Upstream hostitelia} other {Upstream hostiteľov}}" }, "user": { "defaultMessage": "používateľa" diff --git a/frontend/src/modals/HelpModal.tsx b/frontend/src/modals/HelpModal.tsx index f058161424..ba0f332a3f 100644 --- a/frontend/src/modals/HelpModal.tsx +++ b/frontend/src/modals/HelpModal.tsx @@ -18,7 +18,8 @@ const showHelpModal = (section: string, color?: string) => { const HelpModal = EasyModal.create(({ section, color, visible, remove }: Props) => { const [markdownText, setMarkdownText] = useState(""); - const lang = getLocale(true); + const locale = getLocale().replace("-", "_").toLowerCase(); + const lang = locale === "pt_br" ? "pt_br" : getLocale(true); useEffect(() => { try { diff --git a/frontend/src/modals/UpstreamHostModal.tsx b/frontend/src/modals/UpstreamHostModal.tsx index 0edeb5e653..67507025f7 100644 --- a/frontend/src/modals/UpstreamHostModal.tsx +++ b/frontend/src/modals/UpstreamHostModal.tsx @@ -27,9 +27,38 @@ const UpstreamHostModal = EasyModal.create(({ id, visible, remove }: Props) => { setIsSubmitting(true); setErrorMsg(null); + // Normalize servers at submit time: trim host, coerce numeric strings, drop + // rows the user left blank. LoadBalancingFields stores the raw editable + // array in Formik (so what you see is what's submitted), so the cleanup + // happens here rather than on every keystroke. + const rawServers = (values?.servers || []) as Array>; + const servers = rawServers + .map((s) => { + const host = typeof s.host === "string" ? s.host.trim() : ""; + const port = Number.parseInt(String(s.port ?? ""), 10); + const weight = Number.parseInt(String(s.weight ?? ""), 10); + const out: { host: string; port: number; weight?: number } = { + host, + port: Number.isFinite(port) ? port : 0, + }; + if (Number.isFinite(weight) && weight > 0) out.weight = weight; + return out; + }) + .filter((s) => s.host !== "" && s.port > 0); + + // Guard at the modal so the user sees a translated message instead of the + // raw AJV string the backend would return for an empty `servers` array. + if (servers.length === 0) { + setErrorMsg(); + setIsSubmitting(false); + setSubmitting(false); + return; + } + const payload = { id: id === "new" ? undefined : id, ...values, + servers, }; setUpstreamHost(payload, { @@ -180,10 +209,7 @@ const UpstreamHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
- +
diff --git a/frontend/src/pages/Nginx/DeadHosts/Table.tsx b/frontend/src/pages/Nginx/DeadHosts/Table.tsx index f8a1a27542..17143e7504 100644 --- a/frontend/src/pages/Nginx/DeadHosts/Table.tsx +++ b/frontend/src/pages/Nginx/DeadHosts/Table.tsx @@ -1,6 +1,12 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; -import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; -import { useMemo } from "react"; +import { + createColumnHelper, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { useMemo, useState } from "react"; import type { DeadHost } from "src/api/backend"; import { CertificateFormatter, @@ -29,6 +35,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog () => [ columnHelper.accessor((row: any) => row.owner, { id: "owner", + enableSorting: false, cell: (info: any) => { const value = info.getValue(); return ; @@ -40,6 +47,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog columnHelper.accessor((row: any) => row, { id: "domainNames", header: intl.formatMessage({ id: "column.source" }), + sortingFn: (a, b) => { + const aVal = a.original.domainNames?.[0] ?? ""; + const bVal = b.original.domainNames?.[0] ?? ""; + return aVal.localeCompare(bVal); + }, cell: (info: any) => { const value = info.getValue(); return ; @@ -47,6 +59,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog }), columnHelper.accessor((row: any) => row.certificate, { id: "certificate", + enableSorting: false, header: intl.formatMessage({ id: "column.ssl" }), cell: (info: any) => { return ; @@ -128,10 +141,15 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog [columnHelper, onDelete, onEdit, onDisableToggle], ); + const [sorting, setSorting] = useState([]); + const tableInstance = useReactTable({ columns, data, + state: { sorting }, + onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), rowCount: data.length, meta: { isFetching, diff --git a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx index d9003ac01a..e4f6fae86d 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx @@ -1,7 +1,13 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; -import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; -import { useMemo } from "react"; -import type { ProxyHost } from "src/api/backend"; +import { + createColumnHelper, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { useMemo, useState } from "react"; +import type { ProxyHost, ProxyLocation } from "src/api/backend"; import { AccessListFormatter, CertificateFormatter, @@ -12,10 +18,85 @@ import { TrueFalseFormatter, } from "src/components"; import { TableLayout } from "src/components/Table/TableLayout"; +import { useUpstreamHosts } from "src/hooks"; import { intl, T } from "src/locale"; import { showUpstreamHostModal } from "src/modals"; import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions"; +// DestinationCell renders the "Destination" column. A proxy host can route +// through an upstream at two levels: proxy-level (the whole host forwards via +// upstream_host_id), or per-location (each /path entry can carry its own +// upstream_host_id). The previous implementation only honored the proxy-level +// case, which made any host using location-based upstream routing look like a +// plain forward (`http://host:port`) — its upstream usage was invisible from +// the list view. +function DestinationCell({ host }: { host: ProxyHost }) { + const { data: upstreams } = useUpstreamHosts(); + + // Proxy-level upstream — preserve original behavior. + const proxyUpstreamId = host.upstreamHostId ?? 0; + if (proxyUpstreamId > 0 && host.upstreamHost) { + return ( + + ); + } + + const forwardLine = `${host.forwardScheme}://${host.forwardHost}:${host.forwardPort}`; + const locationUpstreams = (host.locations || []).filter( + (l: ProxyLocation) => l.upstreamHostId && l.upstreamHostId > 0, + ); + + if (locationUpstreams.length === 0) { + return forwardLine; + } + + // If `/` is itself routed via an upstream, every request matches a location + // override and the proxy-level forward is unreachable — showing it just + // confuses operators. Suppress it in that case. + const rootIsCovered = locationUpstreams.some((l) => l.path === "/"); + + return ( +
+ {!rootIsCovered &&
{forwardLine}
} +
+ {locationUpstreams.map((loc, i) => { + const up = upstreams?.find((u) => u.id === loc.upstreamHostId); + const upId = up?.id ?? 0; + return ( +
+ {loc.path} + {" → "} + {up && upId > 0 ? ( + + ) : ( + upstream #{loc.upstreamHostId} + )} +
+ ); + })} +
+
+ ); +} + interface Props { data: ProxyHost[]; isFiltered?: boolean; @@ -31,6 +112,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog () => [ columnHelper.accessor((row: any) => row.owner, { id: "owner", + enableSorting: false, cell: (info: any) => { const value = info.getValue(); return ; @@ -42,6 +124,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog columnHelper.accessor((row: any) => row, { id: "domainNames", header: intl.formatMessage({ id: "column.source" }), + sortingFn: (a, b) => { + const aVal = a.original.domainNames?.[0] ?? ""; + const bVal = b.original.domainNames?.[0] ?? ""; + return aVal.localeCompare(bVal); + }, cell: (info: any) => { const value = info.getValue(); return ; @@ -50,27 +137,16 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog columnHelper.accessor((row: any) => row, { id: "forwardHost", header: intl.formatMessage({ id: "column.destination" }), - cell: (info: any) => { - const value = info.getValue(); - if (value.upstreamHostId > 0 && value.upstreamHost) { - return ( - - ); - } - return `${value.forwardScheme}://${value.forwardHost}:${value.forwardPort}`; + sortingFn: (a, b) => { + const aVal = `${a.original.forwardHost}:${a.original.forwardPort}`; + const bVal = `${b.original.forwardHost}:${b.original.forwardPort}`; + return aVal.localeCompare(bVal); }, + cell: (info: any) => , }), columnHelper.accessor((row: any) => row.certificate, { id: "certificate", + enableSorting: false, header: intl.formatMessage({ id: "column.ssl" }), cell: (info: any) => { return ; @@ -78,6 +154,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog }), columnHelper.accessor((row: any) => row.accessList, { id: "accessList", + enableSorting: false, header: intl.formatMessage({ id: "column.access" }), cell: (info: any) => { return ; @@ -159,10 +236,15 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog [columnHelper, onEdit, onDisableToggle, onDelete], ); + const [sorting, setSorting] = useState([]); + const tableInstance = useReactTable({ columns, data, + state: { sorting }, + onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), rowCount: data.length, meta: { isFetching, diff --git a/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx index 759c1f9b9b..eb1ea21629 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx @@ -99,14 +99,27 @@ export default function TableWrapper() { isFiltered={!!search} isFetching={isFetching} onEdit={(id: number) => showProxyHostModal(id)} - onDelete={(id: number) => + onDelete={(id: number) => { + const host = data?.find((h) => h.id === id); showDeleteConfirmModal({ title: , onConfirm: () => handleDelete(id), invalidations: [["proxy-hosts"], ["proxy-host", id]], - children: , - }) - } + children: ( + <> + + {host?.domainNames?.length ? ( +
{host.domainNames.join(", ")}
+ ) : null} + {host?.forwardHost ? ( +
+ ({host.forwardScheme}://{host.forwardHost}:{host.forwardPort}) +
+ ) : null} + + ), + }); + }} onDisableToggle={handleDisableToggle} onNew={() => showProxyHostModal("new")} /> diff --git a/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx b/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx index 6ac4152348..8e2d90f555 100644 --- a/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx +++ b/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx @@ -1,6 +1,12 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; -import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; -import { useMemo } from "react"; +import { + createColumnHelper, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { useMemo, useState } from "react"; import type { RedirectionHost } from "src/api/backend"; import { CertificateFormatter, @@ -29,6 +35,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog () => [ columnHelper.accessor((row: any) => row.owner, { id: "owner", + enableSorting: false, cell: (info: any) => { const value = info.getValue(); return ; @@ -40,6 +47,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog columnHelper.accessor((row: any) => row, { id: "domainNames", header: intl.formatMessage({ id: "column.source" }), + sortingFn: (a, b) => { + const aVal = a.original.domainNames?.[0] ?? ""; + const bVal = b.original.domainNames?.[0] ?? ""; + return aVal.localeCompare(bVal); + }, cell: (info: any) => { const value = info.getValue(); return ; @@ -68,6 +80,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog }), columnHelper.accessor((row: any) => row.certificate, { id: "certificate", + enableSorting: false, header: intl.formatMessage({ id: "column.ssl" }), cell: (info: any) => { return ; @@ -149,10 +162,15 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog [columnHelper, onEdit, onDisableToggle, onDelete], ); + const [sorting, setSorting] = useState([]); + const tableInstance = useReactTable({ columns, data, + state: { sorting }, + onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), rowCount: data.length, meta: { isFetching, diff --git a/frontend/src/pages/Nginx/Streams/Table.tsx b/frontend/src/pages/Nginx/Streams/Table.tsx index 4b9ff7d600..341317bf45 100644 --- a/frontend/src/pages/Nginx/Streams/Table.tsx +++ b/frontend/src/pages/Nginx/Streams/Table.tsx @@ -1,6 +1,12 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; -import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; -import { useMemo } from "react"; +import { + createColumnHelper, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { useMemo, useState } from "react"; import type { Stream } from "src/api/backend"; import { CertificateFormatter, @@ -29,6 +35,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, () => [ columnHelper.accessor((row: any) => row.owner, { id: "owner", + enableSorting: false, cell: (info: any) => { const value = info.getValue(); return ; @@ -40,6 +47,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, columnHelper.accessor((row: any) => row, { id: "incomingPort", header: intl.formatMessage({ id: "column.incoming-port" }), + sortingFn: (a, b) => (a.original.incomingPort ?? 0) - (b.original.incomingPort ?? 0), cell: (info: any) => { const value = info.getValue(); return ; @@ -48,6 +56,11 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, columnHelper.accessor((row: any) => row, { id: "forwardHttpCode", header: intl.formatMessage({ id: "column.destination" }), + sortingFn: (a, b) => { + const aVal = `${a.original.forwardingHost}:${a.original.forwardingPort}`; + const bVal = `${b.original.forwardingHost}:${b.original.forwardingPort}`; + return aVal.localeCompare(bVal); + }, cell: (info: any) => { const value = info.getValue(); return `${value.forwardingHost}:${value.forwardingPort}`; @@ -55,6 +68,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, }), columnHelper.accessor((row: any) => row, { id: "tcpForwarding", + enableSorting: false, header: intl.formatMessage({ id: "column.protocol" }), cell: (info: any) => { const value = info.getValue(); @@ -76,6 +90,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, }), columnHelper.accessor((row: any) => row.certificate, { id: "certificate", + enableSorting: false, header: intl.formatMessage({ id: "column.ssl" }), cell: (info: any) => { return ; @@ -130,7 +145,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, }} > - +
([]); + const tableInstance = useReactTable({ columns, data, + state: { sorting }, + onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), rowCount: data.length, meta: { isFetching, diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index fb86d45b53..8eba5b172b 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -7,9 +7,11 @@ "DOM", "DOM.Iterable" ], + "types": [ + "node" + ], "module": "ESNext", "skipLibCheck": true, - "baseUrl": ".", /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1e21d90f0c..1e00d067f2 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,6 @@ import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; import checker from "vite-plugin-checker"; -import tsconfigPaths from "vite-tsconfig-paths"; import "vitest/config"; import { execFile } from "node:child_process"; @@ -45,8 +44,10 @@ export default defineConfig({ // e.g. use TypeScript check typescript: true, }), - tsconfigPaths(), ], + resolve: { + tsconfigPaths: true, + }, server: { host: true, port: 5173, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 401d0590fd..d5f803762f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.4.tgz#2856c55443d3d461693f32d2b96fb6ea92e1ffa9" integrity sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.27.1", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== @@ -16,32 +16,6 @@ js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/compat-data@^7.28.6": - version "7.29.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" - integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== - -"@babel/core@^7.29.0": - version "7.29.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" - integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== - dependencies: - "@babel/code-frame" "^7.29.0" - "@babel/generator" "^7.29.0" - "@babel/helper-compilation-targets" "^7.28.6" - "@babel/helper-module-transforms" "^7.28.6" - "@babel/helpers" "^7.28.6" - "@babel/parser" "^7.29.0" - "@babel/template" "^7.28.6" - "@babel/traverse" "^7.29.0" - "@babel/types" "^7.29.0" - "@jridgewell/remapping" "^2.3.5" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - "@babel/generator@^7.29.0": version "7.29.1" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" @@ -53,23 +27,12 @@ "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" -"@babel/helper-compilation-targets@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" - integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== - dependencies: - "@babel/compat-data" "^7.28.6" - "@babel/helper-validator-option" "^7.27.1" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - "@babel/helper-globals@^7.28.0": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== -"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.28.6": +"@babel/helper-module-imports@^7.16.7": version "7.28.6" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== @@ -77,20 +40,6 @@ "@babel/traverse" "^7.28.6" "@babel/types" "^7.28.6" -"@babel/helper-module-transforms@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" - integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== - dependencies: - "@babel/helper-module-imports" "^7.28.6" - "@babel/helper-validator-identifier" "^7.28.5" - "@babel/traverse" "^7.28.6" - -"@babel/helper-plugin-utils@^7.27.1": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" - integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== - "@babel/helper-string-parser@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" @@ -101,40 +50,13 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== -"@babel/helper-validator-option@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" - integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - -"@babel/helpers@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" - integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== - dependencies: - "@babel/template" "^7.28.6" - "@babel/types" "^7.28.6" - -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": +"@babel/parser@^7.28.6", "@babel/parser@^7.29.0": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== dependencies: "@babel/types" "^7.29.0" -"@babel/plugin-transform-react-jsx-self@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz#af678d8506acf52c577cac73ff7fe6615c85fc92" - integrity sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-react-jsx-source@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz#dcfe2c24094bb757bf73960374e7c55e434f19f0" - integrity sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.24.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": version "7.28.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.6.tgz#d267a43cb1836dc4d182cce93ae75ba954ef6d2b" @@ -149,7 +71,7 @@ "@babel/parser" "^7.28.6" "@babel/types" "^7.28.6" -"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": +"@babel/traverse@^7.28.6": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== @@ -162,7 +84,7 @@ "@babel/types" "^7.29.0" debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.29.0": +"@babel/types@^7.28.6", "@babel/types@^7.29.0": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== @@ -170,59 +92,81 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" -"@biomejs/biome@^2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.4.5.tgz#90e75c1b6c60eb5bf8e8bfb193a1fbe9dc868f16" - integrity sha512-OWNCyMS0Q011R6YifXNOg6qsOg64IVc7XX6SqGsrGszPbkVCoaO7Sr/lISFnXZ9hjQhDewwZ40789QmrG0GYgQ== +"@biomejs/biome@^2.4.15": + version "2.4.15" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.4.15.tgz#cb84ad6eb4235e7230b3c105a825e9bc03399944" + integrity sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw== optionalDependencies: - "@biomejs/cli-darwin-arm64" "2.4.5" - "@biomejs/cli-darwin-x64" "2.4.5" - "@biomejs/cli-linux-arm64" "2.4.5" - "@biomejs/cli-linux-arm64-musl" "2.4.5" - "@biomejs/cli-linux-x64" "2.4.5" - "@biomejs/cli-linux-x64-musl" "2.4.5" - "@biomejs/cli-win32-arm64" "2.4.5" - "@biomejs/cli-win32-x64" "2.4.5" - -"@biomejs/cli-darwin-arm64@2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.5.tgz#a62472ab3529a3905b16e1f3fdbbc74f2e5f0023" - integrity sha512-lGS4Nd5O3KQJ6TeWv10mElnx1phERhBxqGP/IKq0SvZl78kcWDFMaTtVK+w3v3lusRFxJY78n07PbKplirsU5g== - -"@biomejs/cli-darwin-x64@2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.5.tgz#e8bb001fcf6a8c751b0971cccf53993e9ba2e6e9" - integrity sha512-6MoH4tyISIBNkZ2Q5T1R7dLd5BsITb2yhhhrU9jHZxnNSNMWl+s2Mxu7NBF8Y3a7JJcqq9nsk8i637z4gqkJxQ== - -"@biomejs/cli-linux-arm64-musl@2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.5.tgz#b7ef7902237f16113061659a4c54aff5ad4513d5" - integrity sha512-iqLDgpzobG7gpBF0fwEVS/LT8kmN7+S0E2YKFDtqliJfzNLnAiV2Nnyb+ehCDCJgAZBASkYHR2o60VQWikpqIg== - -"@biomejs/cli-linux-arm64@2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.5.tgz#f110af748965cb1b57624dbbbd7acba729da8780" - integrity sha512-U1GAG6FTjhAO04MyH4xn23wRNBkT6H7NentHh+8UxD6ShXKBm5SY4RedKJzkUThANxb9rUKIPc7B8ew9Xo/cWg== - -"@biomejs/cli-linux-x64-musl@2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.5.tgz#c3493eba094216e735538c55354dbc8867b51909" - integrity sha512-NlKa7GpbQmNhZf9kakQeddqZyT7itN7jjWdakELeXyTU3pg/83fTysRRDPJD0akTfKDl6vZYNT9Zqn4MYZVBOA== - -"@biomejs/cli-linux-x64@2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.5.tgz#15805550db4e45ffbd6c42d140d0cb5c6dbe07af" - integrity sha512-NdODlSugMzTlENPTa4z0xB82dTUlCpsrOxc43///aNkTLblIYH4XpYflBbf5ySlQuP8AA4AZd1qXhV07IdrHdQ== - -"@biomejs/cli-win32-arm64@2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.5.tgz#8dae57dc8ffc1e82e00a59e623b5023c09726a4d" - integrity sha512-EBfrTqRIWOFSd7CQb/0ttjHMR88zm3hGravnDwUA9wHAaCAYsULKDebWcN5RmrEo1KBtl/gDVJMrFjNR0pdGUw== - -"@biomejs/cli-win32-x64@2.4.5": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.5.tgz#0b4d3355e0d6856cff8ed722e05ec7b0652e43ab" - integrity sha512-Pmhv9zT95YzECfjEHNl3mN9Vhusw9VA5KHY0ZvlGsxsjwS5cb7vpRnHzJIv0vG7jB0JI7xEaMH9ddfZm/RozBw== + "@biomejs/cli-darwin-arm64" "2.4.15" + "@biomejs/cli-darwin-x64" "2.4.15" + "@biomejs/cli-linux-arm64" "2.4.15" + "@biomejs/cli-linux-arm64-musl" "2.4.15" + "@biomejs/cli-linux-x64" "2.4.15" + "@biomejs/cli-linux-x64-musl" "2.4.15" + "@biomejs/cli-win32-arm64" "2.4.15" + "@biomejs/cli-win32-x64" "2.4.15" + +"@biomejs/cli-darwin-arm64@2.4.15": + version "2.4.15" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.15.tgz#3469daa56ac3ff4f16588a120df706381a96f65c" + integrity sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg== + +"@biomejs/cli-darwin-x64@2.4.15": + version "2.4.15" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.15.tgz#0697b81089409635da16682ac1e539165c262006" + integrity sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ== + +"@biomejs/cli-linux-arm64-musl@2.4.15": + version "2.4.15" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.15.tgz#c6af054e3732c361e9ad8c44070f909666b5616f" + integrity sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ== + +"@biomejs/cli-linux-arm64@2.4.15": + version "2.4.15" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.15.tgz#527cef60339649a442d51a9cd129ae9dfe9da926" + integrity sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug== + +"@biomejs/cli-linux-x64-musl@2.4.15": + version "2.4.15" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.15.tgz#b39292ad106c3d5a612bf3c61ba3119f66833013" + integrity sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w== + +"@biomejs/cli-linux-x64@2.4.15": + version "2.4.15" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.15.tgz#7360b7f81ff03ec6d9350bedc76b89f783b0945d" + integrity sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g== + +"@biomejs/cli-win32-arm64@2.4.15": + version "2.4.15" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.15.tgz#9542aac679174892a9379267e0c0048f8eee4d9f" + integrity sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w== + +"@biomejs/cli-win32-x64@2.4.15": + version "2.4.15" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.15.tgz#80288e4eea8f916fc5c876e9a486baadb8de537d" + integrity sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ== + +"@emnapi/core@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.10.0.tgz#380ccc8f2412ea22d1d972df7f8ee23a3b9c7467" + integrity sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw== + dependencies: + "@emnapi/wasi-threads" "1.2.1" + tslib "^2.4.0" + +"@emnapi/runtime@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c" + integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz#28fed21a1ba1ce797c44a070abc94d42f3ae8548" + integrity sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w== + dependencies: + tslib "^2.4.0" "@emotion/babel-plugin@^11.13.5": version "11.13.5" @@ -312,136 +256,6 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== -"@esbuild/aix-ppc64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz#815b39267f9bffd3407ea6c376ac32946e24f8d2" - integrity sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg== - -"@esbuild/android-arm64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz#19b882408829ad8e12b10aff2840711b2da361e8" - integrity sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg== - -"@esbuild/android-arm@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz#90be58de27915efa27b767fcbdb37a4470627d7b" - integrity sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA== - -"@esbuild/android-x64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz#d7dcc976f16e01a9aaa2f9b938fbec7389f895ac" - integrity sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ== - -"@esbuild/darwin-arm64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz#9f6cac72b3a8532298a6a4493ed639a8988e8abd" - integrity sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg== - -"@esbuild/darwin-x64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz#ac61d645faa37fd650340f1866b0812e1fb14d6a" - integrity sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg== - -"@esbuild/freebsd-arm64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz#b8625689d73cf1830fe58c39051acdc12474ea1b" - integrity sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w== - -"@esbuild/freebsd-x64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz#07be7dd3c9d42fe0eccd2ab9f9ded780bc53bead" - integrity sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA== - -"@esbuild/linux-arm64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz#bf31918fe5c798586460d2b3d6c46ed2c01ca0b6" - integrity sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg== - -"@esbuild/linux-arm@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz#28493ee46abec1dc3f500223cd9f8d2df08f9d11" - integrity sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw== - -"@esbuild/linux-ia32@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz#750752a8b30b43647402561eea764d0a41d0ee29" - integrity sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg== - -"@esbuild/linux-loong64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz#a5a92813a04e71198c50f05adfaf18fc1e95b9ed" - integrity sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA== - -"@esbuild/linux-mips64el@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz#deb45d7fd2d2161eadf1fbc593637ed766d50bb1" - integrity sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw== - -"@esbuild/linux-ppc64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz#6f39ae0b8c4d3d2d61a65b26df79f6e12a1c3d78" - integrity sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA== - -"@esbuild/linux-riscv64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz#4c5c19c3916612ec8e3915187030b9df0b955c1d" - integrity sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ== - -"@esbuild/linux-s390x@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz#9ed17b3198fa08ad5ccaa9e74f6c0aff7ad0156d" - integrity sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw== - -"@esbuild/linux-x64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz#12383dcbf71b7cf6513e58b4b08d95a710bf52a5" - integrity sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA== - -"@esbuild/netbsd-arm64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz#dd0cb2fa543205fcd931df44f4786bfcce6df7d7" - integrity sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA== - -"@esbuild/netbsd-x64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz#028ad1807a8e03e155153b2d025b506c3787354b" - integrity sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA== - -"@esbuild/openbsd-arm64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz#e3c16ff3490c9b59b969fffca87f350ffc0e2af5" - integrity sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw== - -"@esbuild/openbsd-x64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz#c5a4693fcb03d1cbecbf8b422422468dfc0d2a8b" - integrity sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ== - -"@esbuild/openharmony-arm64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz#082082444f12db564a0775a41e1991c0e125055e" - integrity sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g== - -"@esbuild/sunos-x64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz#5ab036c53f929e8405c4e96e865a424160a1b537" - integrity sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA== - -"@esbuild/win32-arm64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz#38de700ef4b960a0045370c171794526e589862e" - integrity sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA== - -"@esbuild/win32-ia32@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz#451b93dc03ec5d4f38619e6cd64d9f9eff06f55c" - integrity sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q== - -"@esbuild/win32-x64@0.27.3": - version "0.27.3" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17" - integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA== - "@floating-ui/core@^1.7.4": version "1.7.4" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.4.tgz#4a006a6e01565c0f87ba222c317b056a2cffd2f4" @@ -462,65 +276,63 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c" integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== -"@formatjs/cli@^6.13.0": - version "6.13.0" - resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.13.0.tgz#e3922c6e35790ffd4fcc56202452e8ecd61a5e06" - integrity sha512-bl4+FNg7S6RPNa9cSAE8HqdXu84n7LpzDdkDAPqS0sk58XNbY/1Le6GdWqCKzELWX+FhI58gyZtZecmWsZ+Bhg== +"@formatjs/cli-native-darwin-arm64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-arm64/-/cli-native-darwin-arm64-1.1.0.tgz#c47df2ec7c1d27643900af77c474bd224b4587dc" + integrity sha512-ygEpFpmRjbAKanWaeto/eUItyuK18667mdDzpDg3CTzc15WYzjZKnwJNFUuPFvFA4hiod1tnhXT1eiXmc1wWNg== -"@formatjs/ecma402-abstract@3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-3.1.1.tgz#329fa5eed8024ee389e9c82be8c798315631b11d" - integrity sha512-jhZbTwda+2tcNrs4kKvxrPLPjx8QsBCLCUgrrJ/S+G9YrGHWLhAyFMMBHJBnBoOwuLHd7L14FgYudviKaxkO2Q== - dependencies: - "@formatjs/fast-memoize" "3.1.0" - "@formatjs/intl-localematcher" "0.8.1" - decimal.js "^10.6.0" - tslib "^2.8.1" +"@formatjs/cli-native-linux-arm64@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64/-/cli-native-linux-arm64-1.2.0.tgz#3ddcafb006351417e82785b809a1ab1fbbb2c577" + integrity sha512-G6YuIVD8D6hZjnsD/aYZQ2qMHSve7IadEiXgKiDfFQBbyrkjDAlt6LDSqq4qnuv3WJ7/jAOn/An6n/bGyt0rLg== -"@formatjs/fast-memoize@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-3.1.0.tgz#f8643d44803df8f579506d40804f4faeba6f5da2" - integrity sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg== - dependencies: - tslib "^2.8.1" +"@formatjs/cli-native-linux-x64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64/-/cli-native-linux-x64-1.1.0.tgz#1a3f01ea47f971a0dd0d7f49b98df237375862fa" + integrity sha512-AxORf5LR14HvXU4ov9gnhLrNIS2DYKqKMkRkprUhuh+ljba1riF05msK8KzslEPYQoeYKc5A0OZp57poh4xz/g== -"@formatjs/icu-messageformat-parser@3.5.1": - version "3.5.1" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.1.tgz#a08faadbbb333d9e4fa7276c92a2b66a37788aa1" - integrity sha512-sSDmSvmmoVQ92XqWb499KrIhv/vLisJU8ITFrx7T7NZHUmMY7EL9xgRowAosaljhqnj/5iufG24QrdzB6X3ItA== - dependencies: - "@formatjs/ecma402-abstract" "3.1.1" - "@formatjs/icu-skeleton-parser" "2.1.1" - tslib "^2.8.1" +"@formatjs/cli-native-win32-x64@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-x64/-/cli-native-win32-x64-1.1.1.tgz#a911b22c8f5ccfe4aff5a00276ee4274f4b81079" + integrity sha512-2vYgm7dzAb7wA9KjOOJGxZiSShwBNK4dEmgiJ1lRpEIz6RKMZ6wBY4UVn03VdAv8djJ7r6RK4Xyd2ouGcbh9Bg== -"@formatjs/icu-skeleton-parser@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.1.tgz#a05b45733bd0f277c50f31b32e4a73375569d3c8" - integrity sha512-PSFABlcNefjI6yyk8f7nyX1DC7NHmq6WaCHZLySEXBrXuLOB2f935YsnzuPjlz+ibhb9yWTdPeVX1OVcj24w2Q== - dependencies: - "@formatjs/ecma402-abstract" "3.1.1" - tslib "^2.8.1" +"@formatjs/cli@^6.16.3": + version "6.16.3" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.16.3.tgz#728a17c1deaf1fec0ace554b2c72695f18253231" + integrity sha512-AN6uZR1xnl3V215HHJiKiA+DvNMrO/XfQ7hOyq3gXfHJwKSgLLQFtSFxw9A1O0SaGC2E2Kwo4mqwS1ZCLGe/qw== + optionalDependencies: + "@formatjs/cli-native-darwin-arm64" "1.1.0" + "@formatjs/cli-native-linux-arm64" "1.2.0" + "@formatjs/cli-native-linux-x64" "1.1.0" + "@formatjs/cli-native-win32-x64" "1.1.1" -"@formatjs/intl-localematcher@0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.8.1.tgz#554fe5f8c746ba52d430674de9a242fae64f67ff" - integrity sha512-xwEuwQFdtSq1UKtQnyTZWC+eHdv7Uygoa+H2k/9uzBVQjDyp9r20LNDNKedWXll7FssT3GRHvqsdJGYSUWqYFA== +"@formatjs/fast-memoize@3.1.5": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-3.1.5.tgz#bdfe0b884cdecc25100534086f36a0b30bd15b3b" + integrity sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A== + +"@formatjs/icu-messageformat-parser@3.5.10": + version "3.5.10" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.10.tgz#0ced2d6e8009eed57c6f0e7b4cc51dfea63b9bff" + integrity sha512-XeJihYLy1lCe19xfK1KWKG/betBOK2rB0luL8lSkjfvJj0zP+LTJvkC+RKd0jsFI8mWxN71LrarHSrEXE8xxOQ== dependencies: - "@formatjs/fast-memoize" "3.1.0" - tslib "^2.8.1" + "@formatjs/icu-skeleton-parser" "2.1.9" -"@formatjs/intl@4.1.2": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-4.1.2.tgz#4a0d487e6290067462f93d034ef36a8aaf6f671b" - integrity sha512-V60fNY/X/7zqmRffr7qPwscGmVGYDmlKF069mSQ2a/7fE22q602NtIfOQY8vzRA63Gr/O/U6vjRVBHMabrnA9A== +"@formatjs/icu-skeleton-parser@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.9.tgz#746edae664b2ef148df1301845d1bb40000bbebb" + integrity sha512-rsxswgHMfU1zUgB2byc08fesf83wLGjFnzLCEtuf00mx2doiqc6pYrf67raI37XqdRcGUviQepk2UKGqpng74Q== + +"@formatjs/intl@4.1.12": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-4.1.12.tgz#a29521f9bba79df1690c430f3d2cee8817778674" + integrity sha512-r288ut+p+CUQZSg+0gAT+D0i6xgrnoxE0B4HTbPY2zei/AtYmFhlu87BKjgCf1CweH4pZIbr152JFjxO8jVb1A== dependencies: - "@formatjs/ecma402-abstract" "3.1.1" - "@formatjs/fast-memoize" "3.1.0" - "@formatjs/icu-messageformat-parser" "3.5.1" - intl-messageformat "11.1.2" - tslib "^2.8.1" + "@formatjs/fast-memoize" "3.1.5" + "@formatjs/icu-messageformat-parser" "3.5.10" + intl-messageformat "11.2.7" -"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": +"@jridgewell/gen-mapping@^0.3.12": version "0.3.13" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== @@ -528,14 +340,6 @@ "@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/remapping@^2.3.5": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" - integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" @@ -554,6 +358,25 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@js-temporal/polyfill@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@js-temporal/polyfill/-/polyfill-0.5.1.tgz#c9726fdb7fbdbc6419292ba94294b10a08852c32" + integrity sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ== + dependencies: + jsbi "^4.3.0" + +"@napi-rs/wasm-runtime@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz#a46bbfedc29751b7170c5d23bc1d8ee8c7e3c1e1" + integrity sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow== + dependencies: + "@tybys/wasm-util" "^0.10.1" + +"@oxc-project/types@=0.132.0": + version "0.132.0" + resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.132.0.tgz#d77243df4fe1a0a1e60e12ac6240fa898d2363ff" + integrity sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ== + "@parcel/watcher-android-arm64@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz#5f32e0dba356f4ac9a11068d2a5c134ca3ba6564" @@ -684,137 +507,91 @@ uncontrollable "^8.0.4" warning "^4.0.3" -"@rolldown/pluginutils@1.0.0-rc.3": - version "1.0.0-rc.3" - resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz#8a88cc92a0f741befc7bc109cb1a4c6b9408e1c5" - integrity sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q== - -"@rollup/rollup-android-arm-eabi@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz#a6742c74c7d9d6d604ef8a48f99326b4ecda3d82" - integrity sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg== - -"@rollup/rollup-android-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz#97247be098de4df0c11971089fd2edf80a5da8cf" - integrity sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q== - -"@rollup/rollup-darwin-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz#674852cf14cf11b8056e0b1a2f4e872b523576cf" - integrity sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg== - -"@rollup/rollup-darwin-x64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz#36dfd7ed0aaf4d9d89d9ef983af72632455b0246" - integrity sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w== - -"@rollup/rollup-freebsd-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz#2f87c2074b4220260fdb52a9996246edfc633c22" - integrity sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA== - -"@rollup/rollup-freebsd-x64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz#9b5a26522a38a95dc06616d1939d4d9a76937803" - integrity sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg== - -"@rollup/rollup-linux-arm-gnueabihf@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz#86aa4859385a8734235b5e40a48e52d770758c3a" - integrity sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw== - -"@rollup/rollup-linux-arm-musleabihf@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz#cbe70e56e6ece8dac83eb773b624fc9e5a460976" - integrity sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA== - -"@rollup/rollup-linux-arm64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz#d14992a2e653bc3263d284bc6579b7a2890e1c45" - integrity sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA== - -"@rollup/rollup-linux-arm64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz#2fdd1ddc434ea90aeaa0851d2044789b4d07f6da" - integrity sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA== - -"@rollup/rollup-linux-loong64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz#8a181e6f89f969f21666a743cd411416c80099e7" - integrity sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg== - -"@rollup/rollup-linux-loong64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz#904125af2babc395f8061daa27b5af1f4e3f2f78" - integrity sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q== - -"@rollup/rollup-linux-ppc64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz#a57970ac6864c9a3447411a658224bdcf948be22" - integrity sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA== - -"@rollup/rollup-linux-ppc64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz#bb84de5b26870567a4267666e08891e80bb56a63" - integrity sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA== - -"@rollup/rollup-linux-riscv64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz#72d00d2c7fb375ce3564e759db33f17a35bffab9" - integrity sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg== - -"@rollup/rollup-linux-riscv64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz#4c166ef58e718f9245bd31873384ba15a5c1a883" - integrity sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg== - -"@rollup/rollup-linux-s390x-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz#bb5025cde9a61db478c2ca7215808ad3bce73a09" - integrity sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w== - -"@rollup/rollup-linux-x64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz#9b66b1f9cd95c6624c788f021c756269ffed1552" - integrity sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg== - -"@rollup/rollup-linux-x64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz#b007ca255dc7166017d57d7d2451963f0bd23fd9" - integrity sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg== - -"@rollup/rollup-openbsd-x64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz#e8b357b2d1aa2c8d76a98f5f0d889eabe93f4ef9" - integrity sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ== - -"@rollup/rollup-openharmony-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz#96c2e3f4aacd3d921981329831ff8dde492204dc" - integrity sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA== - -"@rollup/rollup-win32-arm64-msvc@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz#2d865149d706d938df8b4b8f117e69a77646d581" - integrity sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A== - -"@rollup/rollup-win32-ia32-msvc@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz#abe1593be0fa92325e9971c8da429c5e05b92c36" - integrity sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA== - -"@rollup/rollup-win32-x64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz#c4af3e9518c9a5cd4b1c163dc81d0ad4d82e7eab" - integrity sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA== - -"@rollup/rollup-win32-x64-msvc@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz#4584a8a87b29188a4c1fe987a9fcf701e256d86c" - integrity sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA== - -"@standard-schema/spec@^1.0.0": +"@rolldown/binding-android-arm64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz#ebe1264e43ba5bb224c58c85e0ac238f87e5ad14" + integrity sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ== + +"@rolldown/binding-darwin-arm64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz#f972fb71bc03840629bee923babbb7048d14a5d0" + integrity sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w== + +"@rolldown/binding-darwin-x64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz#da1c062c135e1e50067084be5ab8055cc1e05b29" + integrity sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA== + +"@rolldown/binding-freebsd-x64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz#a4c8532072705ce597c0d75418513709b75b3f1d" + integrity sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA== + +"@rolldown/binding-linux-arm-gnueabihf@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz#78f3bd274a1bab07356017d728b70289f04011c1" + integrity sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w== + +"@rolldown/binding-linux-arm64-gnu@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz#4dc530aed0b554762ffc1a5e4f016b8be495eea0" + integrity sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig== + +"@rolldown/binding-linux-arm64-musl@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz#56043cdf4768ea3184bb08a3f45b24f183003877" + integrity sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw== + +"@rolldown/binding-linux-ppc64-gnu@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz#92eba998d5cd9e4cfc8b95407b4b80f46dacadf4" + integrity sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA== + +"@rolldown/binding-linux-s390x-gnu@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz#50b0e95dc3c22af1ecd1669ad7e6030e6860819e" + integrity sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ== + +"@rolldown/binding-linux-x64-gnu@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz#d9fd5af004a373a2d26a09ce8fb66bd0927cbc0b" + integrity sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ== + +"@rolldown/binding-linux-x64-musl@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz#c0ad80adf4d4df430d0ec309e97924db85065c3c" + integrity sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw== + +"@rolldown/binding-openharmony-arm64@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz#341fc254ef7759f865bb219beb5cea6f5c9a44f6" + integrity sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w== + +"@rolldown/binding-wasm32-wasi@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz#65f9389f74168fd54e67b12d0630fb90348051f0" + integrity sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ== + dependencies: + "@emnapi/core" "1.10.0" + "@emnapi/runtime" "1.10.0" + "@napi-rs/wasm-runtime" "^1.1.4" + +"@rolldown/binding-win32-arm64-msvc@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz#17e80f3402308f12c76ff4172768d51715b522ac" + integrity sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A== + +"@rolldown/binding-win32-x64-msvc@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz#875cfa37f26d11692dbe28b8331499c97aa99d1f" + integrity sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ== + +"@rolldown/pluginutils@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz#e3fcee093fbb5ce765e1ad088ff4de2889f6f9be" + integrity sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw== + +"@standard-schema/spec@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== @@ -834,41 +611,41 @@ "@popperjs/core" "^2.11.8" bootstrap "5.3.7" -"@tabler/icons-react@^3.38.0": - version "3.38.0" - resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.38.0.tgz#c568dabae83fd3bf68f09e7db55c72c6df0b77b4" - integrity sha512-kR5wv+m4+GgmnSszg3rQd6SrTFAQ/XnQC/yTwIfuRJSfqB12KoIC7fPbIijFgOHTFlBN5DARnN0IVrR7KYG6/A== +"@tabler/icons-react@^3.44.0": + version "3.44.0" + resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.44.0.tgz#26e84ee0ef756bfae5dcbd8265312ba3cd5a91d9" + integrity sha512-8+rvzBbVm/1Z3sG3x7GUNAaxIKxwgz8xaMhRs23nrCnMTKRFAhEC+82zAIFeAA0seXdrAGX5HFCkaLpGK2rVHg== dependencies: - "@tabler/icons" "3.38.0" + "@tabler/icons" "3.44.0" -"@tabler/icons@3.38.0": - version "3.38.0" - resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.38.0.tgz#cb7bf6802a6d64b65cddda2c81fc21300d13dfe1" - integrity sha512-FdETQSpQ3lN7BEjEUzjKhsfTDCamrvMDops4HEMphTm3DmkIFpThoODn8XXZ8Q9MhjshIvphIYVHHB7zpq167w== +"@tabler/icons@3.44.0": + version "3.44.0" + resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.44.0.tgz#286b203aac98591e27360de03974762568bbb272" + integrity sha512-Wn0AOZG9sg0L+bjfMqq4eNhC6pQjIrk94LvvWYNYkY8KH8wC3YILRzQlrnVJc4FUeMxH/AK97QsYCX35H3LndA== -"@tanstack/query-core@5.90.20": - version "5.90.20" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.90.20.tgz#e12128e39210715d4ce4fb299c33498ac297771e" - integrity sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg== +"@tanstack/query-core@5.100.14": + version "5.100.14" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.100.14.tgz#6bf11c9a6a33dbbbba21f05d880b93930d80000c" + integrity sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew== -"@tanstack/query-devtools@5.93.0": - version "5.93.0" - resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.93.0.tgz#517f61d4e2cfb9af671e34ad5e7e871052bca814" - integrity sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg== +"@tanstack/query-devtools@5.100.14": + version "5.100.14" + resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.100.14.tgz#1555c8e1e4ea74db85dd2faaea2b7b50e135faf1" + integrity sha512-g96SmSSQecYTYcyuAMRXr895GplJv01UGt7qttQWPOUyZ5EGz5tbRc589bMc2m5BsPFD6O0PCEAHdbDYNP6UBw== -"@tanstack/react-query-devtools@^5.91.3": - version "5.91.3" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.91.3.tgz#0f65340fa3f7e7d5575de928ad70cfa6b5f74ff1" - integrity sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA== +"@tanstack/react-query-devtools@^5.100.14": + version "5.100.14" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.100.14.tgz#0774021fdc0b417a86b67b196d5ffb9820cff10c" + integrity sha512-JkP5VDgKOw3t/QSA1OABRHEqx8BuNs5MfvZRooNqdvN57SzTuGq3fKR1a2IH5rqa5HDLUm+FOXUEnB9ueHiLzg== dependencies: - "@tanstack/query-devtools" "5.93.0" + "@tanstack/query-devtools" "5.100.14" -"@tanstack/react-query@^5.90.21": - version "5.90.21" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.90.21.tgz#e0eb40831a76510be438109435b8807ef63ab1b9" - integrity sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg== +"@tanstack/react-query@^5.100.14": + version "5.100.14" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.100.14.tgz#799fe305421fd84bf2f6df40930c859c945b42ab" + integrity sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw== dependencies: - "@tanstack/query-core" "5.90.20" + "@tanstack/query-core" "5.100.14" "@tanstack/react-table@^8.21.3": version "8.21.3" @@ -915,44 +692,18 @@ dependencies: "@babel/runtime" "^7.12.5" +"@tybys/wasm-util@^0.10.1": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.2.tgz#12b3a1b33db1f9cad4ddff1f604ab7dd00bf464e" + integrity sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg== + dependencies: + tslib "^2.4.0" + "@types/aria-query@^5.0.1": version "5.0.4" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== -"@types/babel__core@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" - integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" - integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== - dependencies: - "@babel/types" "^7.28.2" - "@types/chai@^5.2.2": version "5.2.3" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.3.tgz#8e9cd9e1c3581fa6b341a5aed5588eb285be0b4a" @@ -985,7 +736,7 @@ dependencies: "@types/estree" "*" -"@types/estree@*", "@types/estree@1.0.8", "@types/estree@^1.0.0": +"@types/estree@*", "@types/estree@^1.0.0": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== @@ -1028,12 +779,12 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== -"@types/node@*", "@types/node@>=20.0.0": - version "25.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-25.3.0.tgz#749b1bd4058e51b72e22bd41e9eab6ebd0180470" - integrity sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A== +"@types/node@*", "@types/node@>=20.0.0", "@types/node@^25.9.1": + version "25.9.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.9.1.tgz#3bda556db500ae4319c08e7fc9ab94f19013ba0b" + integrity sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg== dependencies: - undici-types "~7.18.0" + undici-types ">=7.24.0 <7.24.7" "@types/parse-json@^4.0.0": version "4.0.2" @@ -1067,10 +818,10 @@ resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== -"@types/react@*", "@types/react@>=16.9.11", "@types/react@^19.2.14": - version "19.2.14" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.14.tgz#39604929b5e3957e3a6fa0001dafb17c7af70bad" - integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== +"@types/react@*", "@types/react@>=16.9.11", "@types/react@^19.2.15": + version "19.2.15" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.15.tgz#9e2c6a4251a290f5c525c3143caa872fa6e01e0d" + integrity sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q== dependencies: csstype "^3.2.2" @@ -1115,75 +866,72 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== -"@vitejs/plugin-react@^5.1.4": - version "5.1.4" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz#5b477e060bf612a7394c4febacc5de33a219b0e4" - integrity sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA== +"@vitejs/plugin-react@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz#f70cb8ed0ce225dbc3055d78070f820d8aa35eda" + integrity sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg== dependencies: - "@babel/core" "^7.29.0" - "@babel/plugin-transform-react-jsx-self" "^7.27.1" - "@babel/plugin-transform-react-jsx-source" "^7.27.1" - "@rolldown/pluginutils" "1.0.0-rc.3" - "@types/babel__core" "^7.20.5" - react-refresh "^0.18.0" + "@rolldown/pluginutils" "^1.0.0" -"@vitest/expect@4.0.18": - version "4.0.18" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.0.18.tgz#361510d99fbf20eb814222e4afcb8539d79dc94d" - integrity sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ== +"@vitest/expect@4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.1.7.tgz#dc2918d51b54fa24ac5b4e7e802ef7440a11027f" + integrity sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w== dependencies: - "@standard-schema/spec" "^1.0.0" + "@standard-schema/spec" "^1.1.0" "@types/chai" "^5.2.2" - "@vitest/spy" "4.0.18" - "@vitest/utils" "4.0.18" - chai "^6.2.1" - tinyrainbow "^3.0.3" + "@vitest/spy" "4.1.7" + "@vitest/utils" "4.1.7" + chai "^6.2.2" + tinyrainbow "^3.1.0" -"@vitest/mocker@4.0.18": - version "4.0.18" - resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.0.18.tgz#b9735da114ef65ea95652c5bdf13159c6fab4865" - integrity sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ== +"@vitest/mocker@4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.1.7.tgz#05c5b42968b56057c40ea3e4546f3227cf1bf6fc" + integrity sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA== dependencies: - "@vitest/spy" "4.0.18" + "@vitest/spy" "4.1.7" estree-walker "^3.0.3" magic-string "^0.30.21" -"@vitest/pretty-format@4.0.18": - version "4.0.18" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.0.18.tgz#fbccd4d910774072ec15463553edb8ca5ce53218" - integrity sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw== +"@vitest/pretty-format@4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.1.7.tgz#86b37ea96d4c5bd1357be66982e60a89a343c1bb" + integrity sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw== dependencies: - tinyrainbow "^3.0.3" + tinyrainbow "^3.1.0" -"@vitest/runner@4.0.18": - version "4.0.18" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.0.18.tgz#c2c0a3ed226ec85e9312f9cc8c43c5b3a893a8b1" - integrity sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw== +"@vitest/runner@4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.1.7.tgz#a7c465a76c0edc7deb1e8927dad452b71c5797b5" + integrity sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw== dependencies: - "@vitest/utils" "4.0.18" + "@vitest/utils" "4.1.7" pathe "^2.0.3" -"@vitest/snapshot@4.0.18": - version "4.0.18" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.0.18.tgz#bcb40fd6d742679c2ac927ba295b66af1c6c34c5" - integrity sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA== +"@vitest/snapshot@4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.1.7.tgz#03ca7bed0cb3f2ce37de9feee7a159ba5d4893a4" + integrity sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw== dependencies: - "@vitest/pretty-format" "4.0.18" + "@vitest/pretty-format" "4.1.7" + "@vitest/utils" "4.1.7" magic-string "^0.30.21" pathe "^2.0.3" -"@vitest/spy@4.0.18": - version "4.0.18" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.0.18.tgz#ba0f20503fb6d08baf3309d690b3efabdfa88762" - integrity sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw== +"@vitest/spy@4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.1.7.tgz#459de6b5f2c80cb589e612b2fa8170a33393dee9" + integrity sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q== -"@vitest/utils@4.0.18": - version "4.0.18" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.0.18.tgz#9636b16d86a4152ec68a8d6859cff702896433d4" - integrity sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA== +"@vitest/utils@4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.1.7.tgz#8d2350588f7054246f24d0dbadffbef5d3a5caff" + integrity sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw== dependencies: - "@vitest/pretty-format" "4.0.18" - tinyrainbow "^3.0.3" + "@vitest/pretty-format" "4.1.7" + convert-source-map "^2.0.0" + tinyrainbow "^3.1.0" ansi-regex@^5.0.1: version "5.0.1" @@ -1231,27 +979,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -baseline-browser-mapping@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz#5b09935025bf8a80e29130251e337c6a7fc8cbb9" - integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA== - bootstrap@5.3.7: version "5.3.7" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.7.tgz#8640065036124d961d885d80b5945745e1154d90" integrity sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw== -browserslist@^4.24.0: - version "4.28.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" - integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== - dependencies: - baseline-browser-mapping "^2.9.0" - caniuse-lite "^1.0.30001759" - electron-to-chromium "^1.5.263" - node-releases "^2.0.27" - update-browserslist-db "^1.2.0" - buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" @@ -1265,17 +997,12 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -caniuse-lite@^1.0.30001759: - version "1.0.30001770" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz#4dc47d3b263a50fbb243448034921e0a88591a84" - integrity sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw== - ccount@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chai@^6.2.1: +chai@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e" integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg== @@ -1300,12 +1027,12 @@ character-reference-invalid@^2.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== -chokidar@^4.0.0, chokidar@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" - integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== +chokidar@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-5.0.0.tgz#949c126a9238a80792be9a0265934f098af369a5" + integrity sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw== dependencies: - readdirp "^4.0.1" + readdirp "^5.0.0" classnames@^2.3.2, classnames@^2.5.1: version "2.5.1" @@ -1348,10 +1075,10 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" -country-flag-icons@^1.6.15: - version "1.6.15" - resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.6.15.tgz#f89014f84534d4f35109280d88930e6c371684a9" - integrity sha512-92HoA8l6DluEidku8tKBftjuFRj4Rv3zDW1lXxCuNnqAxhUSkvso9gM/Afj4F5BnK+wneHIe3ydI+s+4NA29/Q== +country-flag-icons@^1.6.17: + version "1.6.17" + resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.6.17.tgz#875cf35eb2a0d7fcb0f07166fed9fc65863e03f8" + integrity sha512-Nmik0289ZVZSI3c7mJR/amg6DyY7Z59b0sTFSKayeX72mHfPzCPJygwJs2pYgQULzuAyWeCUgwAJ+Dq8OR+JFw== css.escape@^1.5.1: version "1.5.1" @@ -1363,23 +1090,18 @@ csstype@^3.0.2, csstype@^3.2.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== -date-fns@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" - integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== +date-fns@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.3.0.tgz#95663b78f2be5ce664eb6606d94582acc39c77ba" + integrity sha512-OYcL+3N/jyWbYdFGqoMAhytDgxP9pbYPUUiRCOgn4Fewaadk9l/Wam4Avciiyp2BgkpfQyBV9B+ehnVJych+eQ== -debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: +debug@^4.0.0, debug@^4.3.1: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" -decimal.js@^10.6.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" - integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== - decode-named-character-reference@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f" @@ -1432,11 +1154,6 @@ dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" -electron-to-chromium@^1.5.263: - version "1.5.286" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" - integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== - entities@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" @@ -1454,47 +1171,10 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-module-lexer@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" - integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== - -esbuild@^0.27.0: - version "0.27.3" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.3.tgz#5859ca8e70a3af956b26895ce4954d7e73bd27a8" - integrity sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg== - optionalDependencies: - "@esbuild/aix-ppc64" "0.27.3" - "@esbuild/android-arm" "0.27.3" - "@esbuild/android-arm64" "0.27.3" - "@esbuild/android-x64" "0.27.3" - "@esbuild/darwin-arm64" "0.27.3" - "@esbuild/darwin-x64" "0.27.3" - "@esbuild/freebsd-arm64" "0.27.3" - "@esbuild/freebsd-x64" "0.27.3" - "@esbuild/linux-arm" "0.27.3" - "@esbuild/linux-arm64" "0.27.3" - "@esbuild/linux-ia32" "0.27.3" - "@esbuild/linux-loong64" "0.27.3" - "@esbuild/linux-mips64el" "0.27.3" - "@esbuild/linux-ppc64" "0.27.3" - "@esbuild/linux-riscv64" "0.27.3" - "@esbuild/linux-s390x" "0.27.3" - "@esbuild/linux-x64" "0.27.3" - "@esbuild/netbsd-arm64" "0.27.3" - "@esbuild/netbsd-x64" "0.27.3" - "@esbuild/openbsd-arm64" "0.27.3" - "@esbuild/openbsd-x64" "0.27.3" - "@esbuild/openharmony-arm64" "0.27.3" - "@esbuild/sunos-x64" "0.27.3" - "@esbuild/win32-arm64" "0.27.3" - "@esbuild/win32-ia32" "0.27.3" - "@esbuild/win32-x64" "0.27.3" - -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +es-module-lexer@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.1.0.tgz#1dfcbb5ea3bbfb63f28e1fc3676c3676d1c9624c" + integrity sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ== escape-string-regexp@^4.0.0: version "4.0.0" @@ -1513,7 +1193,7 @@ estree-walker@^3.0.3: dependencies: "@types/estree" "^1.0.0" -expect-type@^1.2.2: +expect-type@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== @@ -1562,7 +1242,7 @@ formik@^2.4.9: tiny-warning "^1.0.2" tslib "^2.0.0" -fsevents@~2.3.2, fsevents@~2.3.3: +fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -1580,20 +1260,15 @@ generate-password-browser@^1.1.0: buffer "^6.0.3" randombytes "^2.0.5" -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -globrex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" - integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== +graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -happy-dom@^20.8.3: - version "20.8.3" - resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-20.8.3.tgz#aa59a13c0e43c48819190d913ab5e36800e87431" - integrity sha512-lMHQRRwIPyJ70HV0kkFT7jH/gXzSI7yDkQFe07E2flwmNDFoWUTRMKpW2sglsnpeA7b6S2TJPp98EbQxai8eaQ== +happy-dom@^20.9.0: + version "20.9.0" + resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-20.9.0.tgz#22fa4f2b8a34d2fbd0fb43eb48227e4bf8e206e8" + integrity sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ== dependencies: "@types/node" ">=20.0.0" "@types/whatwg-mimetype" "^3.0.2" @@ -1723,7 +1398,7 @@ hastscript@^9.0.0: property-information "^7.0.0" space-separated-tokens "^2.0.0" -hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -1750,10 +1425,10 @@ ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -immutable@^5.0.2: - version "5.1.4" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.4.tgz#e3f8c1fe7b567d56cf26698f31918c241dae8c1f" - integrity sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA== +immutable@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.5.tgz#93ee4db5c2a9ab42a4a783069f3c5d8847d40165" + integrity sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A== import-fresh@^3.2.1: version "3.3.1" @@ -1773,15 +1448,13 @@ inline-style-parser@0.2.7: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909" integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA== -intl-messageformat@11.1.2: - version "11.1.2" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-11.1.2.tgz#c72879165d15633f38b092479822c5d831c21ac5" - integrity sha512-ucSrQmZGAxfiBHfBRXW/k7UC8MaGFlEj4Ry1tKiDcmgwQm1y3EDl40u+4VNHYomxJQMJi9NEI3riDRlth96jKg== +intl-messageformat@11.2.7: + version "11.2.7" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-11.2.7.tgz#1d922a7df8964ec496c56f62928ef2134674bdae" + integrity sha512-+q6Ktg119nULZEpZ8YTuGOst9MyEzFtjD63FTGBlN1mLz0Z/MOUYDIvnpVKwq17eezIEh+cfJIebfJoCetpiNw== dependencies: - "@formatjs/ecma402-abstract" "3.1.1" - "@formatjs/fast-memoize" "3.1.0" - "@formatjs/icu-messageformat-parser" "3.5.1" - tslib "^2.8.1" + "@formatjs/fast-memoize" "3.1.5" + "@formatjs/icu-messageformat-parser" "3.5.10" invariant@^2.2.4: version "2.2.4" @@ -1847,6 +1520,11 @@ is-plain-obj@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +jsbi@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-4.3.2.tgz#8a4d05d4e09907d73042135b6aa55a6d161ee955" + integrity sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew== + jsesc@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" @@ -1857,10 +1535,79 @@ json-parse-even-better-errors@^2.3.0: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +lightningcss-android-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz#f033885116dfefd9c6f54787523e3514b61e1968" + integrity sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg== + +lightningcss-darwin-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz#50b71871b01c8199584b649e292547faea7af9b5" + integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== + +lightningcss-darwin-x64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz#35f3e97332d130b9ca181e11b568ded6aebc6d5e" + integrity sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w== + +lightningcss-freebsd-x64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz#9777a76472b64ed6ff94342ad64c7bafd794a575" + integrity sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig== + +lightningcss-linux-arm-gnueabihf@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz#13ae652e1ab73b9135d7b7da172f666c410ad53d" + integrity sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw== + +lightningcss-linux-arm64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz#417858795a94592f680123a1b1f9da8a0e1ef335" + integrity sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ== + +lightningcss-linux-arm64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz#6be36692e810b718040802fd809623cffe732133" + integrity sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg== + +lightningcss-linux-x64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz#0b7803af4eb21cfd38dd39fe2abbb53c7dd091f6" + integrity sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA== + +lightningcss-linux-x64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz#88dc8ba865ddddb1ac5ef04b0f161804418c163b" + integrity sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg== + +lightningcss-win32-arm64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz#4f30ba3fa5e925f5b79f945e8cc0d176c3b1ab38" + integrity sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw== + +lightningcss-win32-x64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz#141aa5605645064928902bb4af045fa7d9f4220a" + integrity sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q== + +lightningcss@^1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.32.0.tgz#b85aae96486dcb1bf49a7c8571221273f4f1e4a9" + integrity sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ== + dependencies: + detect-libc "^2.0.3" + optionalDependencies: + lightningcss-android-arm64 "1.32.0" + lightningcss-darwin-arm64 "1.32.0" + lightningcss-darwin-x64 "1.32.0" + lightningcss-freebsd-x64 "1.32.0" + lightningcss-linux-arm-gnueabihf "1.32.0" + lightningcss-linux-arm64-gnu "1.32.0" + lightningcss-linux-arm64-musl "1.32.0" + lightningcss-linux-x64-gnu "1.32.0" + lightningcss-linux-x64-musl "1.32.0" + lightningcss-win32-arm64-msvc "1.32.0" + lightningcss-win32-x64-msvc "1.32.0" lines-and-columns@^1.1.6: version "1.2.4" @@ -1868,9 +1615,9 @@ lines-and-columns@^1.1.6: integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== lodash-es@^4.17.21: - version "4.17.23" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.23.tgz#58c4360fd1b5d33afc6c0bbd3d1149349b1138e0" - integrity sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg== + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.18.1.tgz#b962eeb80d9d983a900bf342961fb7418ca10b1d" + integrity sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A== lodash.debounce@^4.0.8: version "4.0.8" @@ -1878,9 +1625,9 @@ lodash.debounce@^4.0.8: integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== lodash@^4.17.21: - version "4.17.23" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" - integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== longest-streak@^3.0.0: version "3.1.0" @@ -1894,13 +1641,6 @@ loose-envify@^1.0.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - lz-string@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" @@ -2227,21 +1967,16 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== +nanoid@^3.3.12: + version "3.3.12" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.12.tgz#ab3d912e217a6d0a514f00a72a16543a28982c05" + integrity sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ== node-addon-api@^7.0.0: version "7.1.1" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== -node-releases@^2.0.27: - version "2.0.27" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" - integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== - npm-run-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-6.0.0.tgz#25cfdc4eae04976f3349c0b1afc089052c362537" @@ -2332,31 +2067,22 @@ picocolors@1.1.1, picocolors@^1.1.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" - integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== +picomatch@^4.0.3, picomatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== postcss-simple-vars@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz#836b3097a54dcd13dbd3c36a5dbdd512fad2954c" integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A== -postcss@^8.5.6: - version "8.5.6" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" - integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== +postcss@^8.5.15: + version "8.5.15" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.15.tgz#d1eaf677a324e9ec02196da2d3fecf4a0b9a735c" + integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A== dependencies: - nanoid "^3.3.11" - picocolors "^1.1.1" - source-map-js "^1.2.1" - -postcss@^8.5.8: - version "8.5.8" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.8.tgz#6230ecc8fb02e7a0f6982e53990937857e13f399" - integrity sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg== - dependencies: - nanoid "^3.3.11" + nanoid "^3.3.12" picocolors "^1.1.1" source-map-js "^1.2.1" @@ -2386,6 +2112,15 @@ prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +proper-lockfile@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" + integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== + dependencies: + graceful-fs "^4.2.4" + retry "^0.12.0" + signal-exit "^3.0.2" + property-information@^6.0.0: version "6.5.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" @@ -2438,10 +2173,10 @@ react-bootstrap@^2.10.10: uncontrollable "^7.2.1" warning "^4.0.3" -react-dom@^19.2.4: - version "19.2.4" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.4.tgz#6fac6bd96f7db477d966c7ec17c1a2b1ad8e6591" - integrity sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ== +react-dom@^19.2.6: + version "19.2.6" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.6.tgz#44a81b0bcca22da814c00847d09d01c8615529b7" + integrity sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g== dependencies: scheduler "^0.27.0" @@ -2450,18 +2185,14 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== -react-intl@^8.1.3: - version "8.1.3" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-8.1.3.tgz#216e0a916c40a535c590995c3103fe9e2525d2e4" - integrity sha512-eL1/d+uQdnapirynOGAriW0K9uAoyarjRGL3V9LaTRuohNSvPgCfJX06EZl5M52h/Hu7Gz7A1sD7dNHcos1lNg== +react-intl@^10.1.9: + version "10.1.9" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-10.1.9.tgz#e256f0c28a049d391d421bf658e49fe137fb2cdf" + integrity sha512-FOWpTmwnZnAfz8JegRzyGnjUiuzLW3xJFjp/o8VR4HZAQ+Eg++YaeMhoUULLY+LMx7gZm3czaZEqcU4hntwerw== dependencies: - "@formatjs/ecma402-abstract" "3.1.1" - "@formatjs/icu-messageformat-parser" "3.5.1" - "@formatjs/intl" "4.1.2" - "@types/hoist-non-react-statics" "^3.3.1" - hoist-non-react-statics "^3.3.2" - intl-messageformat "11.1.2" - tslib "^2.8.1" + "@formatjs/icu-messageformat-parser" "3.5.10" + "@formatjs/intl" "4.1.12" + intl-messageformat "11.2.7" react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0: version "16.13.1" @@ -2495,22 +2226,17 @@ react-markdown@^10.1.0: unist-util-visit "^5.0.0" vfile "^6.0.0" -react-refresh@^0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.18.0.tgz#2dce97f4fe932a4d8142fa1630e475c1729c8062" - integrity sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw== - -react-router-dom@^7.13.1: - version "7.13.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.13.1.tgz#74c045acc333ca94612b889cd1b1e1ee9534dead" - integrity sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw== +react-router-dom@^7.15.1: + version "7.15.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.15.1.tgz#cf5aee65a44e407a17a2e9718d5350482b1e281f" + integrity sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg== dependencies: - react-router "7.13.1" + react-router "7.15.1" -react-router@7.13.1: - version "7.13.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.13.1.tgz#5e2b3ebafd6c78d9775e135474bf5060645077f7" - integrity sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA== +react-router@7.15.1: + version "7.15.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.15.1.tgz#0a12fece05887a47c54970480745385c793bcaac" + integrity sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A== dependencies: cookie "^1.0.1" set-cookie-parser "^2.6.0" @@ -2530,10 +2256,10 @@ react-select@^5.10.2: react-transition-group "^4.3.0" use-isomorphic-layout-effect "^1.2.0" -react-toastify@^11.0.5: - version "11.0.5" - resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-11.0.5.tgz#ce4c42d10eeb433988ab2264d3e445c4e9d13313" - integrity sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA== +react-toastify@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-11.1.0.tgz#3d3c73a44d5cac868ee9a52e0f90dd706532c4a4" + integrity sha512-e9h23x3phN0wbFeB6yovmWp7lobzV4CaCH0LO8nVP6H7Y+3GbcLpIzMm9dJhcp1RXbpyfvjgpfXqO80QAmn7sg== dependencies: clsx "^2.1.1" @@ -2547,15 +2273,15 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^19.2.4: - version "19.2.4" - resolved "https://registry.yarnpkg.com/react/-/react-19.2.4.tgz#438e57baa19b77cb23aab516cf635cd0579ee09a" - integrity sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ== +react@^19.2.6: + version "19.2.6" + resolved "https://registry.yarnpkg.com/react/-/react-19.2.6.tgz#3dadb8e12b2a7934c1d5317973e5dce1301f9a4d" + integrity sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q== -readdirp@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" - integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== +readdirp@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-5.0.0.tgz#fbf1f71a727891d685bb1786f9ba74084f6e2f91" + integrity sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ== redent@^3.0.0: version "3.0.0" @@ -2650,62 +2376,59 @@ resolve@^1.19.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -rollup@^4.43.0: - version "4.59.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.59.0.tgz#cf74edac17c1486f562d728a4d923a694abdf06f" - integrity sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg== +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +rolldown@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.0.2.tgz#ea2c786c5f063d08fd22b49e51997f15ec532bbd" + integrity sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g== dependencies: - "@types/estree" "1.0.8" + "@oxc-project/types" "=0.132.0" + "@rolldown/pluginutils" "^1.0.0" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.59.0" - "@rollup/rollup-android-arm64" "4.59.0" - "@rollup/rollup-darwin-arm64" "4.59.0" - "@rollup/rollup-darwin-x64" "4.59.0" - "@rollup/rollup-freebsd-arm64" "4.59.0" - "@rollup/rollup-freebsd-x64" "4.59.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.59.0" - "@rollup/rollup-linux-arm-musleabihf" "4.59.0" - "@rollup/rollup-linux-arm64-gnu" "4.59.0" - "@rollup/rollup-linux-arm64-musl" "4.59.0" - "@rollup/rollup-linux-loong64-gnu" "4.59.0" - "@rollup/rollup-linux-loong64-musl" "4.59.0" - "@rollup/rollup-linux-ppc64-gnu" "4.59.0" - "@rollup/rollup-linux-ppc64-musl" "4.59.0" - "@rollup/rollup-linux-riscv64-gnu" "4.59.0" - "@rollup/rollup-linux-riscv64-musl" "4.59.0" - "@rollup/rollup-linux-s390x-gnu" "4.59.0" - "@rollup/rollup-linux-x64-gnu" "4.59.0" - "@rollup/rollup-linux-x64-musl" "4.59.0" - "@rollup/rollup-openbsd-x64" "4.59.0" - "@rollup/rollup-openharmony-arm64" "4.59.0" - "@rollup/rollup-win32-arm64-msvc" "4.59.0" - "@rollup/rollup-win32-ia32-msvc" "4.59.0" - "@rollup/rollup-win32-x64-gnu" "4.59.0" - "@rollup/rollup-win32-x64-msvc" "4.59.0" - fsevents "~2.3.2" - -rooks@^9.5.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/rooks/-/rooks-9.5.0.tgz#bbe15fdf64fff44b9a1750cc913de3401572f012" - integrity sha512-AtmaX8yjQkJAW7EXW+UU481bpGwuk455hjD/aEUuy7N7VjvXlNmO8BErQ+jEUQp1DRA/PTWonv+Dq1nEkJdgkw== + "@rolldown/binding-android-arm64" "1.0.2" + "@rolldown/binding-darwin-arm64" "1.0.2" + "@rolldown/binding-darwin-x64" "1.0.2" + "@rolldown/binding-freebsd-x64" "1.0.2" + "@rolldown/binding-linux-arm-gnueabihf" "1.0.2" + "@rolldown/binding-linux-arm64-gnu" "1.0.2" + "@rolldown/binding-linux-arm64-musl" "1.0.2" + "@rolldown/binding-linux-ppc64-gnu" "1.0.2" + "@rolldown/binding-linux-s390x-gnu" "1.0.2" + "@rolldown/binding-linux-x64-gnu" "1.0.2" + "@rolldown/binding-linux-x64-musl" "1.0.2" + "@rolldown/binding-openharmony-arm64" "1.0.2" + "@rolldown/binding-wasm32-wasi" "1.0.2" + "@rolldown/binding-win32-arm64-msvc" "1.0.2" + "@rolldown/binding-win32-x64-msvc" "1.0.2" + +rooks@^9.8.0: + version "9.8.0" + resolved "https://registry.yarnpkg.com/rooks/-/rooks-9.8.0.tgz#bf4790f1d379cb9d2f60570202e39d297ed88f8a" + integrity sha512-S6FqnmERx5zgl8ZUEcnyTe1jgjwE5xeFCgOV4bzgQHKp26P7YA7uPnzzOgacojtoX6E7pQTcewSGqN83wVyz+g== dependencies: fast-deep-equal "^3.1.3" lodash.debounce "^4.0.8" raf "^3.4.1" - use-sync-external-store "^1.4.0" + use-sync-external-store "^1.6.0" + optionalDependencies: + "@js-temporal/polyfill" "^0.5.1" safe-buffer@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -sass@^1.97.3: - version "1.97.3" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.97.3.tgz#9cb59339514fa7e2aec592b9700953ac6e331ab2" - integrity sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg== +sass@^1.100.0: + version "1.100.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.100.0.tgz#b4cab1bed286fe22ac6c879c514f71cd36aa06c8" + integrity sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ== dependencies: - chokidar "^4.0.0" - immutable "^5.0.2" + chokidar "^5.0.0" + immutable "^5.1.5" source-map-js ">=0.6.2 <2.0.0" optionalDependencies: "@parcel/watcher" "^2.4.1" @@ -2715,11 +2438,6 @@ scheduler@^0.27.0: resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== -semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - set-cookie-parser@^2.6.0: version "2.7.2" resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68" @@ -2730,6 +2448,11 @@ siginfo@^2.0.0: resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== +signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" @@ -2755,10 +2478,10 @@ stackback@0.0.2: resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== -std-env@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b" - integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== +std-env@^4.0.0-rc.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-4.1.0.tgz#45899abc590d86d682e87f0acd1033a75084cd3f" + integrity sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ== stringify-entities@^4.0.0: version "4.0.4" @@ -2819,23 +2542,23 @@ tinyexec@^1.0.2: resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.2.tgz#bdd2737fe2ba40bd6f918ae26642f264b99ca251" integrity sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg== -tinyglobby@^0.2.15: - version "0.2.15" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" - integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== +tinyglobby@^0.2.15, tinyglobby@^0.2.16: + version "0.2.16" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6" + integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== dependencies: fdir "^6.5.0" - picomatch "^4.0.3" + picomatch "^4.0.4" -tinyrainbow@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-3.0.3.tgz#984a5b1c1b25854a9b6bccbe77964d0593d1ea42" - integrity sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q== +tinyrainbow@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-3.1.0.tgz#1d8a623893f95cf0a2ddb9e5d11150e191409421" + integrity sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw== -tmp@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" - integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== +tmp@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.6.tgz#0dfac10fd09a9319288eb0e8f0ed524604e183b4" + integrity sha512-5sJPdPjfI5Kx+qbrDesxkglRBxW//g7hCsqspEjwkewGvBMGIKMOTKzLt1hFVJzyadba3lDUN20O9qhvbQUSTA== trim-lines@^3.0.0: version "3.0.1" @@ -2847,20 +2570,15 @@ trough@^2.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== -tsconfck@^3.0.3: - version "3.1.6" - resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.6.tgz#da1f0b10d82237ac23422374b3fce1edb23c3ead" - integrity sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w== - -tslib@^2.0.0, tslib@^2.8.0, tslib@^2.8.1: +tslib@^2.0.0, tslib@^2.4.0, tslib@^2.8.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -typescript@5.9.3: - version "5.9.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" - integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== +typescript@6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== uncontrollable@^7.2.1: version "7.2.1" @@ -2877,10 +2595,10 @@ uncontrollable@^8.0.4: resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-8.0.4.tgz#a0a8307f638795162fafd0550f4a1efa0f8c5eb6" integrity sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ== -undici-types@~7.18.0: - version "7.18.2" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" - integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== +"undici-types@>=7.24.0 <7.24.7": + version "7.24.6" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.24.6.tgz#61275b485d7fd4e9d269c7cf04ec2873c9cc0f91" + integrity sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg== unicorn-magic@^0.3.0: version "0.3.0" @@ -2947,20 +2665,12 @@ unist-util-visit@^5.0.0: unist-util-is "^6.0.0" unist-util-visit-parents "^6.0.0" -update-browserslist-db@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" - integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - use-isomorphic-layout-effect@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz#2f11a525628f56424521c748feabc2ffcc962fce" integrity sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA== -use-sync-external-store@^1.4.0: +use-sync-external-store@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== @@ -2989,74 +2699,58 @@ vfile@^6.0.0: "@types/unist" "^3.0.0" vfile-message "^4.0.0" -vite-plugin-checker@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.12.0.tgz#1e9688a5a10f5de1fd833bc1351618eed54db3bc" - integrity sha512-CmdZdDOGss7kdQwv73UyVgLPv0FVYe5czAgnmRX2oKljgEvSrODGuClaV3PDR2+3ou7N/OKGauDDBjy2MB07Rg== +vite-plugin-checker@^0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.14.1.tgz#0fe694c6d90905c9da7658772fadab4823d7b881" + integrity sha512-Mv8oQc9XYBYf+XkP/riqqQCt8lBP6Iad75PZPho1lHRrpxQI0BwX2gwE10enn4f6Hgc+PvR1F7N38KARcaJtzw== dependencies: - "@babel/code-frame" "^7.27.1" - chokidar "^4.0.3" + "@babel/code-frame" "^7.29.0" + chokidar "^5.0.0" npm-run-path "^6.0.0" picocolors "^1.1.1" - picomatch "^4.0.3" + picomatch "^4.0.4" + proper-lockfile "^4.1.2" tiny-invariant "^1.3.3" - tinyglobby "^0.2.15" - vscode-uri "^3.1.0" - -vite-tsconfig-paths@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.1.tgz#d5c28cba79c89ebf76489ef1040024b21df6da3a" - integrity sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg== - dependencies: - debug "^4.1.1" - globrex "^0.1.2" - tsconfck "^3.0.3" -"vite@^6.0.0 || ^7.0.0", vite@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/vite/-/vite-7.3.1.tgz#7f6cfe8fb9074138605e822a75d9d30b814d6507" - integrity sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA== +"vite@^6.0.0 || ^7.0.0 || ^8.0.0", vite@^8.0.14: + version "8.0.14" + resolved "https://registry.yarnpkg.com/vite/-/vite-8.0.14.tgz#da5d8d1f63dbd106385cbe9c211acbc7a7a5b192" + integrity sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw== dependencies: - esbuild "^0.27.0" - fdir "^6.5.0" - picomatch "^4.0.3" - postcss "^8.5.6" - rollup "^4.43.0" - tinyglobby "^0.2.15" + lightningcss "^1.32.0" + picomatch "^4.0.4" + postcss "^8.5.15" + rolldown "1.0.2" + tinyglobby "^0.2.16" optionalDependencies: fsevents "~2.3.3" -vitest@^4.0.18: - version "4.0.18" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.0.18.tgz#56f966353eca0b50f4df7540cd4350ca6d454a05" - integrity sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ== - dependencies: - "@vitest/expect" "4.0.18" - "@vitest/mocker" "4.0.18" - "@vitest/pretty-format" "4.0.18" - "@vitest/runner" "4.0.18" - "@vitest/snapshot" "4.0.18" - "@vitest/spy" "4.0.18" - "@vitest/utils" "4.0.18" - es-module-lexer "^1.7.0" - expect-type "^1.2.2" +vitest@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.1.7.tgz#5ae483c0a901081bbf1428e07dafe80c36e62e6c" + integrity sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA== + dependencies: + "@vitest/expect" "4.1.7" + "@vitest/mocker" "4.1.7" + "@vitest/pretty-format" "4.1.7" + "@vitest/runner" "4.1.7" + "@vitest/snapshot" "4.1.7" + "@vitest/spy" "4.1.7" + "@vitest/utils" "4.1.7" + es-module-lexer "^2.0.0" + expect-type "^1.3.0" magic-string "^0.30.21" obug "^2.1.1" pathe "^2.0.3" picomatch "^4.0.3" - std-env "^3.10.0" + std-env "^4.0.0-rc.1" tinybench "^2.9.0" tinyexec "^1.0.2" tinyglobby "^0.2.15" - tinyrainbow "^3.0.3" - vite "^6.0.0 || ^7.0.0" + tinyrainbow "^3.1.0" + vite "^6.0.0 || ^7.0.0 || ^8.0.0" why-is-node-running "^2.3.0" -vscode-uri@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c" - integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ== - warning@^4.0.0, warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" @@ -3087,15 +2781,10 @@ ws@^8.18.3: resolved "https://registry.yarnpkg.com/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + version "1.10.3" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3" + integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA== zwitch@^2.0.0, zwitch@^2.0.4: version "2.0.4" diff --git a/scripts/ci/fulltest-cypress b/scripts/ci/fulltest-cypress index 54fa0528d6..8fb896967c 100755 --- a/scripts/ci/fulltest-cypress +++ b/scripts/ci/fulltest-cypress @@ -52,7 +52,7 @@ jq --arg a "$PDNS_IP" '.servers[0].upstreams[1].upstream = $a' "$LOCAL_DNSROUTER docker compose up -d dnsrouter DNSROUTER_IP=$(get_container_ip "dnsrouter") -echo -e "${BLUE}❯ ${YELLOW}DNS Router IP is ${DNSROUTER_IP}" +echo -e "${BLUE}❯ ${YELLOW}DNS Router IP is ${DNSROUTER_IP}${RESET}" if [ "${DNSROUTER_IP:-}" = "" ]; then echo -e "${RED}❯ ERROR: DNS Router IP is not set${RESET}" @@ -76,7 +76,8 @@ bash "$DIR/../wait-healthy" "$(docker compose ps --all -q fullstack)" 120 # Run tests rm -rf "$DIR/../../test/results" -docker compose up --build cypress +docker compose build cypress +docker compose up cypress # Get results docker cp -L "$(docker compose ps --all -q cypress):/test/results" "$DIR/../../test/" @@ -88,4 +89,3 @@ if [ "$2" = "cleanup" ]; then fi echo -e "${BLUE}❯ ${GREEN}Fullstack cypress testing complete${RESET}" - diff --git a/test/cypress/Dockerfile b/test/cypress/Dockerfile index bd821085a2..cf071bafd9 100644 --- a/test/cypress/Dockerfile +++ b/test/cypress/Dockerfile @@ -1,4 +1,4 @@ -FROM cypress/included:15.11.0 +FROM cypress/included:15.15.0 # Disable Cypress CLI colors ENV FORCE_COLOR=0 diff --git a/test/cypress/config/ci.mjs b/test/cypress/config/ci.mjs index 2597c194ce..ea203a89ee 100644 --- a/test/cypress/config/ci.mjs +++ b/test/cypress/config/ci.mjs @@ -2,6 +2,7 @@ import { defineConfig } from 'cypress'; import pluginSetup from '../plugins/index.mjs'; export default defineConfig({ + allowCypressEnv: false, requestTimeout: 30000, defaultCommandTimeout: 20000, reporter: "cypress-multi-reporters", diff --git a/test/cypress/config/dev.mjs b/test/cypress/config/dev.mjs index f32d973c85..636c3095cd 100644 --- a/test/cypress/config/dev.mjs +++ b/test/cypress/config/dev.mjs @@ -2,6 +2,7 @@ import { defineConfig } from 'cypress'; import pluginSetup from '../plugins/index.mjs'; export default defineConfig({ + allowCypressEnv: false, requestTimeout: 30000, defaultCommandTimeout: 20000, reporter: "cypress-multi-reporters", diff --git a/test/cypress/e2e/api/CertbotPlugins.cy.js b/test/cypress/e2e/api/CertbotPlugins.cy.js new file mode 100644 index 0000000000..554f97b60c --- /dev/null +++ b/test/cypress/e2e/api/CertbotPlugins.cy.js @@ -0,0 +1,28 @@ +/// + +// Only tested once in the sqlite stack + +describe('CertbotPlugins', { tags: '@isolated' }, () => { + it('Should install all certbot plugins', () => { + cy.env(['stack']).then(({ stack }) => { + cy.log(`CertbotPlugins.cy.js - Running tests for stack: ${stack}`); + if (stack === 'sqlite') { + cy.task('backendApiGet', { + path: '/api/ci/certbot-plugins', + }).then((data) => { + expect(data).to.be.an('object'); + + // Install each plugin + for (const plugin of Object.keys(data)) { + cy.log(`Installing plugin: ${plugin}`); + cy.task('backendApiPost', { + path: `/api/ci/certbot-plugins/${plugin}`, + }).then((result) => { + expect(result).to.be.true; + }); + } + }); + } + }); + }); +}); diff --git a/test/cypress/e2e/api/Certificates.cy.js b/test/cypress/e2e/api/Certificates.cy.js index 00da38b224..25d1b8c41f 100644 --- a/test/cypress/e2e/api/Certificates.cy.js +++ b/test/cypress/e2e/api/Certificates.cy.js @@ -4,7 +4,16 @@ describe('Certificates endpoints', () => { let token; let certID; + const certFile = 'test.example.com.pem'; + const keyFile = 'test.example.com-key.pem'; + before(() => { + cy.createCustomCerts({ + domain: 'test.example.com', + certFile, + keyFile, + }) + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; @@ -16,8 +25,8 @@ describe('Certificates endpoints', () => { token: token, path: '/api/nginx/certificates/validate', files: { - certificate: 'test.example.com.pem', - certificate_key: 'test.example.com-key.pem', + certificate: certFile, + certificate_key: keyFile, }, }).then((data) => { cy.validateSwaggerSchema('post', 200, '/nginx/certificates/validate', data); @@ -45,8 +54,8 @@ describe('Certificates endpoints', () => { token: token, path: `/api/nginx/certificates/${certID}/upload`, files: { - certificate: 'test.example.com.pem', - certificate_key: 'test.example.com-key.pem', + certificate: certFile, + certificate_key: keyFile, }, }).then((data) => { cy.validateSwaggerSchema('post', 200, '/nginx/certificates/{certID}/upload', data); diff --git a/test/cypress/e2e/api/Ldap.cy.js b/test/cypress/e2e/api/Ldap.cy.js deleted file mode 100644 index 64e177303d..0000000000 --- a/test/cypress/e2e/api/Ldap.cy.js +++ /dev/null @@ -1,65 +0,0 @@ -/// - -describe('LDAP with Authentik', () => { - let _token; - if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') { - - before(() => { - cy.resetUsers(); - cy.getToken().then((tok) => { - _token = tok; - - // cy.task('backendApiPut', { - // token: token, - // path: '/api/settings/ldap-auth', - // data: { - // value: { - // host: 'authentik-ldap:3389', - // base_dn: 'ou=users,DC=ldap,DC=goauthentik,DC=io', - // user_dn: 'cn={{USERNAME}},ou=users,DC=ldap,DC=goauthentik,DC=io', - // email_property: 'mail', - // name_property: 'sn', - // self_filter: '(&(cn={{USERNAME}})(ak-active=TRUE))', - // auto_create_user: true - // } - // } - // }).then((data) => { - // cy.validateSwaggerSchema('put', 200, '/settings/{name}', data); - // expect(data.result).to.have.property('id'); - // expect(data.result.id).to.be.greaterThan(0); - // }); - - // cy.task('backendApiPut', { - // token: token, - // path: '/api/settings/auth-methods', - // data: { - // value: [ - // 'local', - // 'ldap' - // ] - // } - // }).then((data) => { - // cy.validateSwaggerSchema('put', 200, '/settings/{name}', data); - // expect(data.result).to.have.property('id'); - // expect(data.result.id).to.be.greaterThan(0); - // }); - }); - }); - - it.skip('Should log in with LDAP', () => { - // cy.task('backendApiPost', { - // token: token, - // path: '/api/auth', - // data: { - // // Authentik LDAP creds: - // type: 'ldap', - // identity: 'cypress', - // secret: 'fqXBfUYqHvYqiwBHWW7f' - // } - // }).then((data) => { - // cy.validateSwaggerSchema('post', 200, '/auth', data); - // expect(data.result).to.have.property('token'); - // }); - }); - } -}); diff --git a/test/cypress/e2e/api/OAuth.cy.js b/test/cypress/e2e/api/OAuth.cy.js deleted file mode 100644 index c5c819f9ba..0000000000 --- a/test/cypress/e2e/api/OAuth.cy.js +++ /dev/null @@ -1,97 +0,0 @@ -/// - -describe('OAuth with Authentik', () => { - let _token; - if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') { - - before(() => { - cy.getToken().then((tok) => { - _token = tok; - - // cy.task('backendApiPut', { - // token: token, - // path: '/api/settings/oauth-auth', - // data: { - // value: { - // client_id: '7iO2AvuUp9JxiSVkCcjiIbQn4mHmUMBj7yU8EjqU', - // client_secret: 'VUMZzaGTrmXJ8PLksyqzyZ6lrtz04VvejFhPMBP9hGZNCMrn2LLBanySs4ta7XGrDr05xexPyZT1XThaf4ubg00WqvHRVvlu4Naa1aMootNmSRx3VAk6RSslUJmGyHzq', - // authorization_url: 'http://authentik:9000/application/o/authorize/', - // resource_url: 'http://authentik:9000/application/o/userinfo/', - // token_url: 'http://authentik:9000/application/o/token/', - // logout_url: 'http://authentik:9000/application/o/npm/end-session/', - // identifier: 'preferred_username', - // scopes: [], - // auto_create_user: true - // } - // } - // }).then((data) => { - // cy.validateSwaggerSchema('put', 200, '/settings/{name}', data); - // expect(data.result).to.have.property('id'); - // expect(data.result.id).to.be.greaterThan(0); - // }); - - // cy.task('backendApiPut', { - // token: token, - // path: '/api/settings/auth-methods', - // data: { - // value: [ - // 'local', - // 'oauth' - // ] - // } - // }).then((data) => { - // cy.validateSwaggerSchema('put', 200, '/settings/{name}', data); - // expect(data.result).to.have.property('id'); - // expect(data.result.id).to.be.greaterThan(0); - // }); - }); - }); - - it.skip('Should log in with OAuth', () => { - // cy.task('backendApiGet', { - // path: '/oauth/login?redirect_base=' + encodeURI(Cypress.config('baseUrl')), - // }).then((data) => { - // expect(data).to.have.property('result'); - - // cy.origin('http://authentik:9000', {args: data.result}, (url) => { - // cy.visit(url); - // cy.get('ak-flow-executor') - // .shadow() - // .find('ak-stage-identification') - // .shadow() - // .find('input[name="uidField"]', { visible: true }) - // .type('cypress'); - - // cy.get('ak-flow-executor') - // .shadow() - // .find('ak-stage-identification') - // .shadow() - // .find('button[type="submit"]', { visible: true }) - // .click(); - - // cy.get('ak-flow-executor') - // .shadow() - // .find('ak-stage-password') - // .shadow() - // .find('input[name="password"]', { visible: true }) - // .type('fqXBfUYqHvYqiwBHWW7f'); - - // cy.get('ak-flow-executor') - // .shadow() - // .find('ak-stage-password') - // .shadow() - // .find('button[type="submit"]', { visible: true }) - // .click(); - // }) - - // // we should be logged in - // cy.get('#root p.chakra-text') - // .first() - // .should('have.text', 'Nginx Proxy Manager'); - - // // logout: - // cy.clearLocalStorage(); - // }); - }); - } -}); diff --git a/test/cypress/e2e/api/Streams.cy.js b/test/cypress/e2e/api/Streams.cy.js index 10809f8948..95c6dba134 100644 --- a/test/cypress/e2e/api/Streams.cy.js +++ b/test/cypress/e2e/api/Streams.cy.js @@ -3,7 +3,16 @@ describe('Streams', () => { let token; + const certFile = 'website1.pem'; + const keyFile = 'website1.key.pem'; + before(() => { + cy.createCustomCerts({ + domain: 'website1.example.com', + certFile, + keyFile, + }) + cy.resetUsers(); cy.getToken().then((tok) => { token = tok; @@ -22,15 +31,6 @@ describe('Streams', () => { }); }); - // Create a custom cert pair - cy.exec('mkcert -cert-file=/test/cypress/fixtures/website1.pem -key-file=/test/cypress/fixtures/website1.key.pem website1.example.com').then((result) => { - expect(result.exitCode).to.eq(0); - // Install CA - cy.exec('mkcert -install').then((result) => { - expect(result.exitCode).to.eq(0); - }); - }); - cy.exec('rm -f /test/results/testssl.json'); }); @@ -134,8 +134,8 @@ describe('Streams', () => { token: token, path: `/api/nginx/certificates/${certID}/upload`, files: { - certificate: 'website1.pem', - certificate_key: 'website1.key.pem', + certificate: certFile, + certificate_key: keyFile, }, }).then((data) => { cy.validateSwaggerSchema('post', 200, '/nginx/certificates/{certID}/upload', data); diff --git a/test/cypress/fixtures/.gitignore b/test/cypress/fixtures/.gitignore new file mode 100644 index 0000000000..cfaad76118 --- /dev/null +++ b/test/cypress/fixtures/.gitignore @@ -0,0 +1 @@ +*.pem diff --git a/test/cypress/fixtures/.gitkeep b/test/cypress/fixtures/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/cypress/fixtures/test.example.com-key.pem b/test/cypress/fixtures/test.example.com-key.pem deleted file mode 100644 index 307cdc307b..0000000000 --- a/test/cypress/fixtures/test.example.com-key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1n9j9C5Bes1nd -qACDckERauxXVNKCnUlUM1buGBx1xc+j2e2Ar23wUJJuWBY18VfT8yqfqVDktO2w -rbmvZvLuPmXePOKbIKS+XXh+2NG9L5bDG9rwGFCRXnbQj+GWCdMfzx14+CR1IHge -Yz6Cv/Si2/LJPCh/CoBfM4hUQJON3lxAWrWBpdbZnKYMrxuPBRfW9OuzTbCVXToQ -oxRAHiOR9081Xn1WeoKr7kVBIa5UphlvWXa12w1YmUwJu7YndnJGIavLWeNCVc7Z -Eo+nS8Wr/4QWicatIWZXpVaEOPhRoeplQDxNWg5b/Q26rYoVd7PrCmRs7sVcH79X -zGONeH1PAgMBAAECggEAANb3Wtwl07pCjRrMvc7WbC0xYIn82yu8/g2qtjkYUJcU -ia5lQbYN7RGCS85Oc/tkq48xQEG5JQWNH8b918jDEMTrFab0aUEyYcru1q9L8PL6 -YHaNgZSrMrDcHcS8h0QOXNRJT5jeGkiHJaTR0irvB526tqF3knbK9yW22KTfycUe -a0Z9voKn5xRk1DCbHi/nk2EpT7xnjeQeLFaTIRXbS68omkr4YGhwWm5OizoyEGZu -W0Zum5BkQyMr6kor3wdxOTG97ske2rcyvvHi+ErnwL0xBv0qY0Dhe8DpuXpDezqw -o72yY8h31Fu84i7sAj24YuE5Df8DozItFXQpkgbQ6QKBgQDPrufhvIFm2S/MzBdW -H8JxY7CJlJPyxOvc1NIl9RczQGAQR90kx52cgIcuIGEG6/wJ/xnGfMmW40F0DnQ+ -N+oLgB9SFxeLkRb7s9Z/8N3uIN8JJFYcerEOiRQeN2BXEEWJ7bUThNtsVrAcKoUh -ELsDmnHW/3V+GKwhd0vpk842+wKBgQDf4PGLG9PTE5tlAoyHFodJRd2RhTJQkwsU -MDNjLJ+KecLv+Nl+QiJhoflG1ccqtSFlBSCG067CDQ5LV0xm3mLJ7pfJoMgjcq31 -qjEmX4Ls91GuVOPtbwst3yFKjsHaSoKB5fBvWRcKFpBUezM7Qcw2JP3+dQT+bQIq -cMTkRWDSvQKBgQDOdCQFDjxg/lR7NQOZ1PaZe61aBz5P3pxNqa7ClvMaOsuEQ7w9 -vMYcdtRq8TsjA2JImbSI0TIg8gb2FQxPcYwTJKl+FICOeIwtaSg5hTtJZpnxX5LO -utTaC0DZjNkTk5RdOdWA8tihyUdGqKoxJY2TVmwGe2rUEDjFB++J4inkEwKBgB6V -g0nmtkxanFrzOzFlMXwgEEHF+Xaqb9QFNa/xs6XeNnREAapO7JV75Cr6H2hFMFe1 -mJjyqCgYUoCWX3iaHtLJRnEkBtNY4kzyQB6m46LtsnnnXO/dwKA2oDyoPfFNRoDq -YatEd3JIXNU9s2T/+x7WdOBjKhh72dTkbPFmTPDdAoGAU6rlPBevqOFdObYxdPq8 -EQWu44xqky3Mf5sBpOwtu6rqCYuziLiN7K4sjN5GD5mb1cEU+oS92ZiNcUQ7MFXk -8yTYZ7U0VcXyAcpYreWwE8thmb0BohJBr+Mp3wLTx32x0HKdO6vpUa0d35LUTUmM -RrKmPK/msHKK/sVHiL+NFqo= ------END PRIVATE KEY----- diff --git a/test/cypress/fixtures/test.example.com.pem b/test/cypress/fixtures/test.example.com.pem deleted file mode 100644 index 16340cdfd2..0000000000 --- a/test/cypress/fixtures/test.example.com.pem +++ /dev/null @@ -1,26 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEYDCCAsigAwIBAgIRAPoSC0hvitb26ODMlsH6YbowDQYJKoZIhvcNAQELBQAw -gZExHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEzMDEGA1UECwwqamN1 -cm5vd0BKYW1pZXMtTGFwdG9wLmxvY2FsIChKYW1pZSBDdXJub3cpMTowOAYDVQQD -DDFta2NlcnQgamN1cm5vd0BKYW1pZXMtTGFwdG9wLmxvY2FsIChKYW1pZSBDdXJu -b3cpMB4XDTI0MTAwOTA3MjIxN1oXDTI3MDEwOTA3MjIxN1owXjEnMCUGA1UEChMe -bWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTMwMQYDVQQLDCpqY3Vybm93 -QEphbWllcy1MYXB0b3AubG9jYWwgKEphbWllIEN1cm5vdykwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQC1n9j9C5Bes1ndqACDckERauxXVNKCnUlUM1bu -GBx1xc+j2e2Ar23wUJJuWBY18VfT8yqfqVDktO2wrbmvZvLuPmXePOKbIKS+XXh+ -2NG9L5bDG9rwGFCRXnbQj+GWCdMfzx14+CR1IHgeYz6Cv/Si2/LJPCh/CoBfM4hU -QJON3lxAWrWBpdbZnKYMrxuPBRfW9OuzTbCVXToQoxRAHiOR9081Xn1WeoKr7kVB -Ia5UphlvWXa12w1YmUwJu7YndnJGIavLWeNCVc7ZEo+nS8Wr/4QWicatIWZXpVaE -OPhRoeplQDxNWg5b/Q26rYoVd7PrCmRs7sVcH79XzGONeH1PAgMBAAGjZTBjMA4G -A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSB -/vfmBUd4W7CvyEMl7YpMVQs8vTAbBgNVHREEFDASghB0ZXN0LmV4YW1wbGUuY29t -MA0GCSqGSIb3DQEBCwUAA4IBgQASwON/jPAHzcARSenY0ZGY1m5OVTYoQ/JWH0oy -l8SyFCQFEXt7UHDD/eTtLT0vMyc190nP57P8lTnZGf7hSinZz1B1d6V4cmzxpk0s -VXZT+irL6bJVJoMBHRpllKAhGULIo33baTrWFKA0oBuWx4AevSWKcLW5j87kEawn -ATCuMQ1I3ifR1mSlB7X8fb+vF+571q0NGuB3a42j6rdtXJ6SmH4+9B4qO0sfHDNt -IImpLCH/tycDpcYrGSCn1QrekFG1bSEh+Bb9i8rqMDSDsYrTFPZTuOQ3EtjGni9u -m+rEP3OyJg+md8c+0LVP7/UU4QWWnw3/Wolo5kSCxE8vNTFqi4GhVbdLnUtcIdTV -XxuR6cKyW87Snj1a0nG76ZLclt/akxDhtzqeV60BO0p8pmiev8frp+E94wFNYCmp -1cr3CnMEGRaficLSDFC6EBENzlZW2BQT6OMIV+g0NBgSyQe39s2zcdEl5+SzDVuw -hp8bJUp/QN7pnOVCDbjTQ+HVMXw= ------END CERTIFICATE----- diff --git a/test/cypress/plugins/index.mjs b/test/cypress/plugins/index.mjs index 1058d63381..6a2ff9ffb8 100644 --- a/test/cypress/plugins/index.mjs +++ b/test/cypress/plugins/index.mjs @@ -1,3 +1,4 @@ +import { plugin as cypressGrepPlugin } from "@cypress/grep/plugin"; import { SwaggerValidation } from "@jc21/cypress-swagger-validation"; import chalk from "chalk"; import backendTask from "./backendApi/task.mjs"; @@ -11,6 +12,8 @@ export default (on, config) => { ); } + cypressGrepPlugin(config); + // Plugin Events on("task", SwaggerValidation(config)); on("task", backendTask(config)); @@ -22,6 +25,11 @@ export default (on, config) => { return null; }, }); + on("task", { + getFixturesFolder() { + return config.fixturesFolder; + }, + }); return config; }; diff --git a/test/cypress/support/commands.mjs b/test/cypress/support/commands.mjs index 7d9224d09b..03c5bf2243 100644 --- a/test/cypress/support/commands.mjs +++ b/test/cypress/support/commands.mjs @@ -48,14 +48,16 @@ Cypress.Commands.add("validateSwaggerFile", (url, savePath) => { * @param {*} data The API response data to check against the swagger schema */ Cypress.Commands.add('validateSwaggerSchema', (method, code, path, data) => { - cy.task('validateSwaggerSchema', { - file: Cypress.env('swaggerBase'), - endpoint: path, - method: method, - statusCode: code, - responseSchema: data, - verbose: true - }).should('equal', null); + cy.env(['swaggerBase']).then(({ swaggerBase }) => { + cy.task('validateSwaggerSchema', { + file: swaggerBase, + endpoint: path, + method: method, + statusCode: code, + responseSchema: data, + verbose: true + }).should('equal', null); + }); }); Cypress.Commands.add('createInitialUser', (defaultUser) => { @@ -151,3 +153,32 @@ Cypress.Commands.add('waitForCertificateStatus', (token, certID, expected, timeo interval: 5000 }); }); + +// Creates CA files for testing, if they already exist they will be deleted +// and recreated with the same content. This is to ensure that the files exist +// for testing and are in a known state. +Cypress.Commands.add('createCustomCerts', ({domain, certFile, keyFile}) => { + domain = domain || 'website1.example.com'; + certFile = certFile || 'website1.pem'; + keyFile = keyFile || 'website1.key.pem'; + + return cy.task('getFixturesFolder').then((fixturesFolder) => { + const fullCertFile = `${fixturesFolder}/${certFile}`; + const fullKeyFile = `${fixturesFolder}/${keyFile}`; + const cmd = `mkcert -cert-file="${fullCertFile}" -key-file="${fullKeyFile}" "${domain}"`; + cy.log(`Creating custom certs with command: ${cmd}`); + return cy.exec(cmd).then((result) => { + cy.log(`mkcert output:\n${JSON.stringify(result)}`); + expect(result.exitCode).to.eq(0); + return cy.exec('mkcert -install').then((result2) => { + cy.log(`mkcert install output:\n${JSON.stringify(result2)}`); + expect(result2.exitCode).to.eq(0); + }).then(() => { + return cy.wrap({ + certFile: fullCertFile, + keyFile: fullKeyFile, + }); + }); + }); + }); +}); diff --git a/test/cypress/support/e2e.js b/test/cypress/support/e2e.js index 2d8a13a76b..f8ef2a6949 100644 --- a/test/cypress/support/e2e.js +++ b/test/cypress/support/e2e.js @@ -1,6 +1,10 @@ -import './commands.mjs'; +import "./commands.mjs"; -Cypress.on('uncaught:exception', (/*err, runnable*/) => { +import { register as registerCypressGrep } from "@cypress/grep"; + +registerCypressGrep(); + +Cypress.on("uncaught:exception", (/*err, runnable*/) => { // returning false here prevents Cypress from // failing the test return false; diff --git a/test/cypress/support/task.mjs b/test/cypress/support/task.mjs new file mode 100644 index 0000000000..edc5015c69 --- /dev/null +++ b/test/cypress/support/task.mjs @@ -0,0 +1,100 @@ +import fs from "node:fs"; +import FormData from "form-data"; +import Client from "./client.mjs"; +import logger from "./logger.mjs"; + +export default (config) => { + logger("Client Ready using", config.baseUrl); + + return { + /** + * @param {object} options + * @param {string} options.path API path + * @param {string} [options.token] JWT + * @param {bool} [options.returnOnError] If true, will return instead of throwing errors + * @returns {string} + */ + backendApiGet: (options) => { + const api = new Client(config); + api.setToken(options.token); + return api.request("get", options.path, options.returnOnError || false); + }, + + /** + * @param {object} options + * @param {string} options.token JWT + * @param {string} options.path API path + * @param {object} options.data + * @param {bool} [options.returnOnError] If true, will return instead of throwing errors + * @returns {string} + */ + backendApiPost: (options) => { + const api = new Client(config); + api.setToken(options.token); + return api.request( + "post", + options.path, + options.returnOnError || false, + options.data, + ); + }, + + /** + * @param {object} options + * @param {string} options.token JWT + * @param {string} options.path API path + * @param {object} options.files + * @param {bool} [options.returnOnError] If true, will return instead of throwing errors + * @returns {string} + */ + backendApiPostFiles: (options) => { + const api = new Client(config); + api.setToken(options.token); + + const form = new FormData(); + for (const [key, value] of Object.entries(options.files)) { + form.append( + key, + fs.createReadStream(`${config.fixturesFolder}/${value}`), + ); + } + return api.postForm(options.path, form, options.returnOnError || false); + }, + + /** + * @param {object} options + * @param {string} options.token JWT + * @param {string} options.path API path + * @param {object} options.data + * @param {bool} [options.returnOnError] If true, will return instead of throwing errors + * @returns {string} + */ + backendApiPut: (options) => { + const api = new Client(config); + api.setToken(options.token); + return api.request( + "put", + options.path, + options.returnOnError || false, + options.data, + ); + }, + + /** + * @param {object} options + * @param {string} options.token JWT + * @param {string} options.path API path + * @param {bool} [options.returnOnError] If true, will return instead of throwing errors + * @returns {string} + */ + backendApiDelete: (options) => { + const api = new Client(config); + api.setToken(options.token); + return api.request( + "delete", + options.path, + options.returnOnError || false, + ); + }, + }; +}; diff --git a/test/package.json b/test/package.json index bb454ba04c..c1a79d9b88 100644 --- a/test/package.json +++ b/test/package.json @@ -4,19 +4,20 @@ "description": "", "main": "index.js", "dependencies": { + "@cypress/grep": "^6.0.0", "@jc21/cypress-swagger-validation": "^0.3.2", - "@quobix/vacuum": "^0.24.0", - "axios": "^1.13.6", + "@quobix/vacuum": "^0.26.4", + "axios": "^1.16.0", "chalk": "^5.6.2", - "cypress": "^15.11.0", + "cypress": "^15.15.0", "cypress-multi-reporters": "^2.0.5", "cypress-wait-until": "^3.0.2", - "eslint": "^10.0.2", + "eslint": "^10.3.0", "eslint-plugin-align-assignments": "^1.1.2", - "eslint-plugin-chai-friendly": "^1.1.0", - "eslint-plugin-cypress": "^6.1.0", + "eslint-plugin-chai-friendly": "^1.2.0", + "eslint-plugin-cypress": "^6.4.1", "form-data": "^4.0.5", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "mocha": "^11.7.5", "mocha-junit-reporter": "^2.2.1" }, diff --git a/test/yarn.lock b/test/yarn.lock index e1be367b1e..0f41aea86c 100644 --- a/test/yarn.lock +++ b/test/yarn.lock @@ -43,10 +43,56 @@ ajv-draft-04 "^1.0.0" call-me-maybe "^1.0.2" -"@cypress/request@^3.0.10": - version "3.0.10" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.10.tgz#e09c695e8460a82acafe6cfaf089cf2ca06dc054" - integrity sha512-hauBrOdvu08vOsagkZ/Aju5XuiZx6ldsLfByg1htFeldhex+PeMrYauANzFsMJeAA0+dyPLbDoX2OYuvVoLDkQ== +"@babel/helper-plugin-utils@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz#c0a0766f1a13617d8a17407d7ab8f9d486225ea4" + integrity sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw== + +"@babel/helper-string-parser@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f" + integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw== + +"@babel/helper-validator-identifier@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" + integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== + +"@babel/parser@^7.27.2": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334" + integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg== + dependencies: + "@babel/types" "^7.29.7" + +"@babel/plugin-syntax-jsx@^7.27.1": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz#622c16f9ad63782fe6e83dadc7e40330744b7f1e" + integrity sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A== + dependencies: + "@babel/helper-plugin-utils" "^7.29.7" + +"@babel/types@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92" + integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA== + dependencies: + "@babel/helper-string-parser" "^7.29.7" + "@babel/helper-validator-identifier" "^7.29.7" + +"@cypress/grep@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@cypress/grep/-/grep-6.0.0.tgz#fcc5f42d0086464e2752c4d84ac563c3cd21eb77" + integrity sha512-n3PCeqt8OwmLFz310igbRUm3qDE5WJgM9LW+2ejdULfMu2Sudqg3UX8koC8/JU/+ZcJ5UbaQAap1Nbi0QvzXwA== + dependencies: + debug "^4.3.4" + find-test-names "^1.28.18" + globby "^11.0.4" + +"@cypress/request@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-4.0.0.tgz#2bc3a10a7d50f338dc644dc5fd14a60107c6f3e2" + integrity sha512-wGTQfwDMMMiz/muFw4YbCLwTh0uZsXKK+6zWBzftADpitSi6iM62C8GzEhNcng2srUiGPksOriQkA8zakW2R0g== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -65,7 +111,6 @@ safe-buffer "^5.1.2" tough-cookie "^5.0.0" tunnel-agent "^0.6.0" - uuid "^8.3.2" "@cypress/xvfb@^1.2.4": version "1.2.4" @@ -87,40 +132,40 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== -"@eslint/config-array@^0.23.2": - version "0.23.2" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.2.tgz#db85beeff7facc685a5775caacb1c845669b9470" - integrity sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A== +"@eslint/config-array@^0.23.5": + version "0.23.5" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.5.tgz#56e86d243049195d8acc0c06a1b3dfdc3fa3de95" + integrity sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA== dependencies: - "@eslint/object-schema" "^3.0.2" + "@eslint/object-schema" "^3.0.5" debug "^4.3.1" - minimatch "^10.2.1" + minimatch "^10.2.4" -"@eslint/config-helpers@^0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.2.tgz#314c7b03d02a371ad8c0a7f6821d5a8a8437ba9d" - integrity sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ== +"@eslint/config-helpers@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.6.0.tgz#ef9a36881d39dfd5dbeac22b0da997fabfb08b03" + integrity sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA== dependencies: - "@eslint/core" "^1.1.0" + "@eslint/core" "^1.2.1" -"@eslint/core@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.1.0.tgz#51f5cd970e216fbdae6721ac84491f57f965836d" - integrity sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw== +"@eslint/core@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.2.1.tgz#c1da7cd1b82fa8787f98b5629fb811848a1b63ce" + integrity sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ== dependencies: "@types/json-schema" "^7.0.15" -"@eslint/object-schema@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.2.tgz#c59c6a94aa4b428ed7f1615b6a4495c0a21f7a22" - integrity sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw== +"@eslint/object-schema@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.5.tgz#88e9bf4d11d2b19c082e78ebe7ce88724a5eb091" + integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw== -"@eslint/plugin-kit@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz#e0cb12ec66719cb2211ad36499fb516f2a63899d" - integrity sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ== +"@eslint/plugin-kit@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz#c4125fd015eceeb09b793109fdbcd4dd0a02d346" + integrity sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ== dependencies: - "@eslint/core" "^1.1.0" + "@eslint/core" "^1.2.1" levn "^0.4.1" "@humanfs/core@^0.19.1": @@ -186,19 +231,40 @@ resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@quobix/vacuum@^0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@quobix/vacuum/-/vacuum-0.24.0.tgz#7461d0f745e1a97827fc20b04f8c1701a5f1a0e0" - integrity sha512-Ee7c2XF6FY3bf3BzOCh/Ku2abxMN2iqYADHf933EkgAQ4u3x3eXXvlAa8UQo/lt7ZhWk0HHXQ55hnMhpbJsuPg== +"@quobix/vacuum@^0.26.4": + version "0.26.8" + resolved "https://registry.yarnpkg.com/@quobix/vacuum/-/vacuum-0.26.8.tgz#1a912c10655ab59c5af7179ff0cbe76609fb44de" + integrity sha512-iTF9MScMs+oqoE9QXQZxo6AfyZMg5YOtAL97oLuZ6/1TBRiq/+tRK2STtaVFxs5Vd05xHeO9hRdssUurzmkIpQ== dependencies: https-proxy-agent "^7.0.6" node-fetch "^3.3.2" - tar "^7.5.2" + tar "^7.5.13" "@types/esrecurse@^4.3.1": version "4.3.1" @@ -215,13 +281,6 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/node@*": - version "25.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-25.3.0.tgz#749b1bd4058e51b72e22bd41e9eab6ebd0180470" - integrity sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A== - dependencies: - undici-types "~7.18.0" - "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -237,36 +296,35 @@ resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.6.tgz#d785ee90c52d7cc020e249c948c36f7b32d1e217" integrity sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA== -"@types/yauzl@^2.9.1": - version "2.10.3" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" - integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== - dependencies: - "@types/node" "*" - acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.16.0: +acorn-walk@^8.2.0: + version "8.3.5" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" + integrity sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.16.0: version "8.16.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + agent-base@^7.1.2: version "7.1.4" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - ajv-draft-04@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" @@ -299,24 +357,19 @@ ajv@^8.0.0, ajv@^8.17.1: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -ansi-colors@^4.1.1: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" - integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== - -ansi-escapes@^4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== +ansi-escapes@^7.0.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.3.0.tgz#5395bb74b2150a4a1d6e3c2565f4aeca78d28627" + integrity sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg== dependencies: - type-fest "^0.21.3" + environment "^1.0.0" ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: +ansi-regex@^6.0.1, ansi-regex@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== @@ -328,7 +381,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^6.1.0: +ansi-styles@^6.1.0, ansi-styles@^6.2.1, ansi-styles@^6.2.3: version "6.2.3" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== @@ -343,6 +396,11 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + asn1@~0.2.3: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" @@ -355,11 +413,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -380,14 +433,15 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== -axios@^1.13.6, axios@^1.7.7: - version "1.13.6" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.6.tgz#c3f92da917dc209a15dd29936d20d5089b6b6c98" - integrity sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ== +axios@^1.16.0, axios@^1.7.7: + version "1.16.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.16.1.tgz#517e29291d19d6e8cf919ff264f4fe157261ba12" + integrity sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A== dependencies: - follow-redirects "^1.15.11" + follow-redirects "^1.16.0" form-data "^4.0.5" - proxy-from-env "^1.1.0" + https-proxy-agent "^5.0.1" + proxy-from-env "^2.1.0" balanced-match@^1.0.0: version "1.0.2" @@ -428,13 +482,20 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -brace-expansion@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.2.tgz#b6c16d0791087af6c2bc463f52a8142046c06b6f" - integrity sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw== +brace-expansion@^5.0.5: + version "5.0.6" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285" + integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g== dependencies: balanced-match "^4.0.2" +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + browser-stdout@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" @@ -453,7 +514,7 @@ buffer@^5.7.1: base64-js "^1.3.1" ieee754 "^1.1.13" -cachedir@^2.3.0: +cachedir@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d" integrity sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ== @@ -524,17 +585,12 @@ ci-info@^4.1.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== +cli-cursor@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38" + integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== dependencies: - restore-cursor "^3.1.0" + restore-cursor "^5.0.0" cli-table3@0.6.1: version "0.6.1" @@ -545,13 +601,13 @@ cli-table3@0.6.1: optionalDependencies: colors "1.4.0" -cli-truncate@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" - integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== +cli-truncate@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-5.2.0.tgz#c8e72aaca8339c773d128c36e0a17c6315b694eb" + integrity sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw== dependencies: - slice-ansi "^3.0.0" - string-width "^4.2.0" + slice-ansi "^8.0.0" + string-width "^8.2.0" cliui@^8.0.1: version "8.0.1" @@ -574,7 +630,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^2.0.16: +colorette@^2.0.20: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== @@ -634,12 +690,12 @@ cypress-wait-until@^3.0.2: resolved "https://registry.yarnpkg.com/cypress-wait-until/-/cypress-wait-until-3.0.2.tgz#c90dddfa4c46a2c422f5b91d486531c560bae46e" integrity sha512-iemies796dD5CgjG5kV0MnpEmKSH+s7O83ZoJLVzuVbZmm4lheMsZqAVT73hlMx4QlkwhxbyUzhOBUOZwoOe0w== -cypress@^15.11.0: - version "15.11.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-15.11.0.tgz#7402d0a2bb4573b6c6655191ad170cff1985ff3f" - integrity sha512-NXDE6/fqZuzh1Zr53nyhCCa4lcANNTYWQNP9fJO+tzD3qVTDaTUni5xXMuigYjMujQ7CRiT9RkJJONmPQSsDFw== +cypress@^15.15.0: + version "15.16.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-15.16.0.tgz#482f77e6f85aee98b94a5ad844d36f69dc212c28" + integrity sha512-fy0M0c9xDLEp4v9y7LLKFeAQhIdDsobxDSKpD3JcZpqQefjy9TSzEyVV3HA0zu7hUi0bGHlSYlI7ASub8wgR9A== dependencies: - "@cypress/request" "^3.0.10" + "@cypress/request" "^4.0.0" "@cypress/xvfb" "^1.2.4" "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" @@ -648,25 +704,21 @@ cypress@^15.11.0: blob-util "^2.0.2" bluebird "^3.7.2" buffer "^5.7.1" - cachedir "^2.3.0" + cachedir "^2.4.0" chalk "^4.1.0" ci-info "^4.1.0" - cli-cursor "^3.1.0" cli-table3 "0.6.1" commander "^6.2.1" common-tags "^1.8.0" dayjs "^1.10.4" debug "^4.3.4" - enquirer "^2.3.6" eventemitter2 "6.4.7" execa "4.1.0" executable "^4.1.1" - extract-zip "2.0.1" - figures "^3.2.0" fs-extra "^9.1.0" hasha "5.2.2" is-installed-globally "~0.4.0" - listr2 "^3.8.3" + listr2 "^9.0.5" lodash "^4.17.23" log-symbols "^4.0.0" minimist "^1.2.8" @@ -681,7 +733,7 @@ cypress@^15.11.0: tree-kill "1.2.2" tslib "1.14.1" untildify "^4.0.0" - yauzl "^2.10.0" + yauzl "^3.3.1" dashdash@^1.12.0: version "1.14.1" @@ -700,7 +752,7 @@ dayjs@^1.10.4: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.19.tgz#15dc98e854bb43917f12021806af897c58ae2938" integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw== -debug@4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0: +debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -734,6 +786,13 @@ diff@^7.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a" integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -756,6 +815,11 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +emoji-regex@^10.3.0: + version "10.6.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.6.0.tgz#bf3d6e8f7f8fd22a65d9703475bc0147357a6b0d" + integrity sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -773,13 +837,10 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enquirer@^2.3.6: - version "2.4.1" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" - integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== - dependencies: - ansi-colors "^4.1.1" - strip-ansi "^6.0.1" +environment@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== es-define-property@^1.0.1: version "1.0.1" @@ -813,11 +874,6 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -839,22 +895,22 @@ eslint-plugin-align-assignments@^1.1.2: resolved "https://registry.yarnpkg.com/eslint-plugin-align-assignments/-/eslint-plugin-align-assignments-1.1.2.tgz#83e1a8a826d4adf29e82b52d0bb39c88b301b576" integrity sha512-I1ZJgk9EjHfGVU9M2Ex8UkVkkjLL5Y9BS6VNnQHq79eHj2H4/Cgxf36lQSUTLgm2ntB03A2NtF+zg9fyi5vChg== -eslint-plugin-chai-friendly@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-1.1.0.tgz#e21cbd301ff0d4c1c563d3bbc865e9ac0296fb7c" - integrity sha512-+T1rClpDdXkgBAhC16vRQMI5umiWojVqkj9oUTdpma50+uByCZM/oBfxitZiOkjMRlm725mwFfz/RVgyDRvCKA== +eslint-plugin-chai-friendly@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-1.2.0.tgz#2a54b481bcbc26ab5a581b2b7e7c304465837c15" + integrity sha512-um2pBb4ZXNCoTRPRAWiUaXeIaw1dRaPOEZ+G/qcZqfyTdkCXXwOBctnfnbIRbZiQf4AXl3ImV1grt423SlK+mg== -eslint-plugin-cypress@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-6.1.0.tgz#5619ceb67d09d74cb5225408bb36c962afb94880" - integrity sha512-B8sxtNpINDxFkmsu1qKYjg70VsP8SGneEXgLcZMk1bUZcW08S+JyaiMdof1x6dmt02FgOD7YkT4wOaOD5HotJw== +eslint-plugin-cypress@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-6.4.1.tgz#2aa64e5a2dcc56b8b916fdf8398a81b31a61c462" + integrity sha512-8mnfR3q0Lr41Fu9SYZGeZ5nbSBgtS44+bbtSs7k0KvfoQ2pPmK43IvaTP6FGTfwBTN6S7w027CpqjrLAd65AYA== dependencies: - globals "^17.3.0" + globals "^17.6.0" -eslint-scope@^9.1.1: - version "9.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.1.tgz#f6a209486e38bd28356b5feb07d445cc99c89967" - integrity sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw== +eslint-scope@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.2.tgz#b9de6ace2fab1cff24d2e58d85b74c8fcea39802" + integrity sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ== dependencies: "@types/esrecurse" "^4.3.1" "@types/estree" "^1.0.8" @@ -871,17 +927,17 @@ eslint-visitor-keys@^5.0.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== -eslint@^10.0.2: - version "10.0.2" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.0.2.tgz#1009263467591810320f2e1ad52b8a750d1acbab" - integrity sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw== +eslint@^10.3.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.4.0.tgz#d86b6c405de0f19f3318c47139b8cb6771b3f592" + integrity sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ== dependencies: "@eslint-community/eslint-utils" "^4.8.0" "@eslint-community/regexpp" "^4.12.2" - "@eslint/config-array" "^0.23.2" - "@eslint/config-helpers" "^0.5.2" - "@eslint/core" "^1.1.0" - "@eslint/plugin-kit" "^0.6.0" + "@eslint/config-array" "^0.23.5" + "@eslint/config-helpers" "^0.6.0" + "@eslint/core" "^1.2.1" + "@eslint/plugin-kit" "^0.7.1" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/retry" "^0.4.2" @@ -890,9 +946,9 @@ eslint@^10.0.2: cross-spawn "^7.0.6" debug "^4.3.2" escape-string-regexp "^4.0.0" - eslint-scope "^9.1.1" + eslint-scope "^9.1.2" eslint-visitor-keys "^5.0.1" - espree "^11.1.1" + espree "^11.2.0" esquery "^1.7.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -903,14 +959,14 @@ eslint@^10.0.2: imurmurhash "^0.1.4" is-glob "^4.0.0" json-stable-stringify-without-jsonify "^1.0.1" - minimatch "^10.2.1" + minimatch "^10.2.4" natural-compare "^1.4.0" optionator "^0.9.3" -espree@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-11.1.1.tgz#866f6bc9ccccd6f28876b7a6463abb281b9cb847" - integrity sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ== +espree@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-11.2.0.tgz#01d5e47dc332aaba3059008362454a8cc34ccaa5" + integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw== dependencies: acorn "^8.16.0" acorn-jsx "^5.3.2" @@ -955,6 +1011,11 @@ eventemitter2@6.4.7: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== +eventemitter3@^5.0.1: + version "5.0.4" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.4.tgz#a86d66170433712dde814707ac52b5271ceb1feb" + integrity sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== + execa@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" @@ -982,17 +1043,6 @@ extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extract-zip@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -1008,6 +1058,17 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-glob@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -1019,16 +1080,21 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-uri@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" - integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.2.tgz#8af3d4fc9d3e71b11572cc2673b514a7d1a8c8ec" + integrity sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ== -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== +fastq@^1.6.0: + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== dependencies: - pend "~1.2.0" + reusify "^1.0.4" + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" @@ -1038,13 +1104,6 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4: node-domexception "^1.0.0" web-streams-polyfill "^3.0.3" -figures@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - file-entry-cache@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" @@ -1052,6 +1111,25 @@ file-entry-cache@^8.0.0: dependencies: flat-cache "^4.0.0" +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-test-names@^1.28.18: + version "1.29.19" + resolved "https://registry.yarnpkg.com/find-test-names/-/find-test-names-1.29.19.tgz#365a18aefbc55f88ba5c96b01145c9b616b0d0d2" + integrity sha512-fSO2GXgOU6dH+FdffmRXYN/kLdnd8zkBGIZrKsmAdfLSFUUDLpDFF7+F/h+wjmjDWQmMgD8hPfJZR+igiEUQHQ== + dependencies: + "@babel/parser" "^7.27.2" + "@babel/plugin-syntax-jsx" "^7.27.1" + acorn-walk "^8.2.0" + debug "^4.3.3" + simple-bin-help "^1.8.0" + tinyglobby "^0.2.13" + find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -1074,14 +1152,14 @@ flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatted@^3.2.9: - version "3.3.3" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" - integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== -follow-redirects@^1.15.11: - version "1.15.11" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" - integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== +follow-redirects@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" + integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== foreground-child@^3.1.0: version "3.3.1" @@ -1134,6 +1212,11 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-east-asian-width@^1.0.0, get-east-asian-width@^1.3.1, get-east-asian-width@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz#216900f91df11a8b2c198c3e1d93d6c035a776b9" + integrity sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA== + get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" @@ -1158,7 +1241,7 @@ get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" -get-stream@^5.0.0, get-stream@^5.1.0: +get-stream@^5.0.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== @@ -1172,6 +1255,13 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" @@ -1198,10 +1288,22 @@ global-dirs@^3.0.0: dependencies: ini "2.0.0" -globals@^17.3.0: - version "17.3.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-17.3.0.tgz#8b96544c2fa91afada02747cc9731c002a96f3b9" - integrity sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw== +globals@^17.6.0: + version "17.6.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.6.0.tgz#0f0be018d5cca8690e6375ead1f65c4bb96191fc" + integrity sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA== + +globby@^11.0.4: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" gopd@^1.2.0: version "1.2.0" @@ -1259,6 +1361,14 @@ http-signature@~1.4.0: jsprim "^2.0.2" sshpk "^1.18.0" +https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + https-proxy-agent@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" @@ -1287,11 +1397,6 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - ini@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" @@ -1312,7 +1417,14 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^4.0.0, is-glob@^4.0.3: +is-fullwidth-code-point@^5.0.0, is-fullwidth-code-point@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz#046b2a6d4f6b156b2233d3207d4b5a9783999b98" + integrity sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ== + dependencies: + get-east-asian-width "^1.3.1" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -1327,6 +1439,11 @@ is-installed-globally@~0.4.0: global-dirs "^3.0.0" is-path-inside "^3.0.2" +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + is-path-inside@^3.0.2, is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -1456,19 +1573,17 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -listr2@^3.8.3: - version "3.14.0" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" - integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== - dependencies: - cli-truncate "^2.1.0" - colorette "^2.0.16" - log-update "^4.0.0" - p-map "^4.0.0" - rfdc "^1.3.0" - rxjs "^7.5.1" - through "^2.3.8" - wrap-ansi "^7.0.0" +listr2@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-9.0.5.tgz#92df7c4416a6da630eb9ef46da469b70de97b316" + integrity sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g== + dependencies: + cli-truncate "^5.0.0" + colorette "^2.0.20" + eventemitter3 "^5.0.1" + log-update "^6.1.0" + rfdc "^1.4.1" + wrap-ansi "^9.0.0" locate-path@^6.0.0: version "6.0.0" @@ -1482,10 +1597,10 @@ lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== -lodash@^4.17.21, lodash@^4.17.23: - version "4.17.23" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" - integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== +lodash@^4.17.21, lodash@^4.17.23, lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== log-symbols@^4.0.0, log-symbols@^4.1.0: version "4.1.0" @@ -1495,15 +1610,16 @@ log-symbols@^4.0.0, log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -log-update@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" - integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== +log-update@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4" + integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== dependencies: - ansi-escapes "^4.3.0" - cli-cursor "^3.1.0" - slice-ansi "^4.0.0" - wrap-ansi "^6.2.0" + ansi-escapes "^7.0.0" + cli-cursor "^5.0.0" + slice-ansi "^7.1.0" + strip-ansi "^7.1.0" + wrap-ansi "^9.0.0" lru-cache@^10.2.0: version "10.4.3" @@ -1529,6 +1645,19 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" @@ -1546,12 +1675,17 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^10.2.1: - version "10.2.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.2.tgz#361603ee323cfb83496fea2ae17cc44ea4e1f99f" - integrity sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw== +mimic-function@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" + integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== + +minimatch@^10.2.4: + version "10.2.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" + integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== dependencies: - brace-expansion "^5.0.2" + brace-expansion "^5.0.5" minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.5" @@ -1594,9 +1728,9 @@ mocha-junit-reporter@^2.2.1: xml "^1.0.1" mocha@^11.7.5: - version "11.7.5" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.7.5.tgz#58f5bbfa5e0211ce7e5ee6128107cefc2515a627" - integrity sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig== + version "11.7.6" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.7.6.tgz#ebbe22989d04cbb9424a36307320476624c41a33" + integrity sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA== dependencies: browser-stdout "^1.3.1" chokidar "^4.0.1" @@ -1670,6 +1804,13 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" +onetime@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" + integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== + dependencies: + mimic-function "^5.0.0" + openapi-types@^12.1.3: version "12.1.3" resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3" @@ -1706,13 +1847,6 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - package-json-from-dist@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" @@ -1736,6 +1870,11 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -1751,6 +1890,16 @@ picocolors@^1.1.0, picocolors@^1.1.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== +picomatch@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" + integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== + +picomatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + pify@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -1776,10 +1925,10 @@ proxy-from-env@1.0.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A== -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +proxy-from-env@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz#a7487568adad577cfaaa7e88c49cab3ab3081aba" + integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA== pump@^3.0.0: version "3.0.3" @@ -1801,6 +1950,11 @@ qs@~6.14.1: dependencies: side-channel "^1.1.0" +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -1830,25 +1984,30 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== +restore-cursor@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7" + integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" + onetime "^7.0.0" + signal-exit "^4.1.0" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== -rfdc@^1.3.0: +rfdc@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== -rxjs@^7.5.1: - version "7.8.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" - integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: - tslib "^2.1.0" + queue-microtask "^1.2.2" safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2: version "5.2.1" @@ -1929,28 +2088,36 @@ signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: +signal-exit@^4.0.1, signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -slice-ansi@^3.0.0: +simple-bin-help@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/simple-bin-help/-/simple-bin-help-1.8.0.tgz#21bb82c6bccd9fa8678f9c0fadf2956b54e2160a" + integrity sha512-0LxHn+P1lF5r2WwVB/za3hLRIsYoLaNq1CXqjbrs3ZvLuvlWnRKrUjEWzV7umZL7hpQ7xULiQMV+0iXdRa5iFg== + +slash@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" - integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^7.1.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.2.tgz#adf7be70aa6d72162d907cd0e6d5c11f507b5403" + integrity sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w== dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" + ansi-styles "^6.2.1" + is-fullwidth-code-point "^5.0.0" -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== +slice-ansi@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-8.0.0.tgz#22d0b66d18bc5c57f488bfcf36cbde3bef731537" + integrity sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg== dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" + ansi-styles "^6.2.3" + is-fullwidth-code-point "^5.1.0" source-map@~0.6.1: version "0.6.1" @@ -2006,6 +2173,23 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string-width@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + +string-width@^8.2.0: + version "8.2.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-8.2.1.tgz#165089cfa527cc88fbc23dd73313f5e334af1ea1" + integrity sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA== + dependencies: + get-east-asian-width "^1.5.0" + strip-ansi "^7.1.2" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -2027,6 +2211,13 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" +strip-ansi@^7.1.0, strip-ansi@^7.1.2: + version "7.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.2.0.tgz#d22a269522836a627af8d04b5c3fd2c7fa3e32e3" + integrity sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== + dependencies: + ansi-regex "^6.2.2" + strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -2052,14 +2243,14 @@ supports-color@^8.1.1: has-flag "^4.0.0" systeminformation@^5.31.1: - version "5.31.1" - resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.31.1.tgz#5f88aa1db7470af87b6288baf1738603cafd1c4a" - integrity sha512-6pRwxoGeV/roJYpsfcP6tN9mep6pPeCtXbUOCdVa0nme05Brwcwdge/fVNhIZn2wuUitAKZm4IYa7QjnRIa9zA== + version "5.31.6" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.31.6.tgz#2da4979a7262974fd068a3a306ded30aed6127c0" + integrity sha512-Uv2b2uGGM6ns+26czgW2cYRabYdnswM0ddSOOlryHOaelzsmDSet1iM/NT7VOYxW8x/BW+HkY+b1Ve2pLTSGSA== -tar@^7.5.2: - version "7.5.9" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.9.tgz#817ac12a54bc4362c51340875b8985d7dc9724b8" - integrity sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg== +tar@^7.5.13: + version "7.5.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.15.tgz#afe6d1316cddf614a566e3813e42fe01aed46fee" + integrity sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ== dependencies: "@isaacs/fs-minipass" "^4.0.0" chownr "^3.0.0" @@ -2072,10 +2263,13 @@ throttleit@^1.0.0: resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.1.tgz#304ec51631c3b770c65c6c6f76938b384000f4d5" integrity sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ== -through@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tinyglobby@^0.2.13: + version "0.2.16" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6" + integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.4" tldts-core@^6.1.86: version "6.1.86" @@ -2094,6 +2288,13 @@ tmp@~0.2.4: resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + tough-cookie@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.2.tgz#66d774b4a1d9e12dc75089725af3ac75ec31bed7" @@ -2111,11 +2312,6 @@ tslib@1.14.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.1.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -2135,11 +2331,6 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - type-fest@^0.8.0: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -2150,11 +2341,6 @@ underscore@1.13.6: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== -undici-types@~7.18.0: - version "7.18.2" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" - integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== - universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -2172,11 +2358,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -2217,15 +2398,6 @@ workerpool@^9.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -2244,6 +2416,15 @@ wrap-ansi@^8.1.0: string-width "^5.0.1" strip-ansi "^7.0.1" +wrap-ansi@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz#956832dea9494306e6d209eb871643bb873d7c98" + integrity sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww== + dependencies: + ansi-styles "^6.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -2292,13 +2473,13 @@ yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== +yauzl@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-3.3.1.tgz#b0de68ae3a862715e130849679a4236f54dba45f" + integrity sha512-RNPCUkiE/ZgO4w8i9U5yDQVHaFDdnzaFANElRvpJteCspvmv2VqrRb9lvS6odVD+jqI/zDsxAHJVsafpcheVQQ== dependencies: buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" + pend "~1.2.0" yocto-queue@^0.1.0: version "0.1.0" From 35c6ac84834204c2bd86f0b72ed5bebb5e7f1288 Mon Sep 17 00:00:00 2001 From: GenticFlow Labs Date: Mon, 1 Jun 2026 17:04:42 +0100 Subject: [PATCH 08/17] Revert vite config changes --- frontend/vite.config.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1e00d067f2..1e21d90f0c 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,6 +1,7 @@ import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; import checker from "vite-plugin-checker"; +import tsconfigPaths from "vite-tsconfig-paths"; import "vitest/config"; import { execFile } from "node:child_process"; @@ -44,10 +45,8 @@ export default defineConfig({ // e.g. use TypeScript check typescript: true, }), + tsconfigPaths(), ], - resolve: { - tsconfigPaths: true, - }, server: { host: true, port: 5173, From a43971ed8bab40aea1ca2e54c1ff16be08d29257 Mon Sep 17 00:00:00 2001 From: Genticflow Labs Date: Mon, 1 Jun 2026 17:18:47 +0100 Subject: [PATCH 09/17] Revert authentik.sql.gz to upstream/develop content The file got mutated locally (the dev compose's authentik service writes to its postgres init fixture during boot). Restore it to upstream so the PR diff doesn't include a noise change to a binary CI fixture. --- docker/ci/postgres/authentik.sql.gz | Bin 1036674 -> 1036690 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docker/ci/postgres/authentik.sql.gz b/docker/ci/postgres/authentik.sql.gz index db7b7a42495e361b81c413aa62e83c306d456212..49665d4e66a29e2859dddc8c457431aa50a1bc18 100644 GIT binary patch delta 171 zcmZo#Y(HtSeZv;UZz~viw*v{?9Y9(+ktxcXk+;2e4HFPEZ?9d$a?S%N?){ZzeH&2x zS2gSQU)5~UPl4>vME32Wi5xe3f%1CqIhuHZ^oBnio2CKjq{*BY;(@f$XU^6Cf%M%^ zT+JasI>DV=g9E6y!=HP5hd)mVkiGp+IM1X|pjh8p9v}wMeB1lh^3M_n%4rv1uZD;J zdj+@udj?zOhg)F>w_9Nd*^Rd~?+Ahox48TWvX-}NlL^pYw>ZTQS_~%4Kmz~( From 7aae915731d93c3040a9684d0c473678338a0d2d Mon Sep 17 00:00:00 2001 From: Genticflow Labs Date: Mon, 1 Jun 2026 17:25:03 +0100 Subject: [PATCH 10/17] Revert vite.config.ts to upstream/develop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the vite-tsconfig-paths plugin import — it was added locally to fix local production builds but was never declared in package.json, which broke CI on a fresh install. Upstream's CI doesn't run vite build (only vitest), and vitest happens to not exercise the src/* alias, so its no-op `resolve.tsconfigPaths: true` is fine for them and now for us too. The production build will hit the same path-resolution issue if anyone tries to run `vite build` directly, but that's a separate concern from this PR; matching upstream behavior so CI is green is the priority here. --- frontend/vite.config.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1e21d90f0c..1e00d067f2 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,6 @@ import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; import checker from "vite-plugin-checker"; -import tsconfigPaths from "vite-tsconfig-paths"; import "vitest/config"; import { execFile } from "node:child_process"; @@ -45,8 +44,10 @@ export default defineConfig({ // e.g. use TypeScript check typescript: true, }), - tsconfigPaths(), ], + resolve: { + tsconfigPaths: true, + }, server: { host: true, port: 5173, From 8404a0b3810d968ecc04ba991d88e47fc1951966 Mon Sep 17 00:00:00 2001 From: Genticflow Labs Date: Mon, 1 Jun 2026 17:29:35 +0100 Subject: [PATCH 11/17] Fix getFlagCodeForLocale to match upstream's expected behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream's new test (cbcbd958) asserts en-US → EN, de-DE → DE, etc. — i.e. flag follows the language, not the region. Our previous version returned the region whenever one was present (US for en-US), which we had introduced to make pt-BR show the Brazilian flag. Rework to keep upstream's language-first behavior and add an explicit exception for pt-BR (the only locale we register with a non-default region). en-US, de-DE, fr-FR, ga-IE all fall through to the language path, matching the test expectations. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/locale/IntlProvider.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index bcdfe2eeba..afcf7a6b96 100755 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -81,9 +81,17 @@ const loadMessages = (locale?: string): typeof langList & LocaleMessages => { const getFlagCodeForLocale = (locale?: string) => { const normalizedLocale = normalizeLocale(locale); - const [, region] = normalizedLocale.split("-"); - if (region) { - return region.toUpperCase(); + + // Locales where the flag should follow the region rather than the language, + // because the locale code itself carries the region (e.g. pt-BR → Brazil + // rather than Portugal). Keep this list small and explicit; ad-hoc inputs + // like "en-US" still fall through to the language-based path below so a + // user with `en-US` still gets the EN flag, not US. + const localeFullCases: Record = { + "pt-br": "br", + }; + if (localeFullCases[normalizedLocale]) { + return localeFullCases[normalizedLocale].toUpperCase(); } const thisLocale = normalizedLocale.slice(0, 2); From 268b192ebf58da66494bafa9ffa24db5b037f743 Mon Sep 17 00:00:00 2001 From: Genticflow Labs Date: Mon, 1 Jun 2026 17:48:55 +0100 Subject: [PATCH 12/17] Run locale-compile synchronously in the vite dev/test plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The configureServer plugin was spawning yarn locale-compile via execFile (async) and not waiting for it. On CI's slower filesystem the truncate phase of the second compile-folder run raced with vitest's JSON imports of the same files and produced "EOF while parsing a value" — the test file failed to load before any tests could run. Switch to execFileSync so the locale rewrite always completes before imports start. Behaviour is identical for `yarn dev`; just blocks briefly on startup instead of running in the background. --- frontend/vite.config.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1e00d067f2..40d32f48c3 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,21 +2,20 @@ import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; import checker from "vite-plugin-checker"; import "vitest/config"; -import { execFile } from "node:child_process"; +import { execFileSync } from "node:child_process"; const runLocaleScripts = () => { - execFile("yarn", ["locale-compile"], (error, stdout, _stderr) => { - if (error) { - throw error; - } - console.log(stdout); - execFile("yarn", ["locale-sort"], (error, stdout, _stderr) => { - if (error) { - throw error; - } - console.log(stdout); - }); - }); + // execFileSync (blocking) instead of execFile (async). This matters when + // the hook fires during vitest startup: an async compile races with + // vitest's JSON imports of the same files, producing "EOF while parsing" + // on slow CI filesystems. Running synchronously means imports happen + // after the lang/*.json files are fully rewritten. + try { + execFileSync("yarn", ["locale-compile"], { stdio: "inherit" }); + execFileSync("yarn", ["locale-sort"], { stdio: "inherit" }); + } catch (err) { + throw err; + } }; // https://vitejs.dev/config/ From c4e15e8b130ae5f56eec0ec6bfcce4f8c97fb1d1 Mon Sep 17 00:00:00 2001 From: Genticflow Labs Date: Mon, 1 Jun 2026 18:04:17 +0100 Subject: [PATCH 13/17] Fix POST /users response example to match the API output The 201 example included id/created_on/modified_on/user_id inside the permissions object, but the API explicitly omits those four keys from the response (backend/internal/user.js omissions list). Adding additionalProperties: false on the permissions sub-schema (PR review fix-up) made the stale example fail Vacuum's oas3-valid-schema-example rule in the Cypress Swagger Schema Linting test. Drop the DB-only fields from the example so it matches the actual response shape; add the new upstream_hosts permission to the example while we're at it. --- backend/schema/paths/users/post.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/backend/schema/paths/users/post.json b/backend/schema/paths/users/post.json index 49025e3250..2b3add33e4 100644 --- a/backend/schema/paths/users/post.json +++ b/backend/schema/paths/users/post.json @@ -63,17 +63,14 @@ "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", "roles": ["admin"], "permissions": { - "id": 3, - "created_on": "2020-01-30T09:41:04.000Z", - "modified_on": "2020-01-30T09:41:04.000Z", - "user_id": 2, "visibility": "user", "proxy_hosts": "manage", "redirection_hosts": "manage", "dead_hosts": "manage", "streams": "manage", "access_lists": "manage", - "certificates": "manage" + "certificates": "manage", + "upstream_hosts": "manage" } } } From e88e7fa6dd152afd8fd253dcee0d049b6423277c Mon Sep 17 00:00:00 2001 From: Genticflow Labs Date: Mon, 1 Jun 2026 18:55:42 +0100 Subject: [PATCH 14/17] Fix Upstream Host radio not toggling on Custom Location entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In LocationsFields the targetType for each location row was derived purely from upstreamHostId (> 0 → "upstream", else "direct"). When the user clicked the Upstream radio on a fresh location with no upstream selected yet, handleTargetTypeChange wrote upstreamHostId: 0, so the next render immediately snapped the radio back to Direct — and only the hover highlight was visible because the input toggled briefly and then reverted. Track the user's explicit choice in a per-index override map, falling back to the derivation only when no choice has been made yet. Compact the map on remove so indices line up after deletion. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/Form/LocationsFields.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Form/LocationsFields.tsx b/frontend/src/components/Form/LocationsFields.tsx index f8713860a6..7a9fc548be 100644 --- a/frontend/src/components/Form/LocationsFields.tsx +++ b/frontend/src/components/Form/LocationsFields.tsx @@ -16,6 +16,11 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) { const [values, setValues] = useState(initialValues || []); const { setFieldValue } = useFormikContext(); const [advVisible, setAdvVisible] = useState([]); + // User's explicit Direct/Upstream radio choice per location index. Needed + // because the persisted shape only carries upstreamHostId — when the user + // switches to Upstream but hasn't picked a host yet, upstreamHostId is 0 + // and a pure derivation would snap the radio back to Direct on re-render. + const [targetTypeOverrides, setTargetTypeOverrides] = useState>({}); const blankItem: ProxyLocation = { path: "", @@ -37,6 +42,16 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) { const newValues = values.filter((_: ProxyLocation, i: number) => i !== idx); setValues(newValues); setFormField(newValues); + // Compact the override map so indices > idx shift down by one. + setTargetTypeOverrides((prev) => { + const next: Record = {}; + for (const [k, v] of Object.entries(prev)) { + const i = Number(k); + if (i < idx) next[i] = v; + else if (i > idx) next[i - 1] = v; + } + return next; + }); }; const handleChange = (idx: number, field: string, fieldValue: string | number) => { @@ -60,6 +75,7 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) { }; const handleTargetTypeChange = (idx: number, targetType: "direct" | "upstream") => { + setTargetTypeOverrides((prev) => ({ ...prev, [idx]: targetType })); const newValues = values.map((v: ProxyLocation, i: number) => i === idx ? { @@ -90,7 +106,8 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) { return ( <> {values.map((item: ProxyLocation, idx: number) => { - const targetType = item.upstreamHostId && item.upstreamHostId > 0 ? "upstream" : "direct"; + const derivedTargetType = item.upstreamHostId && item.upstreamHostId > 0 ? "upstream" : "direct"; + const targetType = targetTypeOverrides[idx] ?? derivedTargetType; return (
From 324fe1723da47fad3d3196838500f37b008ca8b0 Mon Sep 17 00:00:00 2001 From: Genticflow Labs Date: Mon, 1 Jun 2026 19:02:08 +0100 Subject: [PATCH 15/17] Fill placeholder forward fields on upstream-mode locations at submit time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a Custom Location targets an upstream host, the user never fills in forward_scheme/host/port for that row, so the payload had forward_host:"" on those locations. The backend schema requires forward_host minLength 1 on every location item regardless of upstream_host_id, so saving failed with 'data/locations/N/forward_host must NOT have fewer than 1 characters'. Mirror the same defaulting we already do for the proxy host's top-level forward fields: at submit time, locations with upstreamHostId > 0 get forward_scheme "http", forward_host "127.0.0.1", forward_port 80. These placeholders never reach nginx — _location.conf switches to the upstream block (proxy_pass upstream_host_) when upstream_host_id > 0 — so the actual routing is unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/modals/ProxyHostModal.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/frontend/src/modals/ProxyHostModal.tsx b/frontend/src/modals/ProxyHostModal.tsx index 9a7bfdd834..27f33540ee 100644 --- a/frontend/src/modals/ProxyHostModal.tsx +++ b/frontend/src/modals/ProxyHostModal.tsx @@ -63,6 +63,25 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => { } } + // Each location has its own forward_scheme/host/port that the backend + // schema requires regardless of whether the location targets an upstream + // host. Fill placeholder values for upstream-mode locations so the + // schema validation passes; nginx ignores them when proxy_pass uses + // the upstream block instead of $forward_scheme://$server:$port. + if (Array.isArray(payload.locations)) { + payload.locations = payload.locations.map((loc: any) => { + if (loc?.upstreamHostId && loc.upstreamHostId > 0) { + return { + ...loc, + forwardScheme: loc.forwardScheme || "http", + forwardHost: loc.forwardHost && loc.forwardHost.trim() !== "" ? loc.forwardHost : "127.0.0.1", + forwardPort: loc.forwardPort && loc.forwardPort >= 1 ? loc.forwardPort : 80, + }; + } + return loc; + }); + } + setProxyHost(payload, { onError: (err: any) => setErrorMsg(), onSuccess: () => { From b16f64d1e0220538bc819f8fe3197b4c37c2524d Mon Sep 17 00:00:00 2001 From: Genticflow Labs Date: Mon, 1 Jun 2026 19:17:13 +0100 Subject: [PATCH 16/17] Always list every custom location in the proxy-host destination cell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DestinationCell previously had two bugs in the same expression: 1. When the proxy host carried a proxy-level upstream_host_id, the cell early-returned with just the upstream button — never reaching the block that renders custom locations. So any host using both a proxy- level upstream and per-location overrides looked single-target. 2. Even when the early return didn't fire, the per-location render only kept locations whose upstream_host_id > 0. Direct-mode custom locations (forward_host:port) were never shown. Rewrite so the cell always shows the proxy-level destination on the first line (upstream button OR scheme://host:port) and every custom location underneath as ``. If `/` is itself a custom location the proxy-level line is suppressed, since every request matches an override and the proxy-level forward is unreachable. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/pages/Nginx/ProxyHosts/Table.tsx | 72 +++++++++---------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx index e4f6fae86d..b9bc6fe1da 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx @@ -24,70 +24,64 @@ import { showUpstreamHostModal } from "src/modals"; import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions"; // DestinationCell renders the "Destination" column. A proxy host can route -// through an upstream at two levels: proxy-level (the whole host forwards via -// upstream_host_id), or per-location (each /path entry can carry its own -// upstream_host_id). The previous implementation only honored the proxy-level -// case, which made any host using location-based upstream routing look like a -// plain forward (`http://host:port`) — its upstream usage was invisible from -// the list view. +// at two levels: proxy-level (the whole host forwards via forwardHost:port +// or upstreamHostId), and per-location (each /path entry carries its own +// direct forward fields OR upstream_host_id). The cell shows the proxy-level +// destination on the first line and every custom location underneath, so +// operators can see at a glance how each path will be routed. function DestinationCell({ host }: { host: ProxyHost }) { const { data: upstreams } = useUpstreamHosts(); - // Proxy-level upstream — preserve original behavior. const proxyUpstreamId = host.upstreamHostId ?? 0; - if (proxyUpstreamId > 0 && host.upstreamHost) { + const allLocations = host.locations || []; + + // If `/` is itself a custom location, the proxy-level destination is + // unreachable (every request matches a location override). Suppress it. + const rootIsCovered = allLocations.some((l: ProxyLocation) => l.path === "/"); + + const renderUpstreamLink = (upId: number, fallbackName?: string) => { + const up = upstreams?.find((u) => u.id === upId); + const name = up?.name ?? fallbackName ?? `upstream #${upId}`; return ( ); - } + }; - const forwardLine = `${host.forwardScheme}://${host.forwardHost}:${host.forwardPort}`; - const locationUpstreams = (host.locations || []).filter( - (l: ProxyLocation) => l.upstreamHostId && l.upstreamHostId > 0, - ); + const proxyLine = + proxyUpstreamId > 0 + ? renderUpstreamLink(proxyUpstreamId, host.upstreamHost?.name) + : `${host.forwardScheme}://${host.forwardHost}:${host.forwardPort}`; - if (locationUpstreams.length === 0) { - return forwardLine; + if (allLocations.length === 0) { + // Plain proxy host with no custom locations — keep the cell tight. + return <>{proxyLine}; } - // If `/` is itself routed via an upstream, every request matches a location - // override and the proxy-level forward is unreachable — showing it just - // confuses operators. Suppress it in that case. - const rootIsCovered = locationUpstreams.some((l) => l.path === "/"); - return (
- {!rootIsCovered &&
{forwardLine}
} + {!rootIsCovered &&
{proxyLine}
}
- {locationUpstreams.map((loc, i) => { - const up = upstreams?.find((u) => u.id === loc.upstreamHostId); - const upId = up?.id ?? 0; + {allLocations.map((loc: ProxyLocation, i: number) => { + const upId = loc.upstreamHostId ?? 0; return (
{loc.path} {" → "} - {up && upId > 0 ? ( - + {upId > 0 ? ( + renderUpstreamLink(upId) ) : ( - upstream #{loc.upstreamHostId} + + {loc.forwardScheme}://{loc.forwardHost}:{loc.forwardPort} + )}
); From 4178b556e7709e9a0fa0a06e489cd184671ee01b Mon Sep 17 00:00:00 2001 From: Genticflow Labs Date: Mon, 1 Jun 2026 19:22:43 +0100 Subject: [PATCH 17/17] =?UTF-8?q?Render=20proxy-level=20destination=20as?= =?UTF-8?q?=20'/=20=E2=86=92=20...'=20when=20locations=20exist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a proxy host has custom locations, show the proxy-level (default) destination as '/ → ' so it reads uniformly alongside the other `` rows. When the host has no custom locations, keep the old tight rendering (just the destination, no '/' prefix). The '/' row is still suppressed when the user has explicitly defined a '/' custom location — that override fully replaces the default, so showing both would be misleading. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/pages/Nginx/ProxyHosts/Table.tsx | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx index b9bc6fe1da..fe4ac70113 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx @@ -66,27 +66,35 @@ function DestinationCell({ host }: { host: ProxyHost }) { return <>{proxyLine}; } + // With custom locations, every routing rule (including the proxy-level + // default) is shown as `` so the listing reads + // uniformly. The "/" line is suppressed when the user has defined an + // explicit "/" location override. return ( -
- {!rootIsCovered &&
{proxyLine}
} -
- {allLocations.map((loc: ProxyLocation, i: number) => { - const upId = loc.upstreamHostId ?? 0; - return ( -
- {loc.path} - {" → "} - {upId > 0 ? ( - renderUpstreamLink(upId) - ) : ( - - {loc.forwardScheme}://{loc.forwardHost}:{loc.forwardPort} - - )} -
- ); - })} -
+
+ {!rootIsCovered && ( +
+ / + {" → "} + {proxyLine} +
+ )} + {allLocations.map((loc: ProxyLocation, i: number) => { + const upId = loc.upstreamHostId ?? 0; + return ( +
+ {loc.path} + {" → "} + {upId > 0 ? ( + renderUpstreamLink(upId) + ) : ( + + {loc.forwardScheme}://{loc.forwardHost}:{loc.forwardPort} + + )} +
+ ); + })}
); }
- {typeof column.columnDef.header === "string" ? `${column.columnDef.header}` : null} + + {headerContent} + {sortIcon}