diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..a12fac3d0
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,7 @@
+# Force LF everywhere: these files run inside Linux containers
+* text=auto eol=lf
+*.png binary
+*.jpg binary
+*.ico binary
+*.woff binary
+*.woff2 binary
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index f3fddbc0c..39e5c0ec6 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,6 +1,16 @@
-
+
+WARNINGS:
+
+1. An AI bot will comment on this PR! If you don't get a perfect score or don't
+address any issues raised, your PR won't get merged!
+
+2. Any PR without a description will be closed. The title is not enough!
+
+3. Don't go creating a duplicate PR! Check that someone hasn't already created
+something that tackles your fix and if so, help them first
+
+-->
## Why
@@ -28,4 +38,3 @@ you change will break API for existing users -->
- [ ] AI was used to write this
- [ ] AI was used to review this
-
diff --git a/.gitignore b/.gitignore
index a83a2110e..667601ec7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,5 @@ test/node_modules
docker/dev/dnsrouter-config.json.tmp
docker/dev/resolv.conf
.claude
+test/__pycache__
diff --git a/.version b/.version
index 752490696..d76bd2ba3 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-2.16.0
+2.17.0
diff --git a/README.md b/README.md
index 0ee51c350..76a24316b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-
+
diff --git a/backend/biome.json b/backend/biome.json
index 5d1794b5a..0f1b5b62c 100644
--- a/backend/biome.json
+++ b/backend/biome.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://biomejs.dev/schemas/2.5.10/schema.json",
+ "$schema": "https://biomejs.dev/schemas/2.6.6/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
diff --git a/backend/certbot/dns-plugins.json b/backend/certbot/dns-plugins.json
index 4467a27af..b66c2684c 100644
--- a/backend/certbot/dns-plugins.json
+++ b/backend/certbot/dns-plugins.json
@@ -44,16 +44,16 @@
"dependencies": "",
"full_plugin_name": "dns-baidu",
"name": "baidu",
- "package_name": "certbot-dns-baidu",
- "version": "~=0.1.1"
+ "package_name": "certbot-dns-baidu-bcd",
+ "version": "~=2.0.0"
},
"beget": {
- "credentials": "# Beget API credentials used by Certbot\nbeget_plugin_username = username\nbeget_plugin_password = password",
+ "credentials": "# Beget API credentials used by Certbot\n# Find on Settings -> Security -> BegetAPI\ndns_beget_plugin_username = username\ndns_beget_plugin_password = password",
"dependencies": "",
- "full_plugin_name": "beget-plugin",
+ "full_plugin_name": "dns-beget-plugin",
"name": "Beget",
- "package_name": "certbot-beget-plugin",
- "version": "~=1.0.0.dev9"
+ "package_name": "certbot-dns-beget-plugin",
+ "version": "~=1.0.0"
},
"bunny": {
"credentials": "# Bunny API token used by Certbot (see https://dash.bunny.net/account/settings)\ndns_bunny_api_key = xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
@@ -173,7 +173,7 @@
"full_plugin_name": "dns-multi",
"name": "DnsMulti",
"package_name": "certbot-dns-multi",
- "version": "~=4.9"
+ "version": "~=5.6.0"
},
"dnspod": {
"credentials": "dns_dnspod_email = \"email@example.com\"\ndns_dnspod_api_token = \"id,key\"",
@@ -224,12 +224,12 @@
"version": "~=0.1.2"
},
"edgedns": {
- "credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf",
- "dependencies": "",
- "full_plugin_name": "edgedns",
- "name": "Akamai Edge DNS",
- "package_name": "certbot-plugin-edgedns",
- "version": "~=0.1.0"
+ "credentials": "client_token = YOUR_CLIENT_TOKEN\naccess_token = YOUR_ACCESS_TOKEN\nclient_secret = YOUR_CLIENT_SECRET\nhost = YOUR_HOST.luna.akamaiapis.net\n# Optional: account_key = YOUR_ACCOUNT_KEY",
+ "dependencies": "",
+ "full_plugin_name": "edgedns",
+ "name": "Akamai Edge DNS",
+ "package_name": "certbot-plugin-edgedns",
+ "version": "==0.3.0"
},
"edgeone": {
"credentials": "dns_edgeone_secret_id = YOUR_TENCENTCLOUD_SECRET_ID\ndns_edgeone_secret_key = YOUR_TENCENTCLOUD_SECRET_KEY",
@@ -333,7 +333,7 @@
"full_plugin_name": "dns-hetzner",
"name": "Hetzner",
"package_name": "certbot-dns-hetzner",
- "version": "~=1.0.4"
+ "version": "~=4.0.0"
},
"hetzner-cloud": {
"credentials": "dns_hetzner_cloud_api_token = your_api_token_here",
@@ -381,7 +381,7 @@
"full_plugin_name": "dns-infomaniak",
"name": "Infomaniak",
"package_name": "certbot-dns-infomaniak",
- "version": "~=0.2.2"
+ "version": "~=0.3.3"
},
"inwx": {
"credentials": "dns_inwx_url = https://api.domrobot.com/xmlrpc/\ndns_inwx_username = your_username\ndns_inwx_password = your_password\ndns_inwx_shared_secret = your_shared_secret optional",
@@ -399,6 +399,14 @@
"package_name": "certbot-dns-ionos",
"version": "==2022.11.24"
},
+ "ip-projects": {
+ "credentials": "dns_ipp_acme_api_key = ",
+ "dependencies": "",
+ "full_plugin_name": "dns-ipp",
+ "name": "ip-projects.de",
+ "package_name": "certbot-dns-ipp",
+ "version": "~=1.0.0"
+ },
"ispconfig": {
"credentials": "dns_ispconfig_username = myremoteuser\ndns_ispconfig_password = verysecureremoteuserpassword\ndns_ispconfig_endpoint = https://localhost:8080",
"dependencies": "",
@@ -599,6 +607,14 @@
"package_name": "certbot-dns-route53",
"version": "=={{certbot-version}}"
},
+ "scaleway": {
+ "credentials": "dns_scaleway_application_token = b3a0b9a9-3814-4f12-95b0-a7473bf8b306",
+ "dependencies": "",
+ "full_plugin_name": "dns-scaleway",
+ "name": "Scaleway",
+ "package_name": "certbot-dns-scaleway",
+ "version": "~=0.0.7"
+ },
"selectelv2": {
"credentials": "dns_selectel_api_v2_account_id = your_account_id\ndns_selectel_api_v2_project_name = your_project\ndns_selectel_api_v2_username = your_username\ndns_selectel_api_v2_password = your_password",
"dependencies": "",
diff --git a/backend/index.js b/backend/index.js
index 002856672..a6afda7bb 100644
--- a/backend/index.js
+++ b/backend/index.js
@@ -26,7 +26,9 @@ async function appStart() {
})
.then(() => {
internalCertificate.initTimer();
- internalIpRanges.initTimer();
+ if (IP_RANGES_FETCH_ENABLED) {
+ internalIpRanges.initTimer();
+ }
const server = app.listen(3000, () => {
logger.info(`Backend PID ${process.pid} listening on port 3000 ...`);
diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js
index 1fdd65b6a..33b49c5d3 100644
--- a/backend/internal/access-list.js
+++ b/backend/internal/access-list.js
@@ -2,6 +2,7 @@ import fs from "node:fs";
import batchflow from "batchflow";
import _ from "lodash";
import errs from "../lib/error.js";
+import { isValid, unsafeValueError } from "../lib/nginx-values.js";
import { isMysql, isPostgres } from "../lib/config.js";
import utils from "../lib/utils.js";
import db from "../db.js";
@@ -27,20 +28,18 @@ const getProxyHostsUsingAccessListInLocations = async (accessListId) => {
let result;
if (isMysql()) {
const searchObj = JSON.stringify([{ access_list_id: accessListId }]);
- result = await db().raw(
- "SELECT id FROM proxy_host WHERE is_deleted = 0 AND JSON_CONTAINS(locations, ?, ?)",
- [searchObj, "$"],
- );
+ result = await db().raw("SELECT id FROM proxy_host WHERE is_deleted = 0 AND JSON_CONTAINS(locations, ?, ?)", [
+ searchObj,
+ "$",
+ ]);
} else if (isPostgres()) {
- result = await db().raw(
- "SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations::jsonb @> ?::jsonb",
- [JSON.stringify([{ access_list_id: accessListId }])],
- );
+ result = await db().raw("SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations::jsonb @> ?::jsonb", [
+ JSON.stringify([{ access_list_id: accessListId }]),
+ ]);
} else {
- result = await db().raw(
- "SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations LIKE ?",
- [`%"access_list_id":${accessListId}%`],
- );
+ result = await db().raw("SELECT id FROM proxy_host WHERE is_deleted = 0 AND locations LIKE ?", [
+ `%"access_list_id":${accessListId}%`,
+ ]);
}
// knex raw() returns [rows, metadata] for MySQL, { rows } for Postgres and rows for SQLite
if (!Array.isArray(result)) {
@@ -225,12 +224,15 @@ const internalAccessList = {
// Also regenerate configs for proxy hosts that reference this access list in their locations
const locationHostRows = await getProxyHostsUsingAccessListInLocations(data.id);
if (locationHostRows?.length) {
- const locationHostIds = locationHostRows.map((r) => r.id).filter((id) => {
- // Exclude hosts already regenerated above
- return !freshRow.proxy_hosts?.find((h) => h.id === id);
- });
+ const locationHostIds = locationHostRows
+ .map((r) => r.id)
+ .filter((id) => {
+ // Exclude hosts already regenerated above
+ return !freshRow.proxy_hosts?.find((h) => h.id === id);
+ });
if (locationHostIds.length) {
- const locationHosts = await proxyHostModel.query()
+ const locationHosts = await proxyHostModel
+ .query()
.where("is_deleted", 0)
.whereIn("id", locationHostIds)
.allowGraph(proxyHostModel.defaultAllowGraph)
@@ -499,6 +501,11 @@ const internalAccessList = {
.sequential()
.each((_i, item, next) => {
if (item.password?.length) {
+ // One "user:hash" line each; a ":" or line break in the name would corrupt the file
+ if (!isValid("accessUsername", item.username)) {
+ reject(unsafeValueError(`Access List #${list.id}: user name is not allowed`));
+ return;
+ }
logger.info(`Adding: ${item.username}`);
utils
diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js
index 963d2bb6a..0c3166a60 100644
--- a/backend/internal/certificate.js
+++ b/backend/internal/certificate.js
@@ -24,8 +24,163 @@ const certbotCommand = "certbot";
const certbotLogsDir = "/data/logs";
const certbotWorkDir = "/tmp/letsencrypt-lib";
+// Certbot output lines that say nothing about why a request failed
+const certbotNoise = [
+ /^Saving debug log/,
+ /^Ask for help/,
+ /^See the logfile/,
+ /^(- )+-?$/,
+ /^Processing \//,
+ /^Renewing an existing/,
+ /^Requesting a certificate/,
+ /^Plugins selected/,
+ /^Account registered/,
+ /^Waiting \d+ seconds/,
+ /^All renewals failed/,
+ /^\S+\/fullchain\.pem \(failure\)$/,
+ /^\d+ renew failure\(s\)/,
+];
+
+// Plain-language hints for failures that are common and fixable outside NPM.
+// [pattern, hint, dnsProvider?]: a hint with a dnsProvider only applies to certificates using that
+// provider, so a generic message (e.g. "Authentication error" from another DNS plugin) isn't
+// reported as a Cloudflare problem.
+const certbotHints = [
+ // Checked first: a rate limit is unambiguous and the most useful thing to report
+ [
+ /too many (certificates|failed authorizations|new orders)|rateLimited/i,
+ () => "Let's Encrypt rate limit reached. Wait before retrying (see https://letsencrypt.org/docs/rate-limits/).",
+ ],
+ [
+ /expected \S+ to be a symlink/i,
+ () =>
+ "This certificate's files in /etc/letsencrypt were copied without their symlinks (live/ must link to archive/). Restore /etc/letsencrypt from a copy made with tar or rsync -a, or delete this certificate and create it again.",
+ ],
+ [
+ /Renewal configuration file \S+ is broken/i,
+ () =>
+ "This certificate's files in /etc/letsencrypt are damaged or missing. Restore /etc/letsencrypt from a backup, or delete this certificate and create it again.",
+ ],
+ [
+ /\b9109\b.*?location: ([0-9a-f.:]+)/i,
+ (m) => {
+ const ip = m[1].replace(/\.+$/, "");
+ return `Cloudflare refused the API token from this server's public IP (${ip}). The token has Client IP Address Filtering: add ${ip} to the token in Cloudflare (My Profile > API Tokens), or remove the filter, then renew again.`;
+ },
+ "cloudflare",
+ ],
+ [
+ // certbot-dns-cloudflare errors read ": ", e.g.
+ // "Error determining zone_id: 9109 Invalid access token."
+ /: (6003|6111|9103|9109|10000) [A-Za-z]|Invalid request headers|Invalid access token|Authentication error/i,
+ () =>
+ "Cloudflare rejected the API token. Check the token value and that it has Zone > DNS > Edit for this domain's zone.",
+ "cloudflare",
+ ],
+ [
+ /NXDOMAIN|DNS problem/i,
+ () => "Let's Encrypt could not look up the domain. Check the DNS records of every domain on the certificate.",
+ ],
+ [
+ /Timeout during connect|Connection refused|Invalid response from/i,
+ () =>
+ "Let's Encrypt could not reach this server over HTTP (port 80) for the challenge. Check that the domain points here and port 80 is open, or use a DNS challenge.",
+ ],
+];
+
+/**
+ * The values in a DNS credentials file ("key = value" lines), for redacting from certbot output.
+ *
+ * @param {String} [credentials]
+ * @returns {String[]}
+ */
+const credentialValues = (credentials) =>
+ `${credentials || ""}`
+ .split("\n")
+ .map((line) => line.replace(/^[^=:]*[=:]/, "").trim())
+ .filter((value) => value.length >= 4);
+
+/**
+ * Removes anything that could be a secret from certbot output before it leaves the server.
+ * DNS plugins can echo provider errors verbatim, including request URLs that carry API keys in
+ * the query string (e.g. "401 Client Error ... for url: https://...?token=..."), so this strips
+ * the given credential values, URL query strings and userinfo, and key=value pairs whose name
+ * looks secret.
+ *
+ * @param {String} text
+ * @param {String[]} [secrets]
+ * @returns {String}
+ */
+const redactCertbotOutput = (text, secrets = []) => {
+ let out = `${text || ""}`;
+ for (const secret of [...secrets].sort((a, b) => b.length - a.length)) {
+ out = out.split(secret).join("[REDACTED]");
+ }
+ return out
+ .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, "$1 [REDACTED]")
+ .replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^/\s@]*@/gi, "$1[REDACTED]@")
+ .replace(/(\b[a-z][a-z0-9+.-]*:\/\/[^\s?#]*)\?[^\s#]*/gi, "$1?[REDACTED]")
+ .replace(
+ /\b([\w-]*(?:token|key|secret|password|passwd|pass|auth|credential|signature|sig)[\w-]*)(["']?\s*[=:]\s*["']?)[^\s"'&,;]+/gi,
+ "$1$2[REDACTED]",
+ );
+};
+
+/**
+ * Turns certbot's stderr into a message the UI can show: a hint for common causes plus certbot's own reason.
+ * The output is redacted first (see redactCertbotOutput), since DNS plugins can echo credentials.
+ *
+ * @param {String} stderr
+ * @param {String} [dnsProvider] The certificate's DNS provider, if it uses a DNS challenge
+ * @param {String[]} [secrets] Values that must never appear in the message
+ * @returns {String}
+ */
+const certbotFailureMessage = (stderr, dnsProvider, secrets) => {
+ const reason = redactCertbotOutput(stderr, secrets)
+ .split("\n")
+ .map((line) => line.trim())
+ .filter((line) => line && !certbotNoise.some((re) => re.test(line)))
+ .join(" ")
+ .slice(0, 800);
+
+ for (const [pattern, hint, provider] of certbotHints) {
+ if (provider && provider !== dnsProvider) {
+ continue;
+ }
+ const match = reason.match(pattern);
+ if (match) {
+ return `${hint(match)} (Certbot: ${reason})`;
+ }
+ }
+ return reason ? `Certbot failed: ${reason}` : "Certbot failed. See /data/logs/letsencrypt.log for details.";
+};
+
+/**
+ * Runs certbot; a failure becomes a public error with the real reason instead of "Internal Error".
+ *
+ * @param {Array} args
+ * @param {Object} [opts]
+ * @param {String} [dnsProvider] Passed through so provider-specific hints only show for that provider
+ * @param {String} [credentials] The DNS credentials file contents, so its values are redacted from errors
+ * @returns {Promise}
+ */
+const runCertbot = async (args, opts, dnsProvider, credentials) => {
+ try {
+ return await utils.execFile(certbotCommand, args, opts);
+ } catch (err) {
+ if (err instanceof error.CommandError) {
+ const secrets = credentialValues(credentials);
+ logger.error(redactCertbotOutput(err.message, secrets));
+ // The raw error is not attached as `previous`: the API error handler returns
+ // `previous` to the client for certificate routes, and it holds unredacted output.
+ throw new error.ValidationError(certbotFailureMessage(err.message, dnsProvider, secrets));
+ }
+ throw err;
+ }
+};
+
const omissions = () => {
- return ["is_deleted", "owner.is_deleted", "meta.dns_provider_credentials"];
+ return ["is_deleted", "owner.is_deleted", "meta.dns_provider_credentials", "meta.certificate_key"];
};
const internalCertificate = {
@@ -75,6 +230,7 @@ const internalCertificate = {
* instance of Certbot is already running.'
*/
let sequence = Promise.resolve();
+ let renewed = 0;
certificates.forEach((certificate) => {
sequence = sequence.then(() =>
@@ -88,7 +244,11 @@ const internalCertificate = {
token: tokenModel(),
},
{ id: certificate.id },
+ { reload: false }, // one reload for the whole batch, below
)
+ .then(() => {
+ renewed++;
+ })
.catch((err) => {
// Don't want to stop the train here, just log the error
logger.error(err.message);
@@ -96,7 +256,11 @@ const internalCertificate = {
);
});
- return sequence;
+ return sequence.then(() => {
+ if (renewed) {
+ return internalCertificate.reloadAfterRenew();
+ }
+ });
})
.then(() => {
logger.info("Completed SSL cert renew process");
@@ -395,12 +559,35 @@ const internalCertificate = {
*/
delete: async (access, data) => {
await access.can("certificates:delete", data.id);
- const row = await internalCertificate.get(access, { id: data.id });
+ const hostRelations = ["proxy_hosts", "redirection_hosts", "dead_hosts", "streams"];
+ const row = await internalCertificate.get(access, { id: data.id, expand: hostRelations });
if (!row?.id) {
throw new error.ItemNotFoundError(data.id);
}
+ // A certificate that hosts still use can't be deleted: their rendered configs include its
+ // files, and revoking it would leave them serving a revoked certificate. Hosts may belong
+ // to other users, so only counts are reported.
+ const labels = {
+ proxy_hosts: "proxy host",
+ redirection_hosts: "redirection host",
+ dead_hosts: "404 host",
+ streams: "stream",
+ };
+ const usage = hostRelations
+ .map((rel) => ({ rel, count: row[rel]?.length || 0 }))
+ .filter((u) => u.count > 0)
+ .map((u) => `${u.count} ${labels[u.rel]}${u.count === 1 ? "" : "s"}`);
+ if (usage.length) {
+ throw new error.ValidationError(
+ `Certificate is in use by ${usage.join(", ")}. Remove it from them before deleting it.`,
+ );
+ }
+ for (const rel of hostRelations) {
+ delete row[rel];
+ }
+
await certificateModel.query().where("id", row.id).patch({
is_deleted: 1,
});
@@ -416,12 +603,47 @@ const internalCertificate = {
});
if (row.provider === "letsencrypt") {
- // Revoke the cert
+ // Revoke the cert. Errors are logged by revokeLetsEncryptSsl.
await internalCertificate.revokeLetsEncryptSsl(row);
+ // Certbot leaves the files behind when it can't revoke (e.g. a broken renewal config),
+ // so remove them either way. No host uses them; that was checked above.
+ // The row is already deleted and audited, so leftover files are logged, not thrown:
+ // an error here would report a completed delete as failed, and a retry would 404.
+ const leftover = internalCertificate.removeLetsEncryptFiles(row.id);
+ if (leftover.length) {
+ logger.warn(
+ `Certificate #${row.id} was deleted, but these files could not be removed and should be deleted by hand: ${leftover.join(", ")}`,
+ );
+ }
}
return true;
},
+ /**
+ * Removes a certificate's certbot files, as `certbot delete` would. Never throws.
+ *
+ * @param {Number} certificateId
+ * @returns {String[]} paths that could not be removed
+ */
+ removeLetsEncryptFiles: (certificateId) => {
+ const paths = [
+ internalCertificate.getLiveCertPath(certificateId),
+ `/etc/letsencrypt/archive/npm-${certificateId}`,
+ `/etc/letsencrypt/renewal/npm-${certificateId}.conf`,
+ `/etc/letsencrypt/credentials/credentials-${certificateId}`,
+ ];
+ const failed = [];
+ for (const p of paths) {
+ try {
+ fs.rmSync(p, { recursive: true, force: true });
+ } catch (err) {
+ logger.error(`Could not remove ${p}: ${err.message}`);
+ failed.push(p);
+ }
+ }
+ return failed;
+ },
+
/**
* All Certs
*
@@ -545,13 +767,15 @@ const internalCertificate = {
/**
* Validates that the certs provided are good.
- * No access required here, nothing is changed or stored.
+ * Nothing is changed or stored, but the files go through openssl, so it needs the same access as creating one.
*
+ * @param {Access} access
* @param {Object} data
* @param {Object} data.files
* @returns {Promise}
*/
- validate: (data) => {
+ validate: async (access, data) => {
+ await access.can("certificates:create");
// Put file contents into an object
const files = {};
_.map(data.files, (file, name) => {
@@ -600,27 +824,75 @@ const internalCertificate = {
throw new error.ValidationError("Cannot upload certificates for this type of provider");
}
- const validations = await internalCertificate.validate(data);
+ const validations = await internalCertificate.validate(access, data);
if (typeof validations.certificate === "undefined") {
throw new error.ValidationError("Certificate file was not provided");
}
+ // row came through get(), which omits meta.certificate_key so it never goes out over
+ // the API. update() replaces meta wholesale, so build on the stored meta (read from the
+ // model directly) or an upload without a key would erase the stored one.
+ const stored = await certificateModel.query().where("id", row.id).first();
+ const meta = { ...(stored?.meta || {}) };
+ const uploaded = [];
+
_.map(data.files, (file, name) => {
if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
- row.meta[name] = file.data.toString();
+ meta[name] = file.data.toString();
+ uploaded.push(name);
}
});
+ // Fail before anything is written: without a key, writeCustomCert would overwrite
+ // fullchain.pem and then fail on privkey.pem, leaving nginx with a mismatched pair.
+ if (!meta.certificate_key) {
+ throw new error.ValidationError("Certificate Key file was not provided");
+ }
+ // The key may be the stored one (a certificate-only upload), so check the pair belongs
+ // together; a mismatch would make nginx -t fail for every host on the next reload.
+ await internalCertificate.checkKeyMatchesCertificate(meta.certificate, meta.certificate_key);
+
const certificate = await internalCertificate.update(access, {
id: data.id,
expires_on: moment(validations.certificate.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"),
domain_names: validations.certificate.cn ? [validations.certificate.cn] : [],
- meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later
+ meta: _.clone(meta), // Prevent the update method from changing this value that we'll use later
});
- certificate.meta = row.meta;
+ certificate.meta = meta;
await internalCertificate.writeCustomCert(certificate);
- return _.pick(row.meta, internalCertificate.allowedSslFiles);
+ // Only echo back what was uploaded in this request, never the stored key
+ return _.pick(meta, uploaded);
+ },
+
+ /**
+ * Throws unless the private key is the one the certificate was issued for, by comparing the
+ * public key derived from each.
+ *
+ * @param {String} certificate PEM certificate (the leaf comes first)
+ * @param {String} privateKey PEM private key
+ * @returns {Promise}
+ */
+ checkKeyMatchesCertificate: async (certificate, privateKey) => {
+ const certPath = await tempWrite(certificate);
+ const keyPath = await tempWrite(privateKey);
+ try {
+ const [certPub, keyPub] = await Promise.all([
+ utils.execFile("openssl", ["x509", "-in", certPath, "-pubkey", "-noout"]),
+ utils.execFile("openssl", ["pkey", "-in", keyPath, "-pubout"]),
+ ]);
+ if (!certPub.trim() || certPub.trim() !== keyPub.trim()) {
+ throw new error.ValidationError("Certificate does not match the Certificate Key");
+ }
+ } catch (err) {
+ if (err instanceof error.ValidationError) {
+ throw err;
+ }
+ throw new error.ValidationError("Could not check that the Certificate matches the Certificate Key");
+ } finally {
+ fs.rmSync(certPath, { force: true });
+ fs.rmSync(keyPath, { force: true });
+ }
},
/**
@@ -811,7 +1083,7 @@ const internalCertificate = {
logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`);
- const result = await utils.execFile(certbotCommand, args, adds.opts);
+ const result = await runCertbot(args, adds.opts);
logger.success(result);
return result;
},
@@ -860,7 +1132,8 @@ const internalCertificate = {
if (hasConfigArg) {
args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation);
}
- if (certificate.meta.propagation_seconds !== undefined) {
+ // The route53 plugin waits for the change through the AWS API and has no propagation option
+ if (certificate.meta.propagation_seconds !== undefined && certificate.meta.dns_provider !== "route53") {
args.push(
`--${dnsPlugin.full_plugin_name}-propagation-seconds`,
certificate.meta.propagation_seconds.toString(),
@@ -878,7 +1151,12 @@ const internalCertificate = {
logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`);
try {
- const result = await utils.execFile(certbotCommand, args, adds.opts);
+ const result = await runCertbot(
+ args,
+ adds.opts,
+ certificate.meta.dns_provider,
+ certificate.meta.dns_provider_credentials,
+ );
logger.info(result);
return result;
} finally {
@@ -904,7 +1182,8 @@ const internalCertificate = {
* @param {Number} data.id
* @returns {Promise}
*/
- renew: async (access, data) => {
+ renew: async (access, data, options = {}) => {
+ const reload = options.reload !== false;
await access.can("certificates:update", data);
const certificate = await internalCertificate.get(access, data);
@@ -930,12 +1209,34 @@ const internalCertificate = {
meta: updatedCertificate,
});
+ // The renewal has succeeded and been recorded at this point. A reload failure
+ // (e.g. an unrelated host's config is broken) must not turn it into an error;
+ // nginx picks the new files up on the next successful reload.
+ if (reload) {
+ await internalCertificate.reloadAfterRenew();
+ }
+
return updatedCertificate;
}
throw new error.ValidationError("Only Let'sEncrypt certificates can be renewed");
},
+ /**
+ * Reloads nginx so it serves renewed certificate files. Errors are logged, not thrown.
+ *
+ * @returns {Promise} whether the reload succeeded
+ */
+ reloadAfterRenew: async () => {
+ try {
+ await internalNginx.reload();
+ return true;
+ } catch (err) {
+ logger.error(`Certificate renewed but nginx could not be reloaded: ${err.message}`);
+ return false;
+ }
+ },
+
/**
* @param {Object} certificate the certificate row
* @returns {Promise}
@@ -972,7 +1273,7 @@ const internalCertificate = {
logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`);
- const result = await utils.execFile(certbotCommand, args, adds.opts);
+ const result = await runCertbot(args, adds.opts);
logger.info(result);
return result;
},
@@ -1056,7 +1357,7 @@ const internalCertificate = {
logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`);
try {
- const result = await utils.execFile(certbotCommand, args, adds.opts);
+ const result = await runCertbot(args, adds.opts, certificate.meta.dns_provider, credentials);
logger.info(result);
return result;
} finally {
@@ -1099,7 +1400,7 @@ const internalCertificate = {
logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`);
try {
- const result = await utils.execFile(certbotCommand, args, adds.opts);
+ const result = await runCertbot(args, adds.opts);
await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`);
logger.info(result);
return result;
diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js
index 6523e6289..5e249de59 100644
--- a/backend/internal/dead-host.js
+++ b/backend/internal/dead-host.js
@@ -54,6 +54,8 @@ const internalDeadHost = {
thisData.advanced_config = "";
}
+ internalHost.assertAdvancedConfigAllowed(access, thisData);
+
const row = await deadHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions()));
// Add to audit log
@@ -130,6 +132,9 @@ const internalDeadHost = {
);
}
+ // Before the certificate is created, so a refused request doesn't leave one behind
+ internalHost.assertAdvancedConfigAllowed(access, data, row);
+
if (createCertificate) {
const cert = await internalCertificate.createQuickCertificate(access, {
domain_names: data.domain_names || row.domain_names,
diff --git a/backend/internal/host.js b/backend/internal/host.js
index 748716262..7bdaa7837 100644
--- a/backend/internal/host.js
+++ b/backend/internal/host.js
@@ -1,10 +1,110 @@
import _ from "lodash";
+import errs from "../lib/error.js";
import { castJsonIfNeed } from "../lib/helpers.js";
import deadHostModel from "../models/dead_host.js";
import proxyHostModel from "../models/proxy_host.js";
import redirectionHostModel from "../models/redirection_host.js";
const internalHost = {
+ /**
+ * Only admins may set or change advanced_config (raw nginx config). Throws for anyone else.
+ * On create (no `row`), any non-empty advanced_config is refused, including on locations.
+ * On update, the host's advanced_config must be unchanged (or omitted), and locations must
+ * only carry advanced configs the host already has (see assertLocationAdvancedConfigUnchanged).
+ *
+ * @param {Access} access
+ * @param {Object} data incoming host data; data.locations may be filled in on update
+ * @param {Object} [row] the existing host, when updating
+ * @throws {PermissionError}
+ */
+ assertAdvancedConfigAllowed: (access, data, row) => {
+ if (access.isAdmin?.()) {
+ return;
+ }
+ const deny = () => {
+ throw new errs.PermissionError("You do not have permission to modify advanced configuration");
+ };
+
+ if (!row) {
+ const hasConfig = (v) => typeof v === "string" && v.trim() !== "";
+ if (hasConfig(data.advanced_config)) {
+ deny();
+ }
+ if (Array.isArray(data.locations) && data.locations.some((loc) => hasConfig(loc?.advanced_config))) {
+ deny();
+ }
+ return;
+ }
+
+ if (typeof data.advanced_config !== "undefined" && data.advanced_config !== row.advanced_config) {
+ deny();
+ }
+ if (Array.isArray(data.locations)) {
+ internalHost.assertLocationAdvancedConfigUnchanged(data.locations, row.locations);
+ }
+ },
+
+ /**
+ * For non-admins: every location's advanced_config must be empty or one that already exists
+ * on this host. Matching is by value, not path, so renaming or reordering a location that
+ * carries admin-written config is allowed, but new or edited config is not. Each existing
+ * config can be used once (no duplicating it onto other paths), and same-path matches are
+ * taken first. Clearing a config is allowed: deleting the location is, and has the same
+ * effect, and a location deleted and re-added at the same path arrives with an empty config.
+ * A location that omits advanced_config keeps the one at its path, unless another location
+ * explicitly carries it. Mutates `locations`.
+ *
+ * @param {Array} locations incoming locations
+ * @param {Array} [existingLocations] the host's current locations
+ * @throws {PermissionError}
+ */
+ assertLocationAdvancedConfigUnchanged: (locations, existingLocations) => {
+ const existing = Array.isArray(existingLocations) ? existingLocations : [];
+ const advAtPath = (path) => existing.find((l) => l.path === path)?.advanced_config || "";
+ const pool = existing.map((l) => l.advanced_config || "").filter(Boolean);
+ const take = (value) => {
+ const i = pool.indexOf(value);
+ if (i === -1) {
+ return false;
+ }
+ pool.splice(i, 1);
+ return true;
+ };
+ const deny = () => {
+ throw new errs.PermissionError("You do not have permission to modify advanced configuration");
+ };
+
+ // Pass 1: locations that keep the config at their own path
+ const unresolved = [];
+ const omitted = [];
+ for (const loc of locations) {
+ const atPath = advAtPath(loc.path);
+ if (typeof loc.advanced_config === "undefined") {
+ omitted.push(loc);
+ } else if ((loc.advanced_config || "") === atPath && (!atPath || take(atPath))) {
+ // unchanged
+ } else {
+ unresolved.push(loc);
+ }
+ }
+
+ // Pass 2: renamed/moved locations may carry an existing config not already claimed, or
+ // none at all
+ for (const loc of unresolved) {
+ if (loc.advanced_config && !take(loc.advanced_config)) {
+ deny();
+ }
+ }
+
+ // Pass 3: locations that omit advanced_config keep the one at their path, if no location
+ // that names it explicitly has claimed it. Running this last stops a new location at an
+ // old path taking the config away from the location it was renamed to.
+ for (const loc of omitted) {
+ const atPath = advAtPath(loc.path);
+ loc.advanced_config = atPath && take(atPath) ? atPath : "";
+ }
+ },
+
/**
* Makes sure that the ssl_* and hsts_* fields play nicely together.
* ie: if there is no cert, then force_ssl is off.
@@ -79,6 +179,9 @@ const internalHost = {
redirection_hosts: [],
};
+ // No relations are eager-loaded here: this scans every host just to match domains, and
+ // the few that match are rendered through internalNginx.generateConfig(), which loads
+ // their certificate and access list itself (loadRenderRelations).
const proxyRes = await proxyHostModel.query().where("is_deleted", 0);
responseObject.proxy_hosts = internalHost._getHostsWithDomains(proxyRes, domainNames);
responseObject.total_count += responseObject.proxy_hosts.length;
diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js
index a50e0780d..06180e011 100644
--- a/backend/internal/nginx.js
+++ b/backend/internal/nginx.js
@@ -4,9 +4,11 @@ import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import _ from "lodash";
import errs from "../lib/error.js";
+import { assertAccessListRenderable, assertHostRenderable, isTokenSafe, unsafeValueError } from "../lib/nginx-values.js";
import utils from "../lib/utils.js";
import { debug, nginx as logger } from "../logger.js";
import accessListModel from "../models/access_list.js";
+import certificateModel from "../models/certificate.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -39,9 +41,30 @@ const internalNginx = {
return internalNginx.deleteConfig(host_type, host, true);
})
.then(() => {
- return internalNginx.generateConfig(host_type, host);
+ return internalNginx.generateConfig(host_type, host).then(
+ () => true,
+ (err) => {
+ if (!err.unsafeNginxValue) {
+ throw err;
+ }
+ // Nothing was written. Report it on the host like a failed nginx test.
+ logger.error(err.message);
+ combined_meta = _.assign({}, host.meta, {
+ nginx_online: false,
+ nginx_err: err.message,
+ });
+ return model
+ .query()
+ .where("id", host.id)
+ .patch({ meta: combined_meta })
+ .then(() => false);
+ },
+ );
})
- .then(() => {
+ .then((generated) => {
+ if (!generated) {
+ return;
+ }
// Test nginx again and update meta with result
return internalNginx
.test()
@@ -181,7 +204,21 @@ const internalNginx = {
.first();
}
if (locationAccessList) {
+ assertAccessListRenderable(
+ `Proxy Host #${host.id} location ${locationCopy.path}`,
+ locationAccessList,
+ );
locationCopy.access_list = locationAccessList;
+ } else if (
+ locationCopy.access_list_id > 0 &&
+ locationCopy.access_list_id !== host.access_list?.id
+ ) {
+ // The location names its own list but it's gone. Don't fall back to the
+ // host's (possibly less restrictive) list; _access.conf denies all instead.
+ logger.error(
+ `Proxy Host #${host.id} location "${locationCopy.path}" references Access List #${locationCopy.access_list_id} which does not exist, all access will be denied`,
+ );
+ locationCopy.access_list = null;
} else {
locationCopy.access_list_id = host.access_list_id;
locationCopy.access_list = host.access_list;
@@ -209,11 +246,16 @@ const internalNginx = {
* @param {Object} host
* @returns {Promise}
*/
- generateConfig: (host_type, host_row) => {
+ generateConfig: async (host_type, host_row) => {
// Prevent modifying the original object:
const host = JSON.parse(JSON.stringify(host_row));
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
+ await internalNginx.loadRenderRelations(nice_host_type, host);
+
+ // Values are written into the config as-is; refuse any that could add directives
+ assertHostRenderable(nice_host_type, host);
+
debug(logger, `Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2));
const renderEngine = utils.getRenderEngine();
@@ -276,29 +318,79 @@ const internalNginx = {
// Set the IPv6 setting for the host
host.ipv6 = internalNginx.ipv6Enabled();
- locationsPromise.then(() => {
- renderEngine
- .parseAndRender(template, host)
- .then((config_text) => {
- fs.writeFileSync(filename, config_text, { encoding: "utf8" });
- debug(logger, "Wrote config:", filename, config_text);
+ locationsPromise
+ .then(() => {
+ renderEngine
+ .parseAndRender(template, host)
+ .then((config_text) => {
+ fs.writeFileSync(filename, config_text, { encoding: "utf8" });
+ debug(logger, "Wrote config:", filename, config_text);
- // Restore locations array
- host.locations = origLocations;
+ // Restore locations array
+ host.locations = origLocations;
- resolve(true);
- })
- .catch((err) => {
- debug(logger, `Could not write ${filename}:`, err.message);
- reject(new errs.ConfigurationError(err.message));
- });
- }).catch((err) => {
- debug(logger, `Could not render locations for ${filename}:`, err.message);
- reject(new errs.ConfigurationError(err.message));
- });
+ resolve(true);
+ })
+ .catch((err) => {
+ debug(logger, `Could not write ${filename}:`, err.message);
+ reject(new errs.ConfigurationError(err.message));
+ });
+ })
+ .catch((err) => {
+ debug(logger, `Could not render locations for ${filename}:`, err.message);
+ reject(new errs.ConfigurationError(err.message));
+ });
});
},
+ /**
+ * The templates rely on the certificate and access list relations being loaded.
+ * Not every caller fetches them (or fetches them fully), and when they're missing
+ * the config silently renders without SSL or without any access rules (#5919).
+ * Load anything that is referenced but not loaded onto the given host object.
+ *
+ * @param {String} host_type
+ * @param {Object} host
+ * @returns {Promise}
+ */
+ loadRenderRelations: async (host_type, host) => {
+ if (host.certificate_id > 0 && host.certificate?.id !== host.certificate_id) {
+ const certificate = await certificateModel
+ .query()
+ .where("is_deleted", 0)
+ .andWhere("id", host.certificate_id)
+ .first();
+ host.certificate = certificate ? JSON.parse(JSON.stringify(certificate)) : null;
+ }
+
+ if (host_type === "proxy_host" && host.access_list_id > 0) {
+ if (
+ host.access_list?.id !== host.access_list_id ||
+ !Array.isArray(host.access_list.clients) ||
+ !Array.isArray(host.access_list.items)
+ ) {
+ const accessList = await accessListModel
+ .query()
+ .where("is_deleted", 0)
+ .andWhere("id", host.access_list_id)
+ .withGraphFetched("[clients,items]")
+ .first();
+ host.access_list = accessList ? JSON.parse(JSON.stringify(accessList)) : null;
+ }
+
+ if (!host.access_list) {
+ // _access.conf renders "deny all" when the list is missing, so fail closed
+ logger.error(
+ `Proxy Host #${host.id} references Access List #${host.access_list_id} which does not exist, all access will be denied`,
+ );
+ } else if (!host.access_list.clients.length && !host.access_list.items.length) {
+ logger.warn(
+ `Proxy Host #${host.id} uses Access List #${host.access_list_id} which has no clients or authorizations, no access restrictions will apply`,
+ );
+ }
+ }
+ },
+
/**
* This generates a temporary nginx config listening on port 80 for the domain names listed
* in the certificate setup. It allows the letsencrypt acme challenge to be requested by letsencrypt
@@ -311,6 +403,15 @@ const internalNginx = {
debug(logger, "Generating LetsEncrypt Request Config:", certificate);
const renderEngine = utils.getRenderEngine();
+ const unsafe = (certificate.domain_names || []).find((name) => !isTokenSafe(name));
+ if (typeof unsafe !== "undefined") {
+ return Promise.reject(
+ unsafeValueError(
+ `Certificate #${certificate.id}: domain name ${JSON.stringify(unsafe)} is not allowed`,
+ ),
+ );
+ }
+
return new Promise((resolve, reject) => {
let template = null;
const filename = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`;
diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js
index eac0c148a..4682a94fa 100644
--- a/backend/internal/proxy-host.js
+++ b/backend/internal/proxy-host.js
@@ -2,7 +2,7 @@ import _ from "lodash";
import errs from "../lib/error.js";
import { castJsonIfNeed } from "../lib/helpers.js";
import utils from "../lib/utils.js";
-import proxyHostModel from "../models/proxy_host.js";
+import proxyHostModel, { scopeToOwnerOrAllowed } from "../models/proxy_host.js";
import internalAuditLog from "./audit-log.js";
import internalCertificate from "./certificate.js";
import internalHost from "./host.js";
@@ -57,6 +57,8 @@ const internalProxyHost = {
thisData.advanced_config = "";
}
+ internalHost.assertAdvancedConfigAllowed(access, thisData);
+
return proxyHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions()));
})
.then((row) => {
@@ -83,28 +85,28 @@ const internalProxyHost = {
expand: ["certificate", "owner", "access_list.[clients,items]"],
});
})
- .then(async (row) => {
- // Configure nginx
- return internalNginx.configure(proxyHostModel, "proxy_host", row).then(() => {
- return row;
- });
- })
- .then((row) => {
- // Audit log
- thisData.meta = _.assign({}, thisData.meta || {}, row.meta);
-
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: "created",
- object_type: "proxy-host",
- object_id: row.id,
- meta: thisData,
- })
- .then(() => {
+ .then(async (row) => {
+ // Configure nginx
+ return internalNginx.configure(proxyHostModel, "proxy_host", row).then(() => {
return row;
});
- });
+ })
+ .then((row) => {
+ // Audit log
+ thisData.meta = _.assign({}, thisData.meta || {}, row.meta);
+
+ // Add to audit log
+ return internalAuditLog
+ .add(access, {
+ action: "created",
+ object_type: "proxy-host",
+ object_id: row.id,
+ meta: thisData,
+ })
+ .then(() => {
+ return row;
+ });
+ });
},
/**
@@ -155,6 +157,9 @@ const internalProxyHost = {
);
}
+ // Before the certificate is created, so a refused request doesn't leave one behind
+ internalHost.assertAdvancedConfigAllowed(access, thisData, row);
+
if (createCertificate) {
return internalCertificate
.createQuickCertificate(access, {
@@ -202,24 +207,24 @@ const internalProxyHost = {
});
});
})
- .then(() => {
- return internalProxyHost
- .get(access, {
- id: thisData.id,
- expand: ["owner", "certificate", "access_list.[clients,items]"],
- })
- .then(async (row) => {
- if (!row.enabled) {
- // No need to add nginx config if host is disabled
- return row;
- }
- // Configure nginx
- return internalNginx.configure(proxyHostModel, "proxy_host", row).then((new_meta) => {
- row.meta = new_meta;
- return _.omit(internalHost.cleanRowCertificateMeta(row), omissions());
+ .then(() => {
+ return internalProxyHost
+ .get(access, {
+ id: thisData.id,
+ expand: ["owner", "certificate", "access_list.[clients,items]"],
+ })
+ .then(async (row) => {
+ if (!row.enabled) {
+ // No need to add nginx config if host is disabled
+ return row;
+ }
+ // Configure nginx
+ return internalNginx.configure(proxyHostModel, "proxy_host", row).then((new_meta) => {
+ row.meta = new_meta;
+ return _.omit(internalHost.cleanRowCertificateMeta(row), omissions());
+ });
});
- });
- });
+ });
},
/**
@@ -243,7 +248,7 @@ const internalProxyHost = {
.first();
if (access_data.permission_visibility !== "all") {
- query.andWhere("owner_user_id", access.token.getUserId(1));
+ scopeToOwnerOrAllowed(query, access.token.getUserId(1), access.getPermissions?.()?.meta);
}
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
@@ -323,39 +328,36 @@ const internalProxyHost = {
.then(() => {
return internalProxyHost.get(access, {
id: data.id,
- expand: ["certificate", "owner", "access_list"],
+ expand: ["certificate", "owner", "access_list.[clients,items]"],
});
})
- .then(async (row) => {
- if (!row?.id) {
- throw new errs.ItemNotFoundError(data.id);
- }
- if (row.enabled) {
- throw new errs.ValidationError("Host is already enabled");
- }
+ .then(async (row) => {
+ if (!row?.id) {
+ throw new errs.ItemNotFoundError(data.id);
+ }
+ if (row.enabled) {
+ throw new errs.ValidationError("Host is already enabled");
+ }
- row.enabled = 1;
+ row.enabled = 1;
- await proxyHostModel
- .query()
- .where("id", row.id)
- .patch({
+ await proxyHostModel.query().where("id", row.id).patch({
enabled: 1,
});
- // Configure nginx
- await internalNginx.configure(proxyHostModel, "proxy_host", row);
+ // Configure nginx
+ await internalNginx.configure(proxyHostModel, "proxy_host", row);
- // Add to audit log
- await internalAuditLog.add(access, {
- action: "enabled",
- object_type: "proxy-host",
- object_id: row.id,
- meta: _.omit(row, omissions()),
+ // Add to audit log
+ await internalAuditLog.add(access, {
+ action: "enabled",
+ object_type: "proxy-host",
+ object_id: row.id,
+ meta: _.omit(row, omissions()),
+ });
+
+ return true;
});
-
- return true;
- });
},
/**
@@ -427,7 +429,7 @@ const internalProxyHost = {
.orderBy(castJsonIfNeed("domain_names"), "ASC");
if (accessData.permission_visibility !== "all") {
- query.andWhere("owner_user_id", access.token.getUserId(1));
+ scopeToOwnerOrAllowed(query, access.token.getUserId(1), access.getPermissions?.()?.meta);
}
// Query is used for searching
@@ -453,18 +455,18 @@ const internalProxyHost = {
*
* @param {Number} user_id
* @param {String} visibility
+ * @param {Object} [permissionsMeta] the user's permissions meta, as already loaded by access
* @returns {Promise}
*/
- getCount: (user_id, visibility) => {
+ getCount: async (user_id, visibility, permissionsMeta) => {
const query = proxyHostModel.query().count("id as count").where("is_deleted", 0);
if (visibility !== "all") {
- query.andWhere("owner_user_id", user_id);
+ scopeToOwnerOrAllowed(query, user_id, permissionsMeta);
}
- return query.first().then((row) => {
- return Number.parseInt(row.count, 10);
- });
+ const row = await query.first();
+ return Number.parseInt(row.count, 10);
},
};
diff --git a/backend/internal/redirection-host.js b/backend/internal/redirection-host.js
index 542439fd3..f5945a6b8 100644
--- a/backend/internal/redirection-host.js
+++ b/backend/internal/redirection-host.js
@@ -53,10 +53,12 @@ const internalRedirectionHost = {
// Fix for db field not having a default value
// for this optional field.
- if (typeof data.advanced_config === "undefined") {
- data.advanced_config = "";
+ if (typeof thisData.advanced_config === "undefined") {
+ thisData.advanced_config = "";
}
+ internalHost.assertAdvancedConfigAllowed(access, thisData);
+
return redirectionHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions()));
})
.then((row) => {
@@ -155,6 +157,9 @@ const internalRedirectionHost = {
);
}
+ // Before the certificate is created, so a refused request doesn't leave one behind
+ internalHost.assertAdvancedConfigAllowed(access, thisData, row);
+
if (createCertificate) {
return internalCertificate
.createQuickCertificate(access, {
diff --git a/backend/internal/report.js b/backend/internal/report.js
index 59f13fe69..547af3ef3 100644
--- a/backend/internal/report.js
+++ b/backend/internal/report.js
@@ -15,7 +15,11 @@ const internalReport = {
const userId = access.token.getUserId(1);
const promises = [
- internalProxyHost.getCount(userId, access_data.permission_visibility),
+ internalProxyHost.getCount(
+ userId,
+ access_data.permission_visibility,
+ access.getPermissions?.()?.meta,
+ ),
internalRedirectionHost.getCount(userId, access_data.permission_visibility),
internalStream.getCount(userId, access_data.permission_visibility),
internalDeadHost.getCount(userId, access_data.permission_visibility),
diff --git a/backend/internal/token.js b/backend/internal/token.js
index 67255b213..d3a178d4f 100644
--- a/backend/internal/token.js
+++ b/backend/internal/token.js
@@ -34,18 +34,20 @@ export default {
.andWhere("is_disabled", 0)
.first();
- if (!user) {
- throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH);
- }
+ // An unknown, disabled or passwordless account goes through the same queries and the same password check
+ // as a real one, and fails with the same error: answering any sooner or any differently would tell anyone
+ // trying addresses which ones have an account. There is no user 0, so its lookup finds nothing. An empty
+ // secret, which creating a user through the API can store, is no hash: bcrypt would reject it at once.
+ const auth = await authModel
+ .query()
+ .where("user_id", "=", user?.id ?? 0)
+ .where("type", "=", "password")
+ .first();
- const auth = await authModel.query().where("user_id", "=", user.id).where("type", "=", "password").first();
-
- if (!auth) {
- throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH);
- }
-
- const valid = await auth.verifyPassword(data.secret);
- if (!valid) {
+ const valid = auth?.secret
+ ? await auth.verifyPassword(data.secret)
+ : await authModel.verifyDummyPassword(data.secret);
+ if (!user || !valid) {
throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH, ERROR_MESSAGE_INVALID_AUTH_I18N);
}
diff --git a/backend/internal/user.js b/backend/internal/user.js
index 8dd86f461..0c0313b44 100644
--- a/backend/internal/user.js
+++ b/backend/internal/user.js
@@ -3,6 +3,7 @@ import _ from "lodash";
import errs from "../lib/error.js";
import utils from "../lib/utils.js";
import authModel from "../models/auth.js";
+import proxyHostModel from "../models/proxy_host.js";
import userModel from "../models/user.js";
import userPermissionModel from "../models/user_permission.js";
import internalAuditLog from "./audit-log.js";
@@ -444,7 +445,7 @@ const internalUser = {
return internalUser.get(access, { id: data.id });
})
.then((user) => {
- if (user.id !== data.id) {
+ if (Number(user.id) !== Number(data.id)) {
// Sanity check that something crazy hasn't happened
throw new errs.InternalValidationError(
`User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
@@ -454,21 +455,23 @@ const internalUser = {
return user;
})
.then((user) => {
+ const permData = _.omit(data, ["id"]);
// Get perms row, patch if it exists
return userPermissionModel
.query()
.where("user_id", user.id)
.first()
- .then((existing_auth) => {
+ .then(async (existing_auth) => {
+ permData.meta = await internalUser.normalisePermissionMeta(existing_auth, permData);
if (existing_auth) {
// patch
return userPermissionModel
.query()
.where("user_id", user.id)
- .patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, data));
+ .patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, permData));
}
// insert
- return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data));
+ return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, permData));
})
.then((permissions) => {
// Add to Audit Log
@@ -488,6 +491,36 @@ const internalUser = {
});
},
+ /**
+ * The permission meta to store. Proxy host grants (meta.proxy_host_ids) only apply while
+ * visibility is "user" and proxy hosts are not hidden, so they're cleared otherwise; kept,
+ * they would silently come back when proxy hosts are shown again. Ids of hosts that no
+ * longer exist are dropped. `permData` may be partial, so the stored values fill the gaps.
+ *
+ * @param {Object} [existing] the stored user_permission row
+ * @param {Object} permData incoming permission fields
+ * @returns {Promise}
+ */
+ normalisePermissionMeta: async (existing, permData) => {
+ const effective = _.assign(
+ { visibility: "user", proxy_hosts: "hidden" },
+ existing || {},
+ _.omit(permData, ["meta"]),
+ );
+ const meta = _.assign({}, existing?.meta || {}, permData.meta || {});
+ const requested = Array.isArray(meta.proxy_host_ids) ? _.uniq(meta.proxy_host_ids) : [];
+
+ if (effective.visibility !== "user" || effective.proxy_hosts === "hidden" || !requested.length) {
+ meta.proxy_host_ids = [];
+ return meta;
+ }
+
+ const rows = await proxyHostModel.query().select("id").where("is_deleted", 0).whereIn("id", requested);
+ const found = new Set(rows.map((r) => r.id));
+ meta.proxy_host_ids = requested.filter((id) => found.has(id));
+ return meta;
+ },
+
/**
* @param {Access} access
* @param {Object} data
diff --git a/backend/lib/access.js b/backend/lib/access.js
index d119a4f92..c38213b33 100644
--- a/backend/lib/access.js
+++ b/backend/lib/access.js
@@ -13,7 +13,7 @@ import Ajv from "ajv/dist/2020.js";
import _ from "lodash";
import { access as logger } from "../logger.js";
import authModel from "../models/auth.js";
-import proxyHostModel from "../models/proxy_host.js";
+import proxyHostModel, { scopeToOwnerOrAllowed } from "../models/proxy_host.js";
import TokenModel from "../models/token.js";
import userModel from "../models/user.js";
import permsSchema from "./access/permissions.json" with { type: "json" };
@@ -139,7 +139,7 @@ export default function (tokenString) {
const query = proxyHostModel.query().select("id").andWhere("is_deleted", 0);
if (permissions.visibility === "user") {
- query.andWhere("owner_user_id", tokenUserId);
+ scopeToOwnerOrAllowed(query, tokenUserId, permissions.meta);
}
const rows = await query;
@@ -213,6 +213,11 @@ export default function (tokenString) {
return {
token: Token,
+ getPermissions: () => permissions,
+ getRoles: () => userRoles,
+ hasRole: (role) => userRoles.includes(role),
+ isAdmin: () => userRoles.includes("admin") || Token.hasScope("admin"),
+
/**
*
* @param {Boolean} [allowInternal]
@@ -220,7 +225,8 @@ export default function (tokenString) {
*/
load: async (allowInternal) => {
if (tokenString) {
- return await Token.load(tokenString);
+ await this.init();
+ return tokenData;
}
allowInternalAccess = allowInternal;
return allowInternal || null;
@@ -255,6 +261,7 @@ export default function (tokenString) {
permission_streams: permissions.streams,
permission_access_lists: permissions.access_lists,
permission_certificates: permissions.certificates,
+ permission_meta: permissions.meta,
},
};
diff --git a/backend/lib/express/rate-limit.js b/backend/lib/express/rate-limit.js
new file mode 100644
index 000000000..7512f7485
--- /dev/null
+++ b/backend/lib/express/rate-limit.js
@@ -0,0 +1,155 @@
+import crypto from "node:crypto";
+import { isCI } from "../config.js";
+
+/**
+ * In-memory fixed window rate limiter for authentication endpoints
+ */
+
+// Keys longer than this are hashed, so a client can't make the store hold arbitrarily large
+// strings (keys can include request body values such as the login identity).
+const MAX_KEY_LENGTH = 128;
+// Entries per limiter. A flood of unique keys sweeps expired entries, then evicts the oldest,
+// instead of growing the store until the periodic cleanup runs.
+const MAX_STORE_SIZE = 50000;
+
+// One store per limiter, so separate endpoints never share buckets
+const stores = new Set();
+
+// Clean up old entries every 2 minutes
+const cleanupTimer = setInterval(() => {
+ const now = Date.now();
+ for (const store of stores) {
+ for (const [key, record] of store.entries()) {
+ if (now - record.startTime > record.windowMs * 2) {
+ store.delete(key);
+ }
+ }
+ }
+}, 120000);
+cleanupTimer.unref();
+
+// Generated at container start (prepare/45-admin-port.sh) and sent by the admin nginx as
+// X-NPM-Peer-Secret alongside X-NPM-Peer-Addr. Unset (development, or a mounted production.conf)
+// means the header is never trusted.
+const peerSecret = Buffer.from(process.env.NPM_PEER_SECRET || "");
+
+const hasPeerSecret = (value) => {
+ if (!peerSecret.length || typeof value !== "string") {
+ return false;
+ }
+ const given = Buffer.from(value);
+ return given.length === peerSecret.length && crypto.timingSafeEqual(given, peerSecret);
+};
+
+/**
+ * The address to rate limit on: the TCP peer of the admin nginx, which it passes as
+ * X-NPM-Peer-Addr ($realip_remote_addr). That header is only honoured from a loopback socket
+ * carrying the startup secret, so it can only have come from the admin nginx: a proxy host or
+ * stream forwarding to the backend port is also a loopback peer, but can't know the secret.
+ * The realip-resolved client address is deliberately not used: nginx trusts X-Real-IP from
+ * private ranges, so a client on the LAN or Docker network could pick a new address, and a fresh
+ * bucket, on every request. Behind another proxy (including an NPM proxy host in front of the
+ * admin UI) this resolves to that proxy's address.
+ *
+ * @param {Object} req
+ * @returns {String}
+ */
+export const getClientIp = (req) => {
+ const socketAddr = req.socket?.remoteAddress || "unknown";
+ const isLoopback = socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1";
+ const trusted = isLoopback && hasPeerSecret(req.headers["x-npm-peer-secret"]);
+ const peerAddr = trusted ? req.headers["x-npm-peer-addr"]?.trim() : null;
+ return peerAddr || socketAddr;
+};
+
+const makeRoom = (store, now) => {
+ for (const [k, r] of store.entries()) {
+ if (now - r.startTime > r.windowMs) {
+ store.delete(k);
+ }
+ }
+ // Maps iterate in insertion order, so this drops the oldest windows first
+ for (const k of store.keys()) {
+ if (store.size < MAX_STORE_SIZE) {
+ break;
+ }
+ store.delete(k);
+ }
+};
+
+const boundedKey = (key) =>
+ key.length > MAX_KEY_LENGTH ? `sha256:${crypto.createHash("sha256").update(key).digest("hex")}` : key;
+
+/**
+ * @param {Object} [options]
+ * @param {Number} [options.windowMs]
+ * @param {Number} [options.max]
+ * @param {String} [options.message]
+ * @param {Boolean} [options.skipSuccessfulRequests]
+ * @param {Function} [options.keyGenerator] (req, clientIp) => String. Defaults to the client IP.
+ * Including the target account in the key stops every
+ * user being locked out when many clients share an IP
+ * (e.g. the admin UI behind another reverse proxy).
+ */
+export default function createRateLimiter(options = {}) {
+ const windowMs = options.windowMs || 60 * 1000; // 1 minute default
+ const max = options.max || 10; // 10 requests default
+ const message = options.message || "Too many attempts from this IP, please try again later.";
+ const skipSuccessfulRequests = options.skipSuccessfulRequests !== false;
+ const keyGenerator = options.keyGenerator || ((_req, clientIp) => clientIp);
+
+ const store = new Map();
+ stores.add(store);
+
+ return (req, res, next) => {
+ // Bypass rate limiting in CI, test environments, or when explicitly disabled
+ if (
+ isCI() ||
+ process.env.CI === "true" ||
+ process.env.NODE_ENV === "test" ||
+ process.env.DISABLE_RATE_LIMIT === "true"
+ ) {
+ return next();
+ }
+
+ const key = boundedKey(String(keyGenerator(req, getClientIp(req))));
+
+ const now = Date.now();
+ let record = store.get(key);
+
+ if (!record || now - record.startTime > windowMs) {
+ record = {
+ count: 0,
+ startTime: now,
+ windowMs,
+ };
+ if (!store.has(key) && store.size >= MAX_STORE_SIZE) {
+ makeRoom(store, now);
+ }
+ store.set(key, record);
+ }
+
+ record.count++;
+
+ if (skipSuccessfulRequests) {
+ const currentRecord = record;
+ res.on("finish", () => {
+ if (res.statusCode < 400 && currentRecord.count > 0) {
+ currentRecord.count--;
+ }
+ });
+ }
+
+ if (record.count > max) {
+ res.setHeader("Retry-After", Math.ceil((record.startTime + windowMs - now) / 1000));
+ return res.status(429).json({
+ error: {
+ code: 429,
+ message,
+ },
+ });
+ }
+
+ next();
+ };
+}
diff --git a/backend/lib/nginx-values.js b/backend/lib/nginx-values.js
new file mode 100644
index 000000000..0f5382623
--- /dev/null
+++ b/backend/lib/nginx-values.js
@@ -0,0 +1,134 @@
+import net from "node:net";
+import errs from "./error.js";
+
+/**
+ * Rules for user-supplied values that the templates write into nginx config as-is.
+ *
+ * Anything that ends or changes an nginx token (whitespace, ";", "{", "}", quotes, "#" which
+ * starts a comment, and "\" which escapes the next character) would let the value add its own
+ * directives, which only admins may do (advanced_config). The API schema rejects these values
+ * with the same patterns (a test keeps the two in step). generateConfig() checks stored values
+ * again, so rows saved before these rules can't add directives either. That check is only about
+ * nginx structure, not format: a stored value in a format the API no longer accepts, but which
+ * can't add directives, still renders.
+ *
+ * Patterns are strings so they can be compared with the JSON schema; compile them with the "u"
+ * flag, as ajv does.
+ */
+
+// One character that can't end or change an unquoted nginx token
+const TOKEN_CHAR = String.raw`[^\s\p{Cc};{}"'#\\]`;
+// Host part of a forward host: also no "$" (no variables in the upstream address) and no "/"
+const HOST_CHARS = String.raw`[^\s\p{Cc};{}"'#\\$/]+`;
+// The existing domain name rule ("a.b.c", no "*", no empty labels), written so it can't
+// backtrack exponentially
+const DOMAIN = String.raw`[^.*]+(?:\.[^.*]+)*\.?[^.]`;
+
+export const patterns = {
+ // server_name entries. The old character blacklist plus whitespace and control characters.
+ domainName: "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"\\s\\p{Cc}]+$",
+ // proxy_host forward_host and location forward_host: a host, optionally followed by a path
+ // (locations split "host/path" at render time)
+ forwardHost: String.raw`^${HOST_CHARS}(?:/${TOKEN_CHAR}*)?$`,
+ // location forward_path, appended straight after the port
+ forwardPath: String.raw`^${TOKEN_CHAR}*$`,
+ // location path: an optional modifier, then one token, either unquoted (a "\" must escape a
+ // visible character, so it can't swallow the space before "{") or double quoted. A modifier
+ // on its own isn't a location.
+ locationPath: String.raw`^(?!(?:=|~\*?|\^~)$)(?:(?:=|~\*?|\^~) +)?(?:"(?:[^"\\\p{Cc}]|\\[^\p{Cc}])+"|(?:[^\s\p{Cc};{}"'#\\]|\\[^\s\p{Cc}])+)$`,
+ // redirection forward_domain_name, e.g. "example.com:8443/path"
+ redirectDomain: String.raw`^(?=${TOKEN_CHAR}+$)${DOMAIN}$`,
+ // stream forwarding_host when it is a domain name (IPs are matched separately)
+ streamDomain: String.raw`^(?=${HOST_CHARS}$)${DOMAIN}$`,
+ // access list client address: IPv6 with an optional prefix. No zone id ("%eth0"): nginx's
+ // allow/deny rejects them, and the old "%.+" let anything through, including "; allow all"
+ clientIpv6: String.raw`^(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:[0-9A-Fa-f]{1,4}|:)|(?:[0-9A-Fa-f]{1,4}:){6}(?::[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::[0-9A-Fa-f]{1,4}){1,2}|:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){4}(?:(?::[0-9A-Fa-f]{1,4}){1,3}|(?::[0-9A-Fa-f]{1,4})?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){3}(?:(?::[0-9A-Fa-f]{1,4}){1,4}|(?::[0-9A-Fa-f]{1,4}){0,2}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){2}(?:(?::[0-9A-Fa-f]{1,4}){1,5}|(?::[0-9A-Fa-f]{1,4}){0,3}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:)(?:(?::[0-9A-Fa-f]{1,4}){1,6}|(?::[0-9A-Fa-f]{1,4}){0,4}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|(?::[0-9A-Fa-f]{1,4}){0,5}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:))(?:/(?:[0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]))?$`,
+ // htpasswd user name: a ":" or a line break would corrupt the file
+ accessUsername: String.raw`^[^:\p{Cc}]+$`,
+};
+
+const re = Object.fromEntries(Object.entries(patterns).map(([k, v]) => [k, new RegExp(v, "u")]));
+const unsafeToken = /[\s\p{Cc};{}"'#\\]/u;
+const ipv4WithPrefix = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}(?:\/(?:[0-9]|[1-2][0-9]|3[0-2]))?$/;
+
+export const isValid = (name, value) => typeof value === "string" && re[name].test(value);
+
+export const isValidClientAddress = (value) =>
+ typeof value === "string" && (value === "all" || ipv4WithPrefix.test(value) || re.clientIpv6.test(value));
+
+export const isValidStreamHost = (value) =>
+ typeof value === "string" && (net.isIP(value) !== 0 || re.streamDomain.test(value));
+
+/**
+ * Thrown when a stored value can't be written into nginx config safely
+ *
+ * @param {String} message
+ */
+export const unsafeValueError = (message) => {
+ const err = new errs.ConfigurationError(message);
+ err.unsafeNginxValue = true;
+ return err;
+};
+
+const describe = (value) => JSON.stringify(String(value)).slice(0, 80);
+
+/**
+ * @param {String} label e.g. "Proxy Host #3"
+ * @param {Object} [accessList]
+ * @throws {ConfigurationError}
+ */
+export const assertAccessListRenderable = (label, accessList) => {
+ for (const client of accessList?.clients || []) {
+ // "address" is rendered unquoted ("allow ;")
+ if (typeof client.address !== "string" || unsafeToken.test(client.address)) {
+ throw unsafeValueError(`${label}: access list client address ${describe(client.address)} is not allowed`);
+ }
+ }
+};
+
+// Can be written into config without ending or changing a token
+export const isTokenSafe = (value) => typeof value === "string" && value.length > 0 && !unsafeToken.test(value);
+
+/**
+ * Checks every user-supplied value the host's template renders. Call before rendering.
+ *
+ * @param {String} hostType proxy_host, redirection_host, dead_host, stream or default
+ * @param {Object} host with access_list loaded, for proxy hosts
+ * @throws {ConfigurationError}
+ */
+export const assertHostRenderable = (hostType, host) => {
+ const label = `${hostType.replace(/_/g, " ")} #${host.id}`;
+ const check = (ok, what, value) => {
+ if (!ok) {
+ throw unsafeValueError(`${label}: ${what} ${describe(value)} is not allowed in nginx config`);
+ }
+ };
+
+ if (hostType !== "stream" && hostType !== "default") {
+ for (const name of host.domain_names || []) {
+ check(isTokenSafe(name), "domain name", name);
+ }
+ }
+
+ if (hostType === "proxy_host") {
+ check(isTokenSafe(host.forward_host), "forward host", host.forward_host);
+ assertAccessListRenderable(label, host.access_list);
+ for (const loc of host.locations || []) {
+ // The path is several tokens (modifier, then a quoted or unquoted URI): its rule is the
+ // structural one
+ check(isValid("locationPath", loc.path), "location path", loc.path);
+ check(isTokenSafe(loc.forward_host), "location forward host", loc.forward_host);
+ if (loc.forward_path) {
+ check(isTokenSafe(loc.forward_path), "location forward path", loc.forward_path);
+ }
+ }
+ }
+
+ if (hostType === "redirection_host") {
+ check(isTokenSafe(host.forward_domain_name), "forward domain", host.forward_domain_name);
+ }
+
+ if (hostType === "stream") {
+ check(isTokenSafe(host.forwarding_host), "forwarding host", host.forwarding_host);
+ }
+};
diff --git a/backend/lib/validator/api.js b/backend/lib/validator/api.js
index 0597ef635..cc25a4f35 100644
--- a/backend/lib/validator/api.js
+++ b/backend/lib/validator/api.js
@@ -35,6 +35,13 @@ const apiValidator = async (schema, payload /*, description*/) => {
return payload;
}
+ // Keep ajv's wording, which clients may match on, but not the regex it quotes: some are long
+ // and mean nothing to a user
+ for (const e of validate.errors || []) {
+ if (e.keyword === "pattern") {
+ e.message = "must match pattern";
+ }
+ }
const message = ajv.errorsText(validate.errors);
const err = new errs.ValidationError(message);
err.debug = { validationErrors: validate.errors, payload };
diff --git a/backend/migrations/20260906220000_user_permission_meta.js b/backend/migrations/20260906220000_user_permission_meta.js
new file mode 100644
index 000000000..1573c77d8
--- /dev/null
+++ b/backend/migrations/20260906220000_user_permission_meta.js
@@ -0,0 +1,41 @@
+import { migrate as logger } from "../logger.js";
+
+const migrateName = "user_permission_meta";
+
+/**
+ * Migrate
+ *
+ * @param {Object} knex
+ * @returns {Promise}
+ */
+const up = (knex) => {
+ logger.info(`[${migrateName}] Migrating Up...`);
+
+ return knex.schema
+ .alterTable("user_permission", (table) => {
+ table.json("meta").nullable();
+ })
+ .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
+ .alterTable("user_permission", (table) => {
+ table.dropColumn("meta");
+ })
+ .then(() => {
+ logger.info(`[${migrateName}] user_permission Table altered`);
+ });
+};
+
+export { up, down };
diff --git a/backend/models/auth.js b/backend/models/auth.js
index e8af582d5..c6228feeb 100644
--- a/backend/models/auth.js
+++ b/backend/models/auth.js
@@ -1,6 +1,7 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
+import crypto from "node:crypto";
import bcrypt from "bcrypt";
import { Model } from "objection";
import db from "../db.js";
@@ -11,10 +12,15 @@ import User from "./user.js";
Model.knex(db());
const boolFields = ["is_deleted"];
+const saltRounds = 13;
+
+// A hash of a random value, for a login that names an account with no password to check it against. Hashed
+// with the same cost as real passwords, and made at startup so the first such login takes no longer either.
+const dummyHash = bcrypt.hash(crypto.randomBytes(32).toString("hex"), saltRounds);
function encryptPassword() {
if (this.type === "password" && this.secret) {
- return bcrypt.hash(this.secret, 13).then((hash) => {
+ return bcrypt.hash(this.secret, saltRounds).then((hash) => {
this.secret = hash;
});
}
@@ -60,6 +66,18 @@ class Auth extends Model {
return bcrypt.compare(password, this.secret);
}
+ /**
+ * Take as long as verifyPassword does, then fail. bcrypt is nearly all of the time a login takes, so a login
+ * that skipped it for an unknown account would answer far sooner, and say which accounts exist.
+ *
+ * @param {String} password
+ * @returns {Promise}
+ */
+ static async verifyDummyPassword(password) {
+ await bcrypt.compare(password, await dummyHash);
+ return false;
+ }
+
static get name() {
return "Auth";
}
diff --git a/backend/models/proxy_host.js b/backend/models/proxy_host.js
index acb8da935..d16ddc912 100644
--- a/backend/models/proxy_host.js
+++ b/backend/models/proxy_host.js
@@ -124,4 +124,23 @@ class ProxyHost extends Model {
}
}
+/**
+ * Restricts a proxy host query to hosts the user owns or has been granted by id
+ * (user permission meta.proxy_host_ids).
+ *
+ * @param {Object} query objection query on proxy_host
+ * @param {Number} userId
+ * @param {Object} [permissionMeta] the user's permission meta
+ * @returns {Object} the query
+ */
+export const scopeToOwnerOrAllowed = (query, userId, permissionMeta) => {
+ const allowedIds = permissionMeta?.proxy_host_ids || [];
+ return query.andWhere((builder) => {
+ builder.where("owner_user_id", userId);
+ if (allowedIds.length > 0) {
+ builder.orWhereIn("id", allowedIds);
+ }
+ });
+};
+
export default ProxyHost;
diff --git a/backend/models/user_permission.js b/backend/models/user_permission.js
index cc6eac2cc..0ad444c9c 100644
--- a/backend/models/user_permission.js
+++ b/backend/models/user_permission.js
@@ -24,6 +24,10 @@ class UserPermission extends Model {
static get tableName() {
return "user_permission";
}
+
+ static get jsonAttributes() {
+ return ["meta"];
+ }
}
export default UserPermission;
diff --git a/backend/package.json b/backend/package.json
index b8e0ed9fc..0a65fbe70 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -8,42 +8,43 @@
"type": "module",
"scripts": {
"lint": "biome lint",
+ "test": "node --test test/",
"prettier": "biome format --write .",
"validate-schema": "node validate-schema.js",
"regenerate-config": "node scripts/regenerate-config"
},
"dependencies": {
- "@apidevtools/json-schema-ref-parser": "^16.0.2",
+ "@apidevtools/json-schema-ref-parser": "^16.0.6",
"ajv": "^8.20.0",
"archiver": "^8.0.0",
"batchflow": "^0.4.0",
"bcrypt": "^6.0.0",
"better-sqlite3": "^13.0.3",
- "body-parser": "^2.3.0",
- "chalk": "5.6.2",
+ "body-parser": "^2.4.0",
+ "chalk": "6.0.1",
"compression": "^1.8.2",
- "express": "^5.2.1",
- "express-fileupload": "^1.5.2",
+ "express": "^5.3.0",
+ "express-fileupload": "^1.6.0",
"gravatar": "^1.8.2",
"jsonwebtoken": "^9.0.3",
"knex": "3.3.0",
- "liquidjs": "10.29.0",
+ "liquidjs": "10.30.1",
"lodash": "^4.18.1",
"moment": "^2.31.0",
- "mysql2": "^3.24.4",
+ "mysql2": "^3.24.6",
"node-rsa": "^2.0.0",
- "objection": "3.1.5",
- "otplib": "^13.5.0",
+ "objection": "4.4.3",
+ "otplib": "^13.6.0",
"path": "^0.12.7",
- "pg": "^8.23.0",
+ "pg": "^8.23.1",
"proxy-agent": "^8.0.2",
"signale": "1.4.0",
"sqlite3": "^6.0.1",
"temp-write": "^6.0.1"
},
"devDependencies": {
- "@apidevtools/swagger-parser": "^13.0.0",
- "@biomejs/biome": "^2.5.10",
+ "@apidevtools/swagger-parser": "^13.1.0",
+ "@biomejs/biome": "^2.6.6",
"nodemon": "^3.1.14"
},
"signale": {
diff --git a/backend/routes/logs.js b/backend/routes/logs.js
index 9088507b3..09841b3bd 100644
--- a/backend/routes/logs.js
+++ b/backend/routes/logs.js
@@ -62,7 +62,10 @@ router
enum: ["system", "letsencrypt", "host"],
},
host_type: {
- anyOf: [{ type: "null" }, { type: "string", enum: ["proxy", "redirection", "dead", "stream"] }],
+ anyOf: [
+ { type: "null" },
+ { type: "string", enum: ["proxy", "redirection", "dead", "stream"] },
+ ],
},
host_id: {
anyOf: [{ type: "null" }, { type: "integer", minimum: 1 }],
@@ -76,7 +79,10 @@ router
level: {
anyOf: [
{ type: "null" },
- { type: "string", enum: ["INFO", "WARN", "ERROR", "DEBUG", "SUCCESS", "FATAL", "COMPLETE"] },
+ {
+ type: "string",
+ enum: ["INFO", "WARN", "ERROR", "DEBUG", "SUCCESS", "FATAL", "COMPLETE"],
+ },
],
},
search: {
diff --git a/backend/routes/nginx/certificates.js b/backend/routes/nginx/certificates.js
index dea809423..90f279c37 100644
--- a/backend/routes/nginx/certificates.js
+++ b/backend/routes/nginx/certificates.js
@@ -161,7 +161,7 @@ router
}
try {
- const result = await internalCertificate.validate({
+ const result = await internalCertificate.validate(res.locals.access, {
files: req.files,
});
res.status(200).send(result);
diff --git a/backend/routes/tokens.js b/backend/routes/tokens.js
index 8a6a1bc0f..f56daa058 100644
--- a/backend/routes/tokens.js
+++ b/backend/routes/tokens.js
@@ -1,10 +1,56 @@
import express from "express";
import internalToken from "../internal/token.js";
import jwtdecode from "../lib/express/jwt-decode.js";
+import createRateLimiter from "../lib/express/rate-limit.js";
import apiValidator from "../lib/validator/api.js";
import { debug, express as logger } from "../logger.js";
import { getValidationSchema } from "../schema/index.js";
+// Two limits apply to logins. Per IP + target account: when the admin UI sits behind another
+// proxy every client can resolve to the same IP, and an IP-only bucket at this size would let
+// anyone lock out every user with a handful of bad logins.
+const tokenRateLimiter = createRateLimiter({
+ windowMs: 60 * 1000,
+ max: 10,
+ message: "Too many login attempts. Please try again in 1 minute.",
+ skipSuccessfulRequests: true,
+ keyGenerator: (req, clientIp) => {
+ const identity = typeof req.body?.identity === "string" ? req.body.identity.trim().toLowerCase() : "";
+ return `${clientIp}|${identity}`;
+ },
+});
+
+// And per IP across all accounts, so one address can't spray passwords over many accounts at
+// 10 a minute each. Only failures count. Behind a shared proxy this cap is shared by everyone
+// using it, which is why it's higher than the per-account one.
+const tokenIpRateLimiter = createRateLimiter({
+ windowMs: 60 * 1000,
+ max: 30,
+ message: "Too many login attempts. Please try again in 1 minute.",
+ skipSuccessfulRequests: true,
+});
+
+// Keyed on the user the challenge belongs to, not the challenge token itself, so fetching
+// fresh challenge tokens doesn't reset the code-guessing budget. The payload is read without
+// verifying the signature; verify2FA does that. A forged token only affects buckets for the
+// sender's own IP.
+const challengeUserId = (token) => {
+ try {
+ const payload = JSON.parse(Buffer.from(String(token).split(".")[1], "base64url").toString());
+ return String(payload?.attrs?.id ?? "");
+ } catch {
+ return "";
+ }
+};
+
+const twoFactorRateLimiter = createRateLimiter({
+ windowMs: 60 * 1000,
+ max: 10,
+ message: "Too many login attempts. Please try again in 1 minute.",
+ skipSuccessfulRequests: true,
+ keyGenerator: (req, clientIp) => `${clientIp}|${challengeUserId(req.body?.challenge_token)}`,
+});
+
const router = express.Router({
caseSensitive: true,
strict: true,
@@ -42,7 +88,7 @@ router
*
* Create a new Token
*/
- .post(async (req, res, next) => {
+ .post(tokenIpRateLimiter, tokenRateLimiter, async (req, res, next) => {
try {
const data = await apiValidator(getValidationSchema("/tokens", "post"), req.body);
const result = await internalToken.getTokenFromEmail(data);
@@ -64,7 +110,7 @@ router
*
* Verify 2FA code and get full token
*/
- .post(async (req, res, next) => {
+ .post(twoFactorRateLimiter, async (req, res, next) => {
try {
const { challenge_token, code } = await apiValidator(getValidationSchema("/tokens/2fa", "post"), req.body);
const result = await internalToken.verify2FA(challenge_token, code);
diff --git a/backend/routes/users.js b/backend/routes/users.js
index 3f972223b..2cdb4101c 100644
--- a/backend/routes/users.js
+++ b/backend/routes/users.js
@@ -260,7 +260,7 @@ router
.put(async (req, res, next) => {
try {
const payload = await apiValidator(getValidationSchema("/users/{userID}/permissions", "put"), req.body);
- payload.id = req.params.user_id;
+ payload.id = Number.parseInt(req.params.user_id, 10);
const result = await internalUser.setPermissions(res.locals.access, payload);
res.status(200).send(result);
} catch (err) {
diff --git a/backend/schema/common.json b/backend/schema/common.json
index 00b06e005..3b12bd143 100644
--- a/backend/schema/common.json
+++ b/backend/schema/common.json
@@ -84,7 +84,7 @@
"uniqueItems": true,
"items": {
"type": "string",
- "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$"
+ "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"\\s\\p{Cc}]+$"
},
"example": ["example.com", "www.example.com"]
},
@@ -147,7 +147,7 @@
},
{
"type": "string",
- "pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
+ "pattern": "^(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:[0-9A-Fa-f]{1,4}|:)|(?:[0-9A-Fa-f]{1,4}:){6}(?::[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::[0-9A-Fa-f]{1,4}){1,2}|:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){4}(?:(?::[0-9A-Fa-f]{1,4}){1,3}|(?::[0-9A-Fa-f]{1,4})?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){3}(?:(?::[0-9A-Fa-f]{1,4}){1,4}|(?::[0-9A-Fa-f]{1,4}){0,2}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:){2}(?:(?::[0-9A-Fa-f]{1,4}){1,5}|(?::[0-9A-Fa-f]{1,4}){0,3}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|(?:[0-9A-Fa-f]{1,4}:)(?:(?::[0-9A-Fa-f]{1,4}){1,6}|(?::[0-9A-Fa-f]{1,4}){0,4}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:)|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|(?::[0-9A-Fa-f]{1,4}){0,5}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}|:))(?:/(?:[0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]))?$"
},
{
"type": "string",
@@ -164,7 +164,8 @@
"properties": {
"username": {
"type": "string",
- "minLength": 1
+ "minLength": 1,
+ "pattern": "^[^:\\p{Cc}]+$"
},
"password": {
"type": "string"
diff --git a/backend/schema/components/certificate-object.json b/backend/schema/components/certificate-object.json
index 80cd92bef..c86a54da7 100644
--- a/backend/schema/components/certificate-object.json
+++ b/backend/schema/components/certificate-object.json
@@ -31,7 +31,7 @@
"uniqueItems": true,
"items": {
"type": "string",
- "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$"
+ "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"\\s\\p{Cc}]+$"
},
"example": ["example.com", "www.example.com"]
},
diff --git a/backend/schema/components/permission-object.json b/backend/schema/components/permission-object.json
index cae9d26c0..c49e6cba9 100644
--- a/backend/schema/components/permission-object.json
+++ b/backend/schema/components/permission-object.json
@@ -1,6 +1,7 @@
{
"type": "object",
"minProperties": 1,
+ "additionalProperties": false,
"properties": {
"visibility": {
"type": "string",
@@ -43,6 +44,25 @@
"description": "Certificates Permissions",
"enum": ["hidden", "view", "manage"],
"example": "hidden"
+ },
+ "meta": {
+ "type": "object",
+ "description": "Permission metadata",
+ "example": {
+ "proxy_host_ids": [1]
+ },
+ "properties": {
+ "proxy_host_ids": {
+ "type": "array",
+ "description": "Allowed Proxy Host IDs",
+ "example": [1],
+ "items": {
+ "type": "integer",
+ "description": "Proxy Host ID",
+ "example": 1
+ }
+ }
+ }
}
}
}
diff --git a/backend/schema/components/proxy-host-object.json b/backend/schema/components/proxy-host-object.json
index 90e6e23bb..dcef18cfb 100644
--- a/backend/schema/components/proxy-host-object.json
+++ b/backend/schema/components/proxy-host-object.json
@@ -45,6 +45,7 @@
"type": "string",
"minLength": 1,
"maxLength": 255,
+ "pattern": "^[^\\s\\p{Cc};{}\"'#\\\\$/]+(?:/[^\\s\\p{Cc};{}\"'#\\\\]*)?$",
"example": "127.0.0.1"
},
"forward_port": {
@@ -108,7 +109,8 @@
},
"path": {
"type": "string",
- "minLength": 1
+ "minLength": 1,
+ "pattern": "^(?!(?:=|~\\*?|\\^~)$)(?:(?:=|~\\*?|\\^~) +)?(?:\"(?:[^\"\\\\\\p{Cc}]|\\\\[^\\p{Cc}])+\"|(?:[^\\s\\p{Cc};{}\"'#\\\\]|\\\\[^\\s\\p{Cc}])+)$"
},
"forward_scheme": {
"$ref": "#/properties/forward_scheme"
@@ -120,7 +122,8 @@
"$ref": "#/properties/forward_port"
},
"forward_path": {
- "type": "string"
+ "type": "string",
+ "pattern": "^[^\\s\\p{Cc};{}\"'#\\\\]*$"
},
"advanced_config": {
"type": "string"
diff --git a/backend/schema/components/redirection-host-object.json b/backend/schema/components/redirection-host-object.json
index 58169720b..199053019 100644
--- a/backend/schema/components/redirection-host-object.json
+++ b/backend/schema/components/redirection-host-object.json
@@ -57,7 +57,7 @@
"forward_domain_name": {
"description": "Domain Name",
"type": "string",
- "pattern": "^(?:[^.*]+\\.?)+[^.]$",
+ "pattern": "^(?=[^\\s\\p{Cc};{}\"'#\\\\]+$)[^.*]+(?:\\.[^.*]+)*\\.?[^.]$",
"example": "jc21.com"
},
"preserve_path": {
diff --git a/backend/schema/components/stream-object.json b/backend/schema/components/stream-object.json
index 3a1cd5210..3b8cd4f55 100644
--- a/backend/schema/components/stream-object.json
+++ b/backend/schema/components/stream-object.json
@@ -39,7 +39,7 @@
{
"description": "Domain Name",
"type": "string",
- "pattern": "^(?:[^.*]+\\.?)+[^.]$",
+ "pattern": "^(?=[^\\s\\p{Cc};{}\"'#\\\\$/]+$)[^.*]+(?:\\.[^.*]+)*\\.?[^.]$",
"example": "example.com"
},
{
diff --git a/backend/schema/paths/nginx/certificates/certID/delete.json b/backend/schema/paths/nginx/certificates/certID/delete.json
index a99f619d6..2c38c388c 100644
--- a/backend/schema/paths/nginx/certificates/certID/delete.json
+++ b/backend/schema/paths/nginx/certificates/certID/delete.json
@@ -35,6 +35,26 @@
}
}
}
+ },
+ "400": {
+ "description": "400 response",
+ "content": {
+ "application/json": {
+ "examples": {
+ "default": {
+ "value": {
+ "error": {
+ "code": 400,
+ "message": "Certificate is in use by 1 proxy host. Remove it from them before deleting it."
+ }
+ }
+ }
+ },
+ "schema": {
+ "$ref": "../../../../components/error.json"
+ }
+ }
+ }
}
}
}
diff --git a/backend/schema/paths/users/userID/permissions/put.json b/backend/schema/paths/users/userID/permissions/put.json
index 764e7466c..0bbef6dfb 100644
--- a/backend/schema/paths/users/userID/permissions/put.json
+++ b/backend/schema/paths/users/userID/permissions/put.json
@@ -35,7 +35,10 @@
"dead_hosts": "hidden",
"proxy_hosts": "manage",
"redirection_hosts": "hidden",
- "streams": "hidden"
+ "streams": "hidden",
+ "meta": {
+ "proxy_host_ids": [1]
+ }
}
}
}
diff --git a/backend/templates/_access.conf b/backend/templates/_access.conf
index 81983f3a3..7ae905472 100644
--- a/backend/templates/_access.conf
+++ b/backend/templates/_access.conf
@@ -1,4 +1,5 @@
{% if access_list_id > 0 %}
+ {% if access_list %}
{% if access_list.items.length > 0 %}
# Authorization
auth_basic "Authorization required";
@@ -24,4 +25,8 @@
{% else %}
satisfy all;
{% endif %}
+ {% else %}
+ # Access List #{{ access_list_id }} could not be loaded: deny rather than serve unrestricted
+ deny all;
+ {% endif %}
{% endif %}
diff --git a/backend/templates/_hsts.conf b/backend/templates/_hsts.conf
index 26c83ee8b..0e9829009 100644
--- a/backend/templates/_hsts.conf
+++ b/backend/templates/_hsts.conf
@@ -2,7 +2,11 @@
{% if ssl_forced == 1 or ssl_forced == true %}
{% if hsts_enabled == 1 or hsts_enabled == true %}
# HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
- add_header Strict-Transport-Security $hsts_header always;
+{% if hsts_subdomains == 1 or hsts_subdomains == true %}
+ add_header Strict-Transport-Security $hsts_header_subdomains always;
+{% else %}
+ add_header Strict-Transport-Security $hsts_header_plain always;
+{% endif %}
{% endif %}
{% endif %}
{% endif %}
diff --git a/backend/templates/_hsts_map.conf b/backend/templates/_hsts_map.conf
index 27dd1f8f4..f9f3339ef 100644
--- a/backend/templates/_hsts_map.conf
+++ b/backend/templates/_hsts_map.conf
@@ -1,3 +1,6 @@
-map $scheme $hsts_header {
- https "max-age=63072000;{% if hsts_subdomains == 1 or hsts_subdomains == true -%} includeSubDomains;{% endif %} preload";
+map $scheme $hsts_header_plain {
+ https "max-age=63072000; preload";
+}
+map $scheme $hsts_header_subdomains {
+ https "max-age=63072000;includeSubDomains; preload";
}
\ No newline at end of file
diff --git a/backend/templates/_location.conf b/backend/templates/_location.conf
index a2ecb166d..550a87e3b 100644
--- a/backend/templates/_location.conf
+++ b/backend/templates/_location.conf
@@ -1,7 +1,7 @@
location {{ path }} {
{{ advanced_config }}
- proxy_set_header Host $host;
+ proxy_set_header Host $forward_host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
diff --git a/backend/test/nginx-values.test.js b/backend/test/nginx-values.test.js
new file mode 100644
index 000000000..a70a986f3
--- /dev/null
+++ b/backend/test/nginx-values.test.js
@@ -0,0 +1,550 @@
+/**
+ * Regression tests: values a non-admin can set must never add nginx directives.
+ *
+ * Templates write these values into config as-is, so each one is checked three ways:
+ * - the API rejects injection attempts (real request schemas, real validator)
+ * - accepted values, rendered through the real templates, produce exactly the same nginx
+ * statement structure as a plain value (parsed with nginx's tokenizer rules)
+ * - generateConfig's render-time check refuses unsafe values already stored in the database
+ */
+import assert from "node:assert/strict";
+import fs from "node:fs";
+import { describe, it } from "node:test";
+import {
+ assertHostRenderable,
+ isValid,
+ isValidClientAddress,
+ isValidStreamHost,
+ patterns,
+} from "../lib/nginx-values.js";
+import utils from "../lib/utils.js";
+import apiValidator from "../lib/validator/api.js";
+import { getCompiledSchema, getValidationSchema } from "../schema/index.js";
+
+const TEMPLATES = new URL("../templates/", import.meta.url);
+
+// ---------------------------------------------------------------------------------------------
+// A tokenizer following ngx_conf_read_token: whitespace separates, ";" ends a statement, "{"
+// opens a block, "}" closes one, "#" at the start of a token comments to end of line, quotes
+// group, "\" escapes the next character, and "${" doesn't open a block.
+// ---------------------------------------------------------------------------------------------
+const parseNginx = (text) => {
+ const root = [];
+ const stack = [root];
+ let args = [];
+ let i = 0;
+ const n = text.length;
+ const isSpace = (c) => c === " " || c === "\t" || c === "\r" || c === "\n";
+
+ while (i < n) {
+ const c = text[i];
+ if (isSpace(c)) {
+ i++;
+ } else if (c === "#") {
+ while (i < n && text[i] !== "\n") i++;
+ } else if (c === ";") {
+ if (!args.length) throw new Error(`unexpected ";" at ${i}`);
+ stack.at(-1).push({ name: args[0], args: args.slice(1) });
+ args = [];
+ i++;
+ } else if (c === "{") {
+ if (!args.length) throw new Error(`unexpected "{" at ${i}`);
+ const block = { name: args[0], args: args.slice(1), block: [] };
+ stack.at(-1).push(block);
+ stack.push(block.block);
+ args = [];
+ i++;
+ } else if (c === "}") {
+ if (args.length) throw new Error(`unexpected "}" at ${i}`);
+ if (stack.length === 1) throw new Error(`unbalanced "}" at ${i}`);
+ stack.pop();
+ i++;
+ } else if (c === '"' || c === "'") {
+ let tok = c;
+ i++;
+ while (i < n && text[i] !== c) {
+ if (text[i] === "\\" && i + 1 < n) {
+ tok += text[i] + text[i + 1];
+ i += 2;
+ } else {
+ tok += text[i++];
+ }
+ }
+ if (i >= n) throw new Error("unterminated quoted string");
+ tok += c;
+ i++;
+ if (i < n && !isSpace(text[i]) && text[i] !== ";" && text[i] !== "{") {
+ throw new Error(`unexpected "${text[i]}" after quoted string`);
+ }
+ args.push(tok);
+ } else {
+ let tok = "";
+ while (i < n) {
+ const d = text[i];
+ if (d === "\\" && i + 1 < n) {
+ tok += d + text[i + 1];
+ i += 2;
+ continue;
+ }
+ if (d === "$" && text[i + 1] === "{") {
+ tok += "${";
+ i += 2;
+ continue;
+ }
+ if (isSpace(d) || d === ";" || d === "{") break;
+ tok += d;
+ i++;
+ }
+ args.push(tok);
+ }
+ }
+ if (args.length) throw new Error("statement without ;");
+ if (stack.length !== 1) throw new Error("unclosed block");
+ return root;
+};
+
+// Statement names and argument counts, nested; values themselves don't matter
+const shape = (stmts) =>
+ stmts.map((s) => `${s.name}/${s.args.length}${s.block ? `{${shape(s.block).join(",")}}` : ""}`);
+
+const find = (stmts, name) => {
+ for (const s of stmts) {
+ if (s.name === name) return s;
+ if (s.block) {
+ const f = find(s.block, name);
+ if (f) return f;
+ }
+ }
+ return null;
+};
+
+const render = (file, ctx) =>
+ utils.getRenderEngine().parseAndRender(fs.readFileSync(new URL(file, TEMPLATES), "utf8"), ctx);
+
+// Same structure as a plain value, or throws (a value that breaks parsing changes it too)
+const sameShapeAs = async (file, makeCtx, plain, value) => {
+ const base = shape(parseNginx(await render(file, makeCtx(plain))));
+ let got;
+ try {
+ got = shape(parseNginx(await render(file, makeCtx(value))));
+ } catch {
+ return false;
+ }
+ return JSON.stringify(got) === JSON.stringify(base);
+};
+
+// ---------------------------------------------------------------------------------------------
+// Corpora. "injections" add or change directives when rendered unchecked; "rejected" are other
+// values the rules refuse (they would break or confuse the config without injecting).
+// ---------------------------------------------------------------------------------------------
+const cases = {
+ locationPath: {
+ valid: [
+ "/",
+ "/api",
+ "/api/",
+ "= /exact",
+ "~ \\.php$",
+ "~* \\.(jpg|png|gif)$",
+ "^~ /static/",
+ '~ "^/v[0-9]{1,2}/"',
+ '"/with space"',
+ "/über",
+ "/a$b",
+ "/a\\;b",
+ ],
+ injections: [
+ "/x { return 200; } location /y",
+ "/x {} include /etc/passwd; location /y",
+ "/a; } server { listen 8080",
+ "/a\nreturn 200 x;\nlocation /b",
+ '"/a" { return 200; } location "/b"',
+ "/a {",
+ ],
+ rejected: ["", " /a", "/a b", "/a#", "/a\\", '"/a', '"/a\\"', "'/a'", "~", "~ /a\tb", "/a}", "/a\u0000"],
+ },
+ forwardHost: {
+ valid: ["127.0.0.1", "backend", "my_service.internal", "10.0.0.1/path/", "backend/$1", "[::1]", "host:8080"],
+ injections: [
+ "a; return 200; #",
+ 'a"; return 200; set $x "',
+ "a:80; } location /x { return 200",
+ "a\nreturn 200;",
+ ],
+ rejected: ["", "a b", "a'", "a{", "a}", "a#b", "a\\b", "$http_host", "a$b", "/path", "a/b c"],
+ },
+ forwardPath: {
+ valid: ["", "/", "/x", "/x/$1/"],
+ injections: ["/x; return 200", "/x\nreturn 200"],
+ rejected: ["/x y", "/x#", "/x{", "/x\\"],
+ },
+ redirectDomain: {
+ valid: [
+ "example.com",
+ "example.com:8443",
+ "example.com/path",
+ "sub.example.co.uk/a?b=c",
+ "example.com/$1",
+ "ab",
+ ],
+ injections: ["example.com; return 200 x; #", "example.com;\nreturn 200", "a.com { return 200; }"],
+ rejected: ["a b.com", "a.com#x", 'a.com"', "a.com\\", ".com", "a..b", "a", "a.com."],
+ },
+ streamHost: {
+ valid: ["example.com", "1.2.3.4", "::1", "backend.internal"],
+ injections: ["a.com:80; } server { listen 9999", "a.com;\nlisten 1"],
+ rejected: ["a.com/x", "$host", "a b", "a.com#"],
+ },
+ domainName: {
+ valid: ["example.com", "*.example.com", "xn--bcher-kva.example", "a-b.example"],
+ injections: ["a.com\nreturn 200 x", "a.com\n} server {"],
+ rejected: ["a\tb.com", "a;b", "a{b", "a b"],
+ },
+ clientAddress: {
+ valid: ["192.168.0.1", "10.0.0.0/8", "all", "::1", "2001:db8::/32", "::ffff:192.168.1.1", "fe80::1"],
+ injections: ["::1%x; allow all", "::1%x\nallow all", "1.2.3.4; allow all"],
+ rejected: ["fe80::1%eth0", "s::1", "1.2.3.4/33", "::ffff:1d.1.1.1"],
+ },
+ accessUsername: {
+ valid: ["admin", "john smith", "ü"],
+ injections: [],
+ rejected: ["a:b", "a\nroot:$apr1$x$y", "\u0000"],
+ },
+};
+
+const checkers = {
+ locationPath: (v) => isValid("locationPath", v),
+ forwardHost: (v) => isValid("forwardHost", v),
+ forwardPath: (v) => isValid("forwardPath", v),
+ redirectDomain: (v) => isValid("redirectDomain", v),
+ streamHost: isValidStreamHost,
+ domainName: (v) => isValid("domainName", v),
+ clientAddress: isValidClientAddress,
+ accessUsername: (v) => isValid("accessUsername", v),
+};
+
+describe("nginx value rules", () => {
+ for (const [name, { valid, injections, rejected }] of Object.entries(cases)) {
+ it(`${name}: accepts valid values`, () => {
+ for (const v of valid) assert.ok(checkers[name](v), `should accept ${JSON.stringify(v)}`);
+ });
+ it(`${name}: rejects injections and other unsafe values`, () => {
+ for (const v of [...injections, ...rejected]) {
+ assert.ok(!checkers[name](v), `should reject ${JSON.stringify(v)}`);
+ }
+ });
+ }
+
+ it("redirect domain rule matches the old rule for token-safe values", () => {
+ const old = /^(?:[^.*]+\.?)+[^.]$/u;
+ const corpus = [
+ "ab",
+ "a.b",
+ "a.b.c",
+ "a..b",
+ ".a",
+ "a.",
+ "a*",
+ "a.*",
+ "*a",
+ "a*b",
+ "a.b:80/x",
+ "x",
+ "ab.",
+ "a.b.",
+ ];
+ for (const v of corpus) {
+ assert.equal(isValid("redirectDomain", v), old.test(v), `differs for ${JSON.stringify(v)}`);
+ }
+ });
+
+ it("domain rules don't backtrack exponentially", () => {
+ const evil = `${"a".repeat(30000)}.`;
+ const start = process.hrtime.bigint();
+ isValid("redirectDomain", evil);
+ isValidStreamHost(evil);
+ const ms = Number(process.hrtime.bigint() - start) / 1e6;
+ assert.ok(ms < 1000, `took ${ms}ms`);
+ });
+});
+
+describe("API request validation", async () => {
+ await getCompiledSchema();
+
+ const location = (o) => ({ path: "/api", forward_scheme: "http", forward_host: "backend", forward_port: 80, ...o });
+ const proxyHost = (o) => ({
+ domain_names: ["example.com"],
+ forward_scheme: "http",
+ forward_host: "backend",
+ forward_port: 80,
+ ...o,
+ });
+ const redirection = (o) => ({
+ domain_names: ["example.com"],
+ forward_scheme: "http",
+ forward_http_code: 301,
+ forward_domain_name: "example.org",
+ ...o,
+ });
+ const stream = (o) => ({
+ incoming_port: 9000,
+ forwarding_host: "backend",
+ forwarding_port: 80,
+ tcp_forwarding: true,
+ udp_forwarding: false,
+ ...o,
+ });
+
+ // [field, endpoint, method, payload builder]
+ const endpoints = {
+ locationPath: [
+ ["/nginx/proxy-hosts", "post", (v) => proxyHost({ locations: [location({ path: v })] })],
+ ["/nginx/proxy-hosts/{hostID}", "put", (v) => ({ locations: [location({ path: v })] })],
+ ],
+ forwardHost: [
+ ["/nginx/proxy-hosts", "post", (v) => proxyHost({ forward_host: v })],
+ ["/nginx/proxy-hosts/{hostID}", "put", (v) => ({ forward_host: v })],
+ ["/nginx/proxy-hosts/{hostID}", "put", (v) => ({ locations: [location({ forward_host: v })] })],
+ ],
+ forwardPath: [["/nginx/proxy-hosts/{hostID}", "put", (v) => ({ locations: [location({ forward_path: v })] })]],
+ redirectDomain: [
+ ["/nginx/redirection-hosts", "post", (v) => redirection({ forward_domain_name: v })],
+ ["/nginx/redirection-hosts/{hostID}", "put", (v) => ({ forward_domain_name: v })],
+ ],
+ streamHost: [
+ ["/nginx/streams", "post", (v) => stream({ forwarding_host: v })],
+ ["/nginx/streams/{streamID}", "put", (v) => ({ forwarding_host: v })],
+ ],
+ domainName: [
+ ["/nginx/proxy-hosts", "post", (v) => proxyHost({ domain_names: [v] })],
+ ["/nginx/redirection-hosts", "post", (v) => redirection({ domain_names: [v] })],
+ ["/nginx/dead-hosts", "post", (v) => ({ domain_names: [v] })],
+ ["/nginx/certificates", "post", (v) => ({ provider: "letsencrypt", domain_names: [v] })],
+ ],
+ clientAddress: [
+ ["/nginx/access-lists", "post", (v) => ({ name: "x", clients: [{ directive: "allow", address: v }] })],
+ ["/nginx/access-lists/{listID}", "put", (v) => ({ clients: [{ directive: "allow", address: v }] })],
+ ],
+ accessUsername: [
+ ["/nginx/access-lists", "post", (v) => ({ name: "x", items: [{ username: v, password: "pw" }] })],
+ ["/nginx/access-lists/{listID}", "put", (v) => ({ items: [{ username: v, password: "pw" }] })],
+ ],
+ };
+
+ const validate = (path, method, payload) => {
+ const schema = getValidationSchema(path, method);
+ assert.ok(schema, `no request schema for ${method} ${path}`);
+ return apiValidator(schema, payload);
+ };
+
+ for (const [name, list] of Object.entries(endpoints)) {
+ for (const [path, method, build] of list) {
+ it(`${method.toUpperCase()} ${path} ${name}: rejects injections`, async () => {
+ for (const v of [...cases[name].injections, ...cases[name].rejected]) {
+ if (v === "") continue; // minLength cases, covered by the rules above
+ await assert.rejects(validate(path, method, build(v)), /must/, `accepted ${JSON.stringify(v)}`);
+ }
+ });
+ it(`${method.toUpperCase()} ${path} ${name}: accepts valid values`, async () => {
+ for (const v of cases[name].valid) {
+ if (name === "forwardHost" && v === "") continue;
+ await validate(path, method, build(v));
+ }
+ });
+ }
+ }
+
+ it("schema patterns are the ones the render-time check uses", async () => {
+ const s = await getCompiledSchema();
+ const body = (path, method) => s.paths[path][method].requestBody.content["application/json"].schema;
+ const ph = body("/nginx/proxy-hosts", "post").properties;
+ const loc = ph.locations.items.properties;
+ const al = body("/nginx/access-lists", "post").properties;
+ assert.equal(ph.domain_names.items.pattern, patterns.domainName);
+ assert.equal(body("/nginx/certificates", "post").properties.domain_names.items.pattern, patterns.domainName);
+ assert.equal(ph.forward_host.pattern, patterns.forwardHost);
+ assert.equal(loc.path.pattern, patterns.locationPath);
+ assert.equal(loc.forward_host.pattern, patterns.forwardHost);
+ assert.equal(loc.forward_path.pattern, patterns.forwardPath);
+ assert.equal(
+ body("/nginx/redirection-hosts", "post").properties.forward_domain_name.pattern,
+ patterns.redirectDomain,
+ );
+ assert.equal(body("/nginx/streams", "post").properties.forwarding_host.anyOf[0].pattern, patterns.streamDomain);
+ assert.equal(al.clients.items.properties.address.oneOf[1].pattern, patterns.clientIpv6);
+ assert.equal(al.items.items.properties.username.pattern, patterns.accessUsername);
+ });
+});
+
+describe("rendered templates", () => {
+ const locationCtx = (field) => (v) => ({
+ path: "/api",
+ forward_scheme: "http",
+ forward_host: "backend",
+ forward_port: 8080,
+ forward_path: "",
+ advanced_config: "",
+ access_list_id: 0,
+ [field]: v,
+ });
+ const proxyCtx = (field) => (v) => ({
+ enabled: true,
+ id: 1,
+ domain_names: ["example.com"],
+ forward_scheme: "http",
+ forward_host: "backend",
+ forward_port: 80,
+ certificate_id: 0,
+ locations: "",
+ advanced_config: "",
+ use_default_location: true,
+ ipv6: true,
+ [field]: v,
+ });
+ const redirectCtx = (v) => ({
+ enabled: true,
+ id: 1,
+ domain_names: ["example.com"],
+ forward_scheme: "http",
+ forward_http_code: 301,
+ forward_domain_name: v,
+ preserve_path: true,
+ advanced_config: "",
+ use_default_location: true,
+ ipv6: true,
+ });
+ const streamCtx = (v) => ({
+ enabled: true,
+ id: 1,
+ incoming_port: 9000,
+ forwarding_host: v,
+ forwarding_port: 80,
+ tcp_forwarding: true,
+ udp_forwarding: true,
+ ipv6: true,
+ });
+ const accessCtx = (v) => ({
+ access_list_id: 1,
+ access_list: { items: [], clients: [{ directive: "allow", address: v }], satisfy_any: false },
+ });
+
+ // A location modifier is a separate argument, so compare with a plain path using the same one
+ const plainPath = (v) => {
+ const m = v.match(/^(=|~\*?|\^~) +/);
+ return m ? `${m[1]} /api` : "/api";
+ };
+
+ // [template, context builder, plain value (or builder), injections that apply to it]
+ const targets = {
+ locationPath: [["_location.conf", locationCtx("path"), plainPath]],
+ forwardHost: [
+ ["_location.conf", locationCtx("forward_host"), "backend"],
+ // Rendered inside double quotes here, so only a quote can break out
+ ["proxy_host.conf", proxyCtx("forward_host"), "backend", ['a"; return 200; set $x "', 'a" ; } server { "']],
+ ],
+ forwardPath: [["_location.conf", locationCtx("forward_path"), "/x"]],
+ redirectDomain: [["redirection_host.conf", redirectCtx, "example.org"]],
+ streamHost: [["stream.conf", streamCtx, "backend"]],
+ domainName: [["proxy_host.conf", (v) => proxyCtx("domain_names")([v]), "example.com"]],
+ clientAddress: [["_access.conf", accessCtx, "10.0.0.1"]],
+ };
+
+ for (const [name, list] of Object.entries(targets)) {
+ for (const [file, ctx, plain, injections = cases[name].injections] of list) {
+ const plainFor = (v) => (typeof plain === "function" ? plain(v) : plain);
+ it(`${file} ${name}: accepted values keep the statement structure`, async () => {
+ for (const v of cases[name].valid) {
+ if (v === "" && name !== "forwardPath") continue;
+ // generateConfig brackets IPv6 stream hosts before rendering
+ const value = name === "streamHost" && v.includes(":") ? `[${v}]` : v;
+ assert.ok(
+ await sameShapeAs(file, ctx, plainFor(v), value),
+ `${JSON.stringify(v)} changed the config`,
+ );
+ }
+ });
+ it(`${file} ${name}: the injection corpus would change it unchecked`, async () => {
+ assert.ok(injections.length > 0);
+ for (const v of injections) {
+ assert.ok(!checkers[name](v), `rules accept ${JSON.stringify(v)}`);
+ assert.ok(
+ !(await sameShapeAs(file, ctx, plainFor(v), v)),
+ `${JSON.stringify(v)} is not an injection`,
+ );
+ }
+ });
+ }
+ }
+
+ it("location path renders as the location's own arguments", async () => {
+ for (const v of cases.locationPath.valid) {
+ const loc = find(parseNginx(await render("_location.conf", locationCtx("path")(v))), "location");
+ assert.equal(loc.args.join(" "), v.replace(/ +/, " "));
+ }
+ });
+});
+
+describe("render-time check (rows saved before validation)", () => {
+ const host = (o) => ({
+ id: 7,
+ domain_names: ["example.com"],
+ forward_host: "backend",
+ locations: [],
+ ...o,
+ });
+
+ it("passes valid hosts", () => {
+ assertHostRenderable("proxy_host", host({ locations: [{ path: "~* \\.php$", forward_host: "10.0.0.1/x/" }] }));
+ assertHostRenderable("redirection_host", host({ forward_domain_name: "example.org/path" }));
+ assertHostRenderable("dead_host", host({}));
+ assertHostRenderable("stream", { id: 1, forwarding_host: "::1" });
+ assertHostRenderable(
+ "proxy_host",
+ host({ access_list: { clients: [{ directive: "allow", address: "10.0.0.0/8" }] } }),
+ );
+ });
+
+ it("still renders stored values the API no longer accepts but that can't add directives", () => {
+ // e.g. an IPv6 literal server name, as the host-header smoke test renders
+ assertHostRenderable("proxy_host", host({ domain_names: ["host.example.test", "[::1]"] }));
+ assertHostRenderable("proxy_host", host({ forward_host: "$upstream" }));
+ assertHostRenderable("redirection_host", host({ forward_domain_name: "a" }));
+ assertHostRenderable("stream", { id: 1, forwarding_host: "a.com/x" });
+ });
+
+ const refuse = (type, row, what) => {
+ assert.throws(
+ () => assertHostRenderable(type, row),
+ (err) => err.unsafeNginxValue === true && err.message.includes(what),
+ );
+ };
+
+ it("refuses every injected field", () => {
+ for (const v of cases.locationPath.injections) {
+ refuse("proxy_host", host({ locations: [{ path: v, forward_host: "b" }] }), "location path");
+ }
+ for (const v of cases.forwardHost.injections) {
+ refuse("proxy_host", host({ forward_host: v }), "forward host");
+ refuse("proxy_host", host({ locations: [{ path: "/a", forward_host: v }] }), "location forward host");
+ }
+ for (const v of cases.forwardPath.injections) {
+ refuse(
+ "proxy_host",
+ host({ locations: [{ path: "/a", forward_host: "b", forward_path: v }] }),
+ "forward path",
+ );
+ }
+ for (const v of cases.redirectDomain.injections) {
+ refuse("redirection_host", host({ forward_domain_name: v }), "forward domain");
+ }
+ for (const v of cases.streamHost.injections) {
+ refuse("stream", { id: 1, forwarding_host: v }, "forwarding host");
+ }
+ for (const v of cases.domainName.injections) {
+ refuse("dead_host", host({ domain_names: [v] }), "domain name");
+ }
+ for (const v of cases.clientAddress.injections) {
+ refuse("proxy_host", host({ access_list: { clients: [{ directive: "allow", address: v }] } }), "address");
+ }
+ });
+});
diff --git a/backend/yarn.lock b/backend/yarn.lock
index 39efe544a..30c1fcc72 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -2,20 +2,12 @@
# yarn lockfile v1
-"@apidevtools/json-schema-ref-parser@15.3.6":
- version "15.3.6"
- resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.3.6.tgz#9e6360264d90b209eb09f9e4dc9a32316d86f4ea"
- integrity sha512-oPFDSviRrreUmCZn09LCD4S9QQa6j7zfEwBM1pkGa/kXY0O4sXsEXn4emLP/Tt6tik2+BtgwKk2eldAeOWZ2Rw==
+"@apidevtools/json-schema-ref-parser@^16.0.3", "@apidevtools/json-schema-ref-parser@^16.0.6":
+ version "16.0.6"
+ resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-16.0.6.tgz#30b21ddbaa6ddd1161d1534829002dab0d720980"
+ integrity sha512-cJXhmvqCn4Ud7X7d4L/BtAiJRIrLNiuIZ48ZTuDfN0mYejVXxdzMGR07teW6eKzLwcN0FwMi7gykE2Kr09LS1g==
dependencies:
- js-yaml "^4.2.0"
-
-"@apidevtools/json-schema-ref-parser@^16.0.2":
- version "16.0.2"
- resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-16.0.2.tgz#21f55458a55d1b02bc62f19c731913d8e74976b3"
- integrity sha512-oCwWUyRdMtt/hnp6ONPudFZzF7tSqiHy46lLx5VsnsLTacs8TSU558VPAiOueFJ8WddCH30PINWDA8ByxhGHmQ==
- dependencies:
- js-yaml "^5.4.1"
- undici "^8.10.2"
+ js-yaml "^5.4.2"
"@apidevtools/openapi-schemas@^2.1.0":
version "2.1.0"
@@ -27,72 +19,84 @@
resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267"
integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==
-"@apidevtools/swagger-parser@^13.0.0":
- version "13.0.0"
- resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-13.0.0.tgz#a8363c086cc2ae0005b270cfc241f353a741e49c"
- integrity sha512-TcvnmBzJD7DBREkc/2qaxMqbKpttOaHhOFsYRYKdYAv6HGY+iNlWY2IUA209C2/NmvL9z8RBZhkrESqvGkjqmw==
+"@apidevtools/swagger-parser@^13.1.0":
+ version "13.1.0"
+ resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-13.1.0.tgz#efc8ffcc573838e715d0bce83d917b79636ca172"
+ integrity sha512-dkCuqCWh5U7u0aurmmc/qvel2KeKDxHTwVtUdYonndIpD7mXV6AVaWNdvaja2HAy628z7FT/Qdz8sCgzwhgwKg==
dependencies:
- "@apidevtools/json-schema-ref-parser" "15.3.6"
+ "@apidevtools/json-schema-ref-parser" "^16.0.3"
"@apidevtools/openapi-schemas" "^2.1.0"
"@apidevtools/swagger-methods" "^3.0.2"
"@types/json-schema" "^7.0.15"
- ajv "^8.17.1"
+ ajv "^8.20.0"
ajv-draft-04 "^1.0.0"
call-me-maybe "^1.0.2"
-"@biomejs/biome@^2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.5.10.tgz#c2a42fc6f9c7d9e12c1f77f63dfb6b5489c497b7"
- integrity sha512-WRKXARA3kTuiV5sxqTpobJ/I0MVd4vk3pOL6wnp5az4LntFIhWTj1RWZq3DI9PCEN3lXcqy7p5aqUHzvq8AXyQ==
+"@biomejs/biome@^2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.6.tgz#b40e03ff3dbe070963dd6332171ffffeb3cf7619"
+ integrity sha512-NCgVJRyrU1+hsW1ILNaRqNAb9fRL4n49bwyef+wjVJQ488/bXbp0BfnNqTTExkiNQT3ugkgYKR1vK8NxsjlNSQ==
optionalDependencies:
- "@biomejs/cli-darwin-arm64" "2.5.10"
- "@biomejs/cli-darwin-x64" "2.5.10"
- "@biomejs/cli-linux-arm64" "2.5.10"
- "@biomejs/cli-linux-arm64-musl" "2.5.10"
- "@biomejs/cli-linux-x64" "2.5.10"
- "@biomejs/cli-linux-x64-musl" "2.5.10"
- "@biomejs/cli-win32-arm64" "2.5.10"
- "@biomejs/cli-win32-x64" "2.5.10"
+ "@biomejs/cli-android-arm64" "2.6.6"
+ "@biomejs/cli-android-x64" "2.6.6"
+ "@biomejs/cli-darwin-arm64" "2.6.6"
+ "@biomejs/cli-darwin-x64" "2.6.6"
+ "@biomejs/cli-linux-arm64" "2.6.6"
+ "@biomejs/cli-linux-arm64-musl" "2.6.6"
+ "@biomejs/cli-linux-x64" "2.6.6"
+ "@biomejs/cli-linux-x64-musl" "2.6.6"
+ "@biomejs/cli-win32-arm64" "2.6.6"
+ "@biomejs/cli-win32-x64" "2.6.6"
-"@biomejs/cli-darwin-arm64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.10.tgz#f1453a7f2e63f228a34645df1c33962f22a6c2a8"
- integrity sha512-ItCrxKK6SXVT6flYs0qIuBd4AA3TTTl4d66Re6YI2FuGZnN85NmuYNzkiTJUyYw8qBLv69L5zTUB6uyWd++h3Q==
+"@biomejs/cli-android-arm64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.6.tgz#dd4a994b35837a521459fea4f4f27a194c47258d"
+ integrity sha512-bicnqNNxEX7pjkDQaptYvPRIWDfgXJLl/Ok7iZjC+F1AhfReor3FSS7SgPYnOvUy/jf++5U/KYN4T8OKH5rrNA==
-"@biomejs/cli-darwin-x64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.10.tgz#e21041e7bfab01cd453f609b940bf613713d0e24"
- integrity sha512-yLsPU9pAmtChXDu8vhKAzErqe+LeeYuwuUB2FZMkRitsmdodxsYRa9KHrFispsUHzzOu+9HB3nP/TQxyia+Sjw==
+"@biomejs/cli-android-x64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.6.tgz#1804e55a64e9e0b11f56492863c98fd80f1a4028"
+ integrity sha512-N443kDSElF4kDMKjarsL+ymj/ewLA5R9VAdawA7fJ7pwgN3JVDZp0K+xhKBglH8EwsKi2+K8xC6WxXSkgziFfw==
-"@biomejs/cli-linux-arm64-musl@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.10.tgz#57fc8aee25c08ca6c14526ccc44e282e7ba96eb6"
- integrity sha512-t1QAKZwQJRB4dvgJSgFiQ4BNfNPChg69BNonz854qLVxnjT3UvDzQg9mbkTJRu35ZqU0Rw10A73J8Urgbg2RPw==
+"@biomejs/cli-darwin-arm64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.6.tgz#dd6be6a1d8457a465c555311c909e6aa81f1c9ea"
+ integrity sha512-/vSAqWx3by3p8w5ew+HVJDgguuaTK3U6Fb5gk/oDUH21iWOopm3gOlhT6tZfy5mTaM5R4CVc3uFWkxBtEDrKyw==
-"@biomejs/cli-linux-arm64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.10.tgz#aef789a71e5aa86f5a0e1aff6cb4c1b757ec5be1"
- integrity sha512-VG8uQW/86a1roLaIFvtIbEigxIdzdJ190oGyg1tV7VYeQtOS+x10sflk7WbuXgw91EtZX5DlIIIej1YqkNLlcg==
+"@biomejs/cli-darwin-x64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.6.tgz#fb2609d97f4ead53dbff30507a42be1fe56a1e9c"
+ integrity sha512-lLdOAJKbrAoDs+3GvTyVmMW345D9IhrIE3GgoL2eeHwO1boN4VhrSlALtFYsP0p+Jy4udUp4bTguij0qjKrH8w==
-"@biomejs/cli-linux-x64-musl@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.10.tgz#1aebe9f928dc72dc3c112e60f1211a4d646990c4"
- integrity sha512-pgDDqp9JybHm2I0KRgzN6i4+lt8xu4iqxUwLzglUMmOmyRTU1AYBGKzh9sNMOtIjah7xoWvKHlLVetvyifzoiQ==
+"@biomejs/cli-linux-arm64-musl@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.6.tgz#28412c5039466b87c9d3a59176994a45422d60a4"
+ integrity sha512-rw2Vsoi/WZGDyRjMXx9KZH9zsHe79LIzkdWHkLqsIkeKZm6qiGxtiIApxn7cmJSoEPaSvHaH5pRj1FhTcyL/XA==
-"@biomejs/cli-linux-x64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.10.tgz#a82b414abf5d9668fb035ee50a710bf78a7c5fbf"
- integrity sha512-4O6T0eq2heoHZN0a9UX+rWQoxXEBaKf+lRi2hbsGlHneUz9BWXM76nEWMK7Eeq8gzMxR1khQB6BFpAASpeXqGg==
+"@biomejs/cli-linux-arm64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.6.tgz#04ced06069af3c7a4d20da44c53c21e70ce42176"
+ integrity sha512-UzJdUCg9dbEGpZOuZRyehEPrnwKjx9z4Um7sWLG5x6txu7UjeWdJZuBXVGlyAdD39w2Hx0nZeK1jKC4NFUTCNQ==
-"@biomejs/cli-win32-arm64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.10.tgz#db671cf67d7040a374e8cdda5aebe73c3f4ffba0"
- integrity sha512-pxAbxduPO4xq/Cvgaa2lOrs9BB0hEXmmDqfMNP4ZOffGOkUrD1/QGw9UAMpFQpX2P8MqTIIRuQKcmetum4Oa6A==
+"@biomejs/cli-linux-x64-musl@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.6.tgz#78109fb485dcbce9eaf5977e2a70658696dc6405"
+ integrity sha512-DiKejs+GnFbOVQsBnukUpiB+T13tqUDFGwwjsTq51vatAsaflzFzVD0BOgjVUGRrqKgE0K0Xf+wNtkcmdN2chw==
-"@biomejs/cli-win32-x64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.10.tgz#3bdeaf5856f21aec6b6c50a7bca5464b9cda5623"
- integrity sha512-M+2dgBsl3lXRiTfgPVc2p3anS4Tocojke4rzFLScZ2Y/wmF+36dRb1iHCLiyGqOzQGyTplZH1HnEYviiAqi3nA==
+"@biomejs/cli-linux-x64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.6.tgz#b5315ccaa511a2bfb103db50013a266887d793bb"
+ integrity sha512-l8/aPXc1cz8VpUyd4hb5Pm5zx+X3voQBks4aLYbp5pC8KSchuHyvqDfY5xax39qCHomI/2xn6NKEot+zebG38g==
+
+"@biomejs/cli-win32-arm64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.6.tgz#41c0a80a9b98e9b879fb2413a5faf0775a526f07"
+ integrity sha512-DjVXwQAQeHrO1JEZAEus54k40Tnh4glyU3VFLcDBKwoz0xnxCEnqw6TCMXoujcQMQynWd3AY8bLrbR1o0XNqow==
+
+"@biomejs/cli-win32-x64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.6.tgz#f3e5230d59a48ac1da6ab951ad5be364591fb64c"
+ integrity sha512-B7KMG26A4I9lAuxwBGMmiVcjMGAeJRoz+pzFt9brRvOAsIrgOu8ptJld5rUWui6tM1uiJwxeJhhDPuAiZtxXdw==
"@isaacs/fs-minipass@^4.0.0":
version "4.0.1"
@@ -101,60 +105,60 @@
dependencies:
minipass "^7.0.4"
-"@noble/hashes@^2.2.0":
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.3.0.tgz#505fd39c3134a37e67c8c4e6c6049a496154879c"
- integrity sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==
+"@noble/hashes@^2.2.0", "@noble/hashes@^2.4.0":
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.4.0.tgz#e6a3e98edbed3c8659b28bb8f9b18ca02ec8ea4b"
+ integrity sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==
-"@otplib/core@13.5.0":
- version "13.5.0"
- resolved "https://registry.yarnpkg.com/@otplib/core/-/core-13.5.0.tgz#629a7fef5b6394f34ff55213b936223b368796a4"
- integrity sha512-2rURdkYkb3BDhMs3j/oCCPTve1ybJ6ruLfLfSe1ZSPV+y6RFTbLfAuT0m0ZCnps88ogkIq9t/+Li/kg5RofQwQ==
+"@otplib/core@13.6.0":
+ version "13.6.0"
+ resolved "https://registry.yarnpkg.com/@otplib/core/-/core-13.6.0.tgz#f939a821604467b87be29b465cb54e2f270c3789"
+ integrity sha512-TL6/FKKzR55Vr7zo3YGFtPMCSV3SpYPtuvE3T1XcwjFczD9pHU4dYYQBl0AcNvGcqAt4/fPqhZEFfB2OBcnlPA==
-"@otplib/hotp@13.5.0":
- version "13.5.0"
- resolved "https://registry.yarnpkg.com/@otplib/hotp/-/hotp-13.5.0.tgz#2832cc6e52f3d037428f094177b4779babe19aa5"
- integrity sha512-1EwwAti05CeWJn4xXOVMBK9N6dIJlJw/cQkQyiL9OVlkeS452wAve3ffodi+5IUZdWoF1wxVug38RisdxDqDWw==
+"@otplib/hotp@13.6.0":
+ version "13.6.0"
+ resolved "https://registry.yarnpkg.com/@otplib/hotp/-/hotp-13.6.0.tgz#b9c0e2ed27f63dff9bb704c881bb006f54a2d4b4"
+ integrity sha512-oPpaQBkAYjBxAs+FWdGNumsEwWJhIZVcsv0vEi+w4OzzLQ6UDZ2qXZNSeF9NljTj6/8d4e1Z4o6RgJa3aIxAOA==
dependencies:
- "@otplib/core" "13.5.0"
- "@otplib/uri" "13.5.0"
+ "@otplib/core" "13.6.0"
+ "@otplib/uri" "13.6.0"
-"@otplib/plugin-base32-scure@13.5.0":
- version "13.5.0"
- resolved "https://registry.yarnpkg.com/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.5.0.tgz#ba2f329aeea3ed4df3c4e21ce2e9c2e8c9aaed4f"
- integrity sha512-3JEIHindUMiIeNL0jXepSAkZ/7IkWq4sPdG9eXK0lrMXZG9RdKu5oz/4tuuTodtsFUPYhKlRMqv5xUvQIkMIaA==
+"@otplib/plugin-base32-scure@13.6.0":
+ version "13.6.0"
+ resolved "https://registry.yarnpkg.com/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.6.0.tgz#73509fbd9946642dd78f2990190f5cdb86aa0f81"
+ integrity sha512-s6uMbj4MBebvAPnBns1y2RhQ765/U04qHlZmFLV02dlD1S06pU46g/7tHX0vkCx5Pnty723loXto/PM5gFEEdg==
dependencies:
- "@otplib/core" "13.5.0"
- "@scure/base" "^2.2.0"
+ "@otplib/core" "13.6.0"
+ "@scure/base" "^2.4.0"
-"@otplib/plugin-crypto-noble@13.5.0":
- version "13.5.0"
- resolved "https://registry.yarnpkg.com/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.5.0.tgz#820227beb159090a7448ce7245a4e615b5579f0e"
- integrity sha512-fihOAGFvc4b8XTKyIK3jifFP2nLrUNc2bOaJ3UkUKJjy1XI2FbHKG0WmOH+jFsPfa6VsOnouCnEmSznDxIe0pA==
+"@otplib/plugin-crypto-noble@13.6.0":
+ version "13.6.0"
+ resolved "https://registry.yarnpkg.com/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.6.0.tgz#c2702906e6cc128cf8b32fafd35163683a02394e"
+ integrity sha512-m/8mOhtIhsr4zLtb9MlLI6+w7ZNiKSFnT8Dv7eHR9TCyf2Sxva5e+MbV0wONmF7ZLuvo9F3DtzfPjc748PhJcw==
dependencies:
- "@noble/hashes" "^2.2.0"
- "@otplib/core" "13.5.0"
+ "@noble/hashes" "^2.4.0"
+ "@otplib/core" "13.6.0"
-"@otplib/totp@13.5.0":
- version "13.5.0"
- resolved "https://registry.yarnpkg.com/@otplib/totp/-/totp-13.5.0.tgz#48a60807457726ea34f1d1185f994120652e4468"
- integrity sha512-GD9LzQnbHDwXCp79s8AaiA5cJR6luX5fEr9AN32AWx4ooRvllPbMkG80gHSA0jbGCnAIX/ChyOtTZ8tgJHMkSg==
+"@otplib/totp@13.6.0":
+ version "13.6.0"
+ resolved "https://registry.yarnpkg.com/@otplib/totp/-/totp-13.6.0.tgz#fa309ced2594e83b060d9b746d0218d2957153a3"
+ integrity sha512-bMlq0MaUaQE3lOSH352Mrzcwpau5pMf7pMLROS+llk2evpC/sE7SHl9ajBl+PiPnGGb6LGYEjDvKqX8caXkGdw==
dependencies:
- "@otplib/core" "13.5.0"
- "@otplib/hotp" "13.5.0"
- "@otplib/uri" "13.5.0"
+ "@otplib/core" "13.6.0"
+ "@otplib/hotp" "13.6.0"
+ "@otplib/uri" "13.6.0"
-"@otplib/uri@13.5.0":
- version "13.5.0"
- resolved "https://registry.yarnpkg.com/@otplib/uri/-/uri-13.5.0.tgz#d2da470be24f872bb1c24717bcdd2e5c122e3ce3"
- integrity sha512-LsL1hqTEgJHY40U2eb/Qp6OR1tKkNGwHQTrqqjIuJj0cawGWkLEmeUFK1MHzWBQ4bgy5IdeB5Et7gIDSAH/CRw==
+"@otplib/uri@13.6.0":
+ version "13.6.0"
+ resolved "https://registry.yarnpkg.com/@otplib/uri/-/uri-13.6.0.tgz#c26e70293ccd4bc8f1d6e46c051e2b70bf1a56dc"
+ integrity sha512-CiLFMONTpCi3ara6yQQX3nlpYhafCALKJweA7+7Z+aDkRrW3QmD5a/9thKmQ0cGp2W70AMG/6BSTy82h74LHRw==
dependencies:
- "@otplib/core" "13.5.0"
+ "@otplib/core" "13.6.0"
-"@scure/base@^2.2.0":
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/@scure/base/-/base-2.3.0.tgz#fc7acb5b7084c53e4e2537d83fa72cddac7143fd"
- integrity sha512-NsG6Y03tY6R5BUis4FdVtHVkur0U6FOzskgs9ZXNl78CUc9fkZ78HmENUle1nSOkCasDmbubmWD9qwB7mm4PZA==
+"@scure/base@^2.4.0":
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/@scure/base/-/base-2.4.0.tgz#61e4ed7d4447afc247f2bff9aeb8fca53267db86"
+ integrity sha512-thZ1TuJwFwBblOhgsjDKvvGirBxNp+wSvY/DR6tJBJOTDhdAAcHJ8Vbr2eFnqaxeca4+t0i9KBf+uHYGWwZORg==
"@types/json-schema@^7.0.15":
version "7.0.15"
@@ -191,10 +195,10 @@ ajv-draft-04@^1.0.0:
resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8"
integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==
-ajv-formats@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
- integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
+ajv-formats@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578"
+ integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==
dependencies:
ajv "^8.0.0"
@@ -250,10 +254,10 @@ archiver@^8.0.0:
tar-stream "^3.0.0"
zip-stream "^7.0.2"
-argparse@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
- integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+argparse@^3.0.3:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-3.0.4.tgz#ba802dd75fdc3d5bdcb4339b713d62c6c5d4565d"
+ integrity sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA==
ast-types@^0.13.4:
version "0.13.4"
@@ -272,25 +276,30 @@ aws-ssl-profiles@^1.1.2:
resolved "https://registry.yarnpkg.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641"
integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==
-b4a@^1.6.4, b4a@^1.8.1:
- version "1.8.1"
- resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.8.1.tgz#7f16334ca80127aeb26064a28841acbf174840a4"
- integrity sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==
+b4a@^1.6.4, b4a@^1.8.1, b4a@^1.9.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.9.0.tgz#6336313b6e76b299c397afa68676ec6ebc788845"
+ integrity sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==
balanced-match@^4.0.2:
version "4.0.4"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a"
integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==
-bare-events@^2.5.4, bare-events@^2.7.0:
- version "2.9.1"
- resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.9.1.tgz#5c86616966343bcb03a1b3155feab253eadbf349"
- integrity sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==
+bare-buffer@^3.0.0:
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/bare-buffer/-/bare-buffer-3.7.1.tgz#dcd2fb7907cfe7df0cf18cb752a04ae2a41140bc"
+ integrity sha512-cIjZnSO+y89ykVX9x96OVnA6tGORBn7vGJ/e3PCbBHMEyfMNG/ixzOS0lLa6saMlWcs69bSKjxFyDwjK152OMg==
-bare-fs@^4.5.5:
- version "4.8.0"
- resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.8.0.tgz#8c0f916833ae11e6d9d347f9e0a7f46f7375bb8f"
- integrity sha512-fM+MhCvdQhZ7NV6S95a07gPSqjIYKn6mFaXfx266wN3ajZGl/+1AzH+ubkXQ0fFZvOe2nk9VHkzdYkQE5zMV3Q==
+bare-events@^2.5.4, bare-events@^2.7.0:
+ version "2.9.2"
+ resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.9.2.tgz#01d377b64c3c7167b28b52da7add3221aecf2d7f"
+ integrity sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==
+
+bare-fs@^4.8.2:
+ version "4.8.4"
+ resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.8.4.tgz#f342288e7e5d9d8e79917078e1f45950507828c5"
+ integrity sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ==
dependencies:
bare-events "^2.5.4"
bare-path "^3.0.0"
@@ -299,24 +308,25 @@ bare-fs@^4.5.5:
fast-fifo "^1.3.2"
bare-path@^3.0.0:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-3.1.1.tgz#d4a207c088609b4663a7556a46a97342398bf7e2"
- integrity sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-3.1.2.tgz#67261bc74a9ba0105ba139b8638f218c09e8d82e"
+ integrity sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==
bare-stream@^2.6.4:
- version "2.13.3"
- resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.13.3.tgz#f6186c7cbb4bbf53a4560f35e48b16373ba51ce6"
- integrity sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==
+ version "2.13.4"
+ resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.13.4.tgz#61d448a268d1efd992103aae6bd729332f278ac0"
+ integrity sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==
dependencies:
b4a "^1.8.1"
streamx "^2.25.0"
teex "^1.0.1"
bare-url@^2.2.2:
- version "2.5.2"
- resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.5.2.tgz#e8dafaefbdb56891a0fbc2b0e1c051296d241cd6"
- integrity sha512-L13PCJzKG8RGvx8V1/DdMi12ERhC3tprr7/8a94BxpmnRsFqxh5XZNdhtMxu5HPkRshYOOWRGY8lDP7ZhpG9Cg==
+ version "2.5.6"
+ resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.5.6.tgz#7f7a96c459bd432bec43f797c0afca1325cb9b5f"
+ integrity sha512-iqkXHkW65De+BT14nHLiBFbNuJ/DdMrZaVNJ1IXPctpK6b1olUJ5Qmy8QBvwROajTiGztso2n82kFEvCsNG7Rg==
dependencies:
+ bare-buffer "^3.0.0"
bare-path "^3.0.0"
base64-js@^1.3.1:
@@ -375,25 +385,25 @@ blueimp-md5@^2.16.0:
resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0"
integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==
-body-parser@^2.2.1, body-parser@^2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.3.0.tgz#6d8662f4d8c336028b8ac9aa24251b0ca64ba437"
- integrity sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==
+body-parser@^2.3.0, body-parser@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.4.0.tgz#337a76249270f83e450ad385a9606ca3b4a75c06"
+ integrity sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==
dependencies:
bytes "^3.1.2"
- content-type "^2.0.0"
+ content-type "^2.1.0"
debug "^4.4.3"
http-errors "^2.0.1"
- iconv-lite "^0.7.2"
+ iconv-lite "^0.7.3"
on-finished "^2.4.1"
- qs "^6.15.2"
+ qs "^6.16.1"
raw-body "^3.0.2"
type-is "^2.1.0"
brace-expansion@^5.0.8:
- version "5.0.9"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.9.tgz#7c72438809b5fa5babf54199a1f1c281a6984fcf"
- integrity sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==
+ version "5.0.12"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.12.tgz#995fbb4750a77c4d16a7dc942ff2ca6ef8e675ec"
+ integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==
dependencies:
balanced-match "^4.0.2"
@@ -468,10 +478,10 @@ camelcase@^5.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-chalk@5.6.2:
- version "5.6.2"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea"
- integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==
+chalk@6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-6.0.1.tgz#077d4667ad6e608c9687782caed3d534d7f9abc3"
+ integrity sha512-/Ce6KNm3vIbWdMlNna6RVIZ/ICQxnJxCicet5LBKK9ZffBkqzDw0xh9EiKSljdRtiIQ1S1z4YgcscUUGzNCWrA==
chalk@^2.3.2:
version "2.4.2"
@@ -582,15 +592,10 @@ compression@^1.8.2:
safe-buffer "5.2.1"
vary "~1.1.2"
-content-disposition@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.1.0.tgz#f3db789c752d45564cc7e9e1e0b31790d4a38e17"
- integrity sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==
-
-content-type@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
- integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+content-disposition@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-2.0.1.tgz#88a75c7a52ef9c407c96035fe83a66939c2a4dc9"
+ integrity sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==
content-type@^2.0.0, content-type@^2.1.0:
version "2.1.0"
@@ -630,11 +635,6 @@ data-uri-to-buffer@8.0.0:
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-8.0.0.tgz#c642ffbc96d8fae76cccecbad43cbddd7818c785"
integrity sha512-6UHfyCux51b8PTGDgveqtz1tvphBku5DrMKKJbFAZAJOI2zsjDpDoYE1+QGj7FOMS4BdTFNJsJiR3zEB0xH0yQ==
-db-errors@^0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/db-errors/-/db-errors-0.2.3.tgz#a6a38952e00b20e790f2695a6446b3c65497ffa2"
- integrity sha512-OOgqgDuCavHXjYSJoV2yGhv6SeG8nk42aoCSoyXLZUH7VwFG27rxbavU1z+VrZbZjphw5UkDQwUlD21MwZpUng==
-
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -729,9 +729,9 @@ emoji-regex@^8.0.0:
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
encodeurl@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
- integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.1.tgz#f268548822bd87b5b4fdce83c56388c6d33c9de6"
+ integrity sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A==
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.5"
@@ -847,22 +847,22 @@ exponential-backoff@^3.1.1:
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz#51cf92c1c0493c766053f9d3abee4434c244d2f6"
integrity sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==
-express-fileupload@^1.5.2:
- version "1.5.2"
- resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.5.2.tgz#4da70ba6f2ffd4c736eab0776445865a9dbd9bfa"
- integrity sha512-wxUJn2vTHvj/kZCVmc5/bJO15C7aSMyHeuXYY3geKpeKibaAoQGcEv5+sM6nHS2T7VF+QHS4hTWPiY2mKofEdg==
+express-fileupload@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.6.0.tgz#fbc7035f090a554ae54005b1d8a7bb46c8440a62"
+ integrity sha512-jIVP7PB+uNLrsglQUKzlK8PjxGkzPy3h0kH0XKpoxQnoT5sPNnHvfWnMMkLoP36LJS5Y8NkkdvkAYQnGCB6tdQ==
dependencies:
busboy "^1.6.0"
-express@^5.2.1:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04"
- integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==
+express@^5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/express/-/express-5.3.0.tgz#ab72dace89c50c8417bd3f865dad0b4fd1ca1c73"
+ integrity sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==
dependencies:
accepts "^2.0.0"
- body-parser "^2.2.1"
- content-disposition "^1.0.0"
- content-type "^1.0.5"
+ body-parser "^2.3.0"
+ content-disposition "^2.0.1"
+ content-type "^2.0.0"
cookie "^0.7.1"
cookie-signature "^1.2.1"
debug "^4.4.0"
@@ -878,14 +878,14 @@ express@^5.2.1:
on-finished "^2.4.1"
once "^1.4.0"
parseurl "^1.3.3"
- proxy-addr "^2.0.7"
- qs "^6.14.0"
+ proxy-addr "^2.0.8"
+ qs "^6.16.0"
range-parser "^1.2.1"
router "^2.2.0"
send "^1.1.0"
serve-static "^2.2.0"
statuses "^2.0.1"
- type-is "^2.0.1"
+ type-is "^2.1.0"
vary "^1.1.2"
fast-deep-equal@^3.1.3:
@@ -893,15 +893,15 @@ 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-fifo@^1.2.0, fast-fifo@^1.3.2:
+fast-fifo@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
fast-uri@^3.0.1:
- version "3.1.7"
- resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.7.tgz#743157d957f3cbb4c65310e033dc2ad4ad7dc60a"
- integrity sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==
+ version "3.1.9"
+ resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.9.tgz#f1deba822b1d6ac79da2f860e872a63470cff203"
+ integrity sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==
fdir@^6.5.0:
version "6.5.0"
@@ -1076,7 +1076,7 @@ has-symbols@^1.1.0:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
-hasown@^2.0.2, hasown@^2.0.3:
+hasown@^2.0.2, hasown@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003"
integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==
@@ -1112,7 +1112,7 @@ https-proxy-agent@9.1.0:
debug "^4.3.4"
proxy-agent-negotiate "1.1.0"
-iconv-lite@^0.7.2, iconv-lite@^0.7.3, iconv-lite@~0.7.0:
+iconv-lite@^0.7.3, iconv-lite@~0.7.0:
version "0.7.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.3.tgz#84ee12f963e7de50bc01a13e160a078b3b0f415f"
integrity sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==
@@ -1150,9 +1150,9 @@ interpret@^2.2.0:
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
ip-address@^10.1.1:
- version "10.5.0"
- resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.5.0.tgz#fcd6d7dfe9e68416b7cb71afe9bc960b3e38c13b"
- integrity sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==
+ version "10.7.6"
+ resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.7.6.tgz#e3c06961cedf352494c06c916c696cfdb11068dd"
+ integrity sha512-00LYkM6byILa9eQIx6qoaXCM40xTLd56AZ4vyaPW3YTyb49VZYPo5xsiAq4tuQ54GH4R7gVdYScJ1B7B77PuTg==
ipaddr.js@1.9.1:
version "1.9.1"
@@ -1171,12 +1171,12 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
-is-core-module@^2.16.1:
- version "2.16.2"
- resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082"
- integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==
+is-core-module@^2.18.0:
+ version "2.18.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.18.0.tgz#8a988af8d3729fb6bbcaf025bf820cfe46b95f4c"
+ integrity sha512-qyax9fgaKOEC4qK8WZn7Q4IcoZC9RC1O1fFxRcRTq/oMMkWT/uXpqpXa12kzWR409zAt1drYN9N9BO26UmBCmw==
dependencies:
- hasown "^2.0.3"
+ hasown "^2.0.4"
is-extglob@^2.1.1:
version "2.1.1"
@@ -1225,19 +1225,12 @@ isexe@^4.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-4.0.0.tgz#48f6576af8e87a18feb796b7ed5e2e5903b43dca"
integrity sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==
-js-yaml@^4.2.0:
- version "4.3.2"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.2.tgz#8e44fb14a2643c59726bb15787b5f1512cb3d3fb"
- integrity sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==
+js-yaml@^5.4.2:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-5.5.0.tgz#ec843f09ffb2a54b6758a41eee109a1815d16442"
+ integrity sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg==
dependencies:
- argparse "^2.0.1"
-
-js-yaml@^5.4.1:
- version "5.4.2"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-5.4.2.tgz#eebc7a57e0bbe1d6b6d9416a72e7463082c7da85"
- integrity sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg==
- dependencies:
- argparse "^2.0.1"
+ argparse "^3.0.3"
json-parse-better-errors@^1.0.1:
version "1.0.2"
@@ -1309,10 +1302,10 @@ lazystream@^1.0.0:
dependencies:
readable-stream "^2.0.5"
-liquidjs@10.29.0:
- version "10.29.0"
- resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.29.0.tgz#ee061890ccf0da26be377aad4f02bd32da0140d8"
- integrity sha512-pCVOhs6FLAR8su3ItJ07diN26t6W5dHQRnmTMy8HPyTFuv1+oSCVJIGp5pGjfQyOZfh50KswvKtMTp6p4JEIdw==
+liquidjs@10.30.1:
+ version "10.30.1"
+ resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.30.1.tgz#aa8c24535d638f88679a22cd8042c38b3ca61dc6"
+ integrity sha512-P1Iz14WUHZdfbOYwHOIhtl/iAWIOYk1ks5l9rPZlDM6vZo+6BLeYIuWHAPkIz+U8Mm8HKZKITIrjX7DQJO2fqA==
dependencies:
commander "^10.0.0"
@@ -1392,9 +1385,9 @@ lru-cache@^7.14.1:
integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==
lru.min@^1.1.0, lru.min@^1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.4.tgz#6ea1737a8c1ba2300cc87ad46910a4bdffa0117b"
- integrity sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.5.tgz#bd32483f4e342261d0eb011b06d2219e6b5ef818"
+ integrity sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==
math-intrinsics@^1.1.0:
version "1.1.0"
@@ -1477,10 +1470,10 @@ ms@^2.1.1, ms@^2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-mysql2@^3.24.4:
- version "3.24.4"
- resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.24.4.tgz#7752de0cf10b1d2984103bdbc60f696c60756f31"
- integrity sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==
+mysql2@^3.24.6:
+ version "3.24.6"
+ resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.24.6.tgz#8e9471f0ceca917599ac021356bb24f08f168b3c"
+ integrity sha512-enu73KhbAua2L3dIxjc6qg6ZJ7H3pt1BImZASWNWY5xSsmJ1ZuUPULTDGXgmJbI61vb+nJAG19IZclIq19UidQ==
dependencies:
aws-ssl-profiles "^1.1.2"
generate-function "^2.3.1"
@@ -1520,9 +1513,9 @@ netmask@^2.0.2:
integrity sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==
node-abi@^3.3.0:
- version "3.94.0"
- resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.94.0.tgz#007181ed0d1b56ae9670ea6c084d2bf83538405f"
- integrity sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==
+ version "3.101.0"
+ resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.101.0.tgz#3c99775cb0a4ddf4f3f0dad2a9ad940d67ef0052"
+ integrity sha512-bkb/RkuWCE64eWu1PCh3Fu2pGc9mKRs+kOE+FU2bGHRYlQa86/D321DSATV54AsCQMKz3/xSt9sRgraZTpqdhQ==
dependencies:
semver "^7.3.5"
@@ -1592,19 +1585,18 @@ object-inspect@^1.13.3, object-inspect@^1.13.4:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
-objection@3.1.5:
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/objection/-/objection-3.1.5.tgz#53c32f6b6cba2958bc28cf723de96c2676da8286"
- integrity sha512-Hx/ipAwXSuRBbOMWFKtRsAN0yITafqXtWB4OT4Z9wED7ty1h7bOnBdhLtcNus23GwLJqcMsRWdodL2p5GwlnfQ==
+objection@4.4.3:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/objection/-/objection-4.4.3.tgz#aa965ccb29cfd97b3717539ac73f9095c94a5cbf"
+ integrity sha512-Ie1jBkoQvzCT7SBuh1nNl/c2gC6lACOy6vY7dygWyZr70bP1Um660XbxCLhXAQ9Uqwok4LWLjzsnKgNd7JpV6w==
dependencies:
ajv "^8.17.1"
- ajv-formats "^2.1.1"
- db-errors "^0.2.3"
+ ajv-formats "^3.0.1"
on-finished@^2.4.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
- integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.2.tgz#6932b04b95bf6a4828201db10af75fe5c9c38b73"
+ integrity sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ==
dependencies:
ee-first "1.1.1"
@@ -1620,17 +1612,17 @@ once@^1.3.1, once@^1.4.0:
dependencies:
wrappy "1"
-otplib@^13.5.0:
- version "13.5.0"
- resolved "https://registry.yarnpkg.com/otplib/-/otplib-13.5.0.tgz#78b5d0d9edf83078d7fe37243bbcdd96d7a65d37"
- integrity sha512-RpcC6aq4rANX6MverMuU3pqHVLgMPO7vl6qR8ga7YzoEHlPJknJ58cSVAaf3jBYnasiQVQHXXl2mj/4pYlsVEQ==
+otplib@^13.6.0:
+ version "13.6.0"
+ resolved "https://registry.yarnpkg.com/otplib/-/otplib-13.6.0.tgz#aa462e7e2c0370278666a608144c3194c7b2f7fe"
+ integrity sha512-PCx6+14PYndCRk0G3JuJHmlExzENs43gafqMUVmSFmzMbEweVhBiYcBCL8B32ZMEDPZA4OsMxFEeAob1H+YFZg==
dependencies:
- "@otplib/core" "13.5.0"
- "@otplib/hotp" "13.5.0"
- "@otplib/plugin-base32-scure" "13.5.0"
- "@otplib/plugin-crypto-noble" "13.5.0"
- "@otplib/totp" "13.5.0"
- "@otplib/uri" "13.5.0"
+ "@otplib/core" "13.6.0"
+ "@otplib/hotp" "13.6.0"
+ "@otplib/plugin-base32-scure" "13.6.0"
+ "@otplib/plugin-crypto-noble" "13.6.0"
+ "@otplib/totp" "13.6.0"
+ "@otplib/uri" "13.6.0"
p-limit@^1.1.0:
version "1.3.0"
@@ -1733,20 +1725,20 @@ path@^0.12.7:
process "^0.11.1"
util "^0.10.3"
-pg-cloudflare@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz#4b4c20e6d8ae531d400730f4804571a8d62f1497"
- integrity sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==
+pg-cloudflare@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz#0fc904c9a8b3ca3a4f71e16bcc9171224ecbb2a8"
+ integrity sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==
pg-connection-string@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475"
integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==
-pg-connection-string@^2.14.0:
- version "2.14.0"
- resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.14.0.tgz#abc26ee4f37c56c0f3ae0fcf0b0653cc4e1c0fd9"
- integrity sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==
+pg-connection-string@^2.14.1:
+ version "2.14.1"
+ resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.14.1.tgz#d9529fe2497c0cac8a9f5f6f059679c52f1bd964"
+ integrity sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==
pg-int8@1.0.1:
version "1.0.1"
@@ -1758,10 +1750,10 @@ pg-pool@^3.14.0:
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.14.0.tgz#f35ae4eb846780cad71af24099b3edfa9781ad90"
integrity sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==
-pg-protocol@^1.16.0:
- version "1.16.0"
- resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.16.0.tgz#cffb008826561ee9770a8a15dc21f269d731b305"
- integrity sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==
+pg-protocol@^1.16.1:
+ version "1.16.1"
+ resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.16.1.tgz#87a3951b57002682d63166c029859bd834e64bf1"
+ integrity sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==
pg-types@2.2.0:
version "2.2.0"
@@ -1774,18 +1766,18 @@ pg-types@2.2.0:
postgres-date "~1.0.4"
postgres-interval "^1.1.0"
-pg@^8.23.0:
- version "8.23.0"
- resolved "https://registry.yarnpkg.com/pg/-/pg-8.23.0.tgz#5c2026d32bd0cb4fbd9196bac1ecf5ae66607180"
- integrity sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==
+pg@^8.23.1:
+ version "8.23.1"
+ resolved "https://registry.yarnpkg.com/pg/-/pg-8.23.1.tgz#e4848051a04aa2f968fd5671d075f89e5e7918b9"
+ integrity sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==
dependencies:
- pg-connection-string "^2.14.0"
+ pg-connection-string "^2.14.1"
pg-pool "^3.14.0"
- pg-protocol "^1.16.0"
+ pg-protocol "^1.16.1"
pg-types "2.2.0"
pgpass "1.0.5"
optionalDependencies:
- pg-cloudflare "^1.4.0"
+ pg-cloudflare "^1.4.1"
pgpass@1.0.5:
version "1.0.5"
@@ -1800,9 +1792,9 @@ picomatch@^2.0.4, picomatch@^2.2.1:
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
picomatch@^4.0.4:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.5.tgz#51ea57a17d86f605f81039595fbc40ed06a55fab"
- integrity sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f"
+ integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==
pify@^3.0.0:
version "3.0.0"
@@ -1872,10 +1864,10 @@ process@^0.11.1, process@^0.11.10:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
-proxy-addr@^2.0.7:
- version "2.0.7"
- resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
- integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
+proxy-addr@^2.0.8:
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.8.tgz#624e760fd8ab06b1b8320c2f8506d4422cf8e932"
+ integrity sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==
dependencies:
forwarded "0.2.0"
ipaddr.js "1.9.1"
@@ -1917,10 +1909,10 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
-qs@^6.14.0, qs@^6.15.2:
- version "6.16.0"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.16.0.tgz#c22c723a28a920f3aacdce8289fabd43eccb79fd"
- integrity sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==
+qs@^6.16.0, qs@^6.16.1:
+ version "6.17.1"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.17.1.tgz#43ac71d29ca0e9d0eed16a218e35b6cde5628e81"
+ integrity sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==
dependencies:
es-define-property "^1.0.1"
side-channel "^1.1.1"
@@ -1983,9 +1975,9 @@ readable-stream@^3.1.1, readable-stream@^3.4.0:
util-deprecate "^1.0.1"
readable-stream@^4.0.0:
- version "4.7.0"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91"
- integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==
+ version "4.8.0"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.8.0.tgz#22af894e6d60cb0064410fc376367ec4ce470adb"
+ integrity sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==
dependencies:
abort-controller "^3.0.0"
buffer "^6.0.3"
@@ -2035,12 +2027,12 @@ resolve-from@^5.0.0:
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
resolve@^1.20.0:
- version "1.22.12"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f"
- integrity sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==
+ version "1.22.14"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.14.tgz#302388cd2d7731a100db1dedce7b050f74ba38f7"
+ integrity sha512-1ZjYvR9sbkiwC5+9QHetqW5HmA2pHRMaoupD01t2HlYnV/vydC2zPwLixhsMieSWVZt0JJRg5LVIk0Ow1D/TrA==
dependencies:
es-errors "^1.3.0"
- is-core-module "^2.16.1"
+ is-core-module "^2.18.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
@@ -2071,9 +2063,9 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
semver@^7.3.5, semver@^7.5.3, semver@^7.5.4:
- version "7.8.5"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69"
- integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==
+ version "7.8.6"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.6.tgz#67c0cda133a3ceb6f70b803e67625bf969000424"
+ integrity sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==
send@^1.1.0, send@^1.2.0:
version "1.2.1"
@@ -2197,9 +2189,9 @@ socks-proxy-agent@10.1.0:
socks "^2.8.3"
socks@^2.8.3:
- version "2.8.9"
- resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.9.tgz#aa5f130ca0f88a43fa44faf4869c50d22aa27752"
- integrity sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==
+ version "2.8.10"
+ resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.10.tgz#23aab4dccc1fdae110aaf4fa51cd190552432dc5"
+ integrity sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==
dependencies:
ip-address "^10.1.1"
smart-buffer "^4.2.0"
@@ -2215,9 +2207,9 @@ split2@^4.1.0:
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
sql-escaper@^1.5.1:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/sql-escaper/-/sql-escaper-1.5.1.tgz#fbaee511294d1f7da92fb68ca1faee2255839c7b"
- integrity sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg==
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/sql-escaper/-/sql-escaper-1.5.2.tgz#5b59840dc4034bd23a9b7d00501ba947a90364b7"
+ integrity sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==
sqlite3@^6.0.1:
version "6.0.1"
@@ -2241,10 +2233,10 @@ streamsearch@^1.1.0:
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
-streamx@^2.12.5, streamx@^2.15.0, streamx@^2.25.0:
- version "2.28.0"
- resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.28.0.tgz#035ab56057b7ed2211b51d532e6973f0f99fbf11"
- integrity sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==
+streamx@^2.12.5, streamx@^2.25.0, streamx@^2.28.1:
+ version "2.28.1"
+ resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.28.1.tgz#376cd42a089505a69bec0efdb5189aa45586141f"
+ integrity sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==
dependencies:
events-universal "^1.0.0"
fast-fifo "^1.3.2"
@@ -2324,14 +2316,14 @@ tar-stream@^2.1.4:
readable-stream "^3.1.1"
tar-stream@^3.0.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.2.0.tgz#0d0064d9b67ea3c9f5abde155e35faab0df37591"
- integrity sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.2.2.tgz#aec7f257e712192a177a93aa0450c2e76855c8be"
+ integrity sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg==
dependencies:
- b4a "^1.6.4"
- bare-fs "^4.5.5"
- fast-fifo "^1.2.0"
- streamx "^2.15.0"
+ b4a "^1.9.0"
+ bare-fs "^4.8.2"
+ fast-fifo "^1.3.2"
+ streamx "^2.28.1"
tar@^7.5.10, tar@^7.5.4:
version "7.5.22"
@@ -2408,9 +2400,9 @@ touch@^3.1.0:
integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==
tslib@^2.0.1:
- version "2.8.1"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
- integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+ version "2.9.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.9.1.tgz#8986213ba2f17f38ba39a7c43d0e565a9134ff5f"
+ integrity sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==
tunnel-agent@^0.6.0:
version "0.6.0"
@@ -2419,7 +2411,7 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
-type-is@^2.0.1, type-is@^2.1.0:
+type-is@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.1.0.tgz#71d1a7053293582e16ac9f3ebaf1ab9aa49e5570"
integrity sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==
@@ -2434,14 +2426,9 @@ undefsafe@^2.0.5:
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
undici@^6.25.0:
- version "6.28.0"
- resolved "https://registry.yarnpkg.com/undici/-/undici-6.28.0.tgz#9f0e385744fef5021d6596c5bccd783f61193c1c"
- integrity sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==
-
-undici@^8.10.2:
- version "8.10.2"
- resolved "https://registry.yarnpkg.com/undici/-/undici-8.10.2.tgz#960bcb0b43c267f86910ea7ca408ada843a44bc7"
- integrity sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==
+ version "6.29.1"
+ resolved "https://registry.yarnpkg.com/undici/-/undici-6.29.1.tgz#a3317c5df0236e0633727d9a7bd4f7ed195d8575"
+ integrity sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==
unpipe@~1.0.0:
version "1.0.0"
diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml
index be6f517d3..2aef4e8ff 100644
--- a/docker/docker-compose.ci.yml
+++ b/docker/docker-compose.ci.yml
@@ -37,6 +37,7 @@ services:
- website1.example.com
- website2.example.com
- website3.example.com
+ - website4.example.com
examplesite:
image: "${IMAGE}-examplesite:ci-${BUILD_NUMBER}"
diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml
index d6b07ec01..20e01e6be 100644
--- a/docker/docker-compose.dev.yml
+++ b/docker/docker-compose.dev.yml
@@ -16,6 +16,7 @@ services:
- website1.example.com
- website2.example.com
- website3.example.com
+ - website4.example.com
environment:
TZ: "${TZ:-Australia/Brisbane}"
PUID: 1000
diff --git a/docker/rootfs/etc/nginx/conf.d/dev.conf b/docker/rootfs/etc/nginx/conf.d/dev.conf
index 67efc0f8a..57008f9a0 100644
--- a/docker/rootfs/etc/nginx/conf.d/dev.conf
+++ b/docker/rootfs/etc/nginx/conf.d/dev.conf
@@ -17,6 +17,9 @@ server {
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
+ # The TCP peer before realip processing. In development there is no peer secret
+ # (see production.conf.template), so the backend ignores this and limits on the socket
+ proxy_set_header X-NPM-Peer-Addr $realip_remote_addr;
proxy_pass http://127.0.0.1:3000/;
proxy_read_timeout 15m;
diff --git a/docker/rootfs/etc/nginx/conf.d/include/assets.conf b/docker/rootfs/etc/nginx/conf.d/include/assets.conf
index 5a90beb8a..9157d172d 100644
--- a/docker/rootfs/etc/nginx/conf.d/include/assets.conf
+++ b/docker/rootfs/etc/nginx/conf.d/include/assets.conf
@@ -3,7 +3,7 @@ location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|woff2|eot|ttf|svg|ico|css\.map|
# use the public cache
proxy_cache public-cache;
- proxy_cache_key $host$request_uri;
+ proxy_cache_key $forward_host$request_uri;
# ignore these headers for media
proxy_ignore_headers Set-Cookie Cache-Control Expires X-Accel-Expires;
diff --git a/docker/rootfs/etc/nginx/conf.d/include/proxy.conf b/docker/rootfs/etc/nginx/conf.d/include/proxy.conf
index fe2c2f213..fdba1bd7c 100644
--- a/docker/rootfs/etc/nginx/conf.d/include/proxy.conf
+++ b/docker/rootfs/etc/nginx/conf.d/include/proxy.conf
@@ -1,5 +1,5 @@
add_header X-Served-By $host;
-proxy_set_header Host $host;
+proxy_set_header Host $forward_host;
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;
diff --git a/docker/rootfs/etc/nginx/conf.d/production.conf.template b/docker/rootfs/etc/nginx/conf.d/production.conf.template
index 452609bcb..be7aca196 100644
--- a/docker/rootfs/etc/nginx/conf.d/production.conf.template
+++ b/docker/rootfs/etc/nginx/conf.d/production.conf.template
@@ -17,6 +17,11 @@ server {
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
+ # The TCP peer before realip processing; the backend rate limits on this because
+ # X-Real-IP can be set by any client in the trusted private ranges. The secret, generated
+ # at startup, is how the backend knows this header came from here.
+ proxy_set_header X-NPM-Peer-Addr $realip_remote_addr;
+ proxy_set_header X-NPM-Peer-Secret "{{NPM_PEER_SECRET}}";
proxy_pass http://127.0.0.1:3000/;
proxy_read_timeout 15m;
diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf
index bdba3b305..1b25cb5fc 100644
--- a/docker/rootfs/etc/nginx/nginx.conf
+++ b/docker/rootfs/etc/nginx/nginx.conf
@@ -52,6 +52,18 @@ http {
# Dynamically generated resolvers file
include /etc/nginx/conf.d/include/resolvers[.]conf;
+ # Forwarded Host: nginx's normalised $host (lowercased, taken from the request line for
+ # absolute-form requests) plus the client's port, so upstreams that compare Host with
+ # Origin/Referer on non-standard ports keep working. The port is only carried over when
+ # the Host header names the same host nginx routed on; otherwise (no Host, absolute-form
+ # URI naming a different host) it falls back to bare $host. Using $host rather than the
+ # raw header keeps case variants and mismatched Host values out of upstream requests and
+ # the asset cache key.
+ map "$host|$http_host" $forward_host {
+ "~*^(?[^|]+)\|\k(?:[0-9]+)$" "$host$fwd_port";
+ default $host;
+ }
+
# Default upstream scheme
map $host $forward_scheme {
default http;
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 2f59ef41a..ebbcd958c 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
@@ -39,3 +39,7 @@ touch /var/log/nginx/error.log || true
chmod 777 /var/log/nginx/error.log || true
chmod -R 777 /var/cache/nginx || true
chmod 644 /etc/logrotate.d/nginx-proxy-manager
+
+# nginx has not been started yet, so any pid file here is stale. Remove it,
+# otherwise a pid file owned by another user stops nginx starting as PUID:PGID
+rm -f /run/nginx/nginx.pid
diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh
index 4236c90a2..1306e7035 100755
--- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh
+++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh
@@ -49,7 +49,9 @@ done
# Ensure the JWT key file is owned by the runtime user, even when the /data
# directory ownership already matches PUID:PGID (chownit skips recursion then)
if [ -f /data/keys.json ]; then
- chown "$PUID:$PGID" /data/keys.json
+ if [ "$(stat -c '%u:%g' '/data/keys.json')" != "$PUID:$PGID" ]; then
+ chown "$PUID:$PGID" /data/keys.json
+ fi
fi
if [ "$(is_true "${SKIP_CERTBOT_OWNERSHIP:-}")" = '1' ]; then
diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh
index 3807c7d76..32252c727 100644
--- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh
+++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh
@@ -16,7 +16,16 @@ if ! [[ "$NPM_ADMIN_PORT" =~ ^[0-9]+$ ]]; then
fi
PRODFILE="/etc/nginx/conf.d/production.conf"
-SED_REGEX="s/\{\{NPM_ADMIN_PORT\}\}/${NPM_ADMIN_PORT}/g"
+
+# A new secret on every start. The admin nginx sends it with X-NPM-Peer-Addr, and the backend
+# only trusts that header when the secret matches, so a proxy host or stream forwarding to the
+# backend port can't pass a forged one through.
+NPM_PEER_SECRET="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"
+if [ "${#NPM_PEER_SECRET}" -ne 64 ]; then
+ log_fatal "Failed to generate the backend peer secret"
+fi
+
+SED_REGEX="s/\{\{NPM_ADMIN_PORT\}\}/${NPM_ADMIN_PORT}/g;s/\{\{NPM_PEER_SECRET\}\}/${NPM_PEER_SECRET}/g"
if is_mounted "$PRODFILE"; then
echo "WARNING: skipping ${PRODFILE} — mounted file" >&2
@@ -24,6 +33,8 @@ elif [ -f "$PRODFILE.template" ]; then
if sed -E "$SED_REGEX" "$PRODFILE.template" > "$PRODFILE" && [ -s "$PRODFILE" ]; then
# success
log_info "Generated ${PRODFILE} from template"
+ # Hand the secret to the backend; with-contenv loads this directory into its environment
+ printf '%s' "$NPM_PEER_SECRET" > /var/run/s6/container_environment/NPM_PEER_SECRET
else
log_fatal "Failed to generate ${PRODFILE} from template"
fi
diff --git a/docker/scripts/install-s6 b/docker/scripts/install-s6
index 55513fe1e..dc8eb4ae2 100755
--- a/docker/scripts/install-s6
+++ b/docker/scripts/install-s6
@@ -8,7 +8,7 @@ BLUE='\E[1;34m'
GREEN='\E[1;32m'
RESET='\E[0m'
-S6_OVERLAY_VERSION=3.2.3.0
+S6_OVERLAY_VERSION=3.2.3.2
TARGETPLATFORM=${1:-linux/amd64}
# Determine the correct binary file for the architecture given
diff --git a/docs/yarn.lock b/docs/yarn.lock
index f8c7568b2..39af0fc23 100644
--- a/docs/yarn.lock
+++ b/docs/yarn.lock
@@ -2,15 +2,15 @@
# yarn lockfile v1
-"@algolia/abtesting@1.12.2":
- version "1.12.2"
- resolved "https://registry.yarnpkg.com/@algolia/abtesting/-/abtesting-1.12.2.tgz#1cba5e3c654d02c6d435822a0a0070a5c435daa6"
- integrity sha512-oWknd6wpfNrmRcH0vzed3UPX0i17o4kYLM5OMITyMVM2xLgaRbIafoxL0e8mcrNNb0iORCJA0evnNDKRYth5WQ==
+"@algolia/abtesting@1.27.0":
+ version "1.27.0"
+ resolved "https://registry.yarnpkg.com/@algolia/abtesting/-/abtesting-1.27.0.tgz#5f507d696271fec578269693faf1adf383c05eb8"
+ integrity sha512-XXm0DTCmSzHT40mn0Hs8H00pBcuaqQET0BsWfaHrQdijNfrhSZPLueGVQvYAeSeXVuS420arotSfgqY25nFNbA==
dependencies:
- "@algolia/client-common" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
"@algolia/autocomplete-core@1.17.7":
version "1.17.7"
@@ -39,146 +39,146 @@
resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz#105e84ad9d1a31d3fb86ba20dc890eefe1a313a0"
integrity sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==
-"@algolia/client-abtesting@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.46.2.tgz#264a72f0e9d2fe0d0dc5c3d2d16bbb9cfe2ce9e8"
- integrity sha512-oRSUHbylGIuxrlzdPA8FPJuwrLLRavOhAmFGgdAvMcX47XsyM+IOGa9tc7/K5SPvBqn4nhppOCEz7BrzOPWc4A==
+"@algolia/client-abtesting@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.61.0.tgz#94c4365c9c206d6f314d04f66ccdb48661c111cf"
+ integrity sha512-NSq61VNhN63nstgbEh2YzygmtybxPnBdFwmjPOa7laJp8RapLrkqwHUaI4ELCyuZMhcuRlUvOL25y+SDVkgnsA==
dependencies:
- "@algolia/client-common" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
-"@algolia/client-analytics@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.46.2.tgz#3f00a237508aa0c46c9c02dea9c855e0a78e241f"
- integrity sha512-EPBN2Oruw0maWOF4OgGPfioTvd+gmiNwx0HmD9IgmlS+l75DatcBkKOPNJN+0z3wBQWUO5oq602ATxIfmTQ8bA==
+"@algolia/client-analytics@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.61.0.tgz#83324527f13047669c2de59099cd811e31e5f8dc"
+ integrity sha512-WBMStmOLas4bWHh0rgOOb8iTQL07fsfPwvEW2vrQL+5v2KWMcRix9cYSQp3SOGjdweNjUszbGO84VBBMHBnHbQ==
dependencies:
- "@algolia/client-common" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
-"@algolia/client-common@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.46.2.tgz#7f282fd8f721b0d96958445df2170f4c7dce6aac"
- integrity sha512-Hj8gswSJNKZ0oyd0wWissqyasm+wTz1oIsv5ZmLarzOZAp3vFEda8bpDQ8PUhO+DfkbiLyVnAxsPe4cGzWtqkg==
+"@algolia/client-common@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.61.0.tgz#053b7bded65eafc13d709f0ad23b6956561c8f5f"
+ integrity sha512-mzrW0KkEiZHB09cU2Sh2vyxUGSi9fVoglCeKnf3wJ3ixo+7ilHH64wLK45eSoX13+CVXA2KTjZJGcEmBS3Z57Q==
-"@algolia/client-insights@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.46.2.tgz#194b7b529ee8a4ffd5d70037745082996c3b9aa0"
- integrity sha512-6dBZko2jt8FmQcHCbmNLB0kCV079Mx/DJcySTL3wirgDBUH7xhY1pOuUTLMiGkqM5D8moVZTvTdRKZUJRkrwBA==
+"@algolia/client-insights@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.61.0.tgz#fe5c960ae71c561e933e959a464e6d9a62e5a791"
+ integrity sha512-TseZAUL8ZUtKQY6MQbJR/Qw9OSf54s9gvcDtQ7GZX9miZD1e24E1/usa1lGrrgq5KDpgQ8+ePCQIITFcq0Y56Q==
dependencies:
- "@algolia/client-common" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
-"@algolia/client-personalization@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.46.2.tgz#d604da7f0a3df1b3e2a9fe338d368e48fb781f8e"
- integrity sha512-1waE2Uqh/PHNeDXGn/PM/WrmYOBiUGSVxAWqiJIj73jqPqvfzZgzdakHscIVaDl6Cp+j5dwjsZ5LCgaUr6DtmA==
+"@algolia/client-personalization@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.61.0.tgz#c2b39d43d845181658f4f1731ad9b11280a1023b"
+ integrity sha512-4CBpVN2WDVLocAzMU8me5UHWSbf6oXI7KDy3K0pL/8i0eB1+nOMQBU7jmUL6kBl2XMoYAu6tnsZmxpZ59b+Y2g==
dependencies:
- "@algolia/client-common" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
-"@algolia/client-query-suggestions@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.46.2.tgz#f13bc5897bfbdc19509d430a26e9bbe2402e00c9"
- integrity sha512-EgOzTZkyDcNL6DV0V/24+oBJ+hKo0wNgyrOX/mePBM9bc9huHxIY2352sXmoZ648JXXY2x//V1kropF/Spx83w==
+"@algolia/client-query-suggestions@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.61.0.tgz#fd70402c32bb3ed4e8bba4a8b89689672f99cdfc"
+ integrity sha512-PU2XREmx7DdZ6SjuuaM+ZwhfMFOUPXjLeUYyvxTFA8oz06yrY/wcn5V/UOMv01rdeTuG2petMZLzTJADF9KyEw==
dependencies:
- "@algolia/client-common" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
-"@algolia/client-search@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.46.2.tgz#771367916aaa3fb7a19d5944f8375504b0568ba6"
- integrity sha512-ZsOJqu4HOG5BlvIFnMU0YKjQ9ZI6r3C31dg2jk5kMWPSdhJpYL9xa5hEe7aieE+707dXeMI4ej3diy6mXdZpgA==
+"@algolia/client-search@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.61.0.tgz#e120030ae546407015bd52e0c73185f8c562de44"
+ integrity sha512-npseUJ0UwaiwU4XbKyHiFcZbrS1AbLszbPRxi3W9YUrrqdpvfSeGKMlLv63haH4PZsCog5wG40lJGKXbnrDAEQ==
dependencies:
- "@algolia/client-common" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
-"@algolia/ingestion@1.46.2":
- version "1.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.46.2.tgz#2a5d8a592d9f864dfb438722506382af56f8554f"
- integrity sha512-1Uw2OslTWiOFDtt83y0bGiErJYy5MizadV0nHnOoHFWMoDqWW0kQoMFI65pXqRSkVvit5zjXSLik2xMiyQJDWQ==
+"@algolia/ingestion@1.61.0":
+ version "1.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.61.0.tgz#ee7fc87d06aff23f1b6f5c30fd352d271cd0cf73"
+ integrity sha512-rA/W43fD2qQemeQ2/uix/KSEUdikY55RTmgbfBRoF9HjCJ/zryD9RAKJXWxBlFXRBg9q8EPPYH3GlSXcRHnpgg==
dependencies:
- "@algolia/client-common" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
-"@algolia/monitoring@1.46.2":
- version "1.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.46.2.tgz#bd199368a49cb799cf12cfe76c49de6dd3021148"
- integrity sha512-xk9f+DPtNcddWN6E7n1hyNNsATBCHIqAvVGG2EAGHJc4AFYL18uM/kMTiOKXE/LKDPyy1JhIerrh9oYb7RBrgw==
+"@algolia/monitoring@1.61.0":
+ version "1.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.61.0.tgz#ad3e3b31ab078b852b07ee7f8e95a08be7201391"
+ integrity sha512-3CEPgD95sBitQVQsPXmIfMkcX0Kh12d5wmI7iOm1yrllPBu4Vx30Q2xMv9wyHm2C2bHQwz+KPBohtq6Hh1K5eg==
dependencies:
- "@algolia/client-common" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
-"@algolia/recommend@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.46.2.tgz#e74bade1254046ed9be8ccd37f2a116ab9799508"
- integrity sha512-NApbTPj9LxGzNw4dYnZmj2BoXiAc8NmbbH6qBNzQgXklGklt/xldTvu+FACN6ltFsTzoNU6j2mWNlHQTKGC5+Q==
+"@algolia/recommend@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.61.0.tgz#de30dfd0799aa5eb6f50c47cb745bb830ea99b7a"
+ integrity sha512-2pCOG8rRFW+0OeUmNUMoRH5xXktCEdUqtjuz6FCFQa1lxqPIUBr4HHIneXTIuAgUC0ckokqBCZk0v5qU7DvBuA==
dependencies:
- "@algolia/client-common" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
-"@algolia/requester-browser-xhr@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.46.2.tgz#7662480143405e815e1eed99136b4b2acd838ee7"
- integrity sha512-ekotpCwpSp033DIIrsTpYlGUCF6momkgupRV/FA3m62SreTSZUKjgK6VTNyG7TtYfq9YFm/pnh65bATP/ZWJEg==
+"@algolia/requester-browser-xhr@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.61.0.tgz#57abc51ff771929f3a08a3c79ea1cfa7aa52e870"
+ integrity sha512-7Y7P6Kp+7DQkrOCzmIYjoCpLPQoA8vlxcIzyjpfMrokBNX0LIQSPyR5JY/EdSzCN6CvNlYGJ5SiO7j6lZad5Jw==
dependencies:
- "@algolia/client-common" "5.46.2"
+ "@algolia/client-common" "5.61.0"
-"@algolia/requester-fetch@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.46.2.tgz#dee07f0131b75f30d083bafd6fb878afe7402eb9"
- integrity sha512-gKE+ZFi/6y7saTr34wS0SqYFDcjHW4Wminv8PDZEi0/mE99+hSrbKgJWxo2ztb5eqGirQTgIh1AMVacGGWM1iw==
+"@algolia/requester-fetch@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.61.0.tgz#e7a2cf2555259cc6d27c47778424f7d90c6e4235"
+ integrity sha512-WNTNQcrWcGAgLDU3+6IFWs3QPosMCYSlwlPEdEO2ivLnlm8u5HtGQvOz/JnY9d2bhofDBL+S5wdtm9+Uob+dsA==
dependencies:
- "@algolia/client-common" "5.46.2"
+ "@algolia/client-common" "5.61.0"
-"@algolia/requester-node-http@5.46.2":
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.46.2.tgz#7869d67cb2926bbdbfbfed2b4757e547c2e227eb"
- integrity sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg==
+"@algolia/requester-node-http@5.61.0":
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.61.0.tgz#5b9c56c2d09bc12bbd29c318c08804dd394f6fc0"
+ integrity sha512-CA+6MlgPkjSlGQpx+rLBSnHFy8pyYpGEjs7Yp0kVzOC6RM5jq99Z4KGOuplcNZBjQlM521t0nEvKLxFOv2NSZA==
dependencies:
- "@algolia/client-common" "5.46.2"
+ "@algolia/client-common" "5.61.0"
-"@babel/helper-string-parser@^7.27.1":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
- integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
+"@babel/helper-string-parser@^7.29.11":
+ version "7.29.13"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.13.tgz#00a43cc19b8f84e9e3f17f32813e69ae9bfceba8"
+ integrity sha512-fWegr+WsEsiMVW4KUNdIIzzMTZSzNiqhy0//6+2MRi2d5NFNsL/AYwBQW8eBnRbTBVRnDVzjaJEUqKaCWjMMdQ==
-"@babel/helper-validator-identifier@^7.28.5":
- version "7.28.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4"
- integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==
+"@babel/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.28.5":
- version "7.28.6"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd"
- integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==
+"@babel/parser@^7.29.12":
+ version "7.29.13"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.13.tgz#dbba85be72b94635123d594035fd087fe7e42bb3"
+ integrity sha512-fJM50YYjlTiBhgdkGmC1tBpv+FMAyo4annf2X6Aks+0yRlaiVkYW/3f07xAfVVkYIoZ9fFZp+mEThUFnuybhkQ==
dependencies:
- "@babel/types" "^7.28.6"
+ "@babel/types" "^7.29.11"
-"@babel/types@^7.28.6":
- version "7.28.6"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df"
- integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==
+"@babel/types@^7.29.11":
+ version "7.29.11"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.11.tgz#774cffe8cac2ca494f1334e4052002c96bf628fc"
+ integrity sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==
dependencies:
- "@babel/helper-string-parser" "^7.27.1"
- "@babel/helper-validator-identifier" "^7.28.5"
+ "@babel/helper-string-parser" "^7.29.11"
+ "@babel/helper-validator-identifier" "^7.29.7"
"@docsearch/css@3.8.2":
version "3.8.2"
@@ -319,9 +319,9 @@
integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
"@iconify-json/simple-icons@^1.2.21":
- version "1.2.66"
- resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.2.66.tgz#d0576ca65f69616b49491129e17132316cf3f309"
- integrity sha512-D1OnnXwiQXFkVMw5M+Bt8mPsXeMkQyGmMdrmN7lsQlKMUkfLOp6JWhnUJ92po51WXT046aF/zzqSmkKqg08p4Q==
+ version "1.2.101"
+ resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.2.101.tgz#323b81ed81d282dbf95eb39329b27e1e398243a0"
+ integrity sha512-MXJkMZDancJ/9I+Bgdthi9kXvn3AwWStxoTHwEPwAFQ9kMLBASO1BSstg52fNtjXeizm1Wi91R+QnWxNGkWHPQ==
dependencies:
"@iconify/types" "*"
@@ -331,134 +331,139 @@
integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==
"@jridgewell/sourcemap-codec@^1.5.5":
- version "1.5.5"
- resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
- integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz#f4c663e862f06dc98ca4d453862c46902789a18d"
+ integrity sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==
-"@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-arm-eabi@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.68.0.tgz#c5a058efb08e5119d3ff95b443d1b4661afba767"
+ integrity sha512-Ta2n0ntLfzP0LqwwcXOIh2z0mnHjlxMS+h4Y9lo+w33Quyf9KFKEi9QZ2IKqeEBKr0qxNpXc/DQjz5KVIs8AEA==
-"@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-android-arm64@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.68.0.tgz#3e77dc3d6f7b9fbab6c76ef8a3f1cf69bb2a185b"
+ integrity sha512-iFDtS0oMaBvKbtrRKXmRcBSsI0UaWL2bWpd62iHzb0A8RSPrVz47mUGE0CfrrD0c/7/7dUhNtoN3cc6aEUn1Ow==
-"@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-android-x64@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-x64/-/rollup-android-x64-4.68.0.tgz#fd2a20f0dd028df151003f1542a3a0504c33d297"
+ integrity sha512-OnYETr+cZYSbesBD1tCvUjrBUTkjSaOcewRUoIHqD0XI3yn5i9WpdFaE3LZwrMlq0yeJNU0N5sxfHK/fNVMqlQ==
-"@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-darwin-arm64@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.68.0.tgz#6dd9ce3f0b148344b87ffb0b0fe3168c87fcacd1"
+ integrity sha512-2qavWZtFeon/6oMkjsBF3OH7ENKgRTW4xRTwmz9VJeNUYhmPitpR9Jd4sAAc2OZJryHs2neoovMTCYnNvhvANQ==
-"@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-darwin-x64@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.68.0.tgz#78092241c8f6860b2275cd0bc7c5bdbbce4d572f"
+ integrity sha512-QpgJO+GwNQDPt6uYcFSjZTFVyHNIeumN2am7RISDR9Nf3e/h8sur9cIfplPGihZPUn+sTw9j3JErZIUkHg8+Pw==
-"@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-freebsd-arm64@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.68.0.tgz#fefaf85dad7595379f417b24a200c3da7fc7f802"
+ integrity sha512-TERqEn6P9bp4lsTyoFjT3nLKV6G++76HhGb4k6dRGcs+jvhVcTGaS2zkeRdLLRjwKxpBtdCZe11kUQ2hNGgFSA==
-"@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-freebsd-x64@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.68.0.tgz#c58f4619f49ed299e99bd72df0c8e9bb9bad1cda"
+ integrity sha512-RBjDrqb7h5l6Xgdgz5eKYvbpnUkii/z5ejWuZmHJsXORi0X0tTtFzEEgJj2DX995kW18c6T9LGFn/deEAMXDcQ==
-"@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-arm-gnueabihf@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.68.0.tgz#630fcb12dcdeae9e7a4d26046e8de8d07cc6501c"
+ integrity sha512-HtLgXBqjF1Ampt9eJjzSmoGDvNb4LP8Y2aPXQZeQ5qhyExXLmoXJB9Jfm/nPi/8fJ3UFn4sZ7pQKyr8V+oTN8g==
-"@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-arm-musleabihf@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.68.0.tgz#da75938ef398e7907af4e8723af970a04387bd56"
+ integrity sha512-q51xJyqjaqCNtDjYD7xNVwsPa2TcVfAIBeP6E20wicKZ6XYp8Taz9SUPYhIENojAQIAoV7cO3mUdMUgWYEAuhQ==
-"@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-arm64-gnu@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.68.0.tgz#1aae89d808ca937a4fad7b28e4008e910dee4923"
+ integrity sha512-wejnVabl92oFC/o4sr/EEJuJZMHtMq4C+LBIPphN4EUiVHe9qvZNw+biqhVHpX2ggs1+Z3ugXGwJxT+qhyaaYQ==
-"@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-arm64-musl@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.68.0.tgz#f70fce082bd03ed7729dd87c62f4eb014443f5f6"
+ integrity sha512-tyu+hrsCWHHqV6+2tligV/recJH+HVvNV2Sp4nS1C+MKQPkNXRovKIzyHy0V0xXjm6igCLgsT2vTky5CvWP37g==
-"@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-loong64-gnu@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.68.0.tgz#70eaf986a324ee721ca3a647f5aeda6b29196533"
+ integrity sha512-5gjJNglKDfgs3OmnGi/RVZQPhEfRoCIHxwSAP9ngmdnxreu0Vl9lI4gxmFBXOhKdftpS82xRdc/7Dp5/A1F0qg==
-"@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-loong64-musl@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.68.0.tgz#3667cf43e4bbd9254e6c12004eb02e110e67ad69"
+ integrity sha512-2IBxTUds33rFYwOIEyf77wfW8WQThwiQQ1ofXSraZQG7PiIq7BnQBf/ge0qEI/Ny/2cSc7SKkGO/wUDqmYXqTg==
-"@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-ppc64-gnu@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.68.0.tgz#c04f13264b418b39374d255ccffb6cb1f4fc9039"
+ integrity sha512-mE/Pj2PPTZIVHkNNuHq+eYoU3td8LCm73zjrXoBWkNlnuULJS2l/fxwgGPpN4WUCNZgKZ1IXwrnnETPyOcWPmg==
-"@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-ppc64-musl@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.68.0.tgz#d54bcd8beddff5cf778c4021be317b48813b4327"
+ integrity sha512-dYuykUkAiTv+RQdP3Ib47XcFiImkBWiRpTGSQWAMfZPQyoQYGe8Je4IUeZYNUQWIojmRCUnt5xsH4BbUb2uqSw==
-"@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-riscv64-gnu@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.68.0.tgz#6d2c020ef52e437115bd3eb9802bebded8b7245e"
+ integrity sha512-OnPDDFXLqJewY56T6ow8sA4wuzbqlliD7QJgkrx2KX+pWOpjI3usYXgB0pwK3nsgTF2odCw9IuJs0Uis/jy2qA==
-"@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-riscv64-musl@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.68.0.tgz#2013db95988d9a8bbf533f752621406d3b6335f0"
+ integrity sha512-CqoQf06cK0brrRfDqH7Mt8G4MdteFGbjBdC61zEMaSVna6xo6QMUixa9bAkAD4a92g3ElPbQAkLMUzdf+Lk8dg==
-"@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-s390x-gnu@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.68.0.tgz#59333d3c2104c649abbf6d9c98382aca4b7aff65"
+ integrity sha512-LPHNmE8GwiyELGLUTkqj4zsP1YEHzGdtwnKxAcdP/swsNxXsfxPtpe5nrD/KGxr136xPB+eWv2r/SGj9md0pnQ==
-"@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-linux-x64-gnu@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.68.0.tgz#def6dd537d19c05834f42d2cc14ce6e4462a6d52"
+ integrity sha512-GDOmvgephpRCNyNGRQOZh0KD2gTyRVM1Bb1iOo759xeCUpCAoRCmJioOOSu2MwyWfNOocGd0V5I9vYLfteC6/A==
-"@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-linux-x64-musl@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.68.0.tgz#4c4e4673d23b301d9d26076983456183966b52e5"
+ integrity sha512-HADRxkvl5eg3hbemCmv81UwTfdQy/CiSJDOabLE+p5tpm3OTjuRtzpLXy5zPfGY9FQD4Qv7qHptXmG/YjmNmyw==
-"@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-openbsd-x64@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.68.0.tgz#7ca2e43427d49333cb211c4d1980f07e6b576d10"
+ integrity sha512-iERrpqu1VEfyX736HLKiXRbwGDkCV/iybARY/mn14KcI6oKhJGBx1e+bU+g+umTKghtiGSPaHkwwpII4/N3RjA==
-"@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-openharmony-arm64@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.68.0.tgz#899bf838d15a8c69174a36886ce8fc55402b8d31"
+ integrity sha512-hBn57IqAbpXpyktckonwePTaFOQS6THph8ZnpRO3eZHY4LncuZGjSsseigBnKuSMKKpLnJJ7PemDvXEzniXC+A==
-"@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-arm64-msvc@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.68.0.tgz#216ca59394b95ccbba5e1ab5b563112198335b0f"
+ integrity sha512-pJWb7mIw9Z25Hl3Q6nHFjl7AnczfOpm2gbOFYguSRv6TiH7DzsoAk/thymb/URSQu9OUEK7uaDMC5Xfm+7TAww==
-"@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-ia32-msvc@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.68.0.tgz#d96453b50d0f212facf6d8cd3e9fa390f1ce5665"
+ integrity sha512-pnrhmoyF2otH2Mezol14/ELUFGuRddOCz6+I+J0pq2Itnp4UxkjABQcXawCNMaKNsCRiuL90BtNzbEd0fHjpIg==
-"@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==
+"@rollup/rollup-win32-x64-gnu@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.68.0.tgz#2ee1a6f01e587e7dafa08fdd586dc710341a63e4"
+ integrity sha512-1dJ3U+ktdtaIdPaOZKwIX57+bKQk4iAjs5M1wQWLKRB6Wglp98qf8LUeuN4u6bGlIYouZKaySZJ5Qd4OalUMnA==
+
+"@rollup/rollup-win32-x64-msvc@4.68.0":
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.68.0.tgz#3d82b1bb57754a15cd05a5223189cdd3e6e96bcd"
+ integrity sha512-e/NRJFl48wL5DFNSzQrvm4kaB5dzNNmqWjIJ6And2Sjuw4klO3w3TXMvCAEF1tHTrQQgpOD4QVkBE/92/yBGrQ==
"@shikijs/core@2.5.0", "@shikijs/core@^2.1.0":
version "2.5.0"
@@ -520,19 +525,19 @@
"@types/hast" "^3.0.4"
"@shikijs/vscode-textmate@^10.0.2":
- version "10.0.2"
- resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224"
- integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.1.0.tgz#de59840c1d9903b5f5732dbc7852437003d35f11"
+ integrity sha512-cVBuhDQjBZaNMTwBquYfPydrUP+E3hE6oYGasx8h6ZrODIM7/r+gxLDXdd66oszbrggZHEntdE6j+5gYIVyMWQ==
-"@types/estree@1.0.8":
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
- integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+"@types/estree@^1.0.10":
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.10.tgz#8a4a266065af8c2fcf5983153cd97c11b313d0c6"
+ integrity sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==
"@types/hast@^3.0.0", "@types/hast@^3.0.4":
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa"
- integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.5.tgz#48020de4c0e63492f4ca9db42068c108f68b7f8f"
+ integrity sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==
dependencies:
"@types/unist" "*"
@@ -542,9 +547,9 @@
integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==
"@types/markdown-it@^14.1.2":
- version "14.1.2"
- resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61"
- integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==
+ version "14.2.0"
+ resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.2.0.tgz#4074873f98fbc791131e31d7446b12dfa3a31df9"
+ integrity sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==
dependencies:
"@types/linkify-it" "^5"
"@types/mdurl" "^2"
@@ -572,70 +577,70 @@
integrity sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==
"@ungap/structured-clone@^1.0.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
- integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.4.0.tgz#5e2e1374c0a30b5a42e8b083523a225c6945f88a"
+ integrity sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==
"@vitejs/plugin-vue@^5.2.1":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz#9e8a512eb174bfc2a333ba959bbf9de428d89ad8"
integrity sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==
-"@vue/compiler-core@3.5.26":
- version "3.5.26"
- resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.26.tgz#1a91ea90980528bedff7b1c292690bfb30612485"
- integrity sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==
+"@vue/compiler-core@3.5.45":
+ version "3.5.45"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.45.tgz#90c17a54f36dcc44f03ffdcacaca08110a89f255"
+ integrity sha512-XGHJrM6ufZARAc/w+tyOswStw9tdKiqCKBScI4gGaNxD+aaGu/HQWgKJ+JItjRLPqenQ7iE7G8AJC7T5ew1p+g==
dependencies:
- "@babel/parser" "^7.28.5"
- "@vue/shared" "3.5.26"
- entities "^7.0.0"
+ "@babel/parser" "^7.29.12"
+ "@vue/shared" "3.5.45"
+ entities "^7.0.1"
estree-walker "^2.0.2"
- source-map-js "^1.2.1"
+ source-map-js "^1.2.2"
-"@vue/compiler-dom@3.5.26":
- version "3.5.26"
- resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz#66c36b6ed8bdf43236d7188ea332bc9d078eb286"
- integrity sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==
+"@vue/compiler-dom@3.5.45":
+ version "3.5.45"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.45.tgz#d2a9d7655c53a93081c88220c385fce676859a6a"
+ integrity sha512-LbP2d19GSOYGPmvMBBCVJVlv7TKtjM1Cfeyu3cYia720pknZP+1cLX7H5MVatvruT6MuJhiPuNbQBW/MDn/EwA==
dependencies:
- "@vue/compiler-core" "3.5.26"
- "@vue/shared" "3.5.26"
+ "@vue/compiler-core" "3.5.45"
+ "@vue/shared" "3.5.45"
-"@vue/compiler-sfc@3.5.26":
- version "3.5.26"
- resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz#fb1c6c4bf9a9e22bb169e039e19437cb6995917a"
- integrity sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==
+"@vue/compiler-sfc@3.5.45":
+ version "3.5.45"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.45.tgz#82cc47d45f60358c0b1b75a8d5f1ec837ed13e26"
+ integrity sha512-MKFjb2UX0M/l/jhfzqMLrI8VM4pfvFKG3pqFZkSZFHRLtNld/O4RtUPZFWSwhro/14qP88UQEkWzHxAZAQ0tsw==
dependencies:
- "@babel/parser" "^7.28.5"
- "@vue/compiler-core" "3.5.26"
- "@vue/compiler-dom" "3.5.26"
- "@vue/compiler-ssr" "3.5.26"
- "@vue/shared" "3.5.26"
+ "@babel/parser" "^7.29.12"
+ "@vue/compiler-core" "3.5.45"
+ "@vue/compiler-dom" "3.5.45"
+ "@vue/compiler-ssr" "3.5.45"
+ "@vue/shared" "3.5.45"
estree-walker "^2.0.2"
magic-string "^0.30.21"
- postcss "^8.5.6"
- source-map-js "^1.2.1"
+ postcss "^8.5.32"
+ source-map-js "^1.2.2"
-"@vue/compiler-ssr@3.5.26":
- version "3.5.26"
- resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz#f6e94bccbb5339180779036ddfb614f998a197ea"
- integrity sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==
+"@vue/compiler-ssr@3.5.45":
+ version "3.5.45"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.45.tgz#a760c005b20a62fc2babb16bd5c45ceab2d6595d"
+ integrity sha512-RF5sbZOfYz5AK00tOJUn2PwFXR2fS1u2HVzI8D9VF9rGAFxuGBqg2bB1kJ3ZHMprqSltXnT9MZE1IsqjSM8oew==
dependencies:
- "@vue/compiler-dom" "3.5.26"
- "@vue/shared" "3.5.26"
+ "@vue/compiler-dom" "3.5.45"
+ "@vue/shared" "3.5.45"
"@vue/devtools-api@^7.7.0":
- version "7.7.9"
- resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz#999dbea50da6b00cf59a1336f11fdc2b43d9e063"
- integrity sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==
+ version "7.7.10"
+ resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-7.7.10.tgz#bfa52dee8918edeea1bfe04cce9f809eeed19862"
+ integrity sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==
dependencies:
- "@vue/devtools-kit" "^7.7.9"
+ "@vue/devtools-kit" "^7.7.10"
-"@vue/devtools-kit@^7.7.9":
- version "7.7.9"
- resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz#bc218a815616e8987df7ab3e10fc1fb3b8706c58"
- integrity sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==
+"@vue/devtools-kit@^7.7.10":
+ version "7.7.10"
+ resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz#2f2b6b6eaad9ba96c031fb20c4d59c80d35dfad3"
+ integrity sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==
dependencies:
- "@vue/devtools-shared" "^7.7.9"
+ "@vue/devtools-shared" "^7.7.10"
birpc "^2.3.0"
hookable "^5.5.3"
mitt "^3.0.1"
@@ -643,50 +648,51 @@
speakingurl "^14.0.1"
superjson "^2.2.2"
-"@vue/devtools-shared@^7.7.9":
- version "7.7.9"
- resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz#fa4c096b744927081a7dda5fcf05f34b1ae6ca14"
- integrity sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==
+"@vue/devtools-shared@^7.7.10":
+ version "7.7.10"
+ resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz#ba45001aeb5f7b99a72db2fb824fc04a280aca53"
+ integrity sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==
dependencies:
rfdc "^1.4.1"
-"@vue/reactivity@3.5.26":
- version "3.5.26"
- resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.26.tgz#59a1edf566dc80133c1c26c93711c877e8602c48"
- integrity sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==
+"@vue/reactivity@3.5.45":
+ version "3.5.45"
+ resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.45.tgz#f1abf52d03d352db1608d788551541fd3c680332"
+ integrity sha512-WqkyubNWpI4OYBOipSrjIssMdJO8K1i9Zts/Wm8Er10/0msOEwCyWWWvZnAI7WW3kQsTong6DJp8Cbb+vd4cPA==
dependencies:
- "@vue/shared" "3.5.26"
+ "@vue/shared" "3.5.45"
-"@vue/runtime-core@3.5.26":
- version "3.5.26"
- resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.26.tgz#3f2c040bcf8018c03a1ab5adb0d788c13c986f0e"
- integrity sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==
+"@vue/runtime-core@3.5.45":
+ version "3.5.45"
+ resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.45.tgz#170664cec5211b4270de3b747db2c8bc5ab39c45"
+ integrity sha512-bzIO3+FABjpj85uIKSc+Qnj36/sxDNI3tcH9i6w6xKA1k6e0ejv0G0jMCfeJBvK3yGL5XbJ3DMZj+ClqyCHFYw==
dependencies:
- "@vue/reactivity" "3.5.26"
- "@vue/shared" "3.5.26"
+ "@vue/reactivity" "3.5.45"
+ "@vue/shared" "3.5.45"
-"@vue/runtime-dom@3.5.26":
- version "3.5.26"
- resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz#5954848614883948ecc1f631a67b32cc32f81936"
- integrity sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==
+"@vue/runtime-dom@3.5.45":
+ version "3.5.45"
+ resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.45.tgz#9a4652ce58c3de3d5f791a63a134abcddbe39a28"
+ integrity sha512-ZvRTFMDgfqTDYWvJfoVpQTJx1RN5M6l7Pyd6wfdY2NaXzbPQVh9ezeQYFbNPP6bIcRVn2SqSSR25N/jSW69BFw==
dependencies:
- "@vue/reactivity" "3.5.26"
- "@vue/runtime-core" "3.5.26"
- "@vue/shared" "3.5.26"
+ "@vue/reactivity" "3.5.45"
+ "@vue/runtime-core" "3.5.45"
+ "@vue/shared" "3.5.45"
csstype "^3.2.3"
-"@vue/server-renderer@3.5.26":
- version "3.5.26"
- resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.26.tgz#269055497fcc75b3984063f866f17c748b565ef4"
- integrity sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==
+"@vue/server-renderer@3.5.45":
+ version "3.5.45"
+ resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.45.tgz#eacee9a2e40166eaaf7e33d3b6709c73d82bd2dc"
+ integrity sha512-1fNXUHhhsWf4EVL4XixbGBU5mKdiIXJAB82feHRTyPPo6ZUEYWIJuEeUl3mVV6QDBp50HJOX4obLhxYXlrjrzA==
dependencies:
- "@vue/compiler-ssr" "3.5.26"
- "@vue/shared" "3.5.26"
+ "@vue/compiler-ssr" "3.5.45"
+ "@vue/runtime-dom" "3.5.45"
+ "@vue/shared" "3.5.45"
-"@vue/shared@3.5.26", "@vue/shared@^3.5.13":
- version "3.5.26"
- resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.26.tgz#1e02ef2d64aced818cd31d81ce5175711dc90a9f"
- integrity sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==
+"@vue/shared@3.5.45", "@vue/shared@^3.5.13":
+ version "3.5.45"
+ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.45.tgz#7cf2a22b13b2f9d0fd01cd45fb6e7c8a41fa22b8"
+ integrity sha512-ue9fgHmVNPlP7iW3+cxoBX/Cnlw53dHlVSw0JKGY6z1lObg8pHoj928M2nzG4/yVsMZ3NaRZ2s2z61uC16NDwQ==
"@vueuse/core@12.8.2", "@vueuse/core@^12.4.0":
version "12.8.2"
@@ -720,24 +726,24 @@
vue "^3.5.13"
algoliasearch@^5.14.2:
- version "5.46.2"
- resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.46.2.tgz#3afba0e53f3293e39cfde9b2ef27c583d44bf2a5"
- integrity sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q==
+ version "5.61.0"
+ resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.61.0.tgz#b3f5d1e5e98057cc16bb8ff1ce551cde3978cb41"
+ integrity sha512-CFtHkGANnIAG6TY5g9s/h+yxZ0SN2/ICMjMY1EWj9gKGiS4LSGFrmMmTA/xxyxOH8AEazFD3YSfRZMh5vIEh2w==
dependencies:
- "@algolia/abtesting" "1.12.2"
- "@algolia/client-abtesting" "5.46.2"
- "@algolia/client-analytics" "5.46.2"
- "@algolia/client-common" "5.46.2"
- "@algolia/client-insights" "5.46.2"
- "@algolia/client-personalization" "5.46.2"
- "@algolia/client-query-suggestions" "5.46.2"
- "@algolia/client-search" "5.46.2"
- "@algolia/ingestion" "1.46.2"
- "@algolia/monitoring" "1.46.2"
- "@algolia/recommend" "5.46.2"
- "@algolia/requester-browser-xhr" "5.46.2"
- "@algolia/requester-fetch" "5.46.2"
- "@algolia/requester-node-http" "5.46.2"
+ "@algolia/abtesting" "1.27.0"
+ "@algolia/client-abtesting" "5.61.0"
+ "@algolia/client-analytics" "5.61.0"
+ "@algolia/client-common" "5.61.0"
+ "@algolia/client-insights" "5.61.0"
+ "@algolia/client-personalization" "5.61.0"
+ "@algolia/client-query-suggestions" "5.61.0"
+ "@algolia/client-search" "5.61.0"
+ "@algolia/ingestion" "1.61.0"
+ "@algolia/monitoring" "1.61.0"
+ "@algolia/recommend" "5.61.0"
+ "@algolia/requester-browser-xhr" "5.61.0"
+ "@algolia/requester-fetch" "5.61.0"
+ "@algolia/requester-node-http" "5.61.0"
birpc@^2.3.0:
version "2.9.0"
@@ -765,11 +771,9 @@ comma-separated-tokens@^2.0.0:
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
copy-anything@^4:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.0.5.tgz#16cabafd1ea4bb327a540b750f2b4df522825aea"
- integrity sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==
- dependencies:
- is-what "^5.2.0"
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.1.5.tgz#11866cab9f08ef1f98daac8c468e10d2830294e3"
+ integrity sha512-vePst2VLTSqPglW7Q98VwFpGiGpOHTyXIo81MO5VyLecA5zMbZw/P/V1vmwD1jksa2iJpn/YkDrJ3XhTfzUyhQ==
csstype@^3.2.3:
version "3.2.3"
@@ -793,10 +797,10 @@ emoji-regex-xs@^1.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724"
integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==
-entities@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.0.tgz#2ae4e443f3f17d152d3f5b0f79b932c1e59deb7a"
- integrity sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==
+entities@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b"
+ integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==
esbuild@^0.21.3:
version "0.21.5"
@@ -845,9 +849,9 @@ fsevents@~2.3.2, fsevents@~2.3.3:
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
hast-util-to-html@^9.0.4:
- version "9.0.5"
- resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005"
- integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==
+ version "9.0.6"
+ resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.6.tgz#249d71c9d2549b4c4953230e9327e5e6d6a22025"
+ integrity sha512-1SZUxCN3TtJAE7e2BQN2MBHsoZ1gnmi9GoCP5Di59CSWptIezSAW09yDZyVbanb78V9eCwrKqdQsjtfVTxYHWA==
dependencies:
"@types/hast" "^3.0.0"
"@types/unist" "^3.0.0"
@@ -878,11 +882,6 @@ html-void-elements@^3.0.0:
resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7"
integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==
-is-what@^5.2.0:
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/is-what/-/is-what-5.5.0.tgz#a3031815757cfe1f03fed990bf6355a2d3f628c4"
- integrity sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==
-
magic-string@^0.30.21:
version "0.30.21"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91"
@@ -911,36 +910,36 @@ mdast-util-to-hast@^13.0.0:
vfile "^6.0.0"
micromark-util-character@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.0.tgz#31320ace16b4644316f6bf057531689c71e2aee1"
- integrity sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6"
+ integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==
dependencies:
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
micromark-util-encode@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz#0921ac7953dc3f1fd281e3d1932decfdb9382ab1"
- integrity sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8"
+ integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==
micromark-util-sanitize-uri@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz#ec8fbf0258e9e6d8f13d9e4770f9be64342673de"
- integrity sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7"
+ integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==
dependencies:
micromark-util-character "^2.0.0"
micromark-util-encode "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-symbol@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz#12225c8f95edf8b17254e47080ce0862d5db8044"
- integrity sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8"
+ integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==
micromark-util-types@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.0.tgz#63b4b7ffeb35d3ecf50d1ca20e68fc7caa36d95e"
- integrity sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.3.tgz#e7b04fa58589199c7ef58b37cd696fcb2bce44e7"
+ integrity sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==
minisearch@^7.1.1:
version "7.2.0"
@@ -952,10 +951,10 @@ mitt@^3.0.1:
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
-nanoid@^3.3.16:
- version "3.3.16"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.16.tgz#a04d8ec4b1f10009d2d533947aefe4293737816c"
- integrity sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==
+nanoid@^3.3.20:
+ version "3.3.20"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.20.tgz#dfd4444d85aa10111ef66ba85ed72e08ba0855d8"
+ integrity sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==
oniguruma-to-es@^3.1.0:
version "3.1.1"
@@ -976,24 +975,24 @@ picocolors@^1.1.1:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
-postcss@^8.4.43, postcss@^8.5.6:
- version "8.5.25"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.25.tgz#5012a598eaaa897f21bbe8553be3cb7bd2bd78cb"
- integrity sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==
+postcss@^8.4.43, postcss@^8.5.32:
+ version "8.5.32"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.32.tgz#fbccef8388a0c77508c713bfd37728d646595803"
+ integrity sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==
dependencies:
- nanoid "^3.3.16"
+ nanoid "^3.3.20"
picocolors "^1.1.1"
- source-map-js "^1.2.1"
+ source-map-js "^1.2.2"
preact@^10.0.0:
- version "10.21.0"
- resolved "https://registry.yarnpkg.com/preact/-/preact-10.21.0.tgz#5b0335c873a1724deb66e517830db4fd310c24f6"
- integrity sha512-aQAIxtzWEwH8ou+OovWVSVNlFImL7xUCwJX3YMqA3U8iKCNC34999fFOnWjYNsylgfPgMexpbk7WYOLtKr/mxg==
+ version "10.29.8"
+ resolved "https://registry.yarnpkg.com/preact/-/preact-10.29.8.tgz#fc85b82dc2474e245b1430b51781d417361f5fc3"
+ integrity sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==
property-information@^7.0.0:
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d"
- integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.2.0.tgz#0809b34264e995c0bfcd3227028a1e35210af80a"
+ integrity sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==
regex-recursion@^6.0.2:
version "6.0.2"
@@ -1020,37 +1019,38 @@ rfdc@^1.4.1:
integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
rollup@^4.20.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==
+ version "4.68.0"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.68.0.tgz#3dad30d9b18a9bced13222183e089e2e63ee05c0"
+ integrity sha512-BIHWnc1Qjdy+WLfOdcSd67sxO4iW6pTnhdZosg+7ywSaiMWxxzKldkkNpUJqeXYAiJzyB14P2cIjqC6tlb6xzw==
dependencies:
- "@types/estree" "1.0.8"
+ "@types/estree" "^1.0.10"
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"
+ "@rollup/rollup-android-arm-eabi" "4.68.0"
+ "@rollup/rollup-android-arm64" "4.68.0"
+ "@rollup/rollup-android-x64" "4.68.0"
+ "@rollup/rollup-darwin-arm64" "4.68.0"
+ "@rollup/rollup-darwin-x64" "4.68.0"
+ "@rollup/rollup-freebsd-arm64" "4.68.0"
+ "@rollup/rollup-freebsd-x64" "4.68.0"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.68.0"
+ "@rollup/rollup-linux-arm-musleabihf" "4.68.0"
+ "@rollup/rollup-linux-arm64-gnu" "4.68.0"
+ "@rollup/rollup-linux-arm64-musl" "4.68.0"
+ "@rollup/rollup-linux-loong64-gnu" "4.68.0"
+ "@rollup/rollup-linux-loong64-musl" "4.68.0"
+ "@rollup/rollup-linux-ppc64-gnu" "4.68.0"
+ "@rollup/rollup-linux-ppc64-musl" "4.68.0"
+ "@rollup/rollup-linux-riscv64-gnu" "4.68.0"
+ "@rollup/rollup-linux-riscv64-musl" "4.68.0"
+ "@rollup/rollup-linux-s390x-gnu" "4.68.0"
+ "@rollup/rollup-linux-x64-gnu" "4.68.0"
+ "@rollup/rollup-linux-x64-musl" "4.68.0"
+ "@rollup/rollup-openbsd-x64" "4.68.0"
+ "@rollup/rollup-openharmony-arm64" "4.68.0"
+ "@rollup/rollup-win32-arm64-msvc" "4.68.0"
+ "@rollup/rollup-win32-ia32-msvc" "4.68.0"
+ "@rollup/rollup-win32-x64-gnu" "4.68.0"
+ "@rollup/rollup-win32-x64-msvc" "4.68.0"
fsevents "~2.3.2"
shiki@^2.1.0:
@@ -1067,10 +1067,10 @@ shiki@^2.1.0:
"@shikijs/vscode-textmate" "^10.0.2"
"@types/hast" "^3.0.4"
-source-map-js@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
- integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+source-map-js@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.2.tgz#79fb88d97535a21cb27162cb05d211b67acea4ab"
+ integrity sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==
space-separated-tokens@^2.0.0:
version "2.0.2"
@@ -1098,9 +1098,9 @@ superjson@^2.2.2:
copy-anything "^4"
tabbable@^6.4.0:
- version "6.4.0"
- resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.4.0.tgz#36eb7a06d80b3924a22095daf45740dea3bf5581"
- integrity sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.5.1.tgz#cfca6f3f397db9edbe08ad35915e0d7807050a18"
+ integrity sha512-dMCuwDUY6Noz3zVKLefBC4wq+aJV20yc7vWmM7uTNGwtQSFF5kX7OUIUsxuVajz/bdt5Xqdah3/0NLjth+cM5A==
trim-lines@^3.0.0:
version "3.0.1"
@@ -1108,9 +1108,9 @@ trim-lines@^3.0.0:
integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
unist-util-is@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424"
- integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.1.tgz#d0a3f86f2dd0db7acd7d8c2478080b5c67f9c6a9"
+ integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==
dependencies:
"@types/unist" "^3.0.0"
@@ -1128,27 +1128,27 @@ unist-util-stringify-position@^4.0.0:
dependencies:
"@types/unist" "^3.0.0"
-unist-util-visit-parents@^6.0.0:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815"
- integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==
+unist-util-visit-parents@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.1.0.tgz#eff74226782b378957731bcf62cf5e9d8a080606"
+ integrity sha512-vKXxz6O38sKDj1mtx/k2cWvkkFBuYGx6uGrlzBkVeWCRF/F8sdI0j3Wbkz/5rVNRRuVc8UrgRdbCxNhmYXgP3w==
dependencies:
"@types/unist" "^3.0.0"
unist-util-is "^6.0.0"
unist-util-visit@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6"
- integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.2.0.tgz#914256bc5e1801e280c12970fad4328352645974"
+ integrity sha512-fpsYs/VVoZxfGY9/j3FQpZmN8wdLJaV1Pt38AaJNZu9uKIHcFU3halCdBveEq1HGKO1OEfJY65w1zvoFV1b3qQ==
dependencies:
"@types/unist" "^3.0.0"
unist-util-is "^6.0.0"
- unist-util-visit-parents "^6.0.0"
+ unist-util-visit-parents "^6.1.0"
vfile-message@^4.0.0:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181"
- integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.4.tgz#24dd2080867c336e121a2b7446c247d935022565"
+ integrity sha512-66ycRswIkNmHH9PHku7ADtv5RVwgIrOcuv3J8Svz6h4qgGRrKVa+9/neuKyJZUXyOpYwLTvS+whZ4R2urHHgQg==
dependencies:
"@types/unist" "^3.0.0"
unist-util-stringify-position "^4.0.0"
@@ -1197,15 +1197,15 @@ vitepress@^1.6.4:
vue "^3.5.13"
vue@^3.5.13:
- version "3.5.26"
- resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.26.tgz#03a0b17311e0e593d34b9358fa249b85e3a6d9fb"
- integrity sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==
+ version "3.5.45"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.45.tgz#a86c2ac53d76c72f919043d4c70ccaa5d8f06a80"
+ integrity sha512-3cvsib8m9c0vB24+MDKsgcW4+Da4xaBl9Z/5Tbm2pmSIsRPxcil4cdXjXlVsmJ2g64XJo5h+IRd7rsdGT9pZaw==
dependencies:
- "@vue/compiler-dom" "3.5.26"
- "@vue/compiler-sfc" "3.5.26"
- "@vue/runtime-dom" "3.5.26"
- "@vue/server-renderer" "3.5.26"
- "@vue/shared" "3.5.26"
+ "@vue/compiler-dom" "3.5.45"
+ "@vue/compiler-sfc" "3.5.45"
+ "@vue/runtime-dom" "3.5.45"
+ "@vue/server-renderer" "3.5.45"
+ "@vue/shared" "3.5.45"
zwitch@^2.0.4:
version "2.0.4"
diff --git a/frontend/biome.json b/frontend/biome.json
index e84969d2b..ff3bbd38a 100644
--- a/frontend/biome.json
+++ b/frontend/biome.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://biomejs.dev/schemas/2.5.10/schema.json",
+ "$schema": "https://biomejs.dev/schemas/2.6.6/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
diff --git a/frontend/check-locales.cjs b/frontend/check-locales.cjs
index 251b19c4b..1b02ce8ec 100644
--- a/frontend/check-locales.cjs
+++ b/frontend/check-locales.cjs
@@ -23,6 +23,7 @@ const allLocales = [
["cs", "cs-CZ"],
["vi", "vi-VN"],
["zh", "zh-CN"],
+ ["zh-tw", "zh-TW"],
["ko", "ko-KR"],
["bg", "bg-BG"],
["id", "id-ID"],
diff --git a/frontend/package.json b/frontend/package.json
index e2cc7ee64..22e0dd6df 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -16,11 +16,11 @@
"test": "vitest"
},
"dependencies": {
- "@tabler/core": "^1.5.1",
- "@tabler/icons-react": "^3.46.0",
- "@tanstack/react-query": "^5.102.8",
- "@tanstack/react-table": "^9.2.4",
- "@uiw/react-textarea-code-editor": "^3.1.1",
+ "@tabler/core": "^1.7.4",
+ "@tabler/icons-react": "^3.51.0",
+ "@tanstack/react-query": "^5.107.1",
+ "@tanstack/react-table": "^9.3.1",
+ "@uiw/react-textarea-code-editor": "^3.1.3",
"classnames": "^2.5.1",
"country-flag-icons": "^1.6.20",
"date-fns": "^4.4.0",
@@ -28,40 +28,40 @@
"formik": "^2.4.9",
"generate-password-browser": "^1.1.0",
"humps": "^2.0.1",
- "query-string": "^9.5.1",
- "react": "^19.3.0",
+ "query-string": "^10.0.0",
+ "react": "^19.3.1",
"react-bootstrap": "^2.10.10",
- "react-dom": "^19.3.0",
- "react-intl": "^10.2.0",
+ "react-dom": "^19.3.1",
+ "react-intl": "^12.1.7",
"react-markdown": "^10.1.0",
"react-qr-code": "^2.2.0",
- "react-router-dom": "^7.18.4",
+ "react-router-dom": "^7.18.5",
"react-select": "^5.10.2",
- "react-toastify": "^11.1.0",
+ "react-toastify": "^11.2.0",
"rooks": "^9.9.0"
},
"devDependencies": {
- "@biomejs/biome": "^2.5.10",
- "@formatjs/cli": "^6.16.30",
- "@tanstack/react-query-devtools": "^5.103.1",
+ "@biomejs/biome": "^2.6.6",
+ "@formatjs/cli": "^6.18.1",
+ "@tanstack/react-query-devtools": "^5.107.1",
"@testing-library/dom": "^10.4.2",
"@testing-library/jest-dom": "^7.0.1",
"@testing-library/react": "^16.3.3",
- "@types/country-flag-icons": "^1.2.2",
+ "@types/country-flag-icons": "^1.6.20",
"@types/humps": "^2.0.6",
- "@types/node": "^26.6.2",
+ "@types/node": "^26.6.6",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@types/react-table": "^7.7.20",
- "@vitejs/plugin-react": "^6.1.1",
- "happy-dom": "^20.14.5",
- "postcss": "^8.5.28",
+ "@vitejs/plugin-react": "^6.2.1",
+ "happy-dom": "^21.0.4",
+ "postcss": "^8.5.32",
"postcss-simple-vars": "^7.0.1",
- "sass": "^1.104.1",
+ "sass": "^1.108.1",
"tmp": "^0.2.7",
"typescript": "7.0.2",
- "vite": "^8.3.0",
- "vite-plugin-checker": "^0.14.5",
- "vitest": "^4.1.11"
+ "vite": "^8.3.6",
+ "vite-plugin-checker": "^1.0.0",
+ "vitest": "^5.1.0"
}
}
diff --git a/frontend/src/App.css b/frontend/src/App.css
index ad335caea..894f70cc9 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -115,4 +115,31 @@ label.row {
:host, :root {
margin-left: 0;
}
+}
+
+/* Keep Tabler 1.5's behaviour on Tabler 1.6/1.7. App.css loads before the
+ dynamically imported Tabler CSS, hence the doubled :root selectors. */
+
+/* 1.6 dropped the reserved scrollbar gutter, so the layout shifted 7px between short and long pages */
+html {
+ scrollbar-gutter: stable;
+}
+@supports not (scrollbar-gutter: stable) {
+ html {
+ overflow-y: scroll;
+ }
+}
+
+/* 1.6+ mixes the focus ring 50% with the background (75% dark, about 2.1:1 on white); restore full strength */
+:root:root {
+ --tblr-focus-ring-opacity: 1;
+ --tblr-focus-ring-opacity-dark: 1;
+}
+
+/* Coloured primary buttons (btn-primary + bg-) got a blue primary border in 1.7 */
+.btn-primary[class*=" bg-"] {
+ --tblr-btn-border-color: transparent;
+ --tblr-btn-hover-border-color: transparent;
+ --tblr-btn-active-border-color: transparent;
+ --tblr-btn-box-shadow: var(--tblr-shadow-input);
}
\ No newline at end of file
diff --git a/frontend/src/api/backend/base.ts b/frontend/src/api/backend/base.ts
index d56f120c7..e91376d44 100644
--- a/frontend/src/api/backend/base.ts
+++ b/frontend/src/api/backend/base.ts
@@ -46,7 +46,7 @@ function buildBody(data?: Record): string | undefined {
}
}
-async function processResponse(response: Response) {
+async function processResponse(response: Response, preserveResponseKeys = false) {
const payload = await response.json();
if (!response.ok) {
if (response.status === 401) {
@@ -59,7 +59,7 @@ async function processResponse(response: Response) {
typeof payload.error.messageI18n !== "undefined" ? payload.error.messageI18n : payload.error.message,
);
}
- return camelizeKeys(payload) as any;
+ return (preserveResponseKeys ? payload : camelizeKeys(payload)) as any;
}
interface GetArgs {
@@ -97,9 +97,10 @@ interface PostArgs {
params?: queryString.StringifiableRecord;
data?: any;
noAuth?: boolean;
+ preserveResponseKeys?: boolean;
}
-export async function post({ url, params, data, noAuth }: PostArgs, abortController?: AbortController) {
+export async function post({ url, params, data, noAuth, preserveResponseKeys }: PostArgs, abortController?: AbortController) {
const apiUrl = buildUrl({ url, params });
const method = "POST";
@@ -126,7 +127,7 @@ export async function post({ url, params, data, noAuth }: PostArgs, abortControl
const signal = abortController?.signal;
const response = await fetch(apiUrl, { method, headers, body, signal });
- return processResponse(response);
+ return processResponse(response, preserveResponseKeys);
}
interface PutArgs {
diff --git a/frontend/src/api/backend/models.ts b/frontend/src/api/backend/models.ts
index 96c1b8a48..347c62744 100644
--- a/frontend/src/api/backend/models.ts
+++ b/frontend/src/api/backend/models.ts
@@ -16,6 +16,11 @@ export interface UserPermissions {
streams: string;
accessLists: string;
certificates: string;
+ meta?: {
+ proxyHostIds?: number[];
+ proxy_host_ids?: number[];
+ [key: string]: any;
+ };
}
export interface User {
diff --git a/frontend/src/api/backend/testHttpCertificate.test.ts b/frontend/src/api/backend/testHttpCertificate.test.ts
new file mode 100644
index 000000000..ecbe1dbed
--- /dev/null
+++ b/frontend/src/api/backend/testHttpCertificate.test.ts
@@ -0,0 +1,92 @@
+import AuthStore from "src/modules/AuthStore";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import * as api from "./base";
+import { testHttpCertificate } from "./testHttpCertificate";
+
+const fetchMock = vi.fn();
+const reply = (payload: unknown, status = 200) => {
+ fetchMock.mockResolvedValueOnce(new Response(JSON.stringify(payload), { status }));
+};
+beforeEach(() => {
+ localStorage.clear();
+ vi.stubGlobal("fetch", fetchMock);
+});
+afterEach(() => {
+ vi.unstubAllGlobals();
+ vi.restoreAllMocks();
+ fetchMock.mockReset();
+});
+describe("certificate HTTP result keys", () => {
+ it.each([
+ "test-domain.example",
+ "test--domain.example",
+ "xn--bcher-kva.example",
+ "one.two-three.example",
+ "_acme-challenge.example",
+ "Test-Domain.example",
+ "plain.example",
+ ])("preserves the exact response key %s", async (domain) => {
+ const result = { [domain]: "wrong-data" };
+ reply(result);
+ expect(await testHttpCertificate([domain])).toEqual(result);
+ });
+ it("keeps colliding spellings and their distinct statuses", async () => {
+ const result = { "test-domain.example": "ok", "test--domain.example": "no-host" };
+ reply(result);
+ expect(await testHttpCertificate(Object.keys(result))).toEqual(result);
+ });
+ it("sends unchanged domains with the normal request headers", async () => {
+ const domains = ["test-domain.example", "test--domain.example"];
+ reply({});
+ await testHttpCertificate(domains);
+ expect(fetchMock).toHaveBeenCalledWith("/api/nginx/certificates/test-http", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ domains }),
+ signal: undefined,
+ });
+ });
+ it("preserves an empty result", async () => {
+ reply({});
+ expect(await testHttpCertificate([])).toEqual({});
+ });
+ it.each([
+ [400, { message: "invalid domain" }, "invalid domain"],
+ [500, { message: "fallback", messageI18n: "certificate-error" }, "certificate-error"],
+ ])("preserves error handling for status %s", async (status, error, message) => {
+ reply({ error }, status);
+ await expect(testHttpCertificate(["test-domain.example"])).rejects.toThrow(message);
+ });
+ it("still clears authentication on 401", async () => {
+ const clear = vi.spyOn(AuthStore, "clear");
+ const reload = vi.spyOn(window.location, "reload").mockImplementation(() => {});
+ reply({ error: { message: "unauthorized" } }, 401);
+ await expect(testHttpCertificate(["test-domain.example"])).rejects.toThrow("unauthorized");
+ expect(clear).toHaveBeenCalledOnce();
+ expect(reload).toHaveBeenCalledOnce();
+ });
+ it.each([
+ ["get", api.get],
+ ["post", api.post],
+ ["put", api.put],
+ ["del", api.del],
+ ] as const)("keeps default %s response normalization", async (_name, method) => {
+ reply({ domain_names: ["test-domain.example"], nested_value: { created_on: "today" } });
+ expect(await method({ url: "/control" })).toEqual({
+ domainNames: ["test-domain.example"],
+ nestedValue: { createdOn: "today" },
+ });
+ });
+ it("preserves POST abort forwarding and request normalization", async () => {
+ const controller = new AbortController();
+ reply({ domain_names: [] });
+ await api.post({ url: "/control", data: { domainNames: ["a-b.example"] } }, controller);
+ expect(fetchMock).toHaveBeenCalledWith(
+ "/api/control",
+ expect.objectContaining({
+ body: JSON.stringify({ domain_names: ["a-b.example"] }),
+ signal: controller.signal,
+ }),
+ );
+ });
+});
diff --git a/frontend/src/api/backend/testHttpCertificate.ts b/frontend/src/api/backend/testHttpCertificate.ts
index 23dfa4b7d..9687d55af 100644
--- a/frontend/src/api/backend/testHttpCertificate.ts
+++ b/frontend/src/api/backend/testHttpCertificate.ts
@@ -3,6 +3,7 @@ import * as api from "./base";
export async function testHttpCertificate(domains: string[]): Promise> {
return await api.post({
url: "/nginx/certificates/test-http",
+ preserveResponseKeys: true,
data: {
domains,
},
diff --git a/frontend/src/components/Form/BasicAuthFields.tsx b/frontend/src/components/Form/BasicAuthFields.tsx
index d8d33d159..0d5548490 100644
--- a/frontend/src/components/Form/BasicAuthFields.tsx
+++ b/frontend/src/components/Form/BasicAuthFields.tsx
@@ -38,8 +38,7 @@ export function BasicAuthFields({ initialValues, name = "items" }: Props) {
};
const setFormField = (newValues: AccessListItem[]) => {
- const filtered = newValues.filter((v: AccessListItem) => v?.username?.trim() !== "");
- setFieldValue(name, filtered);
+ setFieldValue(name, newValues);
};
return (
diff --git a/frontend/src/components/Form/FormModal.drafts.test.tsx b/frontend/src/components/Form/FormModal.drafts.test.tsx
new file mode 100644
index 000000000..7e1e11633
--- /dev/null
+++ b/frontend/src/components/Form/FormModal.drafts.test.tsx
@@ -0,0 +1,63 @@
+import { cleanup, fireEvent, render, screen } from "@testing-library/react";
+import { Form } from "formik";
+import type { ReactNode } from "react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { BasicAuthFields } from "./BasicAuthFields";
+import { FormModal } from "./FormModal";
+import { LocationsFields } from "./LocationsFields";
+
+vi.mock("./AccessField", () => ({ AccessField: () => null }));
+
+const renderDraft = (initialValues: Record, children: ReactNode) => {
+ const onHide = vi.fn();
+ render(
+
+ {({ onHide: hide }) => (
+
+ )}
+ ,
+ );
+ return onHide;
+};
+
+describe("FormModal local drafts", () => {
+ beforeEach(() => {
+ vi.stubGlobal(
+ "confirm",
+ vi.fn(() => false),
+ );
+ });
+
+ afterEach(() => {
+ cleanup();
+ vi.unstubAllGlobals();
+ });
+
+ it("protects a location draft without a path", () => {
+ const onHide = renderDraft({ locations: [] }, );
+ fireEvent.click(screen.getByRole("button", { name: "Add Location" }));
+ fireEvent.change(screen.getByPlaceholderText("eg: 10.0.0.1/path/"), { target: { value: "example.com" } });
+
+ fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
+
+ expect(window.confirm).toHaveBeenCalledOnce();
+ expect(onHide).not.toHaveBeenCalled();
+ });
+
+ it("protects a basic auth password draft without a username", () => {
+ const onHide = renderDraft({ items: [] }, );
+ fireEvent.change(document.querySelector('input[type="password"]') as Element, {
+ target: { value: "secret" },
+ });
+
+ fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
+
+ expect(window.confirm).toHaveBeenCalledOnce();
+ expect(onHide).not.toHaveBeenCalled();
+ });
+});
diff --git a/frontend/src/components/Form/FormModal.test.tsx b/frontend/src/components/Form/FormModal.test.tsx
new file mode 100644
index 000000000..de4080596
--- /dev/null
+++ b/frontend/src/components/Form/FormModal.test.tsx
@@ -0,0 +1,82 @@
+import { cleanup, fireEvent, render, screen } from "@testing-library/react";
+import { Field, Form } from "formik";
+import Modal from "react-bootstrap/Modal";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { FormModal } from "./FormModal";
+
+const renderModal = (onHide = vi.fn(), preventHide = false) => {
+ render(
+
+ {({ onHide: hide }) => (
+
+ )}
+ ,
+ );
+ return onHide;
+};
+
+describe("FormModal", () => {
+ beforeEach(() => {
+ vi.stubGlobal("confirm", vi.fn());
+ });
+
+ afterEach(() => {
+ cleanup();
+ vi.restoreAllMocks();
+ vi.unstubAllGlobals();
+ });
+
+ it("dismisses a pristine form without confirmation", () => {
+ const onHide = renderModal();
+ const confirm = vi.spyOn(window, "confirm");
+
+ fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
+
+ expect(confirm).not.toHaveBeenCalled();
+ expect(onHide).toHaveBeenCalledOnce();
+ });
+
+ it("keeps a dirty form open when dismissal is cancelled", () => {
+ const onHide = renderModal();
+ const confirm = vi.spyOn(window, "confirm").mockReturnValue(false);
+ fireEvent.change(screen.getByLabelText("Name"), { target: { value: "example.com" } });
+
+ fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
+ fireEvent.click(screen.getByRole("button", { name: "Close" }));
+ fireEvent.click(document.querySelector(".modal") as Element);
+ fireEvent.keyDown(document, { key: "Escape", keyCode: 27 });
+
+ expect(confirm).toHaveBeenCalledTimes(4);
+ expect(onHide).not.toHaveBeenCalled();
+ expect(screen.getByLabelText("Name")).toHaveValue("example.com");
+ });
+
+ it("dismisses a dirty form after confirmation", () => {
+ const onHide = renderModal();
+ vi.spyOn(window, "confirm").mockReturnValue(true);
+ fireEvent.change(screen.getByLabelText("Name"), { target: { value: "example.com" } });
+
+ fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
+
+ expect(onHide).toHaveBeenCalledOnce();
+ });
+
+ it("does not offer to discard changes while submission is in progress", () => {
+ const onHide = renderModal(vi.fn(), true);
+ const confirm = vi.spyOn(window, "confirm");
+ fireEvent.change(screen.getByLabelText("Name"), { target: { value: "example.com" } });
+
+ fireEvent.click(screen.getByRole("button", { name: "Close" }));
+
+ expect(confirm).not.toHaveBeenCalled();
+ expect(onHide).not.toHaveBeenCalled();
+ expect(screen.getByLabelText("Name")).toHaveValue("example.com");
+ });
+});
diff --git a/frontend/src/components/Form/FormModal.tsx b/frontend/src/components/Form/FormModal.tsx
new file mode 100644
index 000000000..d9698521d
--- /dev/null
+++ b/frontend/src/components/Form/FormModal.tsx
@@ -0,0 +1,55 @@
+import { Formik, type FormikConfig, type FormikProps, type FormikValues } from "formik";
+import { type ReactNode, useEffect } from "react";
+import Modal from "react-bootstrap/Modal";
+import { intl } from "src/locale";
+
+const confirmDiscardChanges = (dirty: boolean) =>
+ !dirty ||
+ window.confirm(
+ intl.formatMessage({
+ id: "form.unsaved-changes",
+ defaultMessage: "You have unsaved changes. Are you sure you want to discard them?",
+ }),
+ );
+
+const FormDirtyState = ({ dirty, onChange }: { dirty: boolean; onChange: (dirty: boolean) => void }) => {
+ useEffect(() => {
+ onChange(dirty);
+ return () => onChange(false);
+ }, [dirty, onChange]);
+
+ return null;
+};
+
+interface FormModalProps extends Omit, "children"> {
+ show: boolean;
+ onHide: () => void;
+ preventHide?: boolean;
+ children: (props: FormikProps & { onHide: () => void }) => ReactNode;
+}
+
+const FormModal = ({
+ show,
+ onHide,
+ preventHide = false,
+ children,
+ ...formikProps
+}: FormModalProps) => (
+
+ {(formikProps) => {
+ const handleHide = () => {
+ if (!preventHide && confirmDiscardChanges(formikProps.dirty)) {
+ onHide();
+ }
+ };
+
+ return (
+
+ {children({ ...formikProps, onHide: handleHide })}
+
+ );
+ }}
+
+);
+
+export { confirmDiscardChanges, FormDirtyState, FormModal };
diff --git a/frontend/src/components/Form/LocationsFields.tsx b/frontend/src/components/Form/LocationsFields.tsx
index a0f64c239..8cd826702 100644
--- a/frontend/src/components/Form/LocationsFields.tsx
+++ b/frontend/src/components/Form/LocationsFields.tsx
@@ -30,8 +30,9 @@ interface Row {
interface Props {
initialValues: ProxyLocation[];
name?: string;
+ allowAdvanced?: boolean;
}
-export function LocationsFields({ initialValues, name = "locations" }: Props) {
+export function LocationsFields({ initialValues, name = "locations", allowAdvanced = true }: Props) {
const [rows, setRows] = useState(() => (initialValues || []).map((value, id) => ({ id, value })));
const { setFieldValue } = useFormikContext();
const [expanded, setExpanded] = useState([]);
@@ -88,8 +89,10 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) {
};
const setFormField = (newRows: Row[]) => {
- const filtered = newRows.map((r: Row) => r.value).filter((v: ProxyLocation) => v?.path?.trim() !== "");
- setFieldValue(name, filtered);
+ setFieldValue(
+ name,
+ newRows.map((r: Row) => r.value),
+ );
};
const forwardSummary = (item: ProxyLocation) => {
@@ -215,7 +218,7 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) {
{isOpen && (
-
+
-
- toggleAdvVisible(row.id)}
- >
-
-
-
+ {allowAdvanced && (
+
+ toggleAdvVisible(row.id)}
+ >
+
+
+
+ )}
-
+
@@ -262,10 +267,10 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) {
-
+
@@ -283,10 +288,10 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) {
/>
-
+
@@ -314,7 +319,7 @@ export function LocationsFields({ initialValues, name = "locations" }: Props) {
onFormChange={(value) => handleAccessListChange(row.id, value)}
inheritHost
/>
- {advVisible.includes(row.id) && (
+ {allowAdvanced && advVisible.includes(row.id) && (
{
+ if (provider === "letsencrypt") {
+ return "lets-encrypt";
+ }
+ if (provider === "other") {
+ return "certificates.custom";
+ }
+ return provider;
+};
interface Props {
certificate?: Certificate;
}
export function CertificateFormatter({ certificate }: Props) {
- let translation = "http-only";
- if (certificate) {
- translation = certificate.provider;
- if (translation === "letsencrypt") {
- translation = "lets-encrypt";
- } else if (translation === "other") {
- translation = "certificates.custom";
- }
+ if (!certificate) {
+ return ;
}
- return ;
+
+ const translation = getProviderLabel(certificate.provider);
+ const expires = certificate.expiresOn ? parseDate(certificate.expiresOn) : null;
+ const expired = expires ? isPast(expires) : false;
+ const daysLeft = expires ? differenceInDays(expires, new Date()) : null;
+ const labelClass = cn({
+ "text-danger": expired,
+ "text-warning": !expired && daysLeft !== null && daysLeft <= 30,
+ });
+
+ const name = certificate.niceName || certificate.domainNames?.[0] || `#${certificate.id}`;
+
+ const popover = (
+
+
+
+ {name}
+ #{certificate.id}
+
+
+
+
+
+
+
+
+
+ {certificate.domainNames?.length ? (
+ <>
+
+
+
+
+
+ {certificate.domainNames.map((domain) => (
+
+ {domain}
+
+ ))}
+ >
+ ) : null}
+ {certificate.expiresOn ? (
+ <>
+
+
+
+
+
+
+
+
+ >
+ ) : null}
+
+
+ );
+
+ return (
+
+
+
+
+
+ {certificate.provider === "other" && certificate.niceName ? (
+
{certificate.niceName}
+ ) : null}
+
+
+ );
}
diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx
index 671ef8ed5..108f75f78 100644
--- a/frontend/src/locale/IntlProvider.tsx
+++ b/frontend/src/locale/IntlProvider.tsx
@@ -18,6 +18,7 @@ import langSk from "./lang/sk.json";
import langCs from "./lang/cs.json";
import langVi from "./lang/vi.json";
import langZh from "./lang/zh.json";
+import langZhTw from "./lang/zh-tw.json";
import langTr from "./lang/tr.json";
import langHu from "./lang/hu.json";
import langNo from "./lang/no.json";
@@ -27,7 +28,7 @@ import langFa from "./lang/fa.json";
import langList from "./lang/lang-list.json";
// first item of each array should be the language code,
-// not the country code
+// or the full locale when multiple variants share a language
// Remember when adding to this list, also update check-locales.js script
const localeOptions = [
["en", "en-US", langEn],
@@ -46,6 +47,7 @@ const localeOptions = [
["cs", "cs-CZ", langCs],
["vi", "vi-VN", langVi],
["zh", "zh-CN", langZh],
+ ["zh-TW", "zh-TW", langZhTw],
["ko", "ko-KR", langKo],
["bg", "bg-BG", langBg],
["id", "id-ID", langId],
@@ -58,18 +60,26 @@ const localeOptions = [
];
const loadMessages = (locale?: string): typeof langList & typeof langEn => {
- const thisLocale = (locale || "en").slice(0, 2);
+ const requestedLocale = (locale || "en").toLowerCase();
+ const shortLocale = requestedLocale.slice(0, 2);
+ const localeOption =
+ localeOptions.find((option) => String(option[1]).toLowerCase() === requestedLocale) ??
+ localeOptions.find((option) => String(option[0]).toLowerCase() === shortLocale);
// ensure this lang exists in localeOptions above, otherwise fallback to en
- if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) {
+ if (!localeOption || localeOption[0] === "en") {
return Object.assign({}, langList, langEn);
}
- return Object.assign({}, langList, langEn, localeOptions.find(([code]) => code === thisLocale)?.[2]);
+ return Object.assign({}, langList, langEn, localeOption[2]);
};
const getFlagCodeForLocale = (locale?: string) => {
- const thisLocale = (locale || "en").slice(0, 2);
+ const requestedLocale = (locale || "en").toLowerCase();
+ if (requestedLocale === "zh-tw") {
+ return "TW";
+ }
+ const thisLocale = requestedLocale.slice(0, 2);
// only add to this if your flag is different from the locale code
const specialCases: Record = {
@@ -160,4 +170,4 @@ const T = ({
//console.log("L:", localeOptions);
-export { localeOptions, getFlagCodeForLocale, getLocale, isRTLLocale, createIntl, changeLocale, intl, T };
+export { localeOptions, loadMessages, getFlagCodeForLocale, getLocale, isRTLLocale, createIntl, changeLocale, intl, T };
diff --git a/frontend/src/locale/Utils.test.tsx b/frontend/src/locale/Utils.test.tsx
index d1c4b2648..0111c242d 100644
--- a/frontend/src/locale/Utils.test.tsx
+++ b/frontend/src/locale/Utils.test.tsx
@@ -1,4 +1,6 @@
-import { formatDateTime, getFlagCodeForLocale } from "src/locale";
+import { changeLocale, formatDateTime, getFlagCodeForLocale, getLocale, loadMessages } from "src/locale";
+import { getHelpFile } from "src/locale/src/HelpDoc";
+import * as zhTwHelp from "src/locale/src/HelpDoc/zh-tw";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
describe("DateFormatter", () => {
@@ -83,6 +85,9 @@ describe("getFlagCodeForLocale", () => {
it("returns correct flag code for special-case locales", () => {
expect(getFlagCodeForLocale("ja-JP")).toBe("JP");
expect(getFlagCodeForLocale("zh-CN")).toBe("CN");
+ expect(getFlagCodeForLocale("zh")).toBe("CN");
+ expect(getFlagCodeForLocale("zh-TW")).toBe("TW");
+ expect(getFlagCodeForLocale("ZH-tw")).toBe("TW");
expect(getFlagCodeForLocale("vi-VN")).toBe("VN");
expect(getFlagCodeForLocale("ko-KR")).toBe("KR");
expect(getFlagCodeForLocale("cs-CZ")).toBe("CZ");
@@ -112,3 +117,51 @@ describe("formatDateTime Estonian locale", () => {
}
});
});
+
+describe("formatDateTime Vietnamese locale", () => {
+ it("uses 24-hour clock for vi and vi-VN", () => {
+ const value = "2024-06-15T15:30:00.000Z";
+ for (const locale of ["vi", "vi-VN"]) {
+ const text = formatDateTime(value, locale);
+ // Vietnamese 12-hour output marks the period with SA (sáng) / CH (chiều)
+ expect(text).not.toMatch(/\b(SA|CH)\b/);
+ }
+ });
+});
+
+describe("Chinese locale variants", () => {
+ it("loads Traditional Chinese by full locale before falling back to Simplified Chinese", () => {
+ expect(loadMessages("ZH-tw")).toEqual(loadMessages("zh-TW"));
+ expect(loadMessages("zh-TW").save).toBe("儲存");
+ expect(loadMessages("zh").save).toBe("保存");
+ expect(loadMessages("zh-CN")).toEqual(loadMessages("zh"));
+ expect(loadMessages("zh-HK")).toEqual(loadMessages("zh"));
+ });
+
+ it("preserves the full locale and resets RTL layout when switching to Traditional Chinese", () => {
+ const originalLocale = getLocale();
+ const savedLocale = window.localStorage.getItem("locale");
+ try {
+ changeLocale("fa-IR");
+ expect(document.documentElement.dir).toBe("rtl");
+ changeLocale("zh-TW");
+ expect(getLocale()).toBe("zh-TW");
+ expect(window.localStorage.getItem("locale")).toBe("zh-TW");
+ expect(document.documentElement.lang).toBe("zh-TW");
+ expect(document.documentElement.dir).toBe("ltr");
+ } finally {
+ changeLocale(originalLocale);
+ if (savedLocale === null) {
+ window.localStorage.removeItem("locale");
+ } else {
+ window.localStorage.setItem("locale", savedLocale);
+ }
+ }
+ });
+
+ it("selects HelpDocs by full locale before falling back to the language code", () => {
+ expect(getHelpFile("ZH-tw", "AccessLists")).toBe(getHelpFile("zh-TW", "AccessLists"));
+ expect(getHelpFile("zh-TW", "AccessLists")).toBe(zhTwHelp.AccessLists.default);
+ expect(getHelpFile("zh-CN", "AccessLists")).toBe(getHelpFile("zh", "AccessLists"));
+ });
+});
diff --git a/frontend/src/locale/Utils.ts b/frontend/src/locale/Utils.ts
index 9def46f80..d274dca2c 100644
--- a/frontend/src/locale/Utils.ts
+++ b/frontend/src/locale/Utils.ts
@@ -26,6 +26,7 @@ const DATE_LOCALE_BY_LANG: Record = {
const HOUR_CYCLE_BY_LANG: Record = {
et: "h23",
+ vi: "h23",
};
const formatDateTime = (value: string | number, locale = "en-US"): string => {
diff --git a/frontend/src/locale/src/HelpDoc/index.ts b/frontend/src/locale/src/HelpDoc/index.ts
index ceddf4c78..08f7618d8 100644
--- a/frontend/src/locale/src/HelpDoc/index.ts
+++ b/frontend/src/locale/src/HelpDoc/index.ts
@@ -17,19 +17,53 @@ import * as sk from "./sk/index";
import * as cs from "./cs/index";
import * as vi from "./vi/index";
import * as zh from "./zh/index";
+import * as zhTw from "./zh-tw/index";
import * as tr from "./tr/index";
import * as hu from "./hu/index";
import * as uk from "./uk/index";
import * as az from "./az/index";
import * as fa from "./fa/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, uk, az, fa };
+const items: any = {
+ en,
+ de,
+ pt,
+ es,
+ et,
+ ja,
+ sk,
+ cs,
+ zh,
+ "zh-TW": zhTw,
+ pl,
+ ru,
+ it,
+ vi,
+ nl,
+ bg,
+ ko,
+ ga,
+ id,
+ fr,
+ tr,
+ hu,
+ uk,
+ az,
+ fa,
+};
const fallbackLang = "en";
export const getHelpFile = (lang: string, section: string): string => {
- if (typeof items[lang] !== "undefined" && typeof items[lang][section] !== "undefined") {
- return items[lang][section].default;
+ const requestedLang = lang.toLowerCase();
+ const exactLang = Object.keys(items).find((key) => key.toLowerCase() === requestedLang);
+ const shortLang = Object.keys(items).find((key) => key.toLowerCase() === requestedLang.slice(0, 2));
+
+ if (exactLang && typeof items[exactLang][section] !== "undefined") {
+ return items[exactLang][section].default;
+ }
+ if (shortLang && typeof items[shortLang][section] !== "undefined") {
+ return items[shortLang][section].default;
}
// Fallback to English
if (typeof items[fallbackLang] !== "undefined" && typeof items[fallbackLang][section] !== "undefined") {
diff --git a/frontend/src/locale/src/HelpDoc/ko/AccessLists.md b/frontend/src/locale/src/HelpDoc/ko/AccessLists.md
index 8e9670122..ead647eb2 100644
--- a/frontend/src/locale/src/HelpDoc/ko/AccessLists.md
+++ b/frontend/src/locale/src/HelpDoc/ko/AccessLists.md
@@ -1,9 +1,9 @@
## 접근 정책이란?
접근 정책은 특정 클라이언트 IP 주소를 허용하거나 거부할 수 있으며,
-프록시 호스트에 기본 HTTP 인증(Basic Auth) 을 적용할 수 있는 기능입니다.
+프록시 호스트에 기본 HTTP 인증(Basic Auth)을 적용할 수 있는 기능입니다.
-하나의 접근 목록에 여러 클라이언트 규칙과 사용자 이름, 비밀번호를 추가한 뒤
+하나의 접근 정책에 여러 클라이언트 규칙과 사용자 이름, 비밀번호를 추가한 뒤
이를 하나 이상의 프록시 호스트에 적용할 수 있습니다.
이 기능은 인증 기능이 없는 웹 서비스에 인증을 추가하거나,
diff --git a/frontend/src/locale/src/HelpDoc/ko/Certificates.md b/frontend/src/locale/src/HelpDoc/ko/Certificates.md
index 660f39620..f0b3a0637 100644
--- a/frontend/src/locale/src/HelpDoc/ko/Certificates.md
+++ b/frontend/src/locale/src/HelpDoc/ko/Certificates.md
@@ -24,5 +24,5 @@ DNS 검증 방식의 인증서는 DNS 공급자 플러그인을 사용해야 합
### 사용자 지정 인증서
-이 옵션을 사용하면 직접 보유한 인증 기관(CA)에서 발급한 SSL 인증서를 직접 업로드하여 사용할 수 있습니다.
+이 옵션을 사용하면 사용 중인 인증 기관(CA)에서 발급받은 SSL 인증서를 직접 업로드하여 사용할 수 있습니다.
diff --git a/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md b/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md
index 032a23c94..3b13b2ee7 100644
--- a/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md
+++ b/frontend/src/locale/src/HelpDoc/ko/DeadHosts.md
@@ -4,7 +4,7 @@
이 기능은 도메인이 검색 엔진에 이미 색인되어 있을 때,
더 깔끔한 오류 페이지를 제공하거나 해당 페이지가 더 이상 존재하지 않음을
-검색 엔진에게 명확하게 알려야 할 때 유용합니다.
+검색 엔진에 명확하게 알려야 할 때 유용합니다.
-또한 404 호스트를 사용하면 접근 로그를 확인하고, 어떤 경로(Referrer)를 통해 들어왔는지 추적할 수 있다는 장점도 있습니다.
+또한 이 호스트로 들어오는 요청을 로그로 추적하고 리퍼러(유입 경로)를 확인할 수 있다는 장점도 있습니다.
diff --git a/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md
index 7494885de..d5c575e5e 100644
--- a/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md
+++ b/frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md
@@ -3,5 +3,5 @@
리다이렉션 호스트는 외부에서 들어오는 도메인 요청을 다른 도메인으로 자동 이동(리다이렉트)시키는 역할을 합니다.
이 유형의 호스트는 주로 웹사이트의 도메인이 변경되었지만,
-검색 엔진이나 다른 사이트에 이전 도메인 링크가 남아 있을 때 사용하면 가장 효과적입니다.
+검색 엔진이나 다른 사이트의 링크가 여전히 이전 도메인을 가리키고 있을 때 사용합니다.
diff --git a/frontend/src/locale/src/HelpDoc/ko/Streams.md b/frontend/src/locale/src/HelpDoc/ko/Streams.md
index 45107b2c3..0d50a979e 100644
--- a/frontend/src/locale/src/HelpDoc/ko/Streams.md
+++ b/frontend/src/locale/src/HelpDoc/ko/Streams.md
@@ -1,6 +1,6 @@
-## 호스트 스트림이란?
+## 스트림이란?
-호스트 스트림은 비교적 최근에 Nginx에 추가된 기능으로,
+스트림은 비교적 최근에 Nginx에 추가된 기능으로,
TCP/UDP 트래픽을 네트워크 내의 다른 컴퓨터로 직접 전달하는 데 사용됩니다.
게임 서버나 FTP, SSH 서버 등을 운영할 때 유용하게 사용할 수 있습니다.
diff --git a/frontend/src/locale/src/HelpDoc/vi/AccessLists.md b/frontend/src/locale/src/HelpDoc/vi/AccessLists.md
index 50e5375a4..0b0b8e77e 100644
--- a/frontend/src/locale/src/HelpDoc/vi/AccessLists.md
+++ b/frontend/src/locale/src/HelpDoc/vi/AccessLists.md
@@ -1,11 +1,10 @@
-## Khái niệm Access List là gì?
+## Danh sách truy cập là gì?
-Access List (Danh sách truy cập) cung cấp cơ chế chặn (blacklist) hoặc cho phép (whitelist) các địa chỉ IP của client, đồng thời hỗ trợ xác thực Basic HTTP Authentication cho các Proxy Host.
+Danh sách truy cập (Access List) cung cấp danh sách chặn hoặc danh sách cho phép theo địa chỉ IP của client, đồng thời hỗ trợ xác thực HTTP cơ bản (Basic HTTP Authentication) cho các máy chủ proxy.
-Bạn có thể cấu hình nhiều quy tắc client, nhiều tên người dùng và mật khẩu trong một Access List duy nhất, sau đó áp dụng Access List đó cho một hoặc nhiều Proxy Host.
+Bạn có thể cấu hình nhiều quy tắc client, nhiều tên người dùng và mật khẩu trong một danh sách truy cập, sau đó áp dụng nó cho một hoặc nhiều _máy chủ proxy_.
-Tính năng này đặc biệt hữu ích đối với:
+Tính năng này đặc biệt hữu ích khi:
-các dịch vụ web được forward mà không có cơ chế xác thực tích hợp, hoặc
-
-khi bạn muốn bảo vệ tài nguyên khỏi những client không xác định.
+- dịch vụ web được chuyển tiếp không có sẵn cơ chế xác thực, hoặc
+- bạn muốn chặn các client không xác định.
diff --git a/frontend/src/locale/src/HelpDoc/vi/Certificates.md b/frontend/src/locale/src/HelpDoc/vi/Certificates.md
index d25ef0078..e633a3a75 100644
--- a/frontend/src/locale/src/HelpDoc/vi/Certificates.md
+++ b/frontend/src/locale/src/HelpDoc/vi/Certificates.md
@@ -1,25 +1,24 @@
-## Hỗ trợ Chứng chỉ
+## Trợ giúp về chứng chỉ
-### Chứng chỉ HTTP (HTTP Certificate)
+### Chứng chỉ HTTP
-Chứng chỉ được xác thực qua HTTP nghĩa là máy chủ của Let's Encrypt sẽ cố gắng truy cập vào tên miền của bạn thông qua HTTP (không phải HTTPS!). Nếu kiểm tra thành công, chứng chỉ sẽ được cấp.
+Chứng chỉ xác thực qua HTTP nghĩa là máy chủ của Let's Encrypt sẽ truy cập vào tên miền của bạn qua HTTP (không phải HTTPS!). Nếu kiểm tra thành công, chứng chỉ sẽ được cấp.
-Với phương thức này, bạn phải tạo trước một Proxy Host cho tên miền, có thể truy cập qua HTTP và trỏ về đúng cài đặt Nginx này.
-Sau khi chứng chỉ được cấp, bạn có thể chỉnh sửa Proxy Host để sử dụng chứng chỉ đó cho kết nối HTTPS.
+Với phương thức này, bạn phải tạo trước một _máy chủ proxy_ cho tên miền, truy cập được qua HTTP và trỏ về máy chủ Nginx này.
+Sau khi chứng chỉ được cấp, bạn có thể chỉnh sửa _máy chủ proxy_ để dùng chứng chỉ đó cho kết nối HTTPS.
+Tuy nhiên, _máy chủ proxy_ vẫn phải truy cập được qua HTTP thì chứng chỉ mới gia hạn được.
-Tuy nhiên, Proxy Host vẫn phải hỗ trợ truy cập HTTP để việc gia hạn chứng chỉ diễn ra bình thường.
+Phương thức này _không hỗ trợ_ tên miền wildcard (ví dụ `*.example.com`).
-Phương thức này _không hỗ trợ_ wildcard domain.
+### Chứng chỉ DNS
-### Chứng chỉ DNS (DNS Certificate)
+Chứng chỉ xác thực qua DNS yêu cầu bạn dùng plugin của nhà cung cấp DNS.
+Plugin sẽ tạo các bản ghi tạm thời cho tên miền của bạn, sau đó Let's Encrypt truy vấn các bản ghi này để xác nhận bạn là chủ sở hữu. Nếu hợp lệ, chứng chỉ sẽ được cấp.
-Chứng chỉ được xác thực qua DNS yêu cầu bạn sử dụng plugin của DNS Provider.
-Plugin này sẽ tạo các bản ghi tạm thời trong DNS của bạn để Let's Encrypt kiểm tra quyền sở hữu tên miền. Nếu hợp lệ, chứng chỉ sẽ được cấp.
+Bạn không cần tạo _máy chủ proxy_ trước khi yêu cầu loại chứng chỉ này, cũng không cần cấu hình _máy chủ proxy_ cho truy cập HTTP.
-Khi dùng phương thức này: Bạn không cần tạo sẵn Proxy Host trước và bạn không cần mở HTTP cho Proxy Host.
+Phương thức này _có hỗ trợ_ tên miền wildcard.
-Phương thức DNS _có hỗ trợ_ wildcard domain.
+### Chứng chỉ tùy chỉnh
-### Chứng chỉ tùy chỉnh (Custom Certificate)
-
-Tùy chọn này cho phép bạn tải lên chứng chỉ SSL của riêng mình, được cung cấp bởi Certificate Authority (CA) mà bạn tự chọn.
+Dùng tùy chọn này để tải lên chứng chỉ SSL của riêng bạn, do tổ chức cấp chứng chỉ (CA) của bạn cung cấp.
diff --git a/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md b/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md
index 4c0aae86c..d380f3564 100644
--- a/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md
+++ b/frontend/src/locale/src/HelpDoc/vi/DeadHosts.md
@@ -1,8 +1,7 @@
-## 404 Host là gì?
+## Máy chủ 404 là gì?
-404 Host đơn giản là một host được thiết lập để hiển thị trang 404.
+Máy chủ 404 đơn giản là một cấu hình máy chủ chỉ hiển thị trang 404.
-Điều này có thể hữu ích khi tên miền của bạn vẫn xuất hiện trên các công cụ tìm kiếm và bạn muốn hiển thị một trang lỗi đẹp hơn, hoặc muốn thông báo rõ ràng cho các trình thu thập dữ liệu tìm kiếm rằng các trang thuộc tên miền đó không còn tồn tại.
+Nó hữu ích khi tên miền của bạn vẫn xuất hiện trên các công cụ tìm kiếm và bạn muốn hiển thị một trang lỗi đẹp hơn, hoặc muốn báo rõ cho các trình thu thập dữ liệu rằng các trang thuộc tên miền đó không còn tồn tại.
-Một lợi ích khác của việc có 404 Host là bạn có thể theo dõi nhật ký truy cập vào nó và
-xem các nguồn giới thiệu (referrers).
\ No newline at end of file
+Một lợi ích khác là bạn có thể theo dõi nhật ký các lượt truy cập vào đó và xem trang giới thiệu (referrer).
diff --git a/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md
index 969f8a773..e04552c65 100644
--- a/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md
+++ b/frontend/src/locale/src/HelpDoc/vi/ProxyHosts.md
@@ -1,7 +1,7 @@
-## Proxy Host là gì?
+## Máy chủ proxy là gì?
-Proxy Host là điểm truy cập đầu vào cho một dịch vụ web mà bạn muốn chuyển tiếp.
+Máy chủ proxy là điểm tiếp nhận yêu cầu cho một dịch vụ web mà bạn muốn chuyển tiếp.
-Nó cung cấp khả năng kết thúc SSL (SSL termination) tùy chọn cho các dịch vụ vốn không hỗ trợ SSL tích hợp.
+Nó có thể đảm nhận phần mã hóa SSL (SSL termination) cho những dịch vụ không có sẵn hỗ trợ SSL.
-Proxy Host là loại cấu hình phổ biến nhất trong Nginx Proxy Manager.
\ No newline at end of file
+Máy chủ proxy là tính năng được dùng nhiều nhất trong Nginx Proxy Manager.
diff --git a/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md
index 433992364..ecda55c10 100644
--- a/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md
+++ b/frontend/src/locale/src/HelpDoc/vi/RedirectionHosts.md
@@ -1,5 +1,5 @@
-## Redirection Host là gì?
+## Máy chủ chuyển hướng là gì?
-Redirection Host sẽ chuyển hướng các yêu cầu từ tên miền truy cập vào và đưa người xem sang một tên miền khác
+Máy chủ chuyển hướng sẽ chuyển các yêu cầu tới tên miền này sang một tên miền khác.
-Lý do phổ biến nhất để sử dụng loại host này là khi trang web của bạn đổi sang tên miền mới nhưng vẫn còn các liên kết từ công cụ tìm kiếm hoặc nguồn giới thiệu trỏ về tên miền cũ.
\ No newline at end of file
+Lý do phổ biến nhất để dùng loại máy chủ này là khi trang web của bạn đổi sang tên miền mới nhưng các công cụ tìm kiếm hoặc trang giới thiệu vẫn còn liên kết trỏ về tên miền cũ.
diff --git a/frontend/src/locale/src/HelpDoc/vi/Streams.md b/frontend/src/locale/src/HelpDoc/vi/Streams.md
index 43cf97b2f..c6b451bc3 100644
--- a/frontend/src/locale/src/HelpDoc/vi/Streams.md
+++ b/frontend/src/locale/src/HelpDoc/vi/Streams.md
@@ -1,6 +1,5 @@
-## Stream là gì?
+## Luồng TCP/UDP là gì?
-Stream là một tính năng tương đối mới của Nginx, dùng để chuyển tiếp lưu lượng
-TCP/UDP trực tiếp tới một máy khác trong mạng.
+Luồng TCP/UDP (Stream) là một tính năng tương đối mới của Nginx, dùng để chuyển tiếp lưu lượng TCP/UDP trực tiếp tới một máy khác trong mạng.
-Nếu bạn đang vận hành các máy chủ game, FTP hoặc SSH thì tính năng này sẽ rất hữu ích.
\ No newline at end of file
+Tính năng này rất hữu ích nếu bạn đang chạy máy chủ game, FTP hoặc SSH.
diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/AccessLists.md b/frontend/src/locale/src/HelpDoc/zh-tw/AccessLists.md
new file mode 100644
index 000000000..d8ba7ba2a
--- /dev/null
+++ b/frontend/src/locale/src/HelpDoc/zh-tw/AccessLists.md
@@ -0,0 +1,7 @@
+## 什麼是存取清單?
+
+存取清單可針對特定用戶端 IP 位址設定黑名單或白名單,並可為代理主機設定 HTTP 基本驗證。
+
+您可以在單一存取清單中設定多項用戶端規則、使用者名稱與密碼,再將其套用至一個或多個 _代理主機_。
+
+對於透過代理轉送、但本身未內建驗證機制的網路服務,或需要防止未知用戶端存取時,這項功能格外實用。
diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/Certificates.md b/frontend/src/locale/src/HelpDoc/zh-tw/Certificates.md
new file mode 100644
index 000000000..4199c2867
--- /dev/null
+++ b/frontend/src/locale/src/HelpDoc/zh-tw/Certificates.md
@@ -0,0 +1,21 @@
+## 憑證說明
+
+### HTTP 憑證
+
+使用 HTTP 驗證方式申請憑證時,Let's Encrypt 伺服器會嘗試透過 HTTP(不是 HTTPS!)連線至您的網域;驗證成功後,便會核發憑證。
+
+使用此方式前,您必須為網域建立一個可透過 HTTP 存取、且指向此 Nginx 安裝環境的 _代理主機_。憑證核發後,您可以修改該 _代理主機_,讓 HTTPS 連線也使用此憑證。不過,為了讓憑證能夠續期,_代理主機_ 仍須保留 HTTP 存取設定。
+
+此流程不支援萬用字元網域。
+
+### DNS 憑證
+
+使用 DNS 驗證方式申請憑證時,您需要使用 DNS 服務供應商外掛程式。系統會透過該 DNS 服務供應商在您的網域中建立暫時性的 DNS 紀錄,接著 Let's Encrypt 會查詢這些紀錄,以確認您是網域所有者;驗證成功後,便會核發憑證。
+
+申請此類憑證前,您不必先建立 _代理主機_,也不必將 _代理主機_ 設定為可透過 HTTP 存取。
+
+此流程支援萬用字元網域。
+
+### 自訂憑證
+
+使用此選項,上傳由您自己的憑證授權單位提供的 SSL 憑證。
diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/DeadHosts.md b/frontend/src/locale/src/HelpDoc/zh-tw/DeadHosts.md
new file mode 100644
index 000000000..1aa6b6457
--- /dev/null
+++ b/frontend/src/locale/src/HelpDoc/zh-tw/DeadHosts.md
@@ -0,0 +1,7 @@
+## 什麼是 404 主機?
+
+404 主機就是用來顯示 404 頁面的主機設定。
+
+當您的網域已被搜尋引擎收錄,而您想提供更友善的錯誤頁面,或明確告知搜尋引擎的索引程式該網域的頁面已不存在時,這項功能會很實用。
+
+使用 404 主機的另一項好處,是可以從記錄檔追蹤對該主機的存取,並查看來源網址。
diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/zh-tw/ProxyHosts.md
new file mode 100644
index 000000000..a6d27c361
--- /dev/null
+++ b/frontend/src/locale/src/HelpDoc/zh-tw/ProxyHosts.md
@@ -0,0 +1,7 @@
+## 什麼是代理主機?
+
+代理主機是接收連線的端點,會將連線轉送至您指定的網路服務。
+
+它可選擇代為處理 SSL 連線,適用於未內建 SSL 支援的服務。
+
+代理主機是 Nginx Proxy Manager 最常見的用途。
diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/zh-tw/RedirectionHosts.md
new file mode 100644
index 000000000..3876a4cec
--- /dev/null
+++ b/frontend/src/locale/src/HelpDoc/zh-tw/RedirectionHosts.md
@@ -0,0 +1,5 @@
+## 什麼是重新導向主機?
+
+重新導向主機會將送往指定網域的請求重新導向,讓訪客前往另一個網域。
+
+這類主機最常用於網站更換網域後,搜尋引擎或其他來源的連結仍指向舊網域的情況。
diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/Streams.md b/frontend/src/locale/src/HelpDoc/zh-tw/Streams.md
new file mode 100644
index 000000000..b068f9b26
--- /dev/null
+++ b/frontend/src/locale/src/HelpDoc/zh-tw/Streams.md
@@ -0,0 +1,5 @@
+## 什麼是連接埠轉送?
+
+連接埠轉送是 Nginx 相對較新的功能,可將 TCP/UDP 流量直接轉送至網路中的另一台電腦。
+
+若您正在架設遊戲伺服器、FTP 伺服器或 SSH 伺服器,這項功能會很方便。
diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/index.ts b/frontend/src/locale/src/HelpDoc/zh-tw/index.ts
new file mode 100644
index 000000000..a9bb46ba7
--- /dev/null
+++ b/frontend/src/locale/src/HelpDoc/zh-tw/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/de.json b/frontend/src/locale/src/de.json
index 6cdb64cf3..e5ce5355a 100644
--- a/frontend/src/locale/src/de.json
+++ b/frontend/src/locale/src/de.json
@@ -71,6 +71,12 @@
"access-list.help.rules-order": {
"defaultMessage": "Beachten Sie, dass die Anweisungen „Erlauben“ und „Verbieten“ in der Reihenfolge ihrer Definition angewendet werden."
},
+ "access-list.inherit": {
+ "defaultMessage": "Vom Proxy Host übernehmen"
+ },
+ "access-list.inherit.subtitle": {
+ "defaultMessage": "Verwendet die Zugriffsliste des Proxy Hosts"
+ },
"access-list.pass-auth": {
"defaultMessage": "Authentifizierung an Upstream weiterleiten"
},
@@ -101,6 +107,9 @@
"action.allow": {
"defaultMessage": "Erlauben"
},
+ "action.clear": {
+ "defaultMessage": "Leeren"
+ },
"action.close": {
"defaultMessage": "Schließen"
},
@@ -122,6 +131,9 @@
"action.enable": {
"defaultMessage": "Aktivieren"
},
+ "action.logs": {
+ "defaultMessage": "Logs"
+ },
"action.permissions": {
"defaultMessage": "Berechtigungen"
},
@@ -266,6 +278,9 @@
"column.email": {
"defaultMessage": "E-Mail"
},
+ "column.error": {
+ "defaultMessage": "Fehler"
+ },
"column.event": {
"defaultMessage": "Ereignis"
},
@@ -461,6 +476,12 @@
"loading": {
"defaultMessage": "Laden…"
},
+ "location.advanced-config": {
+ "defaultMessage": "Hat eine eigene Nginx-Konfiguration"
+ },
+ "location.filter": {
+ "defaultMessage": "Nach Pfad oder Ziel filtern"
+ },
"login.2fa-code": {
"defaultMessage": "Verifizierungscode"
},
@@ -479,6 +500,51 @@
"login.title": {
"defaultMessage": "Anmelden"
},
+ "logs": {
+ "defaultMessage": "Logs"
+ },
+ "logs.channel.access": {
+ "defaultMessage": "Zugriff"
+ },
+ "logs.channel.error": {
+ "defaultMessage": "Fehler"
+ },
+ "logs.download": {
+ "defaultMessage": "Herunterladen"
+ },
+ "logs.empty": {
+ "defaultMessage": "Noch keine Log-Einträge."
+ },
+ "logs.level": {
+ "defaultMessage": "Level"
+ },
+ "logs.level.all": {
+ "defaultMessage": "Alle Level"
+ },
+ "logs.lines": {
+ "defaultMessage": "Zeilen"
+ },
+ "logs.live": {
+ "defaultMessage": "Live"
+ },
+ "logs.paused": {
+ "defaultMessage": "Pausiert"
+ },
+ "logs.refresh": {
+ "defaultMessage": "Aktualisieren"
+ },
+ "logs.search-placeholder": {
+ "defaultMessage": "Log durchsuchen…"
+ },
+ "logs.source": {
+ "defaultMessage": "Quelle"
+ },
+ "logs.source.system": {
+ "defaultMessage": "System"
+ },
+ "logs.truncated-warning": {
+ "defaultMessage": "Es wird nur der neueste Teil dieser Datei angezeigt."
+ },
"nginx-config.label": {
"defaultMessage": "Benutzerdefinierte Nginx Konfiguration"
},
diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json
index 11d09ea71..8f7150a3b 100644
--- a/frontend/src/locale/src/en.json
+++ b/frontend/src/locale/src/en.json
@@ -437,6 +437,9 @@
"footer.github-fork": {
"defaultMessage": "Fork me on Github"
},
+ "form.unsaved-changes": {
+ "defaultMessage": "You have unsaved changes. Are you sure you want to discard them?"
+ },
"host.flags.block-exploits": {
"defaultMessage": "Block Common Exploits"
},
@@ -641,12 +644,21 @@
"password.show": {
"defaultMessage": "Show Password"
},
+ "permissions.allowed-proxy-hosts": {
+ "defaultMessage": "Allowed Proxy Hosts"
+ },
+ "permissions.allowed-proxy-hosts-help": {
+ "defaultMessage": "Grants additional access to selected proxy hosts created by other users (in addition to the user's own created hosts)."
+ },
"permissions.hidden": {
"defaultMessage": "Hidden"
},
"permissions.manage": {
"defaultMessage": "Manage"
},
+ "permissions.no-proxy-hosts": {
+ "defaultMessage": "No proxy hosts found"
+ },
"permissions.view": {
"defaultMessage": "View Only"
},
diff --git a/frontend/src/locale/src/et.json b/frontend/src/locale/src/et.json
index 316ede752..380fceced 100644
--- a/frontend/src/locale/src/et.json
+++ b/frontend/src/locale/src/et.json
@@ -437,6 +437,9 @@
"footer.github-fork": {
"defaultMessage": "Tee GitHubis fork"
},
+ "form.unsaved-changes": {
+ "defaultMessage": "Sul on salvestamata muudatusi. Kas oled kindel, et soovid need tühistada?"
+ },
"host.flags.block-exploits": {
"defaultMessage": "Blokeeri levinud ründed"
},
@@ -641,12 +644,21 @@
"password.show": {
"defaultMessage": "Näita parooli"
},
+ "permissions.allowed-proxy-hosts": {
+ "defaultMessage": "Lubatud puhverserverid"
+ },
+ "permissions.allowed-proxy-hosts-help": {
+ "defaultMessage": "Annab täiendava ligipääsu teiste kasutajate loodud valitud puhverserveritele (lisaks kasutaja enda loodud kirjetele)."
+ },
"permissions.hidden": {
"defaultMessage": "Peidetud"
},
"permissions.manage": {
"defaultMessage": "Halda"
},
+ "permissions.no-proxy-hosts": {
+ "defaultMessage": "Ühtegi puhverserverit ei leitud"
+ },
"permissions.view": {
"defaultMessage": "Ainult vaatamine"
},
diff --git a/frontend/src/locale/src/fr.json b/frontend/src/locale/src/fr.json
index 0911eedc3..88add74bd 100644
--- a/frontend/src/locale/src/fr.json
+++ b/frontend/src/locale/src/fr.json
@@ -1,6 +1,6 @@
{
"2fa.backup-codes-remaining": {
- "defaultMessage": "Codes de secours restants : {count}"
+ "defaultMessage": "Codes de secours restants : {count}"
},
"2fa.backup-warning": {
"defaultMessage": "Sauvegardez ces codes de secours dans un endroit sûr. Chaque code ne peut être utilisé qu'une seule fois."
@@ -71,6 +71,12 @@
"access-list.help.rules-order": {
"defaultMessage": "Notez que les directives autoriser et refuser seront appliquées dans l'ordre où elles sont définies."
},
+ "access-list.inherit": {
+ "defaultMessage": "Hériter de l'hôte proxy"
+ },
+ "access-list.inherit.subtitle": {
+ "defaultMessage": "Utilise la liste d'accès de l'hôte proxy"
+ },
"access-list.pass-auth": {
"defaultMessage": "Transmettre l'authentification au serveur en amont"
},
@@ -84,10 +90,10 @@
"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"
+ "defaultMessage": "Satisfaire au moins une condition"
},
"access-list.subtitle": {
- "defaultMessage": "{utilisateurs} {utilisateurs, plural, one {Utilisateur} other {Utilisateurs}}, {règles} {règles, plural, one {Règle} other {Règles}} - Crée : {date}"
+ "defaultMessage": "{users} {users, plural, one {Utilisateur} other {Utilisateurs}}, {rules} {rules, plural, one {Règle} other {Règles}} - Créée le {date}"
},
"access-lists": {
"defaultMessage": "Listes d'accès"
@@ -96,11 +102,14 @@
"defaultMessage": "Ajouter"
},
"action.add-location": {
- "defaultMessage": "Ajouter localisation"
+ "defaultMessage": "Ajouter un emplacement"
},
"action.allow": {
"defaultMessage": "Autoriser"
},
+ "action.clear": {
+ "defaultMessage": "Effacer"
+ },
"action.close": {
"defaultMessage": "Fermer"
},
@@ -122,6 +131,9 @@
"action.enable": {
"defaultMessage": "Activer"
},
+ "action.logs": {
+ "defaultMessage": "Journaux"
+ },
"action.permissions": {
"defaultMessage": "Permissions"
},
@@ -129,7 +141,7 @@
"defaultMessage": "Renouveler"
},
"action.view-details": {
- "defaultMessage": "Voir les Détails"
+ "defaultMessage": "Voir les détails"
},
"auditlogs": {
"defaultMessage": "Journaux d'audit"
@@ -147,7 +159,7 @@
"defaultMessage": "Certificat"
},
"certificate.custom-certificate-key": {
- "defaultMessage": "Clé du Certificat"
+ "defaultMessage": "Clé du certificat"
},
"certificate.custom-intermediate": {
"defaultMessage": "Certificat intermédiaire"
@@ -168,7 +180,7 @@
"defaultMessage": "Non utilisé"
},
"certificate.renew": {
- "defaultMessage": "Renouveler Certificat"
+ "defaultMessage": "Renouveler le certificat"
},
"certificates": {
"defaultMessage": "Certificats"
@@ -189,7 +201,7 @@
"defaultMessage": "Ces données seront stockées en clair dans la base de données et dans un fichier !"
},
"certificates.dns.propagation-seconds": {
- "defaultMessage": "Propagation Seconds"
+ "defaultMessage": "Délai de propagation (secondes)"
},
"certificates.dns.propagation-seconds-note": {
"defaultMessage": "Laisser vide pour utiliser la valeur par défaut du plugin. Nombre de secondes à attendre pour la propagation DNS."
@@ -216,10 +228,10 @@
"defaultMessage": "Votre serveur est accessible et la création de certificats devrait être possible."
},
"certificates.http.reachability-other": {
- "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il a renvoyé un code d'état inattendu {code}. S'agit-il du serveur NPM ? Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée."
+ "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il a renvoyé un code d'état inattendu {code}. S'agit-il du serveur NPM ? Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée."
},
"certificates.http.reachability-wrong-data": {
- "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il a renvoyé des données inattendues. S'agit-il du serveur NPM ? Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée."
+ "defaultMessage": "Un serveur a été trouvé sur ce domaine, mais il a renvoyé des données inattendues. S'agit-il du serveur NPM ? Veuillez vérifier que votre domaine pointe bien vers l'adresse IP où votre instance NPM est exécutée."
},
"certificates.http.test-results": {
"defaultMessage": "Résultats du test"
@@ -255,7 +267,7 @@
"defaultMessage": "Autorisations"
},
"column.custom-locations": {
- "defaultMessage": "Emplacement personnalisé"
+ "defaultMessage": "Emplacements personnalisés"
},
"column.destination": {
"defaultMessage": "Destination"
@@ -266,6 +278,9 @@
"column.email": {
"defaultMessage": "e-mail"
},
+ "column.error": {
+ "defaultMessage": "Erreurs"
+ },
"column.event": {
"defaultMessage": "Évènement"
},
@@ -294,13 +309,13 @@
"defaultMessage": "Règles"
},
"column.satisfy": {
- "defaultMessage": "Valide"
+ "defaultMessage": "Conditions requises"
},
"column.satisfy-all": {
- "defaultMessage": "All"
+ "defaultMessage": "Toutes"
},
"column.satisfy-any": {
- "defaultMessage": "Any"
+ "defaultMessage": "Au moins une"
},
"column.scheme": {
"defaultMessage": "Schéma"
@@ -315,7 +330,7 @@
"defaultMessage": "Statut"
},
"created-on": {
- "defaultMessage": "Créé : {date}"
+ "defaultMessage": "Création : {date}"
},
"dashboard": {
"defaultMessage": "Tableau de bord"
@@ -342,10 +357,10 @@
"defaultMessage": "Commencez à écrire pour ajouter un domaine…"
},
"domain-names.wildcards-not-permitted": {
- "defaultMessage": "Les Wildcards ne sont pas permises dans ce cas"
+ "defaultMessage": "Les wildcards ne sont pas permises dans ce cas"
},
"domain-names.wildcards-not-supported": {
- "defaultMessage": "Les Wildcards ne sont pas prises en charge par cette autorité de certification."
+ "defaultMessage": "Les wildcards ne sont pas prises en charge par cette autorité de certification."
},
"domains.advanced": {
"defaultMessage": "Avancé"
@@ -375,7 +390,7 @@
"defaultMessage": "Aucun résultat trouvé"
},
"empty-subtitle": {
- "defaultMessage": "Pourquoi n'en créez-vous pas un ?"
+ "defaultMessage": "Pourquoi n'en créez-vous pas un ?"
},
"enabled": {
"defaultMessage": "Activé"
@@ -390,7 +405,7 @@
"defaultMessage": "Adresse e-mail ou mot de passe invalide"
},
"error.invalid-domain": {
- "defaultMessage": "Domaine invalide : {domain}"
+ "defaultMessage": "Domaine invalide : {domain}"
},
"error.invalid-email": {
"defaultMessage": "Adresse e-mail invalide"
@@ -417,16 +432,16 @@
"defaultMessage": "Ceci est obligatoire"
},
"expires.on": {
- "defaultMessage": "Expire : {date}"
+ "defaultMessage": "Expire le {date}"
},
"footer.github-fork": {
- "defaultMessage": "Forkez-moi sur Github"
+ "defaultMessage": "Forkez-moi sur GitHub"
},
"host.flags.block-exploits": {
"defaultMessage": "Bloquer les exploits courants"
},
"host.flags.cache-assets": {
- "defaultMessage": "Ressources du cache"
+ "defaultMessage": "Mise en cache des ressources"
},
"host.flags.preserve-path": {
"defaultMessage": "Préserver le chemin"
@@ -435,10 +450,10 @@
"defaultMessage": "Protocoles"
},
"host.flags.websockets-upgrade": {
- "defaultMessage": "Prise en charge de Websockets"
+ "defaultMessage": "Prise en charge de WebSockets"
},
"host.forward-port": {
- "defaultMessage": "Port de redirection"
+ "defaultMessage": "Port de destination"
},
"host.forward-scheme": {
"defaultMessage": "Schéma"
@@ -461,6 +476,12 @@
"loading": {
"defaultMessage": "Chargement…"
},
+ "location.advanced-config": {
+ "defaultMessage": "Configuration Nginx personnalisée"
+ },
+ "location.filter": {
+ "defaultMessage": "Filtrer par chemin ou destination"
+ },
"login.2fa-code": {
"defaultMessage": "Code de vérification"
},
@@ -479,11 +500,56 @@
"login.title": {
"defaultMessage": "Connectez-vous à votre compte"
},
+ "logs": {
+ "defaultMessage": "Journaux"
+ },
+ "logs.channel.access": {
+ "defaultMessage": "Accès"
+ },
+ "logs.channel.error": {
+ "defaultMessage": "Erreurs"
+ },
+ "logs.download": {
+ "defaultMessage": "Télécharger"
+ },
+ "logs.empty": {
+ "defaultMessage": "Aucune entrée pour le moment."
+ },
+ "logs.level": {
+ "defaultMessage": "Niveau"
+ },
+ "logs.level.all": {
+ "defaultMessage": "Tous les niveaux"
+ },
+ "logs.lines": {
+ "defaultMessage": "Lignes"
+ },
+ "logs.live": {
+ "defaultMessage": "En direct"
+ },
+ "logs.paused": {
+ "defaultMessage": "En pause"
+ },
+ "logs.refresh": {
+ "defaultMessage": "Actualiser"
+ },
+ "logs.search-placeholder": {
+ "defaultMessage": "Rechercher dans les journaux…"
+ },
+ "logs.source": {
+ "defaultMessage": "Source"
+ },
+ "logs.source.system": {
+ "defaultMessage": "Système"
+ },
+ "logs.truncated-warning": {
+ "defaultMessage": "Seule la partie la plus récente de ce fichier est affichée."
+ },
"nginx-config.label": {
"defaultMessage": "Configuration Nginx personnalisée"
},
"nginx-config.placeholder": {
- "defaultMessage": "# Mettez ici votre configuration Nginx personnalisé à vos risques et périls !"
+ "defaultMessage": "# Mettez ici votre configuration Nginx personnalisée à vos risques et périls !"
},
"no-permission-error": {
"defaultMessage": "Vous n'avez pas la permission de voir ce contenu."
@@ -597,7 +663,7 @@
"defaultMessage": "Hôte proxy"
},
"proxy-host.forward-host": {
- "defaultMessage": "Nom d'hôte de redirection / IP"
+ "defaultMessage": "Nom d'hôte / IP de destination"
},
"proxy-hosts": {
"defaultMessage": "Hôtes proxy"
@@ -606,7 +672,7 @@
"defaultMessage": "{count} {count, plural, one {Hôte proxy} other {Hôtes proxy}}"
},
"public": {
- "defaultMessage": "Publique"
+ "defaultMessage": "Public"
},
"redirection-host": {
"defaultMessage": "Hôte de redirection"
@@ -669,7 +735,7 @@
"defaultMessage": "Page de félicitations"
},
"settings.default-site.description": {
- "defaultMessage": "ce qu'il faut afficher lorsqu'un hôte inconnu est détecté par Nginx"
+ "defaultMessage": "Ce qu'il faut afficher lorsqu'un hôte inconnu est détecté par Nginx"
},
"settings.default-site.html": {
"defaultMessage": "HTML personnalisé"
@@ -696,7 +762,7 @@
"defaultMessage": "Stream"
},
"stream.forward-host": {
- "defaultMessage": "Hôte destinataire"
+ "defaultMessage": "Hôte de destination"
},
"stream.forward-host.placeholder": {
"defaultMessage": "example.com ou 10.0.0.1 ou 2001:db8:3333:4444:5555:6666:7777:8888"
@@ -720,7 +786,7 @@
"defaultMessage": "Test"
},
"update-available": {
- "defaultMessage": "Mise à jour disponible : {latestVersion}"
+ "defaultMessage": "Mise à jour disponible : {latestVersion}"
},
"user": {
"defaultMessage": "Utilisateur"
@@ -759,10 +825,10 @@
"defaultMessage": "Définir les autorisations pour {name}"
},
"user.switch-dark": {
- "defaultMessage": "Passer au mode Sombre"
+ "defaultMessage": "Passer au mode sombre"
},
"user.switch-light": {
- "defaultMessage": "Passer au mode Lumineux"
+ "defaultMessage": "Passer au mode clair"
},
"user.two-factor": {
"defaultMessage": "Auth. à deux facteurs"
diff --git a/frontend/src/locale/src/it.json b/frontend/src/locale/src/it.json
index 7e5ca7711..d8506bd95 100644
--- a/frontend/src/locale/src/it.json
+++ b/frontend/src/locale/src/it.json
@@ -1,4 +1,61 @@
{
+ "2fa.backup-codes-remaining": {
+ "defaultMessage": "Codici di backup rimanenti: {count}"
+ },
+ "2fa.backup-warning": {
+ "defaultMessage": "Conserva questi codici di backup in un luogo sicuro. Ogni codice può essere usato una sola volta."
+ },
+ "2fa.disable": {
+ "defaultMessage": "Disattiva Autenticazione a Due Fattori"
+ },
+ "2fa.disable-confirm": {
+ "defaultMessage": "Disattiva 2FA"
+ },
+ "2fa.disable-warning": {
+ "defaultMessage": "Disattivare l'autenticazione a due fattori renderà il tuo account meno sicuro."
+ },
+ "2fa.disabled": {
+ "defaultMessage": "Disattivata"
+ },
+ "2fa.done": {
+ "defaultMessage": "Ho salvato i miei codici di backup"
+ },
+ "2fa.enable": {
+ "defaultMessage": "Attiva Autenticazione a Due Fattori"
+ },
+ "2fa.enabled": {
+ "defaultMessage": "Attivata"
+ },
+ "2fa.enter-code": {
+ "defaultMessage": "Inserisci il codice di verifica"
+ },
+ "2fa.enter-code-disable": {
+ "defaultMessage": "Inserisci il codice di verifica per disattivarla"
+ },
+ "2fa.regenerate": {
+ "defaultMessage": "Rigenera"
+ },
+ "2fa.regenerate-backup": {
+ "defaultMessage": "Rigenera Codici di Backup"
+ },
+ "2fa.regenerate-instructions": {
+ "defaultMessage": "Inserisci un codice di verifica per generare nuovi codici di backup. I vecchi codici non saranno più validi."
+ },
+ "2fa.secret-key": {
+ "defaultMessage": "Chiave Segreta"
+ },
+ "2fa.setup-instructions": {
+ "defaultMessage": "Scansiona questo codice QR con la tua app di autenticazione, oppure inserisci la chiave segreta manualmente."
+ },
+ "2fa.status": {
+ "defaultMessage": "Stato"
+ },
+ "2fa.title": {
+ "defaultMessage": "Autenticazione a Due Fattori"
+ },
+ "2fa.verify-enable": {
+ "defaultMessage": "Verifica e Attiva"
+ },
"access-list": {
"defaultMessage": "Lista di Accesso"
},
@@ -14,6 +71,12 @@
"access-list.help.rules-order": {
"defaultMessage": "Nota che le direttive di allow e deny saranno applicate nell'ordine in cui sono definite."
},
+ "access-list.inherit": {
+ "defaultMessage": "Eredita dal Proxy Host"
+ },
+ "access-list.inherit.subtitle": {
+ "defaultMessage": "Usa la lista di accesso del proxy host"
+ },
"access-list.pass-auth": {
"defaultMessage": "Passa Autenticazione all'Upstream"
},
@@ -23,6 +86,9 @@
"access-list.public.subtitle": {
"defaultMessage": "Nessuna autenticazione base richiesta"
},
+ "access-list.rule-source.placeholder": {
+ "defaultMessage": "192.168.1.100 o 192.168.1.0/24 o 2001:0db8::/32"
+ },
"access-list.satisfy-any": {
"defaultMessage": "Soddisfa Qualsiasi"
},
@@ -38,12 +104,21 @@
"action.add-location": {
"defaultMessage": "Aggiungi Percorso"
},
+ "action.allow": {
+ "defaultMessage": "Consenti"
+ },
+ "action.clear": {
+ "defaultMessage": "Svuota"
+ },
"action.close": {
"defaultMessage": "Chiudi"
},
"action.delete": {
"defaultMessage": "Elimina"
},
+ "action.deny": {
+ "defaultMessage": "Nega"
+ },
"action.disable": {
"defaultMessage": "Disabilita"
},
@@ -56,6 +131,9 @@
"action.enable": {
"defaultMessage": "Abilita"
},
+ "action.logs": {
+ "defaultMessage": "Log"
+ },
"action.permissions": {
"defaultMessage": "Permessi"
},
@@ -68,6 +146,9 @@
"auditlogs": {
"defaultMessage": "Log di Audit"
},
+ "auto": {
+ "defaultMessage": "Auto"
+ },
"cancel": {
"defaultMessage": "Annulla"
},
@@ -128,6 +209,9 @@
"certificates.dns.provider": {
"defaultMessage": "Provider DNS"
},
+ "certificates.dns.provider.placeholder": {
+ "defaultMessage": "Seleziona un Provider..."
+ },
"certificates.dns.warning": {
"defaultMessage": "Questa sezione richiede conoscenze su Certbot e i relativi plugin DNS. Consulta la documentazione del plugin."
},
@@ -194,6 +278,9 @@
"column.email": {
"defaultMessage": "Email"
},
+ "column.error": {
+ "defaultMessage": "Errore"
+ },
"column.event": {
"defaultMessage": "Evento"
},
@@ -275,6 +362,9 @@
"domain-names.wildcards-not-supported": {
"defaultMessage": "Wildcard non supportate per questa CA"
},
+ "domains.advanced": {
+ "defaultMessage": "Avanzate"
+ },
"domains.force-ssl": {
"defaultMessage": "Forza SSL"
},
@@ -287,6 +377,9 @@
"domains.http2-support": {
"defaultMessage": "Supporto HTTP/2"
},
+ "domains.trust-forwarded-proto": {
+ "defaultMessage": "Considera Attendibili gli Header Forwarded Proto dell'Upstream"
+ },
"domains.use-dns": {
"defaultMessage": "Usa Challenge DNS"
},
@@ -383,9 +476,75 @@
"loading": {
"defaultMessage": "Caricamento…"
},
+ "location.advanced-config": {
+ "defaultMessage": "Ha una configurazione Nginx personalizzata"
+ },
+ "location.filter": {
+ "defaultMessage": "Filtra per percorso o destinazione"
+ },
+ "login.2fa-code": {
+ "defaultMessage": "Codice di Verifica"
+ },
+ "login.2fa-code-placeholder": {
+ "defaultMessage": "Inserisci il codice"
+ },
+ "login.2fa-description": {
+ "defaultMessage": "Inserisci il codice della tua app di autenticazione"
+ },
+ "login.2fa-title": {
+ "defaultMessage": "Autenticazione a Due Fattori"
+ },
+ "login.2fa-verify": {
+ "defaultMessage": "Verifica"
+ },
"login.title": {
"defaultMessage": "Accedi al tuo account"
},
+ "logs": {
+ "defaultMessage": "Log"
+ },
+ "logs.channel.access": {
+ "defaultMessage": "Accessi"
+ },
+ "logs.channel.error": {
+ "defaultMessage": "Errori"
+ },
+ "logs.download": {
+ "defaultMessage": "Scarica"
+ },
+ "logs.empty": {
+ "defaultMessage": "Nessuna voce di log per ora."
+ },
+ "logs.level": {
+ "defaultMessage": "Livello"
+ },
+ "logs.level.all": {
+ "defaultMessage": "Tutti i Livelli"
+ },
+ "logs.lines": {
+ "defaultMessage": "Righe"
+ },
+ "logs.live": {
+ "defaultMessage": "Live"
+ },
+ "logs.paused": {
+ "defaultMessage": "In Pausa"
+ },
+ "logs.refresh": {
+ "defaultMessage": "Aggiorna"
+ },
+ "logs.search-placeholder": {
+ "defaultMessage": "Cerca nel log…"
+ },
+ "logs.source": {
+ "defaultMessage": "Origine"
+ },
+ "logs.source.system": {
+ "defaultMessage": "Sistema"
+ },
+ "logs.truncated-warning": {
+ "defaultMessage": "Viene mostrata solo la parte più recente di questo file."
+ },
"nginx-config.label": {
"defaultMessage": "Configurazione Nginx Personalizzata"
},
@@ -530,6 +689,24 @@
"redirection-hosts.count": {
"defaultMessage": "{count} {count, plural, one {Host di Reindirizzamento} other {Host di Reindirizzamento}}"
},
+ "redirection-hosts.http-code.300": {
+ "defaultMessage": "300 Scelte multiple"
+ },
+ "redirection-hosts.http-code.301": {
+ "defaultMessage": "301 Spostato permanentemente"
+ },
+ "redirection-hosts.http-code.302": {
+ "defaultMessage": "302 Spostato temporaneamente"
+ },
+ "redirection-hosts.http-code.303": {
+ "defaultMessage": "303 Vedi altro"
+ },
+ "redirection-hosts.http-code.307": {
+ "defaultMessage": "307 Reindirizzamento temporaneo"
+ },
+ "redirection-hosts.http-code.308": {
+ "defaultMessage": "308 Reindirizzamento permanente"
+ },
"role.admin": {
"defaultMessage": "Amministratore"
},
@@ -587,6 +764,9 @@
"stream.forward-host": {
"defaultMessage": "Host di Destinazione"
},
+ "stream.forward-host.placeholder": {
+ "defaultMessage": "example.com o 10.0.0.1 o 2001:db8:3333:4444:5555:6666:7777:8888"
+ },
"stream.incoming-port": {
"defaultMessage": "Porta in Ingresso"
},
@@ -650,6 +830,9 @@
"user.switch-light": {
"defaultMessage": "Passa alla modalità Chiara"
},
+ "user.two-factor": {
+ "defaultMessage": "Autenticazione 2FA"
+ },
"username": {
"defaultMessage": "Nome Utente"
},
diff --git a/frontend/src/locale/src/ko.json b/frontend/src/locale/src/ko.json
index 9c0093591..fc54b5201 100644
--- a/frontend/src/locale/src/ko.json
+++ b/frontend/src/locale/src/ko.json
@@ -1,12 +1,69 @@
{
+ "2fa.backup-codes-remaining": {
+ "defaultMessage": "남은 백업 코드: {count}개"
+ },
+ "2fa.backup-warning": {
+ "defaultMessage": "백업 코드를 안전한 곳에 보관하세요. 각 코드는 한 번만 사용할 수 있습니다."
+ },
+ "2fa.disable": {
+ "defaultMessage": "2단계 인증 비활성화"
+ },
+ "2fa.disable-confirm": {
+ "defaultMessage": "2FA 비활성화"
+ },
+ "2fa.disable-warning": {
+ "defaultMessage": "2단계 인증을 비활성화하면 계정 보안이 약해집니다."
+ },
+ "2fa.disabled": {
+ "defaultMessage": "비활성화"
+ },
+ "2fa.done": {
+ "defaultMessage": "백업 코드를 저장했습니다"
+ },
+ "2fa.enable": {
+ "defaultMessage": "2단계 인증 활성화"
+ },
+ "2fa.enabled": {
+ "defaultMessage": "활성화"
+ },
+ "2fa.enter-code": {
+ "defaultMessage": "인증 코드 입력"
+ },
+ "2fa.enter-code-disable": {
+ "defaultMessage": "비활성화하려면 인증 코드를 입력하세요"
+ },
+ "2fa.regenerate": {
+ "defaultMessage": "재발급"
+ },
+ "2fa.regenerate-backup": {
+ "defaultMessage": "백업 코드 재발급"
+ },
+ "2fa.regenerate-instructions": {
+ "defaultMessage": "새 백업 코드를 발급하려면 인증 코드를 입력하세요. 기존 코드는 더 이상 사용할 수 없게 됩니다."
+ },
+ "2fa.secret-key": {
+ "defaultMessage": "비밀 키"
+ },
+ "2fa.setup-instructions": {
+ "defaultMessage": "인증 앱으로 이 QR 코드를 스캔하거나 비밀 키를 직접 입력하세요."
+ },
+ "2fa.status": {
+ "defaultMessage": "상태"
+ },
+ "2fa.title": {
+ "defaultMessage": "2단계 인증"
+ },
+ "2fa.verify-enable": {
+ "defaultMessage": "확인 후 활성화"
+ },
"access-list": {
"defaultMessage": "접근 정책"
},
"access-list.access-count": {
- "defaultMessage": "{count}개의 정책"
+ "defaultMessage": "규칙 {count}개"
},
"access-list.auth-count": {
- "defaultMessage": "{count}명의 사용자"
+ "defaultMessage": "사용자 {count}명"
},
"access-list.help-rules-last": {
"defaultMessage": "규칙이 하나라도 있으면 아래 ‘전체 거부’ 규칙이 마지막에 추가됩니다."
@@ -14,6 +71,12 @@
"access-list.help.rules-order": {
"defaultMessage": "허용/거부 규칙은 정의된 순서대로 적용됩니다."
},
+ "access-list.inherit": {
+ "defaultMessage": "프록시 호스트 설정 상속"
+ },
+ "access-list.inherit.subtitle": {
+ "defaultMessage": "프록시 호스트의 접근 정책을 사용합니다"
+ },
"access-list.pass-auth": {
"defaultMessage": "인증 정보를 원본 서버로 전달"
},
@@ -24,13 +87,13 @@
"defaultMessage": "기본 인증 필요 없음"
},
"access-list.rule-source.placeholder": {
- "defaultMessage": "192.168.1.100 / 192.168.1.0/24 / IPv6"
+ "defaultMessage": "192.168.1.100 또는 192.168.1.0/24 또는 2001:0db8::/32"
},
"access-list.satisfy-any": {
"defaultMessage": "조건 중 하나라도 충족"
},
"access-list.subtitle": {
- "defaultMessage": "{users}명 {users, plural, one {사용자} other {사용자}}, {rules}개 {rules, plural, one {규칙} other {규칙}} - 생성일: {date}"
+ "defaultMessage": "사용자 {users}명, 규칙 {rules}개 - 생성일: {date}"
},
"access-lists": {
"defaultMessage": "접근 정책"
@@ -44,6 +107,9 @@
"action.allow": {
"defaultMessage": "허용"
},
+ "action.clear": {
+ "defaultMessage": "지우기"
+ },
"action.close": {
"defaultMessage": "닫기"
},
@@ -65,6 +131,9 @@
"action.enable": {
"defaultMessage": "활성화"
},
+ "action.logs": {
+ "defaultMessage": "로그"
+ },
"action.permissions": {
"defaultMessage": "권한"
},
@@ -102,13 +171,13 @@
"defaultMessage": "지정된 인증서 없음"
},
"certificate.none.subtitle.for-http": {
- "defaultMessage": "이 호스트는 HTTPS를 사용하지 않습니다."
+ "defaultMessage": "이 호스트는 HTTPS를 사용하지 않습니다"
},
"certificate.none.title": {
"defaultMessage": "없음"
},
"certificate.not-in-use": {
- "defaultMessage": "사용 안 함"
+ "defaultMessage": "미사용"
},
"certificate.renew": {
"defaultMessage": "인증서 갱신"
@@ -123,19 +192,19 @@
"defaultMessage": "비밀번호로 보호된 키 파일은 지원되지 않습니다."
},
"certificates.dns.credentials": {
- "defaultMessage": "DNS 자격 증명 입력"
+ "defaultMessage": "자격 증명 파일 내용"
},
"certificates.dns.credentials-note": {
- "defaultMessage": "이 플러그인은 API 토큰 등이 포함된 설정 파일이 필요합니다."
+ "defaultMessage": "이 플러그인을 사용하려면 DNS 공급자의 API 토큰 또는 기타 자격 증명이 포함된 설정 파일이 필요합니다."
},
"certificates.dns.credentials-warning": {
"defaultMessage": "입력한 정보는 데이터베이스와 파일에 평문으로 저장됩니다."
},
"certificates.dns.propagation-seconds": {
- "defaultMessage": "DNS 전파 시간"
+ "defaultMessage": "DNS 전파 대기 시간(초)"
},
"certificates.dns.propagation-seconds-note": {
- "defaultMessage": "비워두면 기본값을 사용합니다. DNS 전파를 기다리는 시간(초)입니다."
+ "defaultMessage": "비워 두면 플러그인 기본값을 사용합니다. DNS 전파를 기다릴 시간(초)입니다."
},
"certificates.dns.provider": {
"defaultMessage": "DNS 공급자"
@@ -144,37 +213,37 @@
"defaultMessage": "공급자를 선택하세요..."
},
"certificates.dns.warning": {
- "defaultMessage": "이 기능을 사용하려면 Certbot과 DNS 플러그인에 대한 기본적인 이해가 필요합니다. 자세한 내용은 관련 문서를 참고해 주세요."
+ "defaultMessage": "이 항목을 사용하려면 Certbot과 DNS 플러그인에 대한 지식이 어느 정도 필요합니다. 각 플러그인의 문서를 참고하세요."
},
"certificates.http.reachability-404": {
- "defaultMessage": "해당 도메인에서 서버가 탐지되었지만 Nginx Proxy Manager가 아닌 것으로 보입니다. 도메인이 NPM이 실행 중인 IP를 가리키는지 확인하세요."
+ "defaultMessage": "해당 도메인에서 서버가 발견되었지만 Nginx Proxy Manager가 아닌 것으로 보입니다. 도메인이 NPM이 실행 중인 IP를 가리키는지 확인하세요."
},
"certificates.http.reachability-failed-to-check": {
- "defaultMessage": "site24x7.com과의 통신 오류로 인해 도달 가능 여부를 확인할 수 없습니다."
+ "defaultMessage": "site24x7.com과 통신하는 중 오류가 발생해 접근 가능 여부를 확인하지 못했습니다."
},
"certificates.http.reachability-not-resolved": {
- "defaultMessage": "해당 도메인에 접근 가능한 서버가 없습니다. 도메인이 존재하며 NPM이 실행되는 IP를 가리키고, 필요하면 라우터에서 80포트가 포워딩되어 있는지 확인하세요."
+ "defaultMessage": "해당 도메인에서 사용 가능한 서버를 찾을 수 없습니다. 도메인이 존재하고 NPM이 실행 중인 IP를 가리키는지, 필요한 경우 라우터에서 80번 포트가 포워딩되어 있는지 확인하세요."
},
"certificates.http.reachability-ok": {
- "defaultMessage": "서버에 정상적으로 접근할 수 있으며 인증서 발급이 가능합니다."
+ "defaultMessage": "서버에 정상적으로 접근할 수 있어 인증서를 발급할 수 있을 것으로 보입니다."
},
"certificates.http.reachability-other": {
- "defaultMessage": "해당 도메인에서 서버가 발견되었지만 예상치 못한 상태 코드 {code}를 반환했습니다. NPM 서버가 맞는지 확인하세요."
+ "defaultMessage": "해당 도메인에서 서버가 발견되었지만 예상하지 못한 상태 코드({code})를 반환했습니다. NPM 서버가 맞나요? 도메인이 NPM이 실행 중인 IP를 가리키는지 확인하세요."
},
"certificates.http.reachability-wrong-data": {
- "defaultMessage": "서버가 응답했지만 예상치 못한 데이터를 반환했습니다. NPM 서버가 맞는지 확인하세요."
+ "defaultMessage": "해당 도메인에서 서버가 발견되었지만 예상하지 못한 데이터를 반환했습니다. NPM 서버가 맞나요? 도메인이 NPM이 실행 중인 IP를 가리키는지 확인하세요."
},
"certificates.http.test-results": {
"defaultMessage": "테스트 결과"
},
"certificates.http.warning": {
- "defaultMessage": "도메인이 이 서버를 가리키도록 설정되어 있어야 합니다."
+ "defaultMessage": "입력한 도메인은 미리 이 서버를 가리키도록 설정되어 있어야 합니다."
},
"certificates.key-type": {
"defaultMessage": "키 유형"
},
"certificates.key-type-description": {
- "defaultMessage": "RSA는 호환성이 넓고, ECDSA는 더 빠르고 안전하지만 오래된 시스템에서 지원되지 않을 수 있습니다"
+ "defaultMessage": "RSA는 호환성이 좋고, ECDSA는 더 빠르고 안전하지만 오래된 시스템에서는 지원되지 않을 수 있습니다"
},
"certificates.key-type-ecdsa": {
"defaultMessage": "ECDSA 256"
@@ -183,16 +252,16 @@
"defaultMessage": "RSA 2048"
},
"certificates.request.subtitle": {
- "defaultMessage": "Let's Encrypt 사용"
+ "defaultMessage": "Let's Encrypt로 발급"
},
"certificates.request.title": {
"defaultMessage": "새 인증서 요청"
},
"column.access": {
- "defaultMessage": "접근 정책"
+ "defaultMessage": "접근"
},
"column.authorization": {
- "defaultMessage": "인증 사용자"
+ "defaultMessage": "인증"
},
"column.authorizations": {
"defaultMessage": "인증 사용자"
@@ -209,6 +278,9 @@
"column.email": {
"defaultMessage": "이메일"
},
+ "column.error": {
+ "defaultMessage": "오류"
+ },
"column.event": {
"defaultMessage": "이벤트"
},
@@ -231,13 +303,13 @@
"defaultMessage": "공급자"
},
"column.roles": {
- "defaultMessage": "권한"
+ "defaultMessage": "역할"
},
"column.rules": {
- "defaultMessage": "IP 정책"
+ "defaultMessage": "접근 규칙"
},
"column.satisfy": {
- "defaultMessage": "조건 방식"
+ "defaultMessage": "충족 조건"
},
"column.satisfy-all": {
"defaultMessage": "모두 충족"
@@ -270,7 +342,7 @@
"defaultMessage": "404 호스트"
},
"dead-hosts.count": {
- "defaultMessage": "{count}개의 404 호스트"
+ "defaultMessage": "404 호스트 {count}개"
},
"disabled": {
"defaultMessage": "비활성화"
@@ -282,14 +354,17 @@
"defaultMessage": "최대 {count}개의 도메인 이름"
},
"domain-names.placeholder": {
- "defaultMessage": "도메인을 입력해주세요."
+ "defaultMessage": "추가할 도메인을 입력하세요..."
},
"domain-names.wildcards-not-permitted": {
- "defaultMessage": "HTTP 방식으로는 와일드카드 인증서를 발급할 수 없습니다."
+ "defaultMessage": "이 방식에서는 와일드카드 도메인을 사용할 수 없습니다."
},
"domain-names.wildcards-not-supported": {
"defaultMessage": "이 인증 기관(CA)은 와일드카드를 지원하지 않습니다."
},
+ "domains.advanced": {
+ "defaultMessage": "고급"
+ },
"domains.force-ssl": {
"defaultMessage": "SSL 강제 적용"
},
@@ -302,6 +377,9 @@
"domains.http2-support": {
"defaultMessage": "HTTP/2 지원"
},
+ "domains.trust-forwarded-proto": {
+ "defaultMessage": "앞단 프록시의 X-Forwarded-Proto 헤더 신뢰"
+ },
"domains.use-dns": {
"defaultMessage": "DNS 챌린지 사용"
},
@@ -318,7 +396,7 @@
"defaultMessage": "활성화"
},
"error.access.at-least-one": {
- "defaultMessage": "인증 또는 접근 규칙 중 하나는 반드시 필요합니다."
+ "defaultMessage": "인증 사용자 또는 접근 규칙이 하나 이상 필요합니다."
},
"error.access.duplicate-usernames": {
"defaultMessage": "인증 사용자 이름은 중복될 수 없습니다."
@@ -348,7 +426,7 @@
"defaultMessage": "최솟값은 {min}입니다."
},
"error.passwords-must-match": {
- "defaultMessage": "비밀번호가 일치해야 합니다."
+ "defaultMessage": "비밀번호가 일치하지 않습니다."
},
"error.required": {
"defaultMessage": "필수 항목입니다."
@@ -360,7 +438,7 @@
"defaultMessage": "GitHub에서 포크하기"
},
"host.flags.block-exploits": {
- "defaultMessage": "일반적인 공격 차단"
+ "defaultMessage": "일반적인 취약점 공격 차단"
},
"host.flags.cache-assets": {
"defaultMessage": "정적 에셋 캐싱"
@@ -381,7 +459,7 @@
"defaultMessage": "프로토콜"
},
"hosts": {
- "defaultMessage": "호스트 목록"
+ "defaultMessage": "호스트"
},
"http-only": {
"defaultMessage": "HTTP 전용"
@@ -398,8 +476,74 @@
"loading": {
"defaultMessage": "불러오는 중…"
},
+ "location.advanced-config": {
+ "defaultMessage": "사용자 지정 Nginx 설정 있음"
+ },
+ "location.filter": {
+ "defaultMessage": "경로 또는 전달 대상으로 필터링"
+ },
+ "login.2fa-code": {
+ "defaultMessage": "인증 코드"
+ },
+ "login.2fa-code-placeholder": {
+ "defaultMessage": "코드 입력"
+ },
+ "login.2fa-description": {
+ "defaultMessage": "인증 앱에 표시된 코드를 입력하세요"
+ },
+ "login.2fa-title": {
+ "defaultMessage": "2단계 인증"
+ },
+ "login.2fa-verify": {
+ "defaultMessage": "확인"
+ },
"login.title": {
- "defaultMessage": "로그인"
+ "defaultMessage": "계정에 로그인"
+ },
+ "logs": {
+ "defaultMessage": "로그"
+ },
+ "logs.channel.access": {
+ "defaultMessage": "접근"
+ },
+ "logs.channel.error": {
+ "defaultMessage": "오류"
+ },
+ "logs.download": {
+ "defaultMessage": "다운로드"
+ },
+ "logs.empty": {
+ "defaultMessage": "아직 로그가 없습니다."
+ },
+ "logs.level": {
+ "defaultMessage": "레벨"
+ },
+ "logs.level.all": {
+ "defaultMessage": "모든 레벨"
+ },
+ "logs.lines": {
+ "defaultMessage": "줄 수"
+ },
+ "logs.live": {
+ "defaultMessage": "실시간"
+ },
+ "logs.paused": {
+ "defaultMessage": "일시 정지"
+ },
+ "logs.refresh": {
+ "defaultMessage": "새로고침"
+ },
+ "logs.search-placeholder": {
+ "defaultMessage": "로그 검색…"
+ },
+ "logs.source": {
+ "defaultMessage": "소스"
+ },
+ "logs.source.system": {
+ "defaultMessage": "시스템"
+ },
+ "logs.truncated-warning": {
+ "defaultMessage": "이 파일의 최근 내용 일부만 표시됩니다."
},
"nginx-config.label": {
"defaultMessage": "사용자 지정 Nginx 설정"
@@ -459,28 +603,28 @@
"defaultMessage": "{objects}이(가) 없습니다."
},
"object.event.created": {
- "defaultMessage": "{object}이(가) 생성됨"
+ "defaultMessage": "{object} 생성"
},
"object.event.deleted": {
- "defaultMessage": "{object}이(가) 삭제됨"
+ "defaultMessage": "{object} 삭제"
},
"object.event.disabled": {
- "defaultMessage": "{object}이(가) 비활성화됨"
+ "defaultMessage": "{object} 비활성화"
},
"object.event.enabled": {
- "defaultMessage": "{object}이(가) 활성화됨"
+ "defaultMessage": "{object} 활성화"
},
"object.event.renewed": {
- "defaultMessage": "{object}이(가) 갱신됨"
+ "defaultMessage": "{object} 갱신"
},
"object.event.updated": {
- "defaultMessage": "{object}이(가) 업데이트됨"
+ "defaultMessage": "{object} 수정"
},
"offline": {
- "defaultMessage": "비활성화"
+ "defaultMessage": "오프라인"
},
"online": {
- "defaultMessage": "활성화"
+ "defaultMessage": "온라인"
},
"options": {
"defaultMessage": "옵션"
@@ -495,7 +639,7 @@
"defaultMessage": "비밀번호 숨기기"
},
"password.show": {
- "defaultMessage": "비밀번호 표시"
+ "defaultMessage": "비밀번호 보기"
},
"permissions.hidden": {
"defaultMessage": "숨김"
@@ -510,10 +654,10 @@
"defaultMessage": "모든 항목"
},
"permissions.visibility.title": {
- "defaultMessage": "항목 표시 설정"
+ "defaultMessage": "항목 표시 범위"
},
"permissions.visibility.user": {
- "defaultMessage": "내가 만든 항목만"
+ "defaultMessage": "본인이 만든 항목만"
},
"proxy-host": {
"defaultMessage": "프록시 호스트"
@@ -525,7 +669,7 @@
"defaultMessage": "프록시 호스트"
},
"proxy-hosts.count": {
- "defaultMessage": "{count}개의 프록시 호스트"
+ "defaultMessage": "프록시 호스트 {count}개"
},
"public": {
"defaultMessage": "공개"
@@ -534,7 +678,7 @@
"defaultMessage": "리다이렉션 호스트"
},
"redirection-host.forward-domain": {
- "defaultMessage": "전달할 도메인"
+ "defaultMessage": "리다이렉트할 도메인"
},
"redirection-host.forward-http-code": {
"defaultMessage": "HTTP 코드"
@@ -543,25 +687,25 @@
"defaultMessage": "리다이렉션 호스트"
},
"redirection-hosts.count": {
- "defaultMessage": "{count}개의 리다이렉션 호스트"
+ "defaultMessage": "리다이렉션 호스트 {count}개"
},
"redirection-hosts.http-code.300": {
- "defaultMessage": "300 Multiple Choices"
+ "defaultMessage": "300 여러 선택 항목"
},
"redirection-hosts.http-code.301": {
- "defaultMessage": "301 Moved permanently"
+ "defaultMessage": "301 영구 이동"
},
"redirection-hosts.http-code.302": {
- "defaultMessage": "302 Moved temporarily"
+ "defaultMessage": "302 임시 이동"
},
"redirection-hosts.http-code.303": {
- "defaultMessage": "303 See other"
+ "defaultMessage": "303 기타 위치 보기"
},
"redirection-hosts.http-code.307": {
- "defaultMessage": "307 Temporary redirect"
+ "defaultMessage": "307 임시 리다이렉션"
},
"redirection-hosts.http-code.308": {
- "defaultMessage": "308 Permanent redirect"
+ "defaultMessage": "308 영구 리다이렉션"
},
"role.admin": {
"defaultMessage": "관리자"
@@ -597,7 +741,7 @@
"defaultMessage": "사용자 지정 HTML"
},
"settings.default-site.html.placeholder": {
- "defaultMessage": ""
+ "defaultMessage": ""
},
"settings.default-site.redirect": {
"defaultMessage": "리다이렉트"
@@ -615,22 +759,22 @@
"defaultMessage": "SSL 인증서"
},
"stream": {
- "defaultMessage": "호스트 스트림"
+ "defaultMessage": "스트림"
},
"stream.forward-host": {
"defaultMessage": "전달할 호스트"
},
"stream.forward-host.placeholder": {
- "defaultMessage": "example.com / 10.0.0.1 / IPv6"
+ "defaultMessage": "example.com 또는 10.0.0.1 또는 2001:db8:3333:4444:5555:6666:7777:8888"
},
"stream.incoming-port": {
"defaultMessage": "수신 포트"
},
"streams": {
- "defaultMessage": "호스트 스트림"
+ "defaultMessage": "스트림"
},
"streams.count": {
- "defaultMessage": "{count}개의 호스트 스트림"
+ "defaultMessage": "스트림 {count}개"
},
"streams.tcp": {
"defaultMessage": "TCP"
@@ -660,10 +804,10 @@
"defaultMessage": "프로필 편집"
},
"user.full-name": {
- "defaultMessage": "전체 이름"
+ "defaultMessage": "이름"
},
"user.login-as": {
- "defaultMessage": "{name}으로 로그인"
+ "defaultMessage": "{name} 계정으로 로그인"
},
"user.logout": {
"defaultMessage": "로그아웃"
@@ -686,6 +830,9 @@
"user.switch-light": {
"defaultMessage": "라이트 모드로 전환"
},
+ "user.two-factor": {
+ "defaultMessage": "2단계 인증"
+ },
"username": {
"defaultMessage": "사용자 이름"
},
diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json
index 9ba12915a..e71e61c79 100644
--- a/frontend/src/locale/src/lang-list.json
+++ b/frontend/src/locale/src/lang-list.json
@@ -36,7 +36,10 @@
"defaultMessage": "Čeština"
},
"locale-zh-CN": {
- "defaultMessage": "中文"
+ "defaultMessage": "简体中文"
+ },
+ "locale-zh-TW": {
+ "defaultMessage": "繁體中文(台灣)"
},
"locale-pl-PL": {
"defaultMessage": "Polski"
diff --git a/frontend/src/locale/src/nl.json b/frontend/src/locale/src/nl.json
index 86d49d95e..140255d7a 100644
--- a/frontend/src/locale/src/nl.json
+++ b/frontend/src/locale/src/nl.json
@@ -71,6 +71,12 @@
"access-list.help.rules-order": {
"defaultMessage": "Houd er rekening mee dat de regels worden toegepast in de volgorde waarin ze zijn gedefinieerd."
},
+ "access-list.inherit": {
+ "defaultMessage": "Overnemen van Proxy Host"
+ },
+ "access-list.inherit.subtitle": {
+ "defaultMessage": "Gebruikt de toegangslijst van de Proxy Host"
+ },
"access-list.pass-auth": {
"defaultMessage": "Authenticatie naar upstream doorgeven"
},
@@ -101,6 +107,9 @@
"action.allow": {
"defaultMessage": "Toestaan"
},
+ "action.clear": {
+ "defaultMessage": "Wissen"
+ },
"action.close": {
"defaultMessage": "Sluiten"
},
@@ -122,6 +131,9 @@
"action.enable": {
"defaultMessage": "Inschakelen"
},
+ "action.logs": {
+ "defaultMessage": "Logboeken"
+ },
"action.permissions": {
"defaultMessage": "Machtigingen"
},
@@ -266,6 +278,9 @@
"column.email": {
"defaultMessage": "E-mail"
},
+ "column.error": {
+ "defaultMessage": "Fout"
+ },
"column.event": {
"defaultMessage": "Gebeurtenis"
},
@@ -422,6 +437,9 @@
"footer.github-fork": {
"defaultMessage": "Maak een Fork op Github"
},
+ "form.unsaved-changes": {
+ "defaultMessage": "Je hebt niet-opgeslagen wijzigingen. Weet je zeker dat je ze wilt weggooien?"
+ },
"host.flags.block-exploits": {
"defaultMessage": "Veelvoorkomende kwetsbaarheden blokkeren"
},
@@ -461,6 +479,12 @@
"loading": {
"defaultMessage": "Laden…"
},
+ "location.advanced-config": {
+ "defaultMessage": "Heeft aangepaste Nginx-configuratie"
+ },
+ "location.filter": {
+ "defaultMessage": "Filteren op pad of bestemming"
+ },
"login.2fa-code": {
"defaultMessage": "Verificatiecode"
},
@@ -479,6 +503,51 @@
"login.title": {
"defaultMessage": "Log in op je account"
},
+ "logs": {
+ "defaultMessage": "Logboeken"
+ },
+ "logs.channel.access": {
+ "defaultMessage": "Toegang"
+ },
+ "logs.channel.error": {
+ "defaultMessage": "Fout"
+ },
+ "logs.download": {
+ "defaultMessage": "Downloaden"
+ },
+ "logs.empty": {
+ "defaultMessage": "Nog geen logboekregistraties."
+ },
+ "logs.level": {
+ "defaultMessage": "Niveau"
+ },
+ "logs.level.all": {
+ "defaultMessage": "Alle niveaus"
+ },
+ "logs.lines": {
+ "defaultMessage": "Regels"
+ },
+ "logs.live": {
+ "defaultMessage": "Live"
+ },
+ "logs.paused": {
+ "defaultMessage": "Gepauzeerd"
+ },
+ "logs.refresh": {
+ "defaultMessage": "Verversen"
+ },
+ "logs.search-placeholder": {
+ "defaultMessage": "Logboek doorzoeken…"
+ },
+ "logs.source": {
+ "defaultMessage": "Bron"
+ },
+ "logs.source.system": {
+ "defaultMessage": "Systeem"
+ },
+ "logs.truncated-warning": {
+ "defaultMessage": "Alleen het meest recente deel van dit bestand wordt getoond."
+ },
"nginx-config.label": {
"defaultMessage": "Aangepaste Nginx-configuratie"
},
@@ -575,12 +644,21 @@
"password.show": {
"defaultMessage": "Wachtwoord weergeven"
},
+ "permissions.allowed-proxy-hosts": {
+ "defaultMessage": "Toegestane Proxy Hosts"
+ },
+ "permissions.allowed-proxy-hosts-help": {
+ "defaultMessage": "Verleent extra toegang tot geselecteerde Proxy Hosts die door andere gebruikers zijn aangemaakt (naast de hosts die door de gebruiker zelf zijn aangemaakt)."
+ },
"permissions.hidden": {
"defaultMessage": "Verborgen"
},
"permissions.manage": {
"defaultMessage": "Beheren"
},
+ "permissions.no-proxy-hosts": {
+ "defaultMessage": "Geen Proxy Hosts gevonden"
+ },
"permissions.view": {
"defaultMessage": "Alleen weergeven"
},
@@ -594,13 +672,13 @@
"defaultMessage": "Alleen aangemaakte items"
},
"proxy-host": {
- "defaultMessage": "Proxyhost"
+ "defaultMessage": "Proxy Host"
},
"proxy-host.forward-host": {
"defaultMessage": "Hostname / IP Doorsturen"
},
"proxy-hosts": {
- "defaultMessage": "Proxyhosts"
+ "defaultMessage": "Proxy Hosts"
},
"proxy-hosts.count": {
"defaultMessage": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}"
@@ -609,7 +687,7 @@
"defaultMessage": "Openbaar"
},
"redirection-host": {
- "defaultMessage": "Omleidingshost"
+ "defaultMessage": "Omgeleide Host"
},
"redirection-host.forward-domain": {
"defaultMessage": "Doorgestuurd Domein"
@@ -624,7 +702,7 @@
"defaultMessage": "{count} {count, plural, one {Omgeleide Host} other {Omgeleide Hosts}}"
},
"redirection-hosts.http-code.300": {
- "defaultMessage": "300 Meerkeuze"
+ "defaultMessage": "300 Meerdere keuzes"
},
"redirection-hosts.http-code.301": {
"defaultMessage": "301 Definitief verplaatst"
@@ -636,10 +714,10 @@
"defaultMessage": "303 Zie andere"
},
"redirection-hosts.http-code.307": {
- "defaultMessage": "307 Tijdelijke omleiding"
+ "defaultMessage": "307 Tijdelijk omgeleid"
},
"redirection-hosts.http-code.308": {
- "defaultMessage": "308 Definitieve omleiding"
+ "defaultMessage": "308 Definitief omgeleid"
},
"role.admin": {
"defaultMessage": "Beheerder"
@@ -774,3 +852,4 @@
"defaultMessage": "Gebruikers"
}
}
+
diff --git a/frontend/src/locale/src/vi.json b/frontend/src/locale/src/vi.json
index 32d26d559..53ee2e6d6 100644
--- a/frontend/src/locale/src/vi.json
+++ b/frontend/src/locale/src/vi.json
@@ -1,4 +1,61 @@
{
+ "2fa.backup-codes-remaining": {
+ "defaultMessage": "Số mã dự phòng còn lại: {count}"
+ },
+ "2fa.backup-warning": {
+ "defaultMessage": "Hãy lưu các mã dự phòng này ở nơi an toàn. Mỗi mã chỉ dùng được một lần."
+ },
+ "2fa.disable": {
+ "defaultMessage": "Tắt xác thực hai lớp"
+ },
+ "2fa.disable-confirm": {
+ "defaultMessage": "Tắt 2FA"
+ },
+ "2fa.disable-warning": {
+ "defaultMessage": "Tắt xác thực hai lớp sẽ khiến tài khoản của bạn kém an toàn hơn."
+ },
+ "2fa.disabled": {
+ "defaultMessage": "Đã tắt"
+ },
+ "2fa.done": {
+ "defaultMessage": "Tôi đã lưu các mã dự phòng"
+ },
+ "2fa.enable": {
+ "defaultMessage": "Bật xác thực hai lớp"
+ },
+ "2fa.enabled": {
+ "defaultMessage": "Đã bật"
+ },
+ "2fa.enter-code": {
+ "defaultMessage": "Nhập mã xác minh"
+ },
+ "2fa.enter-code-disable": {
+ "defaultMessage": "Nhập mã xác minh để tắt"
+ },
+ "2fa.regenerate": {
+ "defaultMessage": "Tạo lại"
+ },
+ "2fa.regenerate-backup": {
+ "defaultMessage": "Tạo lại mã dự phòng"
+ },
+ "2fa.regenerate-instructions": {
+ "defaultMessage": "Nhập mã xác minh để tạo mã dự phòng mới. Các mã cũ sẽ không còn hiệu lực."
+ },
+ "2fa.secret-key": {
+ "defaultMessage": "Khóa bí mật"
+ },
+ "2fa.setup-instructions": {
+ "defaultMessage": "Quét mã QR này bằng ứng dụng xác thực, hoặc nhập khóa bí mật thủ công."
+ },
+ "2fa.status": {
+ "defaultMessage": "Trạng thái"
+ },
+ "2fa.title": {
+ "defaultMessage": "Xác thực hai lớp"
+ },
+ "2fa.verify-enable": {
+ "defaultMessage": "Xác minh và bật"
+ },
"access-list": {
"defaultMessage": "Danh sách truy cập"
},
@@ -9,13 +66,19 @@
"defaultMessage": "{count} người dùng"
},
"access-list.help-rules-last": {
- "defaultMessage": "Quy tắc từ chối tất cả này sẽ được thêm vào cuối khi tồn tại ít nhất 1 quy tắc"
+ "defaultMessage": "Khi có ít nhất 1 quy tắc, quy tắc từ chối tất cả này sẽ được thêm vào cuối"
},
"access-list.help.rules-order": {
- "defaultMessage": "Các quy tắc cho phép và từ chối sẽ được thực thi theo thứ tự được xác định."
+ "defaultMessage": "Lưu ý: các quy tắc cho phép và từ chối được áp dụng theo đúng thứ tự khai báo."
+ },
+ "access-list.inherit": {
+ "defaultMessage": "Kế thừa từ máy chủ proxy"
+ },
+ "access-list.inherit.subtitle": {
+ "defaultMessage": "Sử dụng danh sách truy cập của máy chủ proxy"
},
"access-list.pass-auth": {
- "defaultMessage": "Chuyển xác thực lên thượng nguồn"
+ "defaultMessage": "Chuyển tiếp thông tin xác thực tới máy chủ đích"
},
"access-list.public": {
"defaultMessage": "Có thể truy cập công khai"
@@ -23,6 +86,9 @@
"access-list.public.subtitle": {
"defaultMessage": "Không cần xác thực cơ bản"
},
+ "access-list.rule-source.placeholder": {
+ "defaultMessage": "192.168.1.100 hoặc 192.168.1.0/24 hoặc 2001:0db8::/32"
+ },
"access-list.satisfy-any": {
"defaultMessage": "Thỏa mãn điều kiện bất kỳ"
},
@@ -36,7 +102,13 @@
"defaultMessage": "Thêm"
},
"action.add-location": {
- "defaultMessage": "Thêm Vị trí"
+ "defaultMessage": "Thêm đường dẫn"
+ },
+ "action.allow": {
+ "defaultMessage": "Cho phép"
+ },
+ "action.clear": {
+ "defaultMessage": "Xóa bộ lọc"
},
"action.close": {
"defaultMessage": "Đóng"
@@ -44,6 +116,9 @@
"action.delete": {
"defaultMessage": "Xóa"
},
+ "action.deny": {
+ "defaultMessage": "Từ chối"
+ },
"action.disable": {
"defaultMessage": "Tắt"
},
@@ -56,6 +131,9 @@
"action.enable": {
"defaultMessage": "Bật"
},
+ "action.logs": {
+ "defaultMessage": "Nhật ký"
+ },
"action.permissions": {
"defaultMessage": "Quyền"
},
@@ -63,10 +141,13 @@
"defaultMessage": "Gia hạn"
},
"action.view-details": {
- "defaultMessage": "Xem Chi tiết"
+ "defaultMessage": "Xem chi tiết"
},
"auditlogs": {
- "defaultMessage": "Nhật ký kiểm tra"
+ "defaultMessage": "Nhật ký hoạt động"
+ },
+ "auto": {
+ "defaultMessage": "Tự động"
},
"cancel": {
"defaultMessage": "Hủy"
@@ -75,19 +156,19 @@
"defaultMessage": "Chứng chỉ"
},
"certificate.custom-certificate": {
- "defaultMessage": "Certificate (crt)"
+ "defaultMessage": "Chứng chỉ"
},
"certificate.custom-certificate-key": {
- "defaultMessage": "Certificate Key"
+ "defaultMessage": "Khóa riêng của chứng chỉ"
},
"certificate.custom-intermediate": {
- "defaultMessage": "Intermediate Certificate"
+ "defaultMessage": "Chứng chỉ trung gian"
},
"certificate.in-use": {
"defaultMessage": "Đang sử dụng"
},
"certificate.none.subtitle": {
- "defaultMessage": "Không có chứng chỉ nào được chỉ định"
+ "defaultMessage": "Chưa gán chứng chỉ nào"
},
"certificate.none.subtitle.for-http": {
"defaultMessage": "Máy chủ này sẽ không sử dụng HTTPS"
@@ -96,19 +177,19 @@
"defaultMessage": "Không có"
},
"certificate.not-in-use": {
- "defaultMessage": "Không được dùng"
+ "defaultMessage": "Chưa sử dụng"
},
"certificate.renew": {
- "defaultMessage": "Gia hạn Chứng chỉ"
+ "defaultMessage": "Gia hạn chứng chỉ"
},
"certificates": {
- "defaultMessage": "Danh sách chứng chỉ"
+ "defaultMessage": "Chứng chỉ"
},
"certificates.custom": {
"defaultMessage": "Chứng chỉ tùy chỉnh"
},
"certificates.custom.warning": {
- "defaultMessage": "Các tệp chính được bảo vệ bằng cụm mật khẩu không được hỗ trợ."
+ "defaultMessage": "Không hỗ trợ tệp khóa có đặt cụm mật khẩu bảo vệ."
},
"certificates.dns.credentials": {
"defaultMessage": "Nội dung tệp thông tin xác thực"
@@ -120,40 +201,43 @@
"defaultMessage": "Dữ liệu này sẽ được lưu trữ dưới dạng bản rõ trong cơ sở dữ liệu và trong một tệp!"
},
"certificates.dns.propagation-seconds": {
- "defaultMessage": "Thời gian lan truyền (Giây)"
+ "defaultMessage": "Thời gian lan truyền (giây)"
},
"certificates.dns.propagation-seconds-note": {
- "defaultMessage": "Để trống để sử dụng giá trị mặc định của plugin. Số giây chờ truyền DNS."
+ "defaultMessage": "Để trống để dùng giá trị mặc định của plugin. Số giây chờ bản ghi DNS lan truyền."
},
"certificates.dns.provider": {
"defaultMessage": "Nhà cung cấp DNS"
},
+ "certificates.dns.provider.placeholder": {
+ "defaultMessage": "Chọn nhà cung cấp…"
+ },
"certificates.dns.warning": {
"defaultMessage": "Phần này yêu cầu một số kiến thức về Certbot và các plugin DNS của nó. Vui lòng tham khảo tài liệu plugin tương ứng."
},
"certificates.http.reachability-404": {
- "defaultMessage": "Có một máy chủ được tìm thấy ở miền này nhưng có vẻ như nó không phải là NPM. Vui lòng đảm bảo tên miền của bạn trỏ đến IP nơi phiên bản NPM của bạn đang chạy."
+ "defaultMessage": "Tìm thấy một máy chủ tại tên miền này nhưng có vẻ không phải Nginx Proxy Manager. Vui lòng đảm bảo tên miền trỏ đến IP của máy đang chạy NPM."
},
"certificates.http.reachability-failed-to-check": {
"defaultMessage": "Không thể kiểm tra khả năng truy cập do lỗi giao tiếp với site24x7.com."
},
"certificates.http.reachability-not-resolved": {
- "defaultMessage": "Không có máy chủ có sẵn tại tên miền này. Vui lòng đảm bảo rằng miền của bạn tồn tại và trỏ đến IP nơi phiên bản NPM của bạn đang chạy và nếu cần, cổng 80 sẽ được chuyển tiếp trong bộ định tuyến của bạn."
+ "defaultMessage": "Không có máy chủ nào tại tên miền này. Vui lòng đảm bảo tên miền tồn tại, trỏ đến IP của máy đang chạy NPM và nếu cần, cổng 80 đã được chuyển tiếp (port forwarding) trên router."
},
"certificates.http.reachability-ok": {
- "defaultMessage": "Máy chủ của bạn có thể truy cập được và có thể tạo chứng chỉ."
+ "defaultMessage": "Máy chủ của bạn truy cập được, có thể tiến hành tạo chứng chỉ."
},
"certificates.http.reachability-other": {
- "defaultMessage": "Có một máy chủ được tìm thấy ở miền này nhưng nó trả về mã trạng thái không mong muốn {code}. Đây có phải là máy chủ NPM không? Vui lòng đảm bảo tên miền của bạn trỏ đến IP nơi phiên bản NPM của bạn đang chạy."
+ "defaultMessage": "Tìm thấy một máy chủ tại tên miền này nhưng nó trả về mã trạng thái không mong đợi {code}. Đây có phải máy chủ NPM không? Vui lòng đảm bảo tên miền trỏ đến IP của máy đang chạy NPM."
},
"certificates.http.reachability-wrong-data": {
- "defaultMessage": "Có một máy chủ được tìm thấy ở miền này nhưng nó trả về một dữ liệu không mong muốn. Đây có phải là máy chủ NPM không? Vui lòng đảm bảo tên miền của bạn trỏ đến IP nơi phiên bản NPM của bạn đang chạy."
+ "defaultMessage": "Tìm thấy một máy chủ tại tên miền này nhưng nó trả về dữ liệu không mong đợi. Đây có phải máy chủ NPM không? Vui lòng đảm bảo tên miền trỏ đến IP của máy đang chạy NPM."
},
"certificates.http.test-results": {
"defaultMessage": "Kết quả kiểm tra"
},
"certificates.http.warning": {
- "defaultMessage": "Các miền này phải được cấu hình sẵn để trỏ đến cài đặt này."
+ "defaultMessage": "Các tên miền này phải được cấu hình sẵn để trỏ đến máy chủ NPM này."
},
"certificates.key-type": {
"defaultMessage": "Loại khóa"
@@ -177,16 +261,16 @@
"defaultMessage": "Truy cập"
},
"column.authorization": {
- "defaultMessage": "Ủy quyền"
+ "defaultMessage": "Xác thực"
},
"column.authorizations": {
- "defaultMessage": "Danh sách ủy quyền"
+ "defaultMessage": "Xác thực"
},
"column.custom-locations": {
- "defaultMessage": "Quy tắc đường dẫn tùy chỉnh (Vị trí)"
+ "defaultMessage": "Đường dẫn tùy chỉnh"
},
"column.destination": {
- "defaultMessage": "Mục tiêu"
+ "defaultMessage": "Đích"
},
"column.details": {
"defaultMessage": "Chi tiết"
@@ -194,6 +278,9 @@
"column.email": {
"defaultMessage": "Email"
},
+ "column.error": {
+ "defaultMessage": "Lỗi"
+ },
"column.event": {
"defaultMessage": "Sự kiện"
},
@@ -201,10 +288,10 @@
"defaultMessage": "Hết hạn"
},
"column.http-code": {
- "defaultMessage": "HTTP Code"
+ "defaultMessage": "Mã HTTP"
},
"column.incoming-port": {
- "defaultMessage": "Cổng đến"
+ "defaultMessage": "Cổng vào"
},
"column.name": {
"defaultMessage": "Tên"
@@ -228,10 +315,10 @@
"defaultMessage": "Tất cả"
},
"column.satisfy-any": {
- "defaultMessage": "Bất kì"
+ "defaultMessage": "Bất kỳ"
},
"column.scheme": {
- "defaultMessage": "Scheme"
+ "defaultMessage": "Giao thức"
},
"column.source": {
"defaultMessage": "Nguồn"
@@ -243,7 +330,7 @@
"defaultMessage": "Trạng thái"
},
"created-on": {
- "defaultMessage": "Đã tạo: {date}"
+ "defaultMessage": "Tạo lúc: {date}"
},
"dashboard": {
"defaultMessage": "Bảng điều khiển"
@@ -255,19 +342,19 @@
"defaultMessage": "Máy chủ 404"
},
"dead-hosts.count": {
- "defaultMessage": "Số trang lỗi {count}"
+ "defaultMessage": "{count} máy chủ 404"
},
"disabled": {
"defaultMessage": "Đã tắt"
},
"domain-names": {
- "defaultMessage": "Danh sách tên miền"
+ "defaultMessage": "Tên miền"
},
"domain-names.max": {
"defaultMessage": "Tối đa {count} tên miền"
},
"domain-names.placeholder": {
- "defaultMessage": "Nhập tên miền vào đây..."
+ "defaultMessage": "Nhập để thêm tên miền…"
},
"domain-names.wildcards-not-permitted": {
"defaultMessage": "Ký tự đại diện không được phép cho loại này"
@@ -275,6 +362,9 @@
"domain-names.wildcards-not-supported": {
"defaultMessage": "Ký tự đại diện không được hỗ trợ cho CA này"
},
+ "domains.advanced": {
+ "defaultMessage": "Nâng cao"
+ },
"domains.force-ssl": {
"defaultMessage": "Bắt buộc SSL"
},
@@ -282,13 +372,16 @@
"defaultMessage": "Bật HSTS"
},
"domains.hsts-subdomains": {
- "defaultMessage": "Tên miền phụ HSTS"
+ "defaultMessage": "HSTS cho tên miền con"
},
"domains.http2-support": {
"defaultMessage": "Hỗ trợ HTTP/2"
},
+ "domains.trust-forwarded-proto": {
+ "defaultMessage": "Tin cậy header X-Forwarded-Proto từ proxy phía trước"
+ },
"domains.use-dns": {
- "defaultMessage": "Dùng thử thách DNS"
+ "defaultMessage": "Xác thực qua DNS"
},
"email-address": {
"defaultMessage": "Địa chỉ email"
@@ -297,19 +390,19 @@
"defaultMessage": "Không có kết quả nào"
},
"empty-subtitle": {
- "defaultMessage": "Tại sao bạn không tạo một cái luôn?"
+ "defaultMessage": "Hãy bắt đầu bằng cách tạo mới."
},
"enabled": {
"defaultMessage": "Đã bật"
},
"error.access.at-least-one": {
- "defaultMessage": "Yêu cầu ít nhất một quy tắc ủy quyền hoặc truy cập"
+ "defaultMessage": "Cần ít nhất một tài khoản xác thực hoặc một quy tắc truy cập"
},
"error.access.duplicate-usernames": {
- "defaultMessage": "Tên người dùng được ủy quyền phải là duy nhất"
+ "defaultMessage": "Tên người dùng xác thực không được trùng nhau"
},
"error.invalid-auth": {
- "defaultMessage": "Email hoặc Mật khẩu không hợp lệ"
+ "defaultMessage": "Email hoặc mật khẩu không hợp lệ"
},
"error.invalid-domain": {
"defaultMessage": "Tên miền không hợp lệ: {domain}"
@@ -336,7 +429,7 @@
"defaultMessage": "Mật khẩu phải khớp"
},
"error.required": {
- "defaultMessage": "Điều này là bắt buộc"
+ "defaultMessage": "Trường này là bắt buộc"
},
"expires.on": {
"defaultMessage": "Hết hạn: {date}"
@@ -344,14 +437,17 @@
"footer.github-fork": {
"defaultMessage": "Fork dự án này trên Github"
},
+ "form.unsaved-changes": {
+ "defaultMessage": "Bạn có các thay đổi chưa được lưu. Bạn có chắc chắn muốn hủy bỏ chúng không?"
+ },
"host.flags.block-exploits": {
"defaultMessage": "Chặn các hoạt động khai thác phổ biến"
},
"host.flags.cache-assets": {
- "defaultMessage": "Cache tài nguyên"
+ "defaultMessage": "Bộ nhớ đệm tài nguyên"
},
"host.flags.preserve-path": {
- "defaultMessage": "Bảo toàn đường dẫn"
+ "defaultMessage": "Giữ nguyên đường dẫn"
},
"host.flags.protocols": {
"defaultMessage": "Giao thức"
@@ -360,16 +456,16 @@
"defaultMessage": "Hỗ trợ Websockets"
},
"host.forward-port": {
- "defaultMessage": "Chuyển tiếp cổng"
+ "defaultMessage": "Cổng đích"
},
"host.forward-scheme": {
- "defaultMessage": "Scheme"
+ "defaultMessage": "Giao thức"
},
"hosts": {
"defaultMessage": "Máy chủ"
},
"http-only": {
- "defaultMessage": "HTTP Only"
+ "defaultMessage": "Chỉ HTTP"
},
"lets-encrypt": {
"defaultMessage": "Let's Encrypt"
@@ -381,28 +477,94 @@
"defaultMessage": "Let's Encrypt qua HTTP"
},
"loading": {
- "defaultMessage": "Đang tải..."
+ "defaultMessage": "Đang tải…"
+ },
+ "location.advanced-config": {
+ "defaultMessage": "Có cấu hình Nginx tùy chỉnh"
+ },
+ "location.filter": {
+ "defaultMessage": "Lọc theo đường dẫn hoặc đích"
+ },
+ "login.2fa-code": {
+ "defaultMessage": "Mã xác minh"
+ },
+ "login.2fa-code-placeholder": {
+ "defaultMessage": "Nhập mã"
+ },
+ "login.2fa-description": {
+ "defaultMessage": "Nhập mã từ ứng dụng xác thực của bạn"
+ },
+ "login.2fa-title": {
+ "defaultMessage": "Xác thực hai lớp"
+ },
+ "login.2fa-verify": {
+ "defaultMessage": "Xác minh"
},
"login.title": {
"defaultMessage": "Đăng nhập vào tài khoản của bạn"
},
+ "logs": {
+ "defaultMessage": "Nhật ký"
+ },
+ "logs.channel.access": {
+ "defaultMessage": "Truy cập"
+ },
+ "logs.channel.error": {
+ "defaultMessage": "Lỗi"
+ },
+ "logs.download": {
+ "defaultMessage": "Tải xuống"
+ },
+ "logs.empty": {
+ "defaultMessage": "Chưa có mục nhật ký nào."
+ },
+ "logs.level": {
+ "defaultMessage": "Mức độ"
+ },
+ "logs.level.all": {
+ "defaultMessage": "Tất cả mức độ"
+ },
+ "logs.lines": {
+ "defaultMessage": "Số dòng"
+ },
+ "logs.live": {
+ "defaultMessage": "Trực tiếp"
+ },
+ "logs.paused": {
+ "defaultMessage": "Đã tạm dừng"
+ },
+ "logs.refresh": {
+ "defaultMessage": "Làm mới"
+ },
+ "logs.search-placeholder": {
+ "defaultMessage": "Tìm trong nhật ký…"
+ },
+ "logs.source": {
+ "defaultMessage": "Nguồn"
+ },
+ "logs.source.system": {
+ "defaultMessage": "Hệ thống"
+ },
+ "logs.truncated-warning": {
+ "defaultMessage": "Chỉ hiển thị phần mới nhất của tệp này."
+ },
"nginx-config.label": {
"defaultMessage": "Cấu hình Nginx tùy chỉnh"
},
"nginx-config.placeholder": {
- "defaultMessage": "# Nhập cấu hình Nginx tùy chỉnh của bạn vào đây và bạn phải tự chịu rủi ro!"
+ "defaultMessage": "# Nhập cấu hình Nginx tùy chỉnh tại đây, bạn tự chịu rủi ro!"
},
"no-permission-error": {
- "defaultMessage": "Bạn không có quyền truy cập trang này."
+ "defaultMessage": "Bạn không có quyền xem nội dung này."
},
"notfound.action": {
"defaultMessage": "Về trang chủ"
},
"notfound.content": {
- "defaultMessage": "Chúng tôi xin lỗi nhưng trang bạn đang tìm kiếm không được tìm thấy"
+ "defaultMessage": "Rất tiếc, không tìm thấy trang bạn yêu cầu"
},
"notfound.title": {
- "defaultMessage": "Rất tiếc… Bạn vừa tìm thấy một trang lỗi"
+ "defaultMessage": "Ối… Bạn vừa lạc vào một trang lỗi"
},
"notification.error": {
"defaultMessage": "Lỗi"
@@ -417,7 +579,7 @@
"defaultMessage": "{object} đã được bật"
},
"notification.object-renewed": {
- "defaultMessage": "{object} đã được làm mới"
+ "defaultMessage": "{object} đã được gia hạn"
},
"notification.object-saved": {
"defaultMessage": "{object} đã được lưu"
@@ -435,13 +597,13 @@
"defaultMessage": "Xóa {object}"
},
"object.delete.content": {
- "defaultMessage": "Bạn có chắc muốn xóa {object} không?"
+ "defaultMessage": "Bạn có chắc muốn xóa {object} này không?"
},
"object.edit": {
"defaultMessage": "Chỉnh sửa {object}"
},
"object.empty": {
- "defaultMessage": "Không có {objects}"
+ "defaultMessage": "Chưa có {objects} nào"
},
"object.event.created": {
"defaultMessage": "Đã tạo {object}"
@@ -477,10 +639,16 @@
"defaultMessage": "Tạo mật khẩu ngẫu nhiên"
},
"password.hide": {
- "defaultMessage": "Ẩn Mật khẩu"
+ "defaultMessage": "Ẩn mật khẩu"
},
"password.show": {
- "defaultMessage": "Hiện Mật khẩu"
+ "defaultMessage": "Hiện mật khẩu"
+ },
+ "permissions.allowed-proxy-hosts": {
+ "defaultMessage": "Máy chủ proxy được phép"
+ },
+ "permissions.allowed-proxy-hosts-help": {
+ "defaultMessage": "Cấp thêm quyền truy cập vào các máy chủ proxy đã chọn do người dùng khác tạo (ngoài các mục do chính người dùng tạo)."
},
"permissions.hidden": {
"defaultMessage": "Ẩn"
@@ -488,6 +656,9 @@
"permissions.manage": {
"defaultMessage": "Quản lý"
},
+ "permissions.no-proxy-hosts": {
+ "defaultMessage": "Không tìm thấy máy chủ proxy nào"
+ },
"permissions.view": {
"defaultMessage": "Chỉ xem"
},
@@ -495,16 +666,16 @@
"defaultMessage": "Tất cả các mục"
},
"permissions.visibility.title": {
- "defaultMessage": "Khả năng hiển thị mục"
+ "defaultMessage": "Phạm vi hiển thị"
},
"permissions.visibility.user": {
- "defaultMessage": "Chỉ các mục đã tạo"
+ "defaultMessage": "Chỉ các mục do mình tạo"
},
"proxy-host": {
"defaultMessage": "Máy chủ proxy"
},
"proxy-host.forward-host": {
- "defaultMessage": "Chuyển tiếp Hostname / IP"
+ "defaultMessage": "Hostname / IP đích"
},
"proxy-hosts": {
"defaultMessage": "Máy chủ proxy"
@@ -516,25 +687,43 @@
"defaultMessage": "Công khai"
},
"redirection-host": {
- "defaultMessage": "Redirection Host"
+ "defaultMessage": "Máy chủ chuyển hướng"
},
"redirection-host.forward-domain": {
- "defaultMessage": "Chuyển tiếp Tên miền"
+ "defaultMessage": "Tên miền đích"
},
"redirection-host.forward-http-code": {
- "defaultMessage": "HTTP Code"
+ "defaultMessage": "Mã HTTP"
},
"redirection-hosts": {
- "defaultMessage": "Redirection Hosts"
+ "defaultMessage": "Máy chủ chuyển hướng"
},
"redirection-hosts.count": {
"defaultMessage": "{count} máy chủ chuyển hướng"
},
+ "redirection-hosts.http-code.300": {
+ "defaultMessage": "300 Nhiều lựa chọn"
+ },
+ "redirection-hosts.http-code.301": {
+ "defaultMessage": "301 Chuyển hướng vĩnh viễn"
+ },
+ "redirection-hosts.http-code.302": {
+ "defaultMessage": "302 Chuyển hướng tạm thời"
+ },
+ "redirection-hosts.http-code.303": {
+ "defaultMessage": "303 Xem trang khác"
+ },
+ "redirection-hosts.http-code.307": {
+ "defaultMessage": "307 Chuyển hướng tạm thời (giữ phương thức)"
+ },
+ "redirection-hosts.http-code.308": {
+ "defaultMessage": "308 Chuyển hướng vĩnh viễn (giữ phương thức)"
+ },
"role.admin": {
"defaultMessage": "Quản trị viên"
},
"role.standard-user": {
- "defaultMessage": "Người dùng bình thường"
+ "defaultMessage": "Người dùng thường"
},
"save": {
"defaultMessage": "Lưu"
@@ -555,10 +744,10 @@
"defaultMessage": "Không có phản hồi (444)"
},
"settings.default-site.congratulations": {
- "defaultMessage": "Trang chào mừng"
+ "defaultMessage": "Trang chúc mừng"
},
"settings.default-site.description": {
- "defaultMessage": "Hiển thị gì khi Nginx gặp phải Máy chủ không xác định"
+ "defaultMessage": "Nội dung hiển thị khi Nginx nhận yêu cầu tới tên miền không xác định"
},
"settings.default-site.html": {
"defaultMessage": "HTML tùy chỉnh"
@@ -582,19 +771,22 @@
"defaultMessage": "Chứng chỉ SSL"
},
"stream": {
- "defaultMessage": "Stream"
+ "defaultMessage": "Luồng TCP/UDP"
},
"stream.forward-host": {
- "defaultMessage": "Chuyển tiếp Host"
+ "defaultMessage": "Máy chủ đích"
+ },
+ "stream.forward-host.placeholder": {
+ "defaultMessage": "example.com hoặc 10.0.0.1 hoặc 2001:db8:3333:4444:5555:6666:7777:8888"
},
"stream.incoming-port": {
"defaultMessage": "Cổng vào"
},
"streams": {
- "defaultMessage": "Danh sách các Stream"
+ "defaultMessage": "Luồng TCP/UDP"
},
"streams.count": {
- "defaultMessage": "Số Stream {count}"
+ "defaultMessage": "{count} luồng TCP/UDP"
},
"streams.tcp": {
"defaultMessage": "TCP"
@@ -606,16 +798,16 @@
"defaultMessage": "Kiểm tra"
},
"update-available": {
- "defaultMessage": "Cập nhật khả dụng: {latestVersion}"
+ "defaultMessage": "Đã có bản cập nhật: {latestVersion}"
},
"user": {
"defaultMessage": "Người dùng"
},
"user.change-password": {
- "defaultMessage": "Đổi Mật khẩu"
+ "defaultMessage": "Đổi mật khẩu"
},
"user.confirm-password": {
- "defaultMessage": "Xác nhận Mật khẩu"
+ "defaultMessage": "Xác nhận mật khẩu"
},
"user.current-password": {
"defaultMessage": "Mật khẩu hiện tại"
@@ -624,10 +816,10 @@
"defaultMessage": "Chỉnh sửa hồ sơ"
},
"user.full-name": {
- "defaultMessage": "Tên"
+ "defaultMessage": "Họ và tên"
},
"user.login-as": {
- "defaultMessage": "Đăng nhập bằng {name}"
+ "defaultMessage": "Đăng nhập với tư cách {name}"
},
"user.logout": {
"defaultMessage": "Đăng xuất"
@@ -639,7 +831,7 @@
"defaultMessage": "Tên hiển thị"
},
"user.set-password": {
- "defaultMessage": "Đặt Mật khẩu"
+ "defaultMessage": "Đặt mật khẩu"
},
"user.set-permissions": {
"defaultMessage": "Đặt quyền cho {name}"
@@ -650,10 +842,13 @@
"user.switch-light": {
"defaultMessage": "Chuyển sang chế độ sáng"
},
+ "user.two-factor": {
+ "defaultMessage": "Xác thực hai lớp"
+ },
"username": {
"defaultMessage": "Tên người dùng"
},
"users": {
- "defaultMessage": "Danh sách người dùng"
+ "defaultMessage": "Người dùng"
}
}
diff --git a/frontend/src/locale/src/zh-tw.json b/frontend/src/locale/src/zh-tw.json
new file mode 100644
index 000000000..fb4c79525
--- /dev/null
+++ b/frontend/src/locale/src/zh-tw.json
@@ -0,0 +1,854 @@
+{
+ "2fa.backup-codes-remaining": {
+ "defaultMessage": "剩餘備用碼:{count}"
+ },
+ "2fa.backup-warning": {
+ "defaultMessage": "請將這些備用碼存放在安全的地方。每個備用碼都只能使用一次。"
+ },
+ "2fa.disable": {
+ "defaultMessage": "停用雙因素驗證"
+ },
+ "2fa.disable-confirm": {
+ "defaultMessage": "停用 2FA"
+ },
+ "2fa.disable-warning": {
+ "defaultMessage": "停用雙因素驗證會降低帳號的安全性。"
+ },
+ "2fa.disabled": {
+ "defaultMessage": "已停用"
+ },
+ "2fa.done": {
+ "defaultMessage": "我已儲存備用碼"
+ },
+ "2fa.enable": {
+ "defaultMessage": "啟用雙因素驗證"
+ },
+ "2fa.enabled": {
+ "defaultMessage": "已啟用"
+ },
+ "2fa.enter-code": {
+ "defaultMessage": "輸入驗證碼"
+ },
+ "2fa.enter-code-disable": {
+ "defaultMessage": "輸入驗證碼以停用"
+ },
+ "2fa.regenerate": {
+ "defaultMessage": "重新產生"
+ },
+ "2fa.regenerate-backup": {
+ "defaultMessage": "重新產生備用碼"
+ },
+ "2fa.regenerate-instructions": {
+ "defaultMessage": "請輸入驗證碼以產生新的備用碼。舊的備用碼將會失效。"
+ },
+ "2fa.secret-key": {
+ "defaultMessage": "祕密金鑰"
+ },
+ "2fa.setup-instructions": {
+ "defaultMessage": "請用驗證器 App 掃描此 QR Code,或手動輸入祕密金鑰。"
+ },
+ "2fa.status": {
+ "defaultMessage": "狀態"
+ },
+ "2fa.title": {
+ "defaultMessage": "雙因素驗證"
+ },
+ "2fa.verify-enable": {
+ "defaultMessage": "驗證並啟用"
+ },
+ "access-list": {
+ "defaultMessage": "存取清單"
+ },
+ "access-list.access-count": {
+ "defaultMessage": "{count} 條規則"
+ },
+ "access-list.auth-count": {
+ "defaultMessage": "{count} 位使用者"
+ },
+ "access-list.help-rules-last": {
+ "defaultMessage": "當至少有 1 條規則時,系統會在最後加入一條「拒絕全部」規則"
+ },
+ "access-list.help.rules-order": {
+ "defaultMessage": "允許與拒絕規則會依照設定順序套用。"
+ },
+ "access-list.inherit": {
+ "defaultMessage": "沿用代理主機的設定"
+ },
+ "access-list.inherit.subtitle": {
+ "defaultMessage": "使用代理主機的存取清單"
+ },
+ "access-list.pass-auth": {
+ "defaultMessage": "將驗證資訊傳送至上游伺服器"
+ },
+ "access-list.public": {
+ "defaultMessage": "可公開存取"
+ },
+ "access-list.public.subtitle": {
+ "defaultMessage": "不需要基本驗證"
+ },
+ "access-list.rule-source.placeholder": {
+ "defaultMessage": "192.168.1.100 或 192.168.1.0/24 或 2001:0db8::/32"
+ },
+ "access-list.satisfy-any": {
+ "defaultMessage": "符合任一條件即可"
+ },
+ "access-list.subtitle": {
+ "defaultMessage": "{users} 位使用者,{rules} 條規則,建立於 {date}"
+ },
+ "access-lists": {
+ "defaultMessage": "存取清單"
+ },
+ "action.add": {
+ "defaultMessage": "新增"
+ },
+ "action.add-location": {
+ "defaultMessage": "新增路徑"
+ },
+ "action.allow": {
+ "defaultMessage": "允許"
+ },
+ "action.clear": {
+ "defaultMessage": "清除"
+ },
+ "action.close": {
+ "defaultMessage": "關閉"
+ },
+ "action.delete": {
+ "defaultMessage": "刪除"
+ },
+ "action.deny": {
+ "defaultMessage": "拒絕"
+ },
+ "action.disable": {
+ "defaultMessage": "停用"
+ },
+ "action.download": {
+ "defaultMessage": "下載"
+ },
+ "action.edit": {
+ "defaultMessage": "編輯"
+ },
+ "action.enable": {
+ "defaultMessage": "啟用"
+ },
+ "action.logs": {
+ "defaultMessage": "紀錄"
+ },
+ "action.permissions": {
+ "defaultMessage": "權限"
+ },
+ "action.renew": {
+ "defaultMessage": "續期"
+ },
+ "action.view-details": {
+ "defaultMessage": "查看詳細資訊"
+ },
+ "auditlogs": {
+ "defaultMessage": "稽核紀錄"
+ },
+ "auto": {
+ "defaultMessage": "自動"
+ },
+ "cancel": {
+ "defaultMessage": "取消"
+ },
+ "certificate": {
+ "defaultMessage": "憑證"
+ },
+ "certificate.custom-certificate": {
+ "defaultMessage": "憑證"
+ },
+ "certificate.custom-certificate-key": {
+ "defaultMessage": "憑證私鑰"
+ },
+ "certificate.custom-intermediate": {
+ "defaultMessage": "中繼憑證"
+ },
+ "certificate.in-use": {
+ "defaultMessage": "使用中"
+ },
+ "certificate.none.subtitle": {
+ "defaultMessage": "未指派憑證"
+ },
+ "certificate.none.subtitle.for-http": {
+ "defaultMessage": "此主機不會使用 HTTPS"
+ },
+ "certificate.none.title": {
+ "defaultMessage": "無"
+ },
+ "certificate.not-in-use": {
+ "defaultMessage": "未使用"
+ },
+ "certificate.renew": {
+ "defaultMessage": "續期憑證"
+ },
+ "certificates": {
+ "defaultMessage": "憑證"
+ },
+ "certificates.custom": {
+ "defaultMessage": "自訂憑證"
+ },
+ "certificates.custom.warning": {
+ "defaultMessage": "不支援受密碼片語保護的私鑰檔案。"
+ },
+ "certificates.dns.credentials": {
+ "defaultMessage": "驗證資料檔案內容"
+ },
+ "certificates.dns.credentials-note": {
+ "defaultMessage": "這個外掛需要一份設定檔,其中包含 API 權杖或服務供應商要求的其他驗證資料。"
+ },
+ "certificates.dns.credentials-warning": {
+ "defaultMessage": "這些資料會以純文字儲存在資料庫與檔案中!"
+ },
+ "certificates.dns.propagation-seconds": {
+ "defaultMessage": "DNS 傳播時間(秒)"
+ },
+ "certificates.dns.propagation-seconds-note": {
+ "defaultMessage": "留空即可使用外掛的預設值。此欄位代表等待 DNS 傳播的秒數。"
+ },
+ "certificates.dns.provider": {
+ "defaultMessage": "DNS 服務供應商"
+ },
+ "certificates.dns.provider.placeholder": {
+ "defaultMessage": "選擇服務供應商…"
+ },
+ "certificates.dns.warning": {
+ "defaultMessage": "這部分需要具備一些 Certbot 與 DNS 外掛的相關知識。請參閱對應外掛的官方文件。"
+ },
+ "certificates.http.reachability-404": {
+ "defaultMessage": "此網域有伺服器回應,但似乎不是 Nginx Proxy Manager。請確認網域已指向執行 NPM 的 IP 位址。"
+ },
+ "certificates.http.reachability-failed-to-check": {
+ "defaultMessage": "由於與 site24x7.com 通訊時發生錯誤,無法檢查是否可連線。"
+ },
+ "certificates.http.reachability-not-resolved": {
+ "defaultMessage": "此網域沒有伺服器回應。請確認網域存在,並指向執行 NPM 的 IP 位址;如有需要,請在路由器上轉送連接埠 80。"
+ },
+ "certificates.http.reachability-ok": {
+ "defaultMessage": "可以連線到您的伺服器,應可正常申請憑證。"
+ },
+ "certificates.http.reachability-other": {
+ "defaultMessage": "此網域有伺服器回應,但傳回了未預期的狀態碼 {code}。該伺服器是 NPM 嗎?請確認網域已指向執行 NPM 的 IP 位址。"
+ },
+ "certificates.http.reachability-wrong-data": {
+ "defaultMessage": "此網域有伺服器回應,但傳回了未預期的資料。該伺服器是 NPM 嗎?請確認網域已指向執行 NPM 的 IP 位址。"
+ },
+ "certificates.http.test-results": {
+ "defaultMessage": "測試結果"
+ },
+ "certificates.http.warning": {
+ "defaultMessage": "這些網域必須已設定為指向目前這套 Nginx Proxy Manager。"
+ },
+ "certificates.key-type": {
+ "defaultMessage": "金鑰類型"
+ },
+ "certificates.key-type-description": {
+ "defaultMessage": "RSA 相容性較廣;ECDSA 速度較快且更安全,但較舊的系統可能不支援。"
+ },
+ "certificates.key-type-ecdsa": {
+ "defaultMessage": "ECDSA 256"
+ },
+ "certificates.key-type-rsa": {
+ "defaultMessage": "RSA 2048"
+ },
+ "certificates.request.subtitle": {
+ "defaultMessage": "使用 Let's Encrypt"
+ },
+ "certificates.request.title": {
+ "defaultMessage": "申請新憑證"
+ },
+ "column.access": {
+ "defaultMessage": "存取規則"
+ },
+ "column.authorization": {
+ "defaultMessage": "驗證帳號"
+ },
+ "column.authorizations": {
+ "defaultMessage": "驗證帳號"
+ },
+ "column.custom-locations": {
+ "defaultMessage": "自訂路徑"
+ },
+ "column.destination": {
+ "defaultMessage": "目的地"
+ },
+ "column.details": {
+ "defaultMessage": "詳細資訊"
+ },
+ "column.email": {
+ "defaultMessage": "電子郵件"
+ },
+ "column.error": {
+ "defaultMessage": "錯誤"
+ },
+ "column.event": {
+ "defaultMessage": "事件"
+ },
+ "column.expires": {
+ "defaultMessage": "到期時間"
+ },
+ "column.http-code": {
+ "defaultMessage": "HTTP 狀態碼"
+ },
+ "column.incoming-port": {
+ "defaultMessage": "連入連接埠"
+ },
+ "column.name": {
+ "defaultMessage": "名稱"
+ },
+ "column.protocol": {
+ "defaultMessage": "通訊協定"
+ },
+ "column.provider": {
+ "defaultMessage": "服務供應商"
+ },
+ "column.roles": {
+ "defaultMessage": "角色"
+ },
+ "column.rules": {
+ "defaultMessage": "規則"
+ },
+ "column.satisfy": {
+ "defaultMessage": "條件"
+ },
+ "column.satisfy-all": {
+ "defaultMessage": "全部"
+ },
+ "column.satisfy-any": {
+ "defaultMessage": "任一"
+ },
+ "column.scheme": {
+ "defaultMessage": "通訊協定"
+ },
+ "column.source": {
+ "defaultMessage": "來源"
+ },
+ "column.ssl": {
+ "defaultMessage": "SSL"
+ },
+ "column.status": {
+ "defaultMessage": "狀態"
+ },
+ "created-on": {
+ "defaultMessage": "建立時間:{date}"
+ },
+ "dashboard": {
+ "defaultMessage": "儀表板"
+ },
+ "dead-host": {
+ "defaultMessage": "404 主機"
+ },
+ "dead-hosts": {
+ "defaultMessage": "404 主機"
+ },
+ "dead-hosts.count": {
+ "defaultMessage": "{count} 個 404 主機"
+ },
+ "disabled": {
+ "defaultMessage": "已停用"
+ },
+ "domain-names": {
+ "defaultMessage": "網域名稱"
+ },
+ "domain-names.max": {
+ "defaultMessage": "最多可使用 {count} 個網域名稱"
+ },
+ "domain-names.placeholder": {
+ "defaultMessage": "輸入網域名稱以新增…"
+ },
+ "domain-names.wildcards-not-permitted": {
+ "defaultMessage": "此類型不允許使用萬用字元"
+ },
+ "domain-names.wildcards-not-supported": {
+ "defaultMessage": "此憑證授權單位不支援萬用字元"
+ },
+ "domains.advanced": {
+ "defaultMessage": "進階"
+ },
+ "domains.force-ssl": {
+ "defaultMessage": "強制使用 SSL"
+ },
+ "domains.hsts-enabled": {
+ "defaultMessage": "啟用 HSTS"
+ },
+ "domains.hsts-subdomains": {
+ "defaultMessage": "HSTS 子網域"
+ },
+ "domains.http2-support": {
+ "defaultMessage": "支援 HTTP/2"
+ },
+ "domains.trust-forwarded-proto": {
+ "defaultMessage": "信任上游代理傳來的 X-Forwarded-Proto/X-Forwarded-Scheme 標頭"
+ },
+ "domains.use-dns": {
+ "defaultMessage": "使用 DNS 驗證"
+ },
+ "email-address": {
+ "defaultMessage": "電子郵件地址"
+ },
+ "empty-search": {
+ "defaultMessage": "找不到結果"
+ },
+ "empty-subtitle": {
+ "defaultMessage": "現在就建立一個吧。"
+ },
+ "enabled": {
+ "defaultMessage": "已啟用"
+ },
+ "error.access.at-least-one": {
+ "defaultMessage": "至少需要一個驗證帳號或一條存取規則"
+ },
+ "error.access.duplicate-usernames": {
+ "defaultMessage": "驗證帳號的使用者名稱不可重複"
+ },
+ "error.invalid-auth": {
+ "defaultMessage": "電子郵件地址或密碼無效"
+ },
+ "error.invalid-domain": {
+ "defaultMessage": "網域無效:{domain}"
+ },
+ "error.invalid-email": {
+ "defaultMessage": "電子郵件地址無效"
+ },
+ "error.max-character-length": {
+ "defaultMessage": "長度不可超過 {max} 個字元"
+ },
+ "error.max-domains": {
+ "defaultMessage": "網域數量過多,最多為 {max} 個"
+ },
+ "error.maximum": {
+ "defaultMessage": "不可大於 {max}"
+ },
+ "error.min-character-length": {
+ "defaultMessage": "長度不得少於 {min} 個字元"
+ },
+ "error.minimum": {
+ "defaultMessage": "不可小於 {min}"
+ },
+ "error.passwords-must-match": {
+ "defaultMessage": "兩次輸入的密碼必須相同"
+ },
+ "error.required": {
+ "defaultMessage": "此欄位為必填"
+ },
+ "expires.on": {
+ "defaultMessage": "到期時間:{date}"
+ },
+ "footer.github-fork": {
+ "defaultMessage": "在 GitHub 上 Fork 本專案"
+ },
+ "form.unsaved-changes": {
+ "defaultMessage": "您有尚未儲存的變更,確定要捨棄嗎?"
+ },
+ "host.flags.block-exploits": {
+ "defaultMessage": "阻擋常見攻擊"
+ },
+ "host.flags.cache-assets": {
+ "defaultMessage": "快取靜態資源"
+ },
+ "host.flags.preserve-path": {
+ "defaultMessage": "保留路徑"
+ },
+ "host.flags.protocols": {
+ "defaultMessage": "通訊協定"
+ },
+ "host.flags.websockets-upgrade": {
+ "defaultMessage": "WebSocket 支援"
+ },
+ "host.forward-port": {
+ "defaultMessage": "轉送連接埠"
+ },
+ "host.forward-scheme": {
+ "defaultMessage": "通訊協定"
+ },
+ "hosts": {
+ "defaultMessage": "主機"
+ },
+ "http-only": {
+ "defaultMessage": "僅限 HTTP"
+ },
+ "lets-encrypt": {
+ "defaultMessage": "Let's Encrypt"
+ },
+ "lets-encrypt-via-dns": {
+ "defaultMessage": "Let's Encrypt(DNS 驗證)"
+ },
+ "lets-encrypt-via-http": {
+ "defaultMessage": "Let's Encrypt(HTTP 驗證)"
+ },
+ "loading": {
+ "defaultMessage": "載入中…"
+ },
+ "location.advanced-config": {
+ "defaultMessage": "含有自訂 Nginx 設定"
+ },
+ "location.filter": {
+ "defaultMessage": "依路徑或目的地篩選"
+ },
+ "login.2fa-code": {
+ "defaultMessage": "驗證碼"
+ },
+ "login.2fa-code-placeholder": {
+ "defaultMessage": "輸入驗證碼"
+ },
+ "login.2fa-description": {
+ "defaultMessage": "請輸入驗證器 App 顯示的驗證碼"
+ },
+ "login.2fa-title": {
+ "defaultMessage": "雙因素驗證"
+ },
+ "login.2fa-verify": {
+ "defaultMessage": "驗證"
+ },
+ "login.title": {
+ "defaultMessage": "登入您的帳號"
+ },
+ "logs": {
+ "defaultMessage": "紀錄"
+ },
+ "logs.channel.access": {
+ "defaultMessage": "存取"
+ },
+ "logs.channel.error": {
+ "defaultMessage": "錯誤"
+ },
+ "logs.download": {
+ "defaultMessage": "下載"
+ },
+ "logs.empty": {
+ "defaultMessage": "尚無紀錄。"
+ },
+ "logs.level": {
+ "defaultMessage": "等級"
+ },
+ "logs.level.all": {
+ "defaultMessage": "所有等級"
+ },
+ "logs.lines": {
+ "defaultMessage": "行數"
+ },
+ "logs.live": {
+ "defaultMessage": "即時更新"
+ },
+ "logs.paused": {
+ "defaultMessage": "已暫停"
+ },
+ "logs.refresh": {
+ "defaultMessage": "重新整理"
+ },
+ "logs.search-placeholder": {
+ "defaultMessage": "搜尋紀錄…"
+ },
+ "logs.source": {
+ "defaultMessage": "來源"
+ },
+ "logs.source.system": {
+ "defaultMessage": "系統"
+ },
+ "logs.truncated-warning": {
+ "defaultMessage": "僅顯示此檔案最新的一部分內容。"
+ },
+ "nginx-config.label": {
+ "defaultMessage": "自訂 Nginx 設定"
+ },
+ "nginx-config.placeholder": {
+ "defaultMessage": "# 在此輸入自訂 Nginx 設定,風險自負!"
+ },
+ "no-permission-error": {
+ "defaultMessage": "您沒有權限查看此內容。"
+ },
+ "notfound.action": {
+ "defaultMessage": "回到首頁"
+ },
+ "notfound.content": {
+ "defaultMessage": "很抱歉,找不到您要查看的頁面。"
+ },
+ "notfound.title": {
+ "defaultMessage": "糟糕…您找到了一個錯誤頁面"
+ },
+ "notification.error": {
+ "defaultMessage": "錯誤"
+ },
+ "notification.object-deleted": {
+ "defaultMessage": "{object} 已刪除"
+ },
+ "notification.object-disabled": {
+ "defaultMessage": "{object} 已停用"
+ },
+ "notification.object-enabled": {
+ "defaultMessage": "{object} 已啟用"
+ },
+ "notification.object-renewed": {
+ "defaultMessage": "{object} 已續期"
+ },
+ "notification.object-saved": {
+ "defaultMessage": "{object} 已儲存"
+ },
+ "notification.success": {
+ "defaultMessage": "成功"
+ },
+ "object.actions-title": {
+ "defaultMessage": "{object} #{id}"
+ },
+ "object.add": {
+ "defaultMessage": "新增 {object}"
+ },
+ "object.delete": {
+ "defaultMessage": "刪除 {object}"
+ },
+ "object.delete.content": {
+ "defaultMessage": "確定要刪除 {object} 嗎?"
+ },
+ "object.edit": {
+ "defaultMessage": "編輯 {object}"
+ },
+ "object.empty": {
+ "defaultMessage": "目前沒有 {objects}"
+ },
+ "object.event.created": {
+ "defaultMessage": "已建立 {object}"
+ },
+ "object.event.deleted": {
+ "defaultMessage": "已刪除 {object}"
+ },
+ "object.event.disabled": {
+ "defaultMessage": "已停用 {object}"
+ },
+ "object.event.enabled": {
+ "defaultMessage": "已啟用 {object}"
+ },
+ "object.event.renewed": {
+ "defaultMessage": "已續期 {object}"
+ },
+ "object.event.updated": {
+ "defaultMessage": "已更新 {object}"
+ },
+ "offline": {
+ "defaultMessage": "離線"
+ },
+ "online": {
+ "defaultMessage": "線上"
+ },
+ "options": {
+ "defaultMessage": "選項"
+ },
+ "password": {
+ "defaultMessage": "密碼"
+ },
+ "password.generate": {
+ "defaultMessage": "產生隨機密碼"
+ },
+ "password.hide": {
+ "defaultMessage": "隱藏密碼"
+ },
+ "password.show": {
+ "defaultMessage": "顯示密碼"
+ },
+ "permissions.allowed-proxy-hosts": {
+ "defaultMessage": "允許存取的代理主機"
+ },
+ "permissions.allowed-proxy-hosts-help": {
+ "defaultMessage": "另外開放存取其他使用者建立的指定代理主機(使用者自己建立的主機原本就能存取)。"
+ },
+ "permissions.hidden": {
+ "defaultMessage": "隱藏"
+ },
+ "permissions.manage": {
+ "defaultMessage": "管理"
+ },
+ "permissions.no-proxy-hosts": {
+ "defaultMessage": "找不到代理主機"
+ },
+ "permissions.view": {
+ "defaultMessage": "僅供查看"
+ },
+ "permissions.visibility.all": {
+ "defaultMessage": "所有項目"
+ },
+ "permissions.visibility.title": {
+ "defaultMessage": "項目可見範圍"
+ },
+ "permissions.visibility.user": {
+ "defaultMessage": "僅限自己建立的項目"
+ },
+ "proxy-host": {
+ "defaultMessage": "代理主機"
+ },
+ "proxy-host.forward-host": {
+ "defaultMessage": "轉送主機名稱 / IP"
+ },
+ "proxy-hosts": {
+ "defaultMessage": "代理主機"
+ },
+ "proxy-hosts.count": {
+ "defaultMessage": "{count} 個代理主機"
+ },
+ "public": {
+ "defaultMessage": "公開"
+ },
+ "redirection-host": {
+ "defaultMessage": "重新導向主機"
+ },
+ "redirection-host.forward-domain": {
+ "defaultMessage": "轉送網域"
+ },
+ "redirection-host.forward-http-code": {
+ "defaultMessage": "HTTP 狀態碼"
+ },
+ "redirection-hosts": {
+ "defaultMessage": "重新導向主機"
+ },
+ "redirection-hosts.count": {
+ "defaultMessage": "{count} 個重新導向主機"
+ },
+ "redirection-hosts.http-code.300": {
+ "defaultMessage": "300 多重選擇"
+ },
+ "redirection-hosts.http-code.301": {
+ "defaultMessage": "301 永久移動"
+ },
+ "redirection-hosts.http-code.302": {
+ "defaultMessage": "302 暫時移動"
+ },
+ "redirection-hosts.http-code.303": {
+ "defaultMessage": "303 查看其他位置"
+ },
+ "redirection-hosts.http-code.307": {
+ "defaultMessage": "307 暫時重新導向"
+ },
+ "redirection-hosts.http-code.308": {
+ "defaultMessage": "308 永久重新導向"
+ },
+ "role.admin": {
+ "defaultMessage": "管理員"
+ },
+ "role.standard-user": {
+ "defaultMessage": "一般使用者"
+ },
+ "save": {
+ "defaultMessage": "儲存"
+ },
+ "setting": {
+ "defaultMessage": "設定"
+ },
+ "settings": {
+ "defaultMessage": "設定"
+ },
+ "settings.default-site": {
+ "defaultMessage": "預設網站"
+ },
+ "settings.default-site.404": {
+ "defaultMessage": "404 頁面"
+ },
+ "settings.default-site.444": {
+ "defaultMessage": "無回應(444)"
+ },
+ "settings.default-site.congratulations": {
+ "defaultMessage": "歡迎頁面"
+ },
+ "settings.default-site.description": {
+ "defaultMessage": "Nginx 收到未知主機的請求時顯示的內容"
+ },
+ "settings.default-site.html": {
+ "defaultMessage": "自訂 HTML"
+ },
+ "settings.default-site.html.placeholder": {
+ "defaultMessage": ""
+ },
+ "settings.default-site.redirect": {
+ "defaultMessage": "重新導向"
+ },
+ "setup.preamble": {
+ "defaultMessage": "建立管理員帳號即可開始使用。"
+ },
+ "setup.title": {
+ "defaultMessage": "歡迎!"
+ },
+ "sign-in": {
+ "defaultMessage": "登入"
+ },
+ "ssl-certificate": {
+ "defaultMessage": "SSL 憑證"
+ },
+ "stream": {
+ "defaultMessage": "連接埠轉送"
+ },
+ "stream.forward-host": {
+ "defaultMessage": "轉送主機"
+ },
+ "stream.forward-host.placeholder": {
+ "defaultMessage": "example.com 或 10.0.0.1 或 2001:db8:3333:4444:5555:6666:7777:8888"
+ },
+ "stream.incoming-port": {
+ "defaultMessage": "連入連接埠"
+ },
+ "streams": {
+ "defaultMessage": "連接埠轉送"
+ },
+ "streams.count": {
+ "defaultMessage": "{count} 組連接埠轉送"
+ },
+ "streams.tcp": {
+ "defaultMessage": "TCP"
+ },
+ "streams.udp": {
+ "defaultMessage": "UDP"
+ },
+ "test": {
+ "defaultMessage": "測試"
+ },
+ "update-available": {
+ "defaultMessage": "有新版本可用:{latestVersion}"
+ },
+ "user": {
+ "defaultMessage": "使用者"
+ },
+ "user.change-password": {
+ "defaultMessage": "變更密碼"
+ },
+ "user.confirm-password": {
+ "defaultMessage": "確認密碼"
+ },
+ "user.current-password": {
+ "defaultMessage": "目前密碼"
+ },
+ "user.edit-profile": {
+ "defaultMessage": "編輯個人資料"
+ },
+ "user.full-name": {
+ "defaultMessage": "姓名"
+ },
+ "user.login-as": {
+ "defaultMessage": "以 {name} 身分登入"
+ },
+ "user.logout": {
+ "defaultMessage": "登出"
+ },
+ "user.new-password": {
+ "defaultMessage": "新密碼"
+ },
+ "user.nickname": {
+ "defaultMessage": "暱稱"
+ },
+ "user.set-password": {
+ "defaultMessage": "設定密碼"
+ },
+ "user.set-permissions": {
+ "defaultMessage": "設定 {name} 的權限"
+ },
+ "user.switch-dark": {
+ "defaultMessage": "切換至深色模式"
+ },
+ "user.switch-light": {
+ "defaultMessage": "切換至淺色模式"
+ },
+ "user.two-factor": {
+ "defaultMessage": "雙因素驗證"
+ },
+ "username": {
+ "defaultMessage": "使用者名稱"
+ },
+ "users": {
+ "defaultMessage": "使用者"
+ }
+}
diff --git a/frontend/src/locale/vi.test.ts b/frontend/src/locale/vi.test.ts
new file mode 100644
index 000000000..3b8cfcb85
--- /dev/null
+++ b/frontend/src/locale/vi.test.ts
@@ -0,0 +1,68 @@
+import { describe, expect, it } from "vitest";
+import en from "./src/en.json";
+import vi from "./src/vi.json";
+
+const english = en as Record;
+const vietnamese = vi as Record;
+
+// Brand names, codes and ICU-only strings can stay the same as English.
+const allowedIdentical = new Set([
+ "certificates.key-type-ecdsa",
+ "certificates.key-type-rsa",
+ "column.email",
+ "column.ssl",
+ "lets-encrypt",
+ "object.actions-title",
+ "streams.tcp",
+ "streams.udp",
+]);
+
+describe("Vietnamese UI locale", () => {
+ it("covers every English UI key", () => {
+ const missing = Object.keys(english).filter((key) => !(key in vietnamese));
+ expect(missing).toEqual([]);
+ });
+
+ it("does not add keys that are missing from English", () => {
+ const extra = Object.keys(vietnamese).filter((key) => !(key in english));
+ expect(extra).toEqual([]);
+ });
+
+ it("is translated, not an English copy", () => {
+ const identical = Object.keys(english).filter(
+ (key) => vietnamese[key]?.defaultMessage === english[key]?.defaultMessage,
+ );
+ const unexpected = identical.filter((key) => !allowedIdentical.has(key));
+ expect(unexpected).toEqual([]);
+ });
+
+ it("keeps ICU data placeholders from English strings", () => {
+ const argNames = [
+ "count",
+ "object",
+ "objects",
+ "id",
+ "date",
+ "users",
+ "rules",
+ "max",
+ "min",
+ "domain",
+ "code",
+ "latestVersion",
+ "name",
+ ];
+ const usedArgs = (message: string) => argNames.filter((name) => message.includes(`{${name}`));
+ const broken: string[] = [];
+
+ for (const key of Object.keys(english)) {
+ const expected = usedArgs(english[key].defaultMessage).join(",");
+ const actual = usedArgs(vietnamese[key]?.defaultMessage ?? "").join(",");
+ if (expected !== actual) {
+ broken.push(`${key}: expected {${expected}} got {${actual}}`);
+ }
+ }
+
+ expect(broken).toEqual([]);
+ });
+});
diff --git a/frontend/src/modals/AccessListModal.tsx b/frontend/src/modals/AccessListModal.tsx
index 79537f5cd..01b68d3d6 100644
--- a/frontend/src/modals/AccessListModal.tsx
+++ b/frontend/src/modals/AccessListModal.tsx
@@ -1,11 +1,12 @@
import cn from "classnames";
import EasyModal, { type InnerModalProps } from "ez-modal-react";
-import { Field, Form, Formik } from "formik";
+import { Field, Form } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import type { AccessList, AccessListClient, AccessListItem } from "src/api/backend";
import { AccessClientFields, BasicAuthFields, Button, Loading } from "src/components";
+import { FormModal } from "src/components/Form";
import { useAccessList, useSetAccessList } from "src/hooks";
import { intl, T } from "src/locale";
import { validateString } from "src/modules/Validations";
@@ -25,13 +26,16 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const validate = (values: any): string | null => {
+ const items = (values.items || []).filter((i: AccessListItem) => i.username?.trim() !== "");
+ const clients = (values.clients || []).filter((i: AccessListClient) => i.address?.trim() !== "");
+
// either Auths or Clients must be defined
- if (values.items?.length === 0 && values.clients?.length === 0) {
+ if (items.length === 0 && clients.length === 0) {
return intl.formatMessage({ id: "error.access.at-least-one" });
}
// ensure the items don't contain the same username twice
- const usernames = values.items.map((i: any) => i.username);
+ const usernames = items.map((i: AccessListItem) => i.username);
const uniqueUsernames = Array.from(new Set(usernames));
if (usernames.length !== uniqueUsernames.length) {
return intl.formatMessage({ id: "error.access.duplicate-usernames" });
@@ -58,10 +62,12 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
};
// Filter out "items" to only use the "username" and "password" fields
- payload.items = (values.items || []).map((i: AccessListItem) => ({
- username: i.username,
- password: i.password,
- }));
+ payload.items = (values.items || [])
+ .filter((i: AccessListItem) => i.username?.trim() !== "")
+ .map((i: AccessListItem) => ({
+ username: i.username,
+ password: i.password,
+ }));
// Filter out "clients" to only use the "directive" and "address" fields
payload.clients = (values.clients || []).map((i: AccessListClient) => ({
@@ -86,15 +92,22 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
const toggleEnabled = cn(toggleClasses, "bg-cyan");
return (
-
- {!isLoading && error && (
-
- {error?.message || "Unknown error"}
-
+ <>
+ {(isLoading || (error && !data)) && (
+
+ {!isLoading && error && (
+
+ {error?.message || "Unknown error"}
+
+ )}
+ {isLoading && }
+
)}
- {isLoading && }
{!isLoading && data && (
- {
}
onSubmit={onSubmit}
>
- {({ setFieldValue }: any) => (
+ {({ onHide, setFieldValue }: any) => (
-
+
{
)}
-
+
)}
-
+ >
);
});
diff --git a/frontend/src/modals/ChangePasswordModal.tsx b/frontend/src/modals/ChangePasswordModal.tsx
index 48221e281..7f1d9668d 100644
--- a/frontend/src/modals/ChangePasswordModal.tsx
+++ b/frontend/src/modals/ChangePasswordModal.tsx
@@ -1,10 +1,11 @@
import EasyModal, { type InnerModalProps } from "ez-modal-react";
-import { Field, Form, Formik } from "formik";
+import { Field, Form } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import { updateAuth } from "src/api/backend";
import { Button } from "src/components";
+import { FormModal } from "src/components/Form";
import { intl, T } from "src/locale";
import { validateString } from "src/modules/Validations";
@@ -41,129 +42,130 @@ const ChangePasswordModal = EasyModal.create(({ id, visible, remove }: Props) =>
};
return (
-
-
- {() => (
-
- )}
-
-
+
+ {({ onHide }) => (
+
+ )}
+
);
});
diff --git a/frontend/src/modals/CustomCertificateModal.tsx b/frontend/src/modals/CustomCertificateModal.tsx
index deab1c523..c373a8d36 100644
--- a/frontend/src/modals/CustomCertificateModal.tsx
+++ b/frontend/src/modals/CustomCertificateModal.tsx
@@ -1,12 +1,13 @@
import { IconAlertTriangle } from "@tabler/icons-react";
import { useQueryClient } from "@tanstack/react-query";
import EasyModal, { type InnerModalProps } from "ez-modal-react";
-import { Field, Form, Formik } from "formik";
+import { Field, Form } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import { type Certificate, createCertificate, uploadCertificate, validateCertificate } from "src/api/backend";
import { Button } from "src/components";
+import { FormModal } from "src/components/Form";
import { T } from "src/locale";
import { validateString } from "src/modules/Validations";
import { showObjectSuccess } from "src/notifications";
@@ -57,175 +58,176 @@ const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModal
};
return (
-
-
- {() => (
-
-
+
{
)}
-
+
)}
-
+ >
);
});
diff --git a/frontend/src/modals/HTTPCertificateModal.test.tsx b/frontend/src/modals/HTTPCertificateModal.test.tsx
new file mode 100644
index 000000000..65d1aaed0
--- /dev/null
+++ b/frontend/src/modals/HTTPCertificateModal.test.tsx
@@ -0,0 +1,57 @@
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
+import EasyModal from "ez-modal-react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { showHTTPCertificateModal } from "./HTTPCertificateModal";
+
+const fetchMock = vi.fn
();
+beforeEach(() => {
+ localStorage.clear();
+ vi.stubGlobal("fetch", fetchMock);
+});
+afterEach(() => {
+ cleanup();
+ vi.unstubAllGlobals();
+ fetchMock.mockReset();
+});
+async function openAndTest(domains: string[], payload: Record) {
+ fetchMock.mockResolvedValueOnce(new Response(JSON.stringify(payload), { status: 200 }));
+ render(
+
+
+ ,
+ );
+ await act(async () => {
+ showHTTPCertificateModal();
+ });
+ const input = await screen.findByRole("combobox", { expanded: false });
+ for (const domain of domains) {
+ fireEvent.change(input, { target: { value: domain } });
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter", keyCode: 13 });
+ await waitFor(() => expect(input).toHaveValue(""));
+ }
+ fireEvent.click(screen.getByRole("button", { name: "Test" }));
+ await waitFor(() => expect(fetchMock).toHaveBeenCalledOnce());
+ expect(JSON.parse(String(fetchMock.mock.calls[0][1]?.body))).toEqual({ domains });
+}
+describe("mounted HTTP certificate modal", () => {
+ it("renders a hyphenated result without changing its label", async () => {
+ await openAndTest(["test-domain.example"], { "test-domain.example": "wrong-data" });
+ expect(await screen.findByText("test-domain.example:")).toBeVisible();
+ expect(screen.queryByText("testDomain.example:")).not.toBeInTheDocument();
+ });
+ it("renders both collision-prone domains with distinct results", async () => {
+ await openAndTest(["test-domain.example", "test--domain.example"], {
+ "test-domain.example": "ok",
+ "test--domain.example": "no-host",
+ });
+ const first = await screen.findByText("test-domain.example:");
+ const second = await screen.findByText("test--domain.example:");
+ expect(
+ first.parentElement?.querySelector('[data-translation-id="certificates.http.reachability-ok"]'),
+ ).not.toBeNull();
+ expect(
+ second.parentElement?.querySelector('[data-translation-id="certificates.http.reachability-not-resolved"]'),
+ ).not.toBeNull();
+ });
+});
diff --git a/frontend/src/modals/HTTPCertificateModal.tsx b/frontend/src/modals/HTTPCertificateModal.tsx
index d7ec75190..653462c8e 100644
--- a/frontend/src/modals/HTTPCertificateModal.tsx
+++ b/frontend/src/modals/HTTPCertificateModal.tsx
@@ -1,12 +1,13 @@
import { IconAlertTriangle } from "@tabler/icons-react";
import { useQueryClient } from "@tanstack/react-query";
import EasyModal, { type InnerModalProps } from "ez-modal-react";
-import { Form, Formik, Field } from "formik";
+import { Field, Form } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import { createCertificate, testHttpCertificate } from "src/api/backend";
import { Button, DomainNamesField } from "src/components";
+import { FormModal } from "src/components/Form";
import { T } from "src/locale";
import { showObjectSuccess } from "src/notifications";
@@ -109,105 +110,106 @@ const HTTPCertificateModal = EasyModal.create(({ visible, remove }: InnerModalPr
};
return (
-
-
- {() => (
-
-
-
-
-
-
-
- setErrorMsg(null)} dismissible>
- {errorMsg}
-
-
-
-
-
-
-
-
{
- setDomains(doms);
- setTestResults(null);
- }}
- />
-
- {({ field }: any) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
+
+ {({ onHide }) => (
+
+
+
+
+
+
+
+ setErrorMsg(null)} dismissible>
+ {errorMsg}
+
+
+
+
+
+
+
+
{
+ setDomains(doms);
+ setTestResults(null);
+ }}
+ />
+
+ {({ field }: any) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ {testResults ? (
+
+
+
+
+ {parseTestResults()}
- {testResults ? (
-
-
-
-
- {parseTestResults()}
-
- ) : null}
-
-
-
-
-
+ ) : null}
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
- )}
-
-
+
+
+
+
+
+
+ )}
+
);
});
diff --git a/frontend/src/modals/HelpModal.tsx b/frontend/src/modals/HelpModal.tsx
index f05816142..1d395fc11 100644
--- a/frontend/src/modals/HelpModal.tsx
+++ b/frontend/src/modals/HelpModal.tsx
@@ -18,7 +18,7 @@ const showHelpModal = (section: string, color?: string) => {
const HelpModal = EasyModal.create(({ section, color, visible, remove }: Props) => {
const [markdownText, setMarkdownText] = useState("");
- const lang = getLocale(true);
+ const lang = getLocale();
useEffect(() => {
try {
diff --git a/frontend/src/modals/PermissionsModal.tsx b/frontend/src/modals/PermissionsModal.tsx
index d363de9ed..8ac607cca 100644
--- a/frontend/src/modals/PermissionsModal.tsx
+++ b/frontend/src/modals/PermissionsModal.tsx
@@ -1,13 +1,14 @@
import { useQueryClient } from "@tanstack/react-query";
import cn from "classnames";
import EasyModal, { type InnerModalProps } from "ez-modal-react";
-import { Field, Form, Formik } from "formik";
+import { Field, Form } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import { setPermissions } from "src/api/backend";
import { Button, Loading } from "src/components";
-import { useUser } from "src/hooks";
+import { FormModal } from "src/components/Form";
+import { useProxyHosts, useUser } from "src/hooks";
import { T } from "src/locale";
import styles from "./PermissionsModal.module.css";
@@ -22,6 +23,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
const queryClient = useQueryClient();
const [errorMsg, setErrorMsg] = useState
(null);
const { data, isLoading, error } = useUser(id);
+ const { data: proxyHosts } = useProxyHosts();
const [isSubmitting, setIsSubmitting] = useState(false);
const onSubmit = async (values: any, { setSubmitting }: any) => {
@@ -29,7 +31,13 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
setIsSubmitting(true);
setErrorMsg(null);
try {
- await setPermissions(id, values);
+ // Proxy host grants only apply to "user" visibility with proxy hosts shown. Don't
+ // submit stale ones; the backend clears them too.
+ const grantsApply = values.visibility === "user" && values.proxyHosts !== "hidden";
+ await setPermissions(id, {
+ ...values,
+ meta: { ...values.meta, proxy_host_ids: grantsApply ? values.meta?.proxy_host_ids || [] : [] },
+ });
remove();
queryClient.invalidateQueries({ queryKey: ["users"] });
queryClient.invalidateQueries({ queryKey: ["user"] });
@@ -133,29 +141,42 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
const isAdmin = data?.roles.indexOf("admin") !== -1;
return (
-
- {!isLoading && error && (
-
- {error?.message || "Unknown error"}
-
+ <>
+ {(isLoading || (error && !data)) && (
+
+ {!isLoading && error && (
+
+ {error?.message || "Unknown error"}
+
+ )}
+ {isLoading && }
+
)}
- {isLoading && }
{!isLoading && data && (
-
- {() => (
+ {({ values, setFieldValue, onHide }: any) => (
@@ -219,6 +240,71 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
{({ field, form }: any) => getPermissionButtons(field, form)}
+ {values.visibility === "user" && values.proxyHosts !== "hidden" && (
+
+
+
+
+
+
+
+
+ {proxyHosts && proxyHosts.length > 0 ? (
+ proxyHosts.map((host) => {
+ const selectedIds: number[] =
+ values.meta?.proxy_host_ids || [];
+ const isChecked = selectedIds.includes(host.id);
+ return (
+
+ {
+ const newIds = e.target.checked
+ ? [...selectedIds, host.id]
+ : selectedIds.filter(
+ (hId) => hId !== host.id,
+ );
+ setFieldValue(
+ "meta.proxy_host_ids",
+ newIds,
+ );
+ }}
+ />
+
+
+ {host.domainNames?.join(", ")}
+
+
+ → {host.forwardScheme}://
+ {host.forwardHost}:{host.forwardPort}
+
+
+
+ );
+ })
+ ) : (
+
+
+
+ )}
+
+
+ )}
@@ -263,7 +349,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
)}
-
+
{
)}
-
+
)}
-
+ >
);
});
diff --git a/frontend/src/modals/ProxyHostModal.tsx b/frontend/src/modals/ProxyHostModal.tsx
index 3227be51b..38cdb75be 100644
--- a/frontend/src/modals/ProxyHostModal.tsx
+++ b/frontend/src/modals/ProxyHostModal.tsx
@@ -1,7 +1,7 @@
import { IconSettings } from "@tabler/icons-react";
import cn from "classnames";
import EasyModal, { type InnerModalProps } from "ez-modal-react";
-import { Field, Form, Formik } from "formik";
+import { Field, Form } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
@@ -16,6 +16,7 @@ import {
SSLCertificateField,
SSLOptionsFields,
} from "src/components";
+import { FormModal } from "src/components/Form";
import { useProxyHost, useSetProxyHost, useUser } from "src/hooks";
import { T } from "src/locale";
import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions";
@@ -36,6 +37,8 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
const [errorMsg, setErrorMsg] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const isAdmin = currentUser?.roles?.includes("admin");
+
const onSubmit = async (values: any, { setSubmitting }: any) => {
if (isSubmitting) return;
setIsSubmitting(true);
@@ -45,6 +48,16 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
id: id === "new" ? undefined : id,
...values,
};
+ payload.locations = (values.locations || []).filter((location: any) => location.path?.trim() !== "");
+
+ if (!isAdmin) {
+ delete payload.advanced_config;
+ delete payload.advancedConfig;
+ // Location advanced configs are sent as-is. Non-admins can't edit them (the editor is
+ // hidden), so each one is still the admin-written value and moves with its location
+ // when the path is renamed; the backend matches them by value. Looking them up by
+ // path here would silently drop the config from a renamed location.
+ }
setProxyHost(payload, {
onError: (err: any) => setErrorMsg( ),
@@ -60,15 +73,22 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
};
return (
-
- {!isLoading && (error || userError) && (
-
- {error?.message || userError?.message || "Unknown error"}
-
+ <>
+ {(isLoading || userIsLoading || ((error || userError) && (!data || !currentUser))) && (
+
+ {!isLoading && (error || userError) && (
+
+ {error?.message || userError?.message || "Unknown error"}
+
+ )}
+ {(isLoading || userIsLoading) && }
+
)}
- {isLoading || (userIsLoading && )}
{!isLoading && !userIsLoading && data && currentUser && (
- {
}
onSubmit={onSubmit}
>
- {() => (
+ {({ onHide }) => (
@@ -145,19 +165,21 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
-
-
-
-
-
+ {isAdmin && (
+
+
+
+
+
+ )}
@@ -165,12 +187,12 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
-
+
{({ field, form }: any) => (
@@ -196,11 +218,14 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
)}
-
+
{({ field, form }: any) => (
-
+
{
)}
-
+
-
+
{
/>
-
-
-
+ {isAdmin && (
+
+
+
+ )}
-
+
@@ -368,9 +401,9 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
)}
-
+
)}
-
+ >
);
});
diff --git a/frontend/src/modals/RedirectionHostModal.tsx b/frontend/src/modals/RedirectionHostModal.tsx
index 9a0d95ce8..d83a81bcf 100644
--- a/frontend/src/modals/RedirectionHostModal.tsx
+++ b/frontend/src/modals/RedirectionHostModal.tsx
@@ -1,7 +1,7 @@
import { IconSettings } from "@tabler/icons-react";
import cn from "classnames";
import EasyModal, { type InnerModalProps } from "ez-modal-react";
-import { Field, Form, Formik } from "formik";
+import { Field, Form } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
@@ -13,7 +13,8 @@ import {
SSLCertificateField,
SSLOptionsFields,
} from "src/components";
-import { useRedirectionHost, useSetRedirectionHost } from "src/hooks";
+import { FormModal } from "src/components/Form";
+import { useRedirectionHost, useSetRedirectionHost, useUser } from "src/hooks";
import { T } from "src/locale";
import { validateString } from "src/modules/Validations";
import { showObjectSuccess } from "src/notifications";
@@ -26,11 +27,14 @@ interface Props extends InnerModalProps {
id: number | "new";
}
const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
+ const { data: currentUser, isLoading: userIsLoading, error: userError } = useUser("me");
const { data, isLoading, error } = useRedirectionHost(id);
const { mutate: setRedirectionHost } = useSetRedirectionHost();
const [errorMsg, setErrorMsg] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const isAdmin = currentUser?.roles?.includes("admin");
+
const onSubmit = async (values: any, { setSubmitting }: any) => {
if (isSubmitting) return;
setIsSubmitting(true);
@@ -41,6 +45,11 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
...values,
};
+ if (!isAdmin) {
+ delete payload.advanced_config;
+ delete payload.advancedConfig;
+ }
+
setRedirectionHost(payload, {
onError: (err: any) => setErrorMsg( ),
onSuccess: () => {
@@ -55,15 +64,22 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
};
return (
-
- {!isLoading && error && (
-
- {error?.message || "Unknown error"}
-
+ <>
+ {(isLoading || userIsLoading || ((error || userError) && (!data || !currentUser))) && (
+
+ {!isLoading && (error || userError) && (
+
+ {error?.message || userError?.message || "Unknown error"}
+
+ )}
+ {(isLoading || userIsLoading) && }
+
)}
- {isLoading && }
- {!isLoading && data && (
-
- {() => (
+ {({ onHide }) => (
@@ -126,19 +142,21 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
-
-
-
-
-
+ {isAdmin && (
+
+
+
+
+
+ )}
@@ -318,15 +336,17 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
/>
-
-
-
+ {isAdmin && (
+
+
+
+ )}
-
+
)}
-
+
)}
-
+ >
);
});
diff --git a/frontend/src/modals/SetPasswordModal.tsx b/frontend/src/modals/SetPasswordModal.tsx
index ef6d99beb..665c00266 100644
--- a/frontend/src/modals/SetPasswordModal.tsx
+++ b/frontend/src/modals/SetPasswordModal.tsx
@@ -1,11 +1,12 @@
import EasyModal, { type InnerModalProps } from "ez-modal-react";
-import { Field, Form, Formik } from "formik";
+import { Field, Form } from "formik";
import { generate } from "generate-password-browser";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import { updateAuth } from "src/api/backend";
import { Button } from "src/components";
+import { FormModal } from "src/components/Form";
import { intl, T } from "src/locale";
import { validateString } from "src/modules/Validations";
@@ -35,103 +36,104 @@ const SetPasswordModal = EasyModal.create(({ id, visible, remove }: Props) => {
};
return (
-
-
- {() => (
-
-
-
-
-
-
-
- setError(null)} dismissible>
- {error}
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
-
+
+ {({ onHide }) => (
+
+
+
+
+
+
+
+ setError(null)} dismissible>
+ {error}
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
);
});
diff --git a/frontend/src/modals/StreamModal.tsx b/frontend/src/modals/StreamModal.tsx
index b87a259c4..417ae3103 100644
--- a/frontend/src/modals/StreamModal.tsx
+++ b/frontend/src/modals/StreamModal.tsx
@@ -1,9 +1,10 @@
import EasyModal, { type InnerModalProps } from "ez-modal-react";
-import { Field, Form, Formik } from "formik";
+import { Field, Form } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import { Button, Loading, SSLCertificateField, SSLOptionsFields } from "src/components";
+import { FormModal } from "src/components/Form";
import { useSetStream, useStream } from "src/hooks";
import { intl, T } from "src/locale";
import { validateNumber, validateString } from "src/modules/Validations";
@@ -46,15 +47,22 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => {
};
return (
-
- {!isLoading && error && (
-
- {error?.message || "Unknown error"}
-
+ <>
+ {(isLoading || (error && !data)) && (
+
+ {!isLoading && error && (
+
+ {error?.message || "Unknown error"}
+
+ )}
+ {isLoading && }
+
)}
- {isLoading && }
{!isLoading && data && (
- {
}
onSubmit={onSubmit}
>
- {({ setFieldValue }: any) => (
+ {({ onHide, setFieldValue }: any) => (
@@ -302,7 +310,7 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => {
-
+
{
)}
-
+
)}
-
+ >
);
});
diff --git a/frontend/src/modals/TwoFactorModal.tsx b/frontend/src/modals/TwoFactorModal.tsx
index aa76d598c..31b2a0ef0 100644
--- a/frontend/src/modals/TwoFactorModal.tsx
+++ b/frontend/src/modals/TwoFactorModal.tsx
@@ -6,6 +6,7 @@ import Modal from "react-bootstrap/Modal";
import QRCode from "react-qr-code";
import { disable2FA, enable2FA, get2FAStatus, regenerateBackupCodes, start2FASetup } from "src/api/backend";
import { Button } from "src/components";
+import { confirmDiscardChanges, FormDirtyState } from "src/components/Form";
import { T } from "src/locale";
import { validateString } from "src/modules/Validations";
import styles from "./TwoFactorModal.module.css";
@@ -28,6 +29,13 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
const [setupData, setSetupData] = useState<{ secret: string; otpauthUrl: string } | null>(null);
const [backupCodes, setBackupCodes] = useState([]);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const [formDirty, setFormDirty] = useState(false);
+
+ const handleHide = () => {
+ if (!isSubmitting && confirmDiscardChanges(formDirty)) {
+ remove();
+ }
+ };
const loadStatus = useCallback(async () => {
try {
@@ -172,8 +180,9 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
/>
- {() => (
+ {({ dirty }) => (
+
{({ field, form }: any) => (
@@ -242,8 +251,9 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
- {() => (
+ {({ dirty }) => (
+
{({ field, form }: any) => (
@@ -290,8 +300,9 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
- {() => (
+ {({ dirty }) => (
+
{({ field, form }: any) => (
@@ -335,7 +346,7 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
};
return (
-
+
diff --git a/frontend/src/modals/UserModal.tsx b/frontend/src/modals/UserModal.tsx
index 06bc38cf9..ff9205626 100644
--- a/frontend/src/modals/UserModal.tsx
+++ b/frontend/src/modals/UserModal.tsx
@@ -1,9 +1,10 @@
import EasyModal, { type InnerModalProps } from "ez-modal-react";
-import { Field, Form, Formik } from "formik";
+import { Field, Form } from "formik";
import { useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import { Button, Loading } from "src/components";
+import { FormModal } from "src/components/Form";
import { useSetUser, useUser } from "src/hooks";
import { intl, T } from "src/locale";
import { validateEmail, validateString } from "src/modules/Validations";
@@ -18,7 +19,7 @@ interface Props extends InnerModalProps {
}
const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
const { data, isLoading, error } = useUser(id);
- const { data: currentUser, isLoading: currentIsLoading } = useUser("me");
+ const { data: currentUser, isLoading: currentIsLoading, error: currentError } = useUser("me");
const { mutate: setUser } = useSetUser();
const [errorMsg, setErrorMsg] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -59,15 +60,22 @@ const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
};
return (
-
- {!isLoading && error && (
-
- {error?.message || "Unknown error"}
-
+ <>
+ {(isLoading || currentIsLoading || ((error || currentError) && (!data || !currentUser))) && (
+
+ {!isLoading && (error || currentError) && (
+
+ {error?.message || currentError?.message || "Unknown error"}
+
+ )}
+ {(isLoading || currentIsLoading) && }
+
)}
- {(isLoading || currentIsLoading) && }
{!isLoading && !currentIsLoading && data && currentUser && (
- {
}
onSubmit={onSubmit}
>
- {() => (
+ {({ onHide }) => (
@@ -222,7 +230,7 @@ const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
) : null}
-
+
{
)}
-
+
)}
-
+ >
);
});
diff --git a/frontend/src/pages/Users/Table.tsx b/frontend/src/pages/Users/Table.tsx
index a5a61057b..49c64e6b8 100644
--- a/frontend/src/pages/Users/Table.tsx
+++ b/frontend/src/pages/Users/Table.tsx
@@ -90,6 +90,49 @@ export default function Table({
return ;
},
}),
+ columnHelper.accessor((row: any) => row.permissions, {
+ id: "permissions",
+ header: intl.formatMessage({ id: "action.permissions" }),
+ cell: (info: any) => {
+ const permissions = info.getValue();
+ const isAdminUser = info.row.original.roles?.includes("admin");
+ if (isAdminUser) {
+ return (
+
+
+
+ );
+ }
+ if (!permissions) {
+ return - ;
+ }
+ const items = [
+ { key: "proxyHosts", label: "proxy-hosts", val: permissions.proxyHosts },
+ { key: "redirectionHosts", label: "redirection-hosts", val: permissions.redirectionHosts },
+ { key: "deadHosts", label: "dead-hosts", val: permissions.deadHosts },
+ { key: "streams", label: "streams", val: permissions.streams },
+ { key: "accessLists", label: "access-lists", val: permissions.accessLists },
+ { key: "certificates", label: "certificates", val: permissions.certificates },
+ ];
+ return (
+
+ {items.map((it) => {
+ if (it.val === "hidden") return null;
+ const isManage = it.val === "manage";
+ return (
+
+ : {it.val}
+
+ );
+ })}
+
+ );
+ },
+ }),
columnHelper.accessor((row: any) => row.isDisabled, {
id: "isDisabled",
header: intl.formatMessage({ id: "column.status" }),
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 1e00d067f..6a00563af 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -57,6 +57,9 @@ export default defineConfig({
test: {
environment: "happy-dom",
setupFiles: ["./vitest-setup.js"],
+ // Node 25+ has a built-in global localStorage (undefined without
+ // --localstorage-file) that shadows happy-dom's; turn it off.
+ execArgv: ["--no-experimental-webstorage"],
},
assetsInclude: ["**/*.md", "**/*.png", "**/*.svg"],
});
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index eb0aa21f9..ace3c7c36 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -7,7 +7,7 @@
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.5.0.tgz#b5b71a25a4d16afa2482592ddfa62fccc60bc7d1"
integrity sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.29.0", "@babel/code-frame@^7.29.7":
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7"
integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==
@@ -16,13 +16,21 @@
js-tokens "^4.0.0"
picocolors "^1.1.1"
-"@babel/generator@^7.29.8":
- version "7.29.8"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.8.tgz#4b0b887885422643339e09022148a4c4ebaa4979"
- integrity sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==
+"@babel/code-frame@^8.0.0":
+ version "8.0.6"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-8.0.6.tgz#5247d36cef9fd187dd4d84a0ba659fa29341616c"
+ integrity sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==
dependencies:
- "@babel/parser" "^7.29.8"
- "@babel/types" "^7.29.8"
+ "@babel/helper-validator-identifier" "^8.0.6"
+ js-tokens "^10.0.0"
+
+"@babel/generator@^7.29.13":
+ version "7.29.13"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.13.tgz#89bd5d368cde7437920ab3f2e67ef24f0ecfe620"
+ integrity sha512-T5FRo+RzVHvGjWOuF1Ljc44b+NXAOazodHkGUHp4I3zCkQ4/sNVJm56h1S278LMR0bY5xBhztGziSbli8zObIw==
+ dependencies:
+ "@babel/parser" "^7.29.13"
+ "@babel/types" "^7.29.11"
"@jridgewell/gen-mapping" "^0.3.12"
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
@@ -40,27 +48,32 @@
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
-"@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-string-parser@^7.29.11":
+ version "7.29.13"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.13.tgz#00a43cc19b8f84e9e3f17f32813e69ae9bfceba8"
+ integrity sha512-fWegr+WsEsiMVW4KUNdIIzzMTZSzNiqhy0//6+2MRi2d5NFNsL/AYwBQW8eBnRbTBVRnDVzjaJEUqKaCWjMMdQ==
"@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.29.7", "@babel/parser@^7.29.8":
- version "7.29.8"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.8.tgz#9653716a2f10c677b98fbc63d4bfb000c302cf17"
- integrity sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==
+"@babel/helper-validator-identifier@^8.0.6":
+ version "8.0.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz#fda51b4365505595779b51ea1ae0899ad9d76866"
+ integrity sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==
+
+"@babel/parser@^7.29.13", "@babel/parser@^7.29.7":
+ version "7.29.13"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.13.tgz#dbba85be72b94635123d594035fd087fe7e42bb3"
+ integrity sha512-fJM50YYjlTiBhgdkGmC1tBpv+FMAyo4annf2X6Aks+0yRlaiVkYW/3f07xAfVVkYIoZ9fFZp+mEThUFnuybhkQ==
dependencies:
- "@babel/types" "^7.29.8"
+ "@babel/types" "^7.29.11"
"@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.29.7"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.7.tgz#12022450c45a4da6d8d8287b18a4ff2ddb23f768"
- integrity sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==
+ version "7.29.13"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.13.tgz#6b33e456181ff15b5585d932f3aebfc0669b32dd"
+ integrity sha512-v+oN26xP4YIlQsbQ/CbTH1WG5m/l/DjKKZNy/Kp3f/AFvbgt7Em/D0FeMM3O2hX0YzeN2M16W8lOpyhIkfCBcQ==
"@babel/template@^7.29.7":
version "7.29.7"
@@ -72,79 +85,91 @@
"@babel/types" "^7.29.7"
"@babel/traverse@^7.29.7":
- version "7.29.8"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.8.tgz#4111014cdc71a0f95d9471907590baa0b8a6b28a"
- integrity sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==
+ version "7.29.13"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.13.tgz#2c536b67d36f8d3cb86393fe0f0567bd01b748a5"
+ integrity sha512-0ve0xMlFA73dgY1oyJRY6RoW+/bncdcOQqg6jjYcVfXBK90OdLXjm9R6lmRetdZVrPjXkKIoMVBqYFPjcAlZ4Q==
dependencies:
"@babel/code-frame" "^7.29.7"
- "@babel/generator" "^7.29.8"
+ "@babel/generator" "^7.29.13"
"@babel/helper-globals" "^7.29.7"
- "@babel/parser" "^7.29.8"
+ "@babel/parser" "^7.29.13"
"@babel/template" "^7.29.7"
- "@babel/types" "^7.29.8"
+ "@babel/types" "^7.29.11"
debug "^4.3.1"
-"@babel/types@^7.29.7", "@babel/types@^7.29.8":
- version "7.29.8"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.8.tgz#1229eef31d85156d70fa3f4cd859376d0eaf6863"
- integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==
+"@babel/types@^7.29.11", "@babel/types@^7.29.7":
+ version "7.29.11"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.11.tgz#774cffe8cac2ca494f1334e4052002c96bf628fc"
+ integrity sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==
dependencies:
- "@babel/helper-string-parser" "^7.29.7"
+ "@babel/helper-string-parser" "^7.29.11"
"@babel/helper-validator-identifier" "^7.29.7"
-"@biomejs/biome@^2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.5.10.tgz#c2a42fc6f9c7d9e12c1f77f63dfb6b5489c497b7"
- integrity sha512-WRKXARA3kTuiV5sxqTpobJ/I0MVd4vk3pOL6wnp5az4LntFIhWTj1RWZq3DI9PCEN3lXcqy7p5aqUHzvq8AXyQ==
+"@biomejs/biome@^2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.6.6.tgz#b40e03ff3dbe070963dd6332171ffffeb3cf7619"
+ integrity sha512-NCgVJRyrU1+hsW1ILNaRqNAb9fRL4n49bwyef+wjVJQ488/bXbp0BfnNqTTExkiNQT3ugkgYKR1vK8NxsjlNSQ==
optionalDependencies:
- "@biomejs/cli-darwin-arm64" "2.5.10"
- "@biomejs/cli-darwin-x64" "2.5.10"
- "@biomejs/cli-linux-arm64" "2.5.10"
- "@biomejs/cli-linux-arm64-musl" "2.5.10"
- "@biomejs/cli-linux-x64" "2.5.10"
- "@biomejs/cli-linux-x64-musl" "2.5.10"
- "@biomejs/cli-win32-arm64" "2.5.10"
- "@biomejs/cli-win32-x64" "2.5.10"
+ "@biomejs/cli-android-arm64" "2.6.6"
+ "@biomejs/cli-android-x64" "2.6.6"
+ "@biomejs/cli-darwin-arm64" "2.6.6"
+ "@biomejs/cli-darwin-x64" "2.6.6"
+ "@biomejs/cli-linux-arm64" "2.6.6"
+ "@biomejs/cli-linux-arm64-musl" "2.6.6"
+ "@biomejs/cli-linux-x64" "2.6.6"
+ "@biomejs/cli-linux-x64-musl" "2.6.6"
+ "@biomejs/cli-win32-arm64" "2.6.6"
+ "@biomejs/cli-win32-x64" "2.6.6"
-"@biomejs/cli-darwin-arm64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.10.tgz#f1453a7f2e63f228a34645df1c33962f22a6c2a8"
- integrity sha512-ItCrxKK6SXVT6flYs0qIuBd4AA3TTTl4d66Re6YI2FuGZnN85NmuYNzkiTJUyYw8qBLv69L5zTUB6uyWd++h3Q==
+"@biomejs/cli-android-arm64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.6.tgz#dd4a994b35837a521459fea4f4f27a194c47258d"
+ integrity sha512-bicnqNNxEX7pjkDQaptYvPRIWDfgXJLl/Ok7iZjC+F1AhfReor3FSS7SgPYnOvUy/jf++5U/KYN4T8OKH5rrNA==
-"@biomejs/cli-darwin-x64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.10.tgz#e21041e7bfab01cd453f609b940bf613713d0e24"
- integrity sha512-yLsPU9pAmtChXDu8vhKAzErqe+LeeYuwuUB2FZMkRitsmdodxsYRa9KHrFispsUHzzOu+9HB3nP/TQxyia+Sjw==
+"@biomejs/cli-android-x64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-android-x64/-/cli-android-x64-2.6.6.tgz#1804e55a64e9e0b11f56492863c98fd80f1a4028"
+ integrity sha512-N443kDSElF4kDMKjarsL+ymj/ewLA5R9VAdawA7fJ7pwgN3JVDZp0K+xhKBglH8EwsKi2+K8xC6WxXSkgziFfw==
-"@biomejs/cli-linux-arm64-musl@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.10.tgz#57fc8aee25c08ca6c14526ccc44e282e7ba96eb6"
- integrity sha512-t1QAKZwQJRB4dvgJSgFiQ4BNfNPChg69BNonz854qLVxnjT3UvDzQg9mbkTJRu35ZqU0Rw10A73J8Urgbg2RPw==
+"@biomejs/cli-darwin-arm64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.6.tgz#dd6be6a1d8457a465c555311c909e6aa81f1c9ea"
+ integrity sha512-/vSAqWx3by3p8w5ew+HVJDgguuaTK3U6Fb5gk/oDUH21iWOopm3gOlhT6tZfy5mTaM5R4CVc3uFWkxBtEDrKyw==
-"@biomejs/cli-linux-arm64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.10.tgz#aef789a71e5aa86f5a0e1aff6cb4c1b757ec5be1"
- integrity sha512-VG8uQW/86a1roLaIFvtIbEigxIdzdJ190oGyg1tV7VYeQtOS+x10sflk7WbuXgw91EtZX5DlIIIej1YqkNLlcg==
+"@biomejs/cli-darwin-x64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.6.tgz#fb2609d97f4ead53dbff30507a42be1fe56a1e9c"
+ integrity sha512-lLdOAJKbrAoDs+3GvTyVmMW345D9IhrIE3GgoL2eeHwO1boN4VhrSlALtFYsP0p+Jy4udUp4bTguij0qjKrH8w==
-"@biomejs/cli-linux-x64-musl@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.10.tgz#1aebe9f928dc72dc3c112e60f1211a4d646990c4"
- integrity sha512-pgDDqp9JybHm2I0KRgzN6i4+lt8xu4iqxUwLzglUMmOmyRTU1AYBGKzh9sNMOtIjah7xoWvKHlLVetvyifzoiQ==
+"@biomejs/cli-linux-arm64-musl@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.6.tgz#28412c5039466b87c9d3a59176994a45422d60a4"
+ integrity sha512-rw2Vsoi/WZGDyRjMXx9KZH9zsHe79LIzkdWHkLqsIkeKZm6qiGxtiIApxn7cmJSoEPaSvHaH5pRj1FhTcyL/XA==
-"@biomejs/cli-linux-x64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.10.tgz#a82b414abf5d9668fb035ee50a710bf78a7c5fbf"
- integrity sha512-4O6T0eq2heoHZN0a9UX+rWQoxXEBaKf+lRi2hbsGlHneUz9BWXM76nEWMK7Eeq8gzMxR1khQB6BFpAASpeXqGg==
+"@biomejs/cli-linux-arm64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.6.tgz#04ced06069af3c7a4d20da44c53c21e70ce42176"
+ integrity sha512-UzJdUCg9dbEGpZOuZRyehEPrnwKjx9z4Um7sWLG5x6txu7UjeWdJZuBXVGlyAdD39w2Hx0nZeK1jKC4NFUTCNQ==
-"@biomejs/cli-win32-arm64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.10.tgz#db671cf67d7040a374e8cdda5aebe73c3f4ffba0"
- integrity sha512-pxAbxduPO4xq/Cvgaa2lOrs9BB0hEXmmDqfMNP4ZOffGOkUrD1/QGw9UAMpFQpX2P8MqTIIRuQKcmetum4Oa6A==
+"@biomejs/cli-linux-x64-musl@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.6.tgz#78109fb485dcbce9eaf5977e2a70658696dc6405"
+ integrity sha512-DiKejs+GnFbOVQsBnukUpiB+T13tqUDFGwwjsTq51vatAsaflzFzVD0BOgjVUGRrqKgE0K0Xf+wNtkcmdN2chw==
-"@biomejs/cli-win32-x64@2.5.10":
- version "2.5.10"
- resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.10.tgz#3bdeaf5856f21aec6b6c50a7bca5464b9cda5623"
- integrity sha512-M+2dgBsl3lXRiTfgPVc2p3anS4Tocojke4rzFLScZ2Y/wmF+36dRb1iHCLiyGqOzQGyTplZH1HnEYviiAqi3nA==
+"@biomejs/cli-linux-x64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.6.tgz#b5315ccaa511a2bfb103db50013a266887d793bb"
+ integrity sha512-l8/aPXc1cz8VpUyd4hb5Pm5zx+X3voQBks4aLYbp5pC8KSchuHyvqDfY5xax39qCHomI/2xn6NKEot+zebG38g==
+
+"@biomejs/cli-win32-arm64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.6.tgz#41c0a80a9b98e9b879fb2413a5faf0775a526f07"
+ integrity sha512-DjVXwQAQeHrO1JEZAEus54k40Tnh4glyU3VFLcDBKwoz0xnxCEnqw6TCMXoujcQMQynWd3AY8bLrbR1o0XNqow==
+
+"@biomejs/cli-win32-x64@2.6.6":
+ version "2.6.6"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.6.tgz#f3e5230d59a48ac1da6ab951ad5be364591fb64c"
+ integrity sha512-B7KMG26A4I9lAuxwBGMmiVcjMGAeJRoz+pzFt9brRvOAsIrgOu8ptJld5rUWui6tM1uiJwxeJhhDPuAiZtxXdw==
"@corvu/utils@~0.4.2":
version "0.4.2"
@@ -261,92 +286,97 @@
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.12.tgz#afefe785949f16ac4cdd1e695935a321572dd56a"
integrity sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==
-"@formatjs/cli-native-darwin-arm64@1.1.24":
- version "1.1.24"
- resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-arm64/-/cli-native-darwin-arm64-1.1.24.tgz#fd3eacb6653b0fdd81f123defbb6752072478a97"
- integrity sha512-2i/fKUcawvTe022vN1LcpCJMBHcN2pFgOzg3Hr24jV0QMpoZVpB5RuM8fhZCy9E/STnOTP+P5W9uuPhg5eCGUA==
+"@formatjs/cli-native-darwin-arm64@1.1.33":
+ version "1.1.33"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-arm64/-/cli-native-darwin-arm64-1.1.33.tgz#50d6a4fd4ff5b842e16aed2c258940e3b5301692"
+ integrity sha512-bsY7KHNytQQMwCjQ7bJeUBPlRloYACKzd+GCrYPaWrqNiOyhDa664p3NcoyY/HZo3Bg5l/5oPo8+Wjp7Kkud+Q==
-"@formatjs/cli-native-linux-arm64-musl@1.0.22":
- version "1.0.22"
- resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64-musl/-/cli-native-linux-arm64-musl-1.0.22.tgz#bd9939c40cfc45d3e2ed73200906dd9aa1aa43aa"
- integrity sha512-YUJ1ArreQGEdr2VaBIvpUVx3242YRFAwaKljlm0bOAckdk1JogzGnxIb5OJzFxlKiZkXWGFlO+erpRB6q0+kHg==
+"@formatjs/cli-native-darwin-x64@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli-native-darwin-x64/-/cli-native-darwin-x64-1.1.1.tgz#53157d342a6e92f3f6eccf40f8f295528d37c35f"
+ integrity sha512-RA4/HfHBGjLXLW18HCeVeS5f0OCfM0Anr/6qQYnXDJP78Fl6QeXb/0HkkhleS7Vc5Zpa5dhGMFWqDeQDPWt9gw==
-"@formatjs/cli-native-linux-arm64@1.2.24":
- version "1.2.24"
- resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64/-/cli-native-linux-arm64-1.2.24.tgz#8ff72faeaa658f69a2f2dc1491decc243ff08411"
- integrity sha512-L0hFov7dgC6m9WM2cqolaL3zA48vpwe/gqd7MKILt+TrHR4ZlVv9CXvzXMDucn//FcTZw0NFBKgVXcHvlimY2g==
+"@formatjs/cli-native-linux-arm64-musl@1.0.31":
+ version "1.0.31"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64-musl/-/cli-native-linux-arm64-musl-1.0.31.tgz#6c520ab5f304f62df80c151e990f112d6f8cdbd5"
+ integrity sha512-dckGwmRYv9rdwu4NDYDlW9ZcyWlMqMYRVSwdAKlU+QAE9iZSnQgWseZ7HXNFbMjvloWlID5OsoP+Vd3S+58tJg==
-"@formatjs/cli-native-linux-x64-musl@1.0.22":
- version "1.0.22"
- resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64-musl/-/cli-native-linux-x64-musl-1.0.22.tgz#5b137b928aff22b496ff5aea6a8f974d4188f149"
- integrity sha512-hUwkDw47LQ0ESJvKsbZOWI50w/NP1hATfRrUy9mGRCRqKj1YTJ/HQxOjVfnrpiVEImjs4Ptws69RuIgxB5QGOA==
+"@formatjs/cli-native-linux-arm64@1.2.33":
+ version "1.2.33"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-arm64/-/cli-native-linux-arm64-1.2.33.tgz#e9403a2e274e3a18a9dd15f74080a52483eb8d93"
+ integrity sha512-LWt4HAuipwTMPsMaMEC6qJ6hybTCsImWLr7QUfywoRU0fHsOlqoAtHyP5uLlHsv3ormEBGOLpKn/Ug2JRhrvYw==
-"@formatjs/cli-native-linux-x64@1.1.24":
- version "1.1.24"
- resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64/-/cli-native-linux-x64-1.1.24.tgz#23a421980b85a60266dbccd1d236d575ebdda54e"
- integrity sha512-bp6FKYz8AQaHdeNI6rvMSk49ni4/uS+6AoxL/GiKXv2fL8zc459mkctTnwGR9Ol2in71Gq+nFKoZGFS50EHWCA==
+"@formatjs/cli-native-linux-x64-musl@1.0.31":
+ version "1.0.31"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64-musl/-/cli-native-linux-x64-musl-1.0.31.tgz#5816ef13fbcedc5e0ae15de5fa6f5ab0073f2c5b"
+ integrity sha512-Tf8/G37cU3nDPLjavm8c6zX0nfUBLSzX2N8Xs5dD7qfYu1/z/mB5E1z5niO4E5PDPqna7f0OumksxuCJzk7EFA==
-"@formatjs/cli-native-win32-x64@1.1.25":
- version "1.1.25"
- resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-x64/-/cli-native-win32-x64-1.1.25.tgz#509060f7e0cc227a52e4677f6421da6fad48b224"
- integrity sha512-tYjaYxtfBoGClwnBMtGuMQV/hbXY3mrGR5QRo9Tzk9Uwpa573gojtI8sqhn5jkzqjKPG1Ob3vVIJxcJmSnqemg==
+"@formatjs/cli-native-linux-x64@1.1.33":
+ version "1.1.33"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli-native-linux-x64/-/cli-native-linux-x64-1.1.33.tgz#82154949db14141217115b28f67ddcc118c8a0e4"
+ integrity sha512-SrtBFI+V5UejF05ZKUi19uWxBQOTPGI3erKMnbJB41aawfl9OryjD8BE/4C3F/g8oigp72sB7/4AAvl5E4uGwA==
-"@formatjs/cli@^6.16.30":
- version "6.16.30"
- resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.16.30.tgz#436549695e81af050747abcfac3af366c96546fa"
- integrity sha512-9pHgUriIJAsHfkoYuShydq5jHMp0IMQk7sdIkXgqD3m5UKTZIDfOyuJyuqJM5T55HOxz97y4iSSoWp7rfgdvNA==
+"@formatjs/cli-native-win32-arm64@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-arm64/-/cli-native-win32-arm64-1.1.1.tgz#de41478bed44ffcc74b62914fb400277807164ca"
+ integrity sha512-e+E6qBML8sMedsSH+tPKR9nZH9Rac865NFfpsJlRsZfPSPamBMOAt3djqa2/cgojNjIsfxt73TSURLOsS3bTGA==
+
+"@formatjs/cli-native-win32-x64@1.1.34":
+ version "1.1.34"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli-native-win32-x64/-/cli-native-win32-x64-1.1.34.tgz#55f72c5954c047b4a92c93f0c0742ba634fdbae4"
+ integrity sha512-DQ0o7chfXTcmucPBJpEHBlVpXcrP11Vz1xOdJeL75x9ErZNeJSA0dX2n1aVvCSzqAuEvh0aJeROpMrkm2tnJQg==
+
+"@formatjs/cli@^6.18.1":
+ version "6.18.1"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.18.1.tgz#e5077c99ee6212370f9a655ed838af462312e0ac"
+ integrity sha512-HIN3BKc3lCUFTjf+e732bqsUYM7ECBnACGnomKPaquAL6nFiVXD0/MJ/xh0a8odigQNxWrzKRsnP8gcxmIx4uA==
optionalDependencies:
- "@formatjs/cli-native-darwin-arm64" "1.1.24"
- "@formatjs/cli-native-linux-arm64" "1.2.24"
- "@formatjs/cli-native-linux-arm64-musl" "1.0.22"
- "@formatjs/cli-native-linux-x64" "1.1.24"
- "@formatjs/cli-native-linux-x64-musl" "1.0.22"
- "@formatjs/cli-native-win32-x64" "1.1.25"
+ "@formatjs/cli-native-darwin-arm64" "1.1.33"
+ "@formatjs/cli-native-darwin-x64" "1.1.1"
+ "@formatjs/cli-native-linux-arm64" "1.2.33"
+ "@formatjs/cli-native-linux-arm64-musl" "1.0.31"
+ "@formatjs/cli-native-linux-x64" "1.1.33"
+ "@formatjs/cli-native-linux-x64-musl" "1.0.31"
+ "@formatjs/cli-native-win32-arm64" "1.1.1"
+ "@formatjs/cli-native-win32-x64" "1.1.34"
"@formatjs/fast-memoize@3.1.7":
version "3.1.7"
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-3.1.7.tgz#5f79b3756e4bcb5faee47b3a2f01216e1212c9e4"
integrity sha512-zXfhLpvA6T7+efdt9JLbBwZ00tT7NsBMDVnDu8rpHeNNv8KfRZAMo2gkG0k9lK/Nzc//3kJ9pImsfuJxk3KhUA==
-"@formatjs/icu-messageformat-parser@3.5.17":
- version "3.5.17"
- resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.17.tgz#7ac90ff6dde99468cd0b4a3f55344ccf8ae2ed87"
- integrity sha512-cN9jhVqT7u0K9tix43fhjoUwL0nazyW6zsNIXs2QdPADr+nurPfYyssUiMqcSCGlPcCiqnYVxgSn7zBSuI+5Bg==
+"@formatjs/icu-messageformat-parser@3.5.23":
+ version "3.5.23"
+ resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.23.tgz#4812fd6d3adf844036108697f0486bf0464b2d5f"
+ integrity sha512-iB1fYfhtSGd8uH9A8b1m01Ujcv6QtQnJasefP3jPApmz72L/zNipzf3BdoinTMd1xuviVChjUJWL60iTwjUQ+w==
dependencies:
- "@formatjs/icu-skeleton-parser" "2.1.11"
+ "@formatjs/icu-skeleton-parser" "2.1.13"
-"@formatjs/icu-skeleton-parser@2.1.11":
- version "2.1.11"
- resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.11.tgz#09226edf55a18e3fea80304d2cc95aaeb0095436"
- integrity sha512-j8cUmOJzVgkHuS0QiQ6ga76UIoLOFSAMWhs7aZJztH3aAdCOAE6vpC8KVvFB4cU10ON0y2/5oOVmPJ43s2lTwA==
+"@formatjs/icu-skeleton-parser@2.1.13":
+ version "2.1.13"
+ resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.13.tgz#b51ec5c826b93c34d4e45390a4d32d05c222cf07"
+ integrity sha512-IdAJ9NrHn7AF0zqncD9UpxQozTYyJtbwwrZnJMEKVxTcOOXtVkUb2XYTpNOGTAv58NCsk0rjRB9G+i2SvBmGAQ==
-"@formatjs/intl@4.2.0":
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-4.2.0.tgz#fadf90f2b1d02faaa68fa770f66a1f384e82653b"
- integrity sha512-FbiaRjnQjjqo8FzayDEuuFLj+lCrSEDEmeXXGRpBUioaukr/+4djq4TYIpXnsVh2DWqYyOrMu8cLpc/rGmVHlw==
+"@formatjs/intl@6.1.5":
+ version "6.1.5"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-6.1.5.tgz#ca9beb65b240b697760af90fd7d91f97b81e0bc2"
+ integrity sha512-WicSXzsz4wO1ksJb6Pp9a+vdOpIkidcEm8ldVtXkqFoMB/o/ETEikyPeCFgTVuSTLBb7ZNJ3ZLKXSQ/X/0+BAg==
dependencies:
"@formatjs/fast-memoize" "3.1.7"
- "@formatjs/icu-messageformat-parser" "3.5.17"
- intl-messageformat "11.2.14"
+ "@formatjs/icu-messageformat-parser" "3.5.23"
+ intl-messageformat "12.1.5"
-"@internationalized/date@^3.12.3":
- version "3.12.3"
- resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.12.3.tgz#e11d14dbf12bf45057dd5996bac77f7c2f89ff9c"
- integrity sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q==
+"@internationalized/date@^3.12.4":
+ version "3.12.4"
+ resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.12.4.tgz#a391e01d54ca80b3cb4c3d5040d46a0b26c3f20f"
+ integrity sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==
dependencies:
"@swc/helpers" "^0.5.0"
-"@internationalized/number@^3.2.1":
- version "3.6.8"
- resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.8.tgz#aaa3e16fb9d64a8d7f130ccf848c626914dfdf9e"
- integrity sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig==
- dependencies:
- "@swc/helpers" "^0.5.0"
-
-"@internationalized/number@^3.6.7":
- version "3.6.7"
- resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.7.tgz#5a0a8fa413b5f8679a59dcf37e2a74dc508b8371"
- integrity sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==
+"@internationalized/number@^3.2.1", "@internationalized/number@^3.6.9":
+ version "3.6.9"
+ resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.9.tgz#dc237daa302bd44248bb03e73abf5ef7549f4902"
+ integrity sha512-GgWy2caO/+hV9sT/oCTWGHvbjmJMPT9kkXzoKRLnkEq7adghhR7UprhvY9sXrKfrp3NcDfhF6QQHXR1KfQbB8g==
dependencies:
"@swc/helpers" "^0.5.0"
@@ -370,12 +400,12 @@
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
-"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5":
- version "1.5.5"
- resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
- integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
+"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz#f4c663e862f06dc98ca4d453862c46902789a18d"
+ integrity sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==
-"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28":
+"@jridgewell/trace-mapping@0.3.31", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
@@ -390,7 +420,7 @@
dependencies:
jsbi "^4.3.0"
-"@kobalte/core@^0.13.4":
+"@kobalte/core@^0.13.14":
version "0.13.14"
resolved "https://registry.yarnpkg.com/@kobalte/core/-/core-0.13.14.tgz#6d46ada9a427e07f73fb61123a03220277a793d7"
integrity sha512-SBVB1lnvYbYt2x3t7fG0lo5fGLEbHQ6PII3NDPva8vD5e2v53On84ZVQiuIk0XI/55R4GQhden7Yem+R/XR8uQ==
@@ -416,10 +446,10 @@
"@solid-primitives/refs" "^1.0.5"
"@solid-primitives/utils" "^6.2.1"
-"@oxc-project/types@=0.150.0":
- version "0.150.0"
- resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.150.0.tgz#e9be0840114655016b16f1c05ddaaa01df5b8853"
- integrity sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==
+"@oxc-project/types@=0.156.0":
+ version "0.156.0"
+ resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.156.0.tgz#00e35b1ead490d81b4c7fe1dbfe35fc55e89c5e6"
+ integrity sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==
"@parcel/watcher-android-arm64@2.6.0":
version "2.6.0"
@@ -517,10 +547,10 @@
"@swc/helpers" "^0.5.0"
react-aria "^3.48.0"
-"@react-types/shared@^3.36.1":
- version "3.36.1"
- resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.36.1.tgz#edb8081e3872ae68a4eb4a65e62233d0754ec186"
- integrity sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==
+"@react-types/shared@^3.37.0":
+ version "3.37.0"
+ resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.37.0.tgz#bbbd3cddc2ba2764a6a915659a1ab0e628779ae9"
+ integrity sha512-VNlZuifu4tHdxWPfHvoMfKsHA9kVTCGkduv5vKVTdinm8wHFJNhyVB5RzIMiRFj0q3RKoCbP68u2zaoBSiFueg==
"@restart/hooks@^0.4.9":
version "0.4.16"
@@ -551,80 +581,80 @@
uncontrollable "^8.0.4"
warning "^4.0.3"
-"@rolldown/binding-android-arm-eabi@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz#b38df5a6997454696207ac16b322a3ac1ff79e16"
- integrity sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==
+"@rolldown/binding-android-arm-eabi@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz#abc3e1d5aacd1983d32a490c3a8793770da3bd1a"
+ integrity sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==
-"@rolldown/binding-android-arm64@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz#bc2bf135c1e787f94b29bc321c1db0bd2884b5a7"
- integrity sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==
+"@rolldown/binding-android-arm64@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz#85c4d52aeb79c69e8abd52e3ba3174bb946bcd49"
+ integrity sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==
-"@rolldown/binding-darwin-arm64@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz#052049707ccc4cfe0b035aa8c37c4e55523eecbb"
- integrity sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==
+"@rolldown/binding-darwin-arm64@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz#d188fd77cdb2b6dc140011a573203e374fdba2d8"
+ integrity sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==
-"@rolldown/binding-darwin-x64@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz#ea67496811e4e1c2f76d5647f248073e204f5462"
- integrity sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==
+"@rolldown/binding-darwin-x64@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz#eb84cc00765ce2f6c48d85624ebecaa2c88470ba"
+ integrity sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==
-"@rolldown/binding-freebsd-x64@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz#4b4a605f8c24a7235afb5da9b4b8bc8974446bb5"
- integrity sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==
+"@rolldown/binding-freebsd-x64@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz#d3c51e8a2cd2977eac7fff0e6655e2715a39328c"
+ integrity sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==
-"@rolldown/binding-linux-arm-gnueabihf@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz#a36c6961fb24b86176463559dcd0edbe48ea74f1"
- integrity sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==
+"@rolldown/binding-linux-arm-gnueabihf@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz#216c99c72c005c375d63d31060209b78a8983fec"
+ integrity sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==
-"@rolldown/binding-linux-arm64-gnu@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz#7b43488618dd6f9d392e9adaaffd4d7f4bb785ce"
- integrity sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==
+"@rolldown/binding-linux-arm64-gnu@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz#4e03383b29c5ba30bbfdd06ec746c13e7dee12dd"
+ integrity sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==
-"@rolldown/binding-linux-arm64-musl@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz#8168a2e2ce7d9e28847f3019c98abf76c0ec5fe2"
- integrity sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==
+"@rolldown/binding-linux-arm64-musl@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz#d159c4c87ecda02962be95a1dccde1ef6934ccf2"
+ integrity sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==
-"@rolldown/binding-linux-ppc64-gnu@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz#8f52aac40788feac33056f111176a7bf8fbc297a"
- integrity sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==
+"@rolldown/binding-linux-ppc64-gnu@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz#6e4be205f9d2188b2a996c0f82242beac7d573f6"
+ integrity sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==
-"@rolldown/binding-linux-s390x-gnu@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz#80cdfa507fd87bc37c47c5a16ca578f619ee3a69"
- integrity sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==
+"@rolldown/binding-linux-s390x-gnu@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz#7e68a24dbc387fdba75d95992974eddf39c9ddaf"
+ integrity sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==
-"@rolldown/binding-linux-x64-gnu@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz#c2f89cdf3b223b2ba3cd4c0fdc760b360c02edf5"
- integrity sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==
+"@rolldown/binding-linux-x64-gnu@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz#591cdec3e65bd701ba2e7b671ca9d694142dec2c"
+ integrity sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==
-"@rolldown/binding-linux-x64-musl@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz#3fbb6bce4c8a8260cb63fc2dba182c8649930c6c"
- integrity sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==
+"@rolldown/binding-linux-x64-musl@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz#833ac527e031f51c2ac23a615a19ed111092088c"
+ integrity sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==
-"@rolldown/binding-openharmony-arm64@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz#c8cd18d7fd14222e2068cd0640bba88d2cd710f8"
- integrity sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==
+"@rolldown/binding-openharmony-arm64@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz#2991fbf22995469b1404afbf3850713fd917b525"
+ integrity sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==
-"@rolldown/binding-win32-arm64-msvc@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz#cff9ed3be36979d7d77306bf9dca1a27ed17c1f0"
- integrity sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==
+"@rolldown/binding-win32-arm64-msvc@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz#52d854f35cccde05f6404262bd35342a372c79f2"
+ integrity sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==
-"@rolldown/binding-win32-x64-msvc@1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz#6e6a3a7ac86eb46bed61e685a9e7cccfacc21b7a"
- integrity sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==
+"@rolldown/binding-win32-x64-msvc@1.2.16":
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz#cda4755739a62beba28a612e243a9a4155c76821"
+ integrity sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==
"@rolldown/pluginutils@^1.0.0", "@rolldown/pluginutils@^1.0.1":
version "1.0.1"
@@ -638,7 +668,7 @@
dependencies:
"@solid-primitives/utils" "^6.4.1"
-"@solid-primitives/keyed@^1.2.0", "@solid-primitives/keyed@^1.2.2":
+"@solid-primitives/keyed@^1.2.0", "@solid-primitives/keyed@^1.5.3":
version "1.5.3"
resolved "https://registry.yarnpkg.com/@solid-primitives/keyed/-/keyed-1.5.3.tgz#df60e7a079cc99ef80d22e4c03bb80eabc106392"
integrity sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA==
@@ -674,7 +704,7 @@
dependencies:
"@solid-primitives/utils" "^6.4.1"
-"@solid-primitives/resize-observer@^2.0.26":
+"@solid-primitives/resize-observer@^2.0.26", "@solid-primitives/resize-observer@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@solid-primitives/resize-observer/-/resize-observer-2.2.0.tgz#f6d76c0f765bb2008117c2e7504eb1707ef1268c"
integrity sha512-9Fuu/EWBeGj+atGHRJp70HKhdfalmpjwxY8a32NZixdLNmfCJ45AfhLQNr6uOzETbbiMx4iCKlTrJ8KZCHC2Ww==
@@ -722,112 +752,113 @@
resolved "https://registry.yarnpkg.com/@solid-primitives/utils/-/utils-6.4.1.tgz#aaa9006ac9dfa7b99b1060ab78669a8723ab2962"
integrity sha512-ISSB5QX1qP2ynrheIpYwc4oKR5Ny4siNuUyf1qZniy+Il+p/PtDB0QK1Dnle8noiHpwRD3gpPdubOC3qI/Zamg==
-"@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==
-
"@swc/helpers@^0.5.0":
- version "0.5.23"
- resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.23.tgz#19287d0d86d962b111376039a50c792902c9a86a"
- integrity sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==
- dependencies:
- tslib "^2.8.0"
+ version "0.5.24"
+ resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.24.tgz#b1a41a35fcfb1413564981be1bab04b46168961b"
+ integrity sha512-6tcXo4kA8HgGO7AVmawB57RYi/piBPYVHVcO6dXjX+SZw8GoHmi262sp4v2Ha2kNP9Cx84qei27yT+fjS1lxgg==
-"@tabler/core@^1.5.1":
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/@tabler/core/-/core-1.5.1.tgz#d083f17521acb7df4bc231bc79e653bfeae268c9"
- integrity sha512-PI9rJq4H4lBh53YP/J+m5Uz6lqVQbsGPmWCMN34IP4KQQ/wy28YMO6a3Eiz1cQHdFFr+3MlU3yYhXdzvFGJfqw==
+"@tabler/core@^1.7.4":
+ version "1.7.4"
+ resolved "https://registry.yarnpkg.com/@tabler/core/-/core-1.7.4.tgz#2e1c25053d79d491319966e5b2864d68f39bcb2c"
+ integrity sha512-fHAPnsKl9JnJJkLxE5UbPkLUBlgn/G1b0urLh5LMGt2DnebpK2tZNEKD/8YiwGz67EoNahGretSsHovDOvJGWA==
dependencies:
"@popperjs/core" "^2.11.8"
-"@tabler/icons-react@^3.46.0":
- version "3.46.0"
- resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.46.0.tgz#9840606d10aab071ad2c0dbdfcfba0eef6e9825d"
- integrity sha512-CCm7xJWhDT2PH4ZIFkP6AgYKtVhq0gpYkjUN+GVh1AzmIQaa77OW0bQPBPQiTE0PsXMR9oSxFqA3qBglzPyrVQ==
+"@tabler/icons-react@^3.51.0":
+ version "3.51.0"
+ resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.51.0.tgz#d6fafa049a74d3b4ad951e3a9e610e6ee36f890a"
+ integrity sha512-F3GGChIUspsCq+HhXtJezc1W9QRcRlEG07Jb4dKUR1TV+mq1uvY7+G46n1fZE02jhQUOVuOoi445Bk1O9mFrDg==
dependencies:
- "@tabler/icons" "3.46.0"
+ "@tabler/icons" "3.51.0"
-"@tabler/icons@3.46.0":
- version "3.46.0"
- resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.46.0.tgz#28ba3f4895715863fdd78b49a5617764c9557fe5"
- integrity sha512-f2RYFl3fzPwj5WO82x6en0dmkjefxEfOm16D1ByM6cj/McNiwOkL4VaPUoP9VVIrXAD9WnTSVFr70px703b//A==
+"@tabler/icons@3.51.0":
+ version "3.51.0"
+ resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.51.0.tgz#41133efa23cc0d13d4e9b0b5148e0e64746dad48"
+ integrity sha512-0pinvPZz7YeX+OSfxuEAbnEPWbocVXW5iU8h5lh41pYG8sZuQfpWECC980lxOT0clGIcNGn8xsBr+zKaRGdA/g==
-"@tanstack/match-sorter-utils@^8.19.4":
- version "8.19.4"
- resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz#dacf772b5d94f4684f10dbeb2518cf72dccab8a5"
- integrity sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==
+"@tanstack/match-sorter-utils@^9.1.2":
+ version "9.2.6"
+ resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-9.2.6.tgz#a16c9e695111c3659805c1b278a7f205233dbe6e"
+ integrity sha512-DIpUSOhPfQ7u/qfUi/Wgm0wBO56MyBYXGvnlI0NcauDRPqdBUlAZZjW2Dxm92m+k1UWVhKw8YJ4f47+75Aujyw==
dependencies:
remove-accents "0.5.0"
-"@tanstack/query-core@5.102.8":
- version "5.102.8"
- resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.102.8.tgz#c55c31b4f99124054805ce8ec8137c84108f3252"
- integrity sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==
+"@tanstack/query-core@5.107.1":
+ version "5.107.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.107.1.tgz#cd1f9952b4d623e66349931df631b287d9c4a3fe"
+ integrity sha512-PSyAik11oQBEVOiVy5hjZWHmoUqOXqK1WYy4v55mcxozEfKaQgpUMsgnXhWjuESzSht8JfzqMS1m6M/9CpNgvg==
-"@tanstack/query-core@5.103.1":
- version "5.103.1"
- resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.103.1.tgz#57c1db20679b98e793a1fe8e4a55a0619d2d681f"
- integrity sha512-rms8HqTGp6zA00dM+cUQ2eBcgzNJefuu5CAMB37i/6MiGT1zulPOytCFu2a0qjLqVR2n1jENPj9woqFQNuCzWA==
-
-"@tanstack/query-devtools@5.103.1":
- version "5.103.1"
- resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.103.1.tgz#4691fc78c33e98304e6b3597d8ac67f3cd2b5ef8"
- integrity sha512-xMOcnULRPEjww0A/0RnweNmcZ5hesLjTekqf8SHxf+BJH729qz6sUu5TsOWFo7J4fu3OR50MeuJUsM3BN78Kfw==
+"@tanstack/query-devtools@5.107.1":
+ version "5.107.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.107.1.tgz#d89f6a73dd7194a07805d5c8e6d9e0f84098c825"
+ integrity sha512-cZvgIPNvsh57mM3yTTwuL2GUMmMp5XR+3k5r2UJDyStmPYCqSwZf8z7OulgA4X8vISziAG5PoykbQ1q+2zemdA==
dependencies:
- "@kobalte/core" "^0.13.4"
- "@solid-primitives/keyed" "^1.2.2"
- "@solid-primitives/resize-observer" "^2.0.26"
+ "@kobalte/core" "^0.13.14"
+ "@solid-primitives/keyed" "^1.5.3"
+ "@solid-primitives/resize-observer" "^2.2.0"
"@solid-primitives/storage" "^1.3.11"
- "@tanstack/match-sorter-utils" "^8.19.4"
- "@tanstack/query-core" "5.103.1"
+ "@tanstack/match-sorter-utils" "^9.1.2"
+ "@tanstack/query-core" "5.107.1"
+ "@tanstack/solid-virtual" "^3.13.52"
clsx "^2.1.1"
- goober "^2.1.16"
- solid-js "^1.9.7"
+ goober "^2.1.19"
+ solid-js "^1.9.15"
solid-transition-group "^0.2.3"
- superjson "^2.2.2"
+ superjson "^2.2.6"
-"@tanstack/react-query-devtools@^5.103.1":
- version "5.103.1"
- resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.103.1.tgz#b3548301405de0b1205bb259f12aba6cbf3266ef"
- integrity sha512-l6OzwPbDCnuim8w9McFs3dC/rdXTMZBIBFMVrXzNuhUEzWheldubOu+nKRDtPf+1nLRuwavb3We+nNJq/0vV6w==
+"@tanstack/react-query-devtools@^5.107.1":
+ version "5.107.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.107.1.tgz#3c57174c77929fb0ec975ef1307b11530690cec4"
+ integrity sha512-A3q9X3iOxtlz1jU/ou7t9CFvNHT84rTMofS1YEkFcBalvwotZ/RJKPDsX1NsQVl10J8XwN0OYMSfdjgIElt0sg==
dependencies:
- "@tanstack/query-devtools" "5.103.1"
+ "@tanstack/query-devtools" "5.107.1"
-"@tanstack/react-query@^5.102.8":
- version "5.102.8"
- resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.102.8.tgz#bdc5c24485258e6ae2e7ecac0a4cf58c59937839"
- integrity sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==
+"@tanstack/react-query@^5.107.1":
+ version "5.107.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.107.1.tgz#277e4b8c4d6d0496047ef15afacbc8c091ce0532"
+ integrity sha512-udCOOLp3ZAheUrIHLCe+ZZge6nr2ZUrGMSWcCuXsjY1Bf2JeeE09oxst97PILiCY6N8xo/7bIo3Hvz13zh06FA==
dependencies:
- "@tanstack/query-core" "5.102.8"
+ "@tanstack/query-core" "5.107.1"
-"@tanstack/react-store@^0.11.1":
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/@tanstack/react-store/-/react-store-0.11.1.tgz#e1e58036ffffa58f5b5d8826c2389f97502fac2c"
- integrity sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==
+"@tanstack/react-store@^0.11.2":
+ version "0.11.2"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-store/-/react-store-0.11.2.tgz#14f81f98f43b04c34e76d348de3fe8488b3c3939"
+ integrity sha512-oj5O5DmiMWE+ih+SioX2ayC1kbK7d3fpLx0B75fPJr9nY3zU+hoW010yr64qXsY/9fyiLVE2bn5mGNV3owm8RQ==
dependencies:
- "@tanstack/store" "0.11.1"
+ "@tanstack/store" "0.11.2"
use-sync-external-store "^1.6.0"
-"@tanstack/react-table@^9.2.4":
- version "9.2.4"
- resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-9.2.4.tgz#4324dd0e08d4ca53a433110d8694ad82a4606bc7"
- integrity sha512-Rzp1Q4e0/nIgEjmISYR5HeEgLTNtrG+C7NFZf/AbCxPO5hg+zC3yNGwcoECigUk8SFzzvhXbd2rFdtP2ZiGrfA==
+"@tanstack/react-table@^9.3.1":
+ version "9.3.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-9.3.1.tgz#7030e813d412f54db6dd34af351774dd1caa929e"
+ integrity sha512-E6Ya5TnY6EaGyZYIUqig/29Lr/gJSAubQkLJFqiWkUl5nRCr0Xr9hvJot+NiRTtG+TzGxHTiqUW3bnLtQxlHug==
dependencies:
- "@tanstack/react-store" "^0.11.1"
- "@tanstack/table-core" "9.2.4"
+ "@tanstack/react-store" "^0.11.2"
+ "@tanstack/table-core" "9.3.1"
-"@tanstack/store@0.11.1", "@tanstack/store@^0.11.1":
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.11.1.tgz#063f634fe3cc925ad83704581a7be75301bec612"
- integrity sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==
-
-"@tanstack/table-core@9.2.4":
- version "9.2.4"
- resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-9.2.4.tgz#8d1b339d6118d306ded4ec8516419320ad2b9e57"
- integrity sha512-GwdDyGGr6UXAtubF14yAwcXvdaogqfsQgIk89Suebjxob/Rjq2xvfmXsjDF1rQmKmhHsJm9TOY7myxv3i6geJw==
+"@tanstack/solid-virtual@^3.13.52":
+ version "3.13.53"
+ resolved "https://registry.yarnpkg.com/@tanstack/solid-virtual/-/solid-virtual-3.13.53.tgz#236ce82d068594ebb9cc68c98bb4a3629b7dc13d"
+ integrity sha512-LUdGSmkTkwtpUea4w7bJp6N6ErnNkWT5iXpvn0mI9ijjtVrOa0JkSX8ZhQfNZxbhm4cnlGYbz/U1SQn/LqSNlw==
dependencies:
- "@tanstack/store" "^0.11.1"
+ "@tanstack/virtual-core" "3.19.11"
+
+"@tanstack/store@0.11.2", "@tanstack/store@^0.11.2":
+ version "0.11.2"
+ resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.11.2.tgz#cea02e554a483c7334a64a3ca2c0e4b703ba8531"
+ integrity sha512-sJ4mjol8uQsHV0gOJzzjwXfh2Fwm+Sz0+8deqiTm4jGbMdjzNSW+xZCFm0kUa870uhd8yi+DpKnZb5Kc4apa0Q==
+
+"@tanstack/table-core@9.3.1":
+ version "9.3.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-9.3.1.tgz#21ea78f0a6dc5fc3a8074c255d5f4841c12f8729"
+ integrity sha512-zQvlxSNi2qGM3L4jh85Z6hqMfdIR5qOfD9KHOU32nuKFJGN0RlJ7UvV1lsPb1fPVhpFYI0VP3jJGxLdLDpTKjQ==
+ dependencies:
+ "@tanstack/store" "^0.11.2"
+
+"@tanstack/virtual-core@3.19.11":
+ version "3.19.11"
+ resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.19.11.tgz#089fd1a9a92503f8f2329c7e48ceb84178726fc1"
+ integrity sha512-/5iHb0RHBvj8Di59FDs4ESNm45jls33aZAi4zccVXZjc/6ZvwQP6nzEYeT6weXjTf9E6/vrp2JhYWRyO+ZP13A==
"@testing-library/dom@^10.4.2":
version "10.4.2"
@@ -875,10 +906,12 @@
"@types/deep-eql" "*"
assertion-error "^2.0.1"
-"@types/country-flag-icons@^1.2.2":
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/@types/country-flag-icons/-/country-flag-icons-1.2.2.tgz#8f51089cab857f0f700feabd38b3960d006d64f2"
- integrity sha512-CefEn/J336TBDp7NX8JqzlDtCBOsm8M3r1Li0gEOt0HOMHF1XemNyrx9lSHjsafcb1yYWybU0N8ZAXuyCaND0w==
+"@types/country-flag-icons@^1.6.20":
+ version "1.6.20"
+ resolved "https://registry.yarnpkg.com/@types/country-flag-icons/-/country-flag-icons-1.6.20.tgz#c92fc9e5180c8e6c4139cc8c1f27ddde0c87fb6d"
+ integrity sha512-rpQPv1G0MlBPEGHL3IxKP1X68uzs+LBK/5IMaEVr6fC0VmX0lYyUvjvCJTCs7P7Xlqfc5k5tRDVv2X1bNPNXnA==
+ dependencies:
+ country-flag-icons "*"
"@types/debug@^4.0.0":
version "4.1.13"
@@ -900,9 +933,9 @@
"@types/estree" "*"
"@types/estree@*", "@types/estree@^1.0.0":
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24"
- integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.10.tgz#8a4a266065af8c2fcf5983153cd97c11b313d0c6"
+ integrity sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==
"@types/hast@^2.0.0":
version "2.3.10"
@@ -942,10 +975,10 @@
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", "@types/node@^26.6.2":
- version "26.6.2"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-26.6.2.tgz#11bfb8e00bafe728d2113e474a7637becc9ab279"
- integrity sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==
+"@types/node@*", "@types/node@>=20.0.0", "@types/node@^26.6.6":
+ version "26.6.6"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-26.6.6.tgz#9ed7b5817e728ed9216ef7c5b7d373fe3b777104"
+ integrity sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==
dependencies:
undici-types "~8.9.0"
@@ -981,14 +1014,7 @@
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":
- version "19.2.18"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.18.tgz#eb0b6a1fb635d1a9692d5f84a3495bd8ad153707"
- integrity sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==
- dependencies:
- csstype "^3.2.2"
-
-"@types/react@^19.3.0":
+"@types/react@*", "@types/react@>=16.9.11", "@types/react@^19.3.0":
version "19.3.0"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.3.0.tgz#3b3ce8d8c1627fb5ccd4dadd57ef445c5a153b77"
integrity sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==
@@ -1016,9 +1042,9 @@
integrity sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==
"@types/ws@^8.18.1":
- version "8.18.1"
- resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9"
- integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==
+ version "8.18.2"
+ resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.2.tgz#25d6321b30ef0fddf6cc7052b4eef9ce4e412b6e"
+ integrity sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==
dependencies:
"@types/node" "*"
@@ -1122,86 +1148,41 @@
resolved "https://registry.yarnpkg.com/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz#cf3b7b0d6ce5635daca4c8e01c189cdcde47ec3c"
integrity sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==
-"@uiw/react-textarea-code-editor@^3.1.1":
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/@uiw/react-textarea-code-editor/-/react-textarea-code-editor-3.1.1.tgz#8ca1b706a3081a51c68bc0df91c9c3cdadd9944e"
- integrity sha512-AERRbp/d85vWR+UPgsB5hEgerNXuyszdmhWl2fV2H2jN63jgOobwEnjIpb76Vwy8SaGa/AdehaoJX2XZgNXtJA==
+"@uiw/react-textarea-code-editor@^3.1.3":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@uiw/react-textarea-code-editor/-/react-textarea-code-editor-3.1.3.tgz#74e0ace20408af9c77930dd877a34274df46a8c2"
+ integrity sha512-R9u0h4IoRpFJWjIP/w7rGnEX6rhIS68WmTn2PdG+btgmyfctENC54AXqWVagkC4pPx2I01Z2+bmYJHVLLAM1IA==
dependencies:
"@babel/runtime" "^7.18.6"
rehype "~13.0.0"
rehype-prism-plus "2.0.0"
"@ungap/structured-clone@^1.0.0":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.3.tgz#094041e1a4cb1987f038335421281ac8be390bcc"
- integrity sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.4.0.tgz#5e2e1374c0a30b5a42e8b083523a225c6945f88a"
+ integrity sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==
-"@vitejs/plugin-react@^6.1.1":
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz#47fa1a2f956e88eaf778ebf65ddce8e2b36597e7"
- integrity sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==
+"@vitejs/plugin-react@^6.2.1":
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.2.1.tgz#b0e5c19cff9cce00dc1d63b963bae273661814ec"
+ integrity sha512-9KyWCij3r0+qLG3Q62vBmv0VyevmYibAtxaCIVK9o0k6CXgaajZRIe8tpfchQcdZArJ5b/7OrIz4W3b2uxVRDA==
dependencies:
"@rolldown/pluginutils" "^1.0.1"
-"@vitest/expect@4.1.11":
- version "4.1.11"
- resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.1.11.tgz#5f580d1f9cdbba314dbf23b2d911f8eb23878f5f"
- integrity sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==
+"@vitest/mocker@5.1.0":
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-5.1.0.tgz#fba68dd77810c2f053b2ad7ea7ffc56e8d94eb8e"
+ integrity sha512-5umB9/jlWcqVS6fsrFmv5Gdnpu6wAUio/lEOwAmvB7v+lFo/IW2B9FhAD0p19GCULTgMzLCdUmuCDCnAzWgppQ==
dependencies:
- "@standard-schema/spec" "^1.1.0"
- "@types/chai" "^5.2.2"
- "@vitest/spy" "4.1.11"
- "@vitest/utils" "4.1.11"
- chai "^6.2.2"
- tinyrainbow "^3.1.0"
-
-"@vitest/mocker@4.1.11":
- version "4.1.11"
- resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.1.11.tgz#8e2906361bc5dfa271757a858ae80643118fcbb4"
- integrity sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==
- dependencies:
- "@vitest/spy" "4.1.11"
+ "@jridgewell/trace-mapping" "0.3.31"
+ "@vitest/spy" "5.1.0"
estree-walker "^3.0.3"
- magic-string "^0.30.21"
+ magic-string "^1.4.3"
-"@vitest/pretty-format@4.1.11":
- version "4.1.11"
- resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.1.11.tgz#8b28eb8240771d6ea970e33beaeb41384b51868e"
- integrity sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==
- dependencies:
- tinyrainbow "^3.1.0"
-
-"@vitest/runner@4.1.11":
- version "4.1.11"
- resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.1.11.tgz#bfbad98c8d6c3f1fb4df12056ad569821ff77f21"
- integrity sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==
- dependencies:
- "@vitest/utils" "4.1.11"
- pathe "^2.0.3"
-
-"@vitest/snapshot@4.1.11":
- version "4.1.11"
- resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.1.11.tgz#df461eb165924a3155986dde68e13360f53f3d4c"
- integrity sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==
- dependencies:
- "@vitest/pretty-format" "4.1.11"
- "@vitest/utils" "4.1.11"
- magic-string "^0.30.21"
- pathe "^2.0.3"
-
-"@vitest/spy@4.1.11":
- version "4.1.11"
- resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.1.11.tgz#0add45cae953afed9c88f98e2f6fc9164558c32a"
- integrity sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==
-
-"@vitest/utils@4.1.11":
- version "4.1.11"
- resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.1.11.tgz#9b27a4293b827942b223539bfab1bd9f7eada31b"
- integrity sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==
- dependencies:
- "@vitest/pretty-format" "4.1.11"
- convert-source-map "^2.0.0"
- tinyrainbow "^3.1.0"
+"@vitest/spy@5.1.0":
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-5.1.0.tgz#0a10d0ca39c0df70951715ba1081dd186a451c23"
+ integrity sha512-2DPO5OMHWKrBngC+fCmTQqemNF0gidRhp3lxbBmRhW+M1XJrfgPpqNqWVevXtn2f2ufD5ZaR1B6B641aAlcX5A==
ansi-regex@^5.0.1:
version "5.0.1"
@@ -1281,10 +1262,10 @@ ccount@^2.0.0:
resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
-chai@^6.2.2:
- version "6.2.2"
- resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e"
- integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==
+chai@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/chai/-/chai-6.3.0.tgz#91f89eb789c2bb1ae1787d78c8518e1d3cebfc07"
+ integrity sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==
character-entities-html4@^2.0.0:
version "2.1.0"
@@ -1313,11 +1294,23 @@ chokidar@^5.0.0:
dependencies:
readdirp "^5.0.0"
+chokidar@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-6.0.0.tgz#66bb84678ab40b8d25679d71f3955bd206ab618b"
+ integrity sha512-Ec2Z7wpcKBiMpR7DxteoqS/sq5G5d2A4fpxzPv6+fD7xNNdrTghn7L11YwV5EN1B3i72Erc3XVT5FkWIDx90+A==
+ dependencies:
+ readdirp "^5.1.1"
+
classnames@^2.3.2, classnames@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
+clickable-path@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/clickable-path/-/clickable-path-0.1.1.tgz#c11c5fddbf0781f6f7382b529c5e1d7f9124839a"
+ integrity sha512-ROn/mdV2pR8WFdpJjFDCSouLRg9myf95xacO1iy8G/AVO/x5y124pjF3EaoWrTBRWC3vh0DPqa/MhKyx3mMeKg==
+
clsx@^2.0.0, clsx@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
@@ -1333,20 +1326,15 @@ convert-source-map@^1.5.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
-convert-source-map@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
- integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
-
cookie@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c"
integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==
copy-anything@^4:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.1.1.tgz#2734bcffa1f01f8e2afb2232f1ee31cd5fcc6834"
- integrity sha512-AoT6Imdr98feSpFfmFwTFN73ccdr7uFPf27cBCgYvyyRyn1BzLRxMvrHNmwXO5LJMddRy4Rdhw2b1h7vSMKsEw==
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.1.5.tgz#11866cab9f08ef1f98daac8c468e10d2830294e3"
+ integrity sha512-vePst2VLTSqPglW7Q98VwFpGiGpOHTyXIo81MO5VyLecA5zMbZw/P/V1vmwD1jksa2iJpn/YkDrJ3XhTfzUyhQ==
cosmiconfig@^7.0.0:
version "7.1.0"
@@ -1359,7 +1347,7 @@ cosmiconfig@^7.0.0:
path-type "^4.0.0"
yaml "^1.10.0"
-country-flag-icons@^1.6.20:
+country-flag-icons@*, country-flag-icons@^1.6.20:
version "1.6.20"
resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.6.20.tgz#aa6f36104568993f9cd43e7da283f9b7d0a802cc"
integrity sha512-py8JiEKzjhYw6HPJ0L7SxLgCYim36UPRTZX43/kqGueUCZLSvnrqAiwW8HtQibur7mdkFQUkjOgdK+o/9FBtaw==
@@ -1393,11 +1381,6 @@ decode-named-character-reference@^1.0.0:
dependencies:
character-entities "^2.0.0"
-decode-uri-component@^0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.5.0.tgz#4592fa1e1d640ec5e2760e2e168ad2ab5f2c9da1"
- integrity sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg==
-
deepmerge@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
@@ -1438,16 +1421,16 @@ dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
-entities@^6.0.0:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694"
- integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==
-
entities@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b"
integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==
+entities@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-9.0.0.tgz#c45df3cdbc5ce805474759f4ec4ea7907d9d5477"
+ integrity sha512-oJFNDJg3kTPwAD1cNn2+WmUq4wFoo09npc3Ydf1D7ED4smRiWrsKnaLxykrTWgyGEcusc87P5dxlAleol9kpkg==
+
error-ex@^1.3.1:
version "1.3.4"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414"
@@ -1460,7 +1443,7 @@ es-errors@^1.3.0:
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
-es-module-lexer@^2.0.0:
+es-module-lexer@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.3.2.tgz#311fa4f40168c1975c505477c51b23234d41ad55"
integrity sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==
@@ -1482,7 +1465,7 @@ estree-walker@^3.0.3:
dependencies:
"@types/estree" "^1.0.0"
-expect-type@^1.3.0:
+expect-type@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.4.0.tgz#24edf7f0cc69a44d008567ba4594ab96f3c3a3d6"
integrity sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==
@@ -1507,10 +1490,10 @@ fdir@^6.5.0:
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
-filter-obj@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed"
- integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==
+filter-obj@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-6.1.0.tgz#58725ceed8f0de54b432d74b6a3eb149453d7ed0"
+ integrity sha512-xdMtCAODmPloU9qtmPcdBV9Kd27NtMse+4ayThxqIHUES5Z2S6bGpap5PpdmNM56ub7y3i1eyr+vJJIIgWGKmA==
find-root@^1.1.0:
version "1.1.0"
@@ -1549,20 +1532,15 @@ generate-password-browser@^1.1.0:
buffer "^6.0.3"
randombytes "^2.0.5"
-goober@^2.1.16:
+goober@^2.1.19:
version "2.1.19"
resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.19.tgz#a4b4dcdbba9325b8c4d7380099f9f281f27c6a6f"
integrity sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==
-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.14.5:
- version "20.14.5"
- resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-20.14.5.tgz#d76cfbb926ebde0575deb3bf92cb3e6bb88b8176"
- integrity sha512-x/RzkpWO40bTjIoT30iQtt64FLLmH/iRcUCN2X//bLx7H3ifkdfPXyqsro/OYtqzIAhiLMMA7mmiOR9C3NOKjQ==
+happy-dom@^21.0.4:
+ version "21.0.4"
+ resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-21.0.4.tgz#3b9e4b23a6ef5f94f6db242857a99742dd14f943"
+ integrity sha512-tFm+GaLBgo6o0awHnvHaNoraVo27qJ+2tZEtZ+cIa08hXbeitrxAJajLI05hsUy/2/YTKcUjQYzHe5mCcraLBw==
dependencies:
"@types/node" ">=20.0.0"
"@types/whatwg-mimetype" "^3.0.2"
@@ -1572,7 +1550,7 @@ happy-dom@^20.14.5:
whatwg-mimetype "^3.0.0"
ws "^8.21.0"
-hasown@^2.0.3:
+hasown@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003"
integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==
@@ -1580,21 +1558,21 @@ hasown@^2.0.3:
function-bind "^1.1.2"
hast-util-from-html@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz#485c74785358beb80c4ba6346299311ac4c49c82"
- integrity sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.1.0.tgz#d80f4949e450ca473e4a4e703313d883daeab604"
+ integrity sha512-873p9AuH93gBnATbH8rU3YvjTKxyrIeWd/M9tiLMzn3G82Os0xhQurctfnbYUSrDXoyjbJGpJ2qmTpefMw7ZaQ==
dependencies:
"@types/hast" "^3.0.0"
devlop "^1.1.0"
hast-util-from-parse5 "^8.0.0"
- parse5 "^7.0.0"
+ parse5 "^8.0.0"
vfile "^6.0.0"
vfile-message "^4.0.0"
hast-util-from-parse5@^8.0.0:
- version "8.0.3"
- resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e"
- integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==
+ version "8.0.4"
+ resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.4.tgz#cdbd8df25b0d031e440f3fa4f76ecbd676b41209"
+ integrity sha512-Yq7XlXor0Cpb71sI2E0zpv8QxQ5a6rfhDEETk4bcgrFX2SvJAY2PHocWwFtL6U9qyfiyByW3wZ6sZEfP2kW81g==
dependencies:
"@types/hast" "^3.0.0"
"@types/unist" "^3.0.0"
@@ -1620,9 +1598,9 @@ hast-util-parse-selector@^4.0.0:
"@types/hast" "^3.0.0"
hast-util-to-html@^9.0.0:
- version "9.0.5"
- resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005"
- integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==
+ version "9.0.6"
+ resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.6.tgz#249d71c9d2549b4c4953230e9327e5e6d6a22025"
+ integrity sha512-1SZUxCN3TtJAE7e2BQN2MBHsoZ1gnmi9GoCP5Di59CSWptIezSAW09yDZyVbanb78V9eCwrKqdQsjtfVTxYHWA==
dependencies:
"@types/hast" "^3.0.0"
"@types/unist" "^3.0.0"
@@ -1637,9 +1615,9 @@ hast-util-to-html@^9.0.0:
zwitch "^2.0.4"
hast-util-to-jsx-runtime@^2.0.0:
- version "2.3.6"
- resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98"
- integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==
+ version "2.3.7"
+ resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.7.tgz#2825e4a628e1ee64017c20ad281d5018c1aef536"
+ integrity sha512-F7DNV2NMcTxA4GqPVHnXzX+mpoPHAIY9rJAVH3CZ1FdBZOcBUEjUuvMkMpHVs/sicEtznh9tMGj5WUR9S4E2rg==
dependencies:
"@types/estree" "^1.0.0"
"@types/hast" "^3.0.0"
@@ -1653,7 +1631,7 @@ hast-util-to-jsx-runtime@^2.0.0:
mdast-util-mdxjs-esm "^2.0.0"
property-information "^7.0.0"
space-separated-tokens "^2.0.0"
- style-to-js "^1.0.0"
+ style-to-object "^2.0.0"
unist-util-position "^5.0.0"
vfile-message "^4.0.0"
@@ -1683,9 +1661,9 @@ hastscript@^7.0.0:
space-separated-tokens "^2.0.0"
hastscript@^9.0.0:
- version "9.0.1"
- resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff"
- integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==
+ version "9.0.3"
+ resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.3.tgz#960b9832fda11fd15b22b8a82e9e313926db9270"
+ integrity sha512-1ALZXSmCGmW3h8AMz4R4+QKSKFi7sNgXLVwmwB8FYVtl1CwQ3WDkSAEY/d5tp1g/vbQgq2s1WlHgfO3jbw1TsA==
dependencies:
"@types/hast" "^3.0.0"
comma-separated-tokens "^2.0.0"
@@ -1738,18 +1716,18 @@ indent-string@^4.0.0:
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
-inline-style-parser@0.2.7:
- version "0.2.7"
- resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909"
- integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==
+inline-style-parser@0.2.9:
+ version "0.2.9"
+ resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.9.tgz#148d759a4b8677a560a1d3e185e6db7f6be57652"
+ integrity sha512-IttN8BqKLxzUrnTqHI+/hwAJQItBAUjdoHERCDv+oOuX48VP6bzW8+QjOXSfoevzq3YrYAwX1uLHp8TqO9yIeg==
-intl-messageformat@11.2.14:
- version "11.2.14"
- resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-11.2.14.tgz#610e4a21511d85dc06634424409d60bb1e9e441f"
- integrity sha512-9f2VD1HFuxUvMw0RxsaP8WmMns6JRTnsNB/zghTFrp11ZktiXWwVDeZBPQchBKEmo+Gx/ZhxI7Qht7YglFD4PA==
+intl-messageformat@12.1.5:
+ version "12.1.5"
+ resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-12.1.5.tgz#1b902dd7f68764c69318a7afe363cbc34b426486"
+ integrity sha512-quhcDFAxy4wr/GrgWgmMjuegoedNUIxGNLORfXgDgdjuv8cBM88ClLTLXmfYC1roMSR1PPc55yUEkpJ5m67W2g==
dependencies:
"@formatjs/fast-memoize" "3.1.7"
- "@formatjs/icu-messageformat-parser" "3.5.17"
+ "@formatjs/icu-messageformat-parser" "3.5.23"
invariant@^2.2.4:
version "2.2.4"
@@ -1776,12 +1754,12 @@ is-arrayish@^0.2.1:
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
-is-core-module@^2.16.1:
- version "2.16.2"
- resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082"
- integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==
+is-core-module@^2.18.0:
+ version "2.18.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.18.0.tgz#8a988af8d3729fb6bbcaf025bf820cfe46b95f4c"
+ integrity sha512-qyax9fgaKOEC4qK8WZn7Q4IcoZC9RC1O1fFxRcRTq/oMMkWT/uXpqpXa12kzWR409zAt1drYN9N9BO26UmBCmw==
dependencies:
- hasown "^2.0.3"
+ hasown "^2.0.4"
is-decimal@^2.0.0:
version "2.0.1"
@@ -1810,6 +1788,11 @@ is-plain-obj@^4.0.0:
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
+js-tokens@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-10.0.0.tgz#dffe7599b4a8bb7fe30aff8d0235234dffb79831"
+ integrity sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -1941,17 +1924,17 @@ lz-string@^1.5.0:
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
-magic-string@^0.30.21:
- version "0.30.21"
- resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91"
- integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==
+magic-string@^1.4.3:
+ version "1.4.5"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-1.4.5.tgz#201c852d51eb0664d6f66cf7ab93b20b416e48d9"
+ integrity sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==
dependencies:
- "@jridgewell/sourcemap-codec" "^1.5.5"
+ "@jridgewell/sourcemap-codec" "^1.6.0"
mdast-util-from-markdown@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz#c95822b91aab75f18a4cbe8b2f51b873ed2cf0c7"
- integrity sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.1.0.tgz#a92a9130c95e0e137e7cffaf85588a7da9e4bda6"
+ integrity sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==
dependencies:
"@types/mdast" "^4.0.0"
"@types/unist" "^3.0.0"
@@ -1979,9 +1962,9 @@ mdast-util-mdx-expression@^2.0.0:
mdast-util-to-markdown "^2.0.0"
mdast-util-mdx-jsx@^3.0.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d"
- integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.1.tgz#40e60b71a5b822b7cb278fac4be2338ae64d7337"
+ integrity sha512-oXp1GIa+E9IwIs3a106+tr3Bz2MlTTBvlXjOZMoIat11kk7vjqgmBnksB9WHmWv+gZh7k3IUgpAHlwHB7DH4GQ==
dependencies:
"@types/estree-jsx" "^1.0.0"
"@types/hast" "^3.0.0"
@@ -2032,17 +2015,19 @@ mdast-util-to-hast@^13.0.0:
vfile "^6.0.0"
mdast-util-to-markdown@^2.0.0:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b"
- integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.2.3.tgz#1131a68290ff68e8718e75749a6e07e7654edef3"
+ integrity sha512-dImdB6VZRMRac1SwAW6o1c0ESk/7pFFAMCMuKM+9v7LpTSthMM5nzXpqKw4Mjo66aXVKhNkyrP4bMB9MurPyWg==
dependencies:
"@types/mdast" "^4.0.0"
"@types/unist" "^3.0.0"
longest-streak "^3.0.0"
mdast-util-phrasing "^4.0.0"
mdast-util-to-string "^4.0.0"
+ micromark-util-character "^2.0.0"
micromark-util-classify-character "^2.0.0"
micromark-util-decode-string "^2.0.0"
+ micromark-util-html-tag-name "^2.0.0"
unist-util-visit "^5.0.0"
zwitch "^2.0.0"
@@ -2059,26 +2044,27 @@ memoize-one@^6.0.0:
integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
micromark-core-commonmark@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4"
- integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.4.tgz#ff748f2327717b3675f0864e5aabc4f2a0028da3"
+ integrity sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==
dependencies:
decode-named-character-reference "^1.0.0"
devlop "^1.0.0"
micromark-factory-destination "^2.0.0"
micromark-factory-label "^2.0.0"
- micromark-factory-space "^2.0.0"
+ micromark-factory-space "^2.1.0"
micromark-factory-title "^2.0.0"
micromark-factory-whitespace "^2.0.0"
micromark-util-character "^2.0.0"
micromark-util-chunked "^2.0.0"
micromark-util-classify-character "^2.0.0"
+ micromark-util-edit-map "^1.0.0"
micromark-util-html-tag-name "^2.0.0"
micromark-util-normalize-identifier "^2.0.0"
micromark-util-resolve-all "^2.0.0"
micromark-util-subtokenize "^2.0.0"
micromark-util-symbol "^2.0.0"
- micromark-util-types "^2.0.0"
+ micromark-util-types "^2.0.3"
micromark-factory-destination@^2.0.0:
version "2.0.1"
@@ -2099,10 +2085,10 @@ micromark-factory-label@^2.0.0:
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
-micromark-factory-space@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc"
- integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==
+micromark-factory-space@^2.0.0, micromark-factory-space@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.1.0.tgz#afc208b018c330f17f438731c2014fdff1cd154f"
+ integrity sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==
dependencies:
micromark-util-character "^2.0.0"
micromark-util-types "^2.0.0"
@@ -2176,6 +2162,13 @@ micromark-util-decode-string@^2.0.0:
micromark-util-decode-numeric-character-reference "^2.0.0"
micromark-util-symbol "^2.0.0"
+micromark-util-edit-map@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-edit-map/-/micromark-util-edit-map-1.0.0.tgz#0b5dd9ab4295f7389472c5fee2b61ffd45774cb2"
+ integrity sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
micromark-util-encode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8"
@@ -2224,15 +2217,15 @@ micromark-util-symbol@^2.0.0:
resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8"
integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==
-micromark-util-types@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e"
- integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==
+micromark-util-types@^2.0.0, micromark-util-types@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.3.tgz#e7b04fa58589199c7ef58b37cd696fcb2bce44e7"
+ integrity sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==
micromark@^4.0.0:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb"
- integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.3.tgz#6d29c0a5383c1dda7975a6c897e0e18d5694f10d"
+ integrity sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==
dependencies:
"@types/debug" "^4.0.0"
debug "^4.0.0"
@@ -2244,6 +2237,7 @@ micromark@^4.0.0:
micromark-util-chunked "^2.0.0"
micromark-util-combine-extensions "^2.0.0"
micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-edit-map "^1.0.0"
micromark-util-encode "^2.0.0"
micromark-util-normalize-identifier "^2.0.0"
micromark-util-resolve-all "^2.0.0"
@@ -2262,33 +2256,25 @@ 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.18:
- version "3.3.19"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.19.tgz#336d4aa4bcd4fb24d2cddede7ffeae40bec03f0a"
- integrity sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==
+nanoid@^3.3.20:
+ version "3.3.20"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.20.tgz#dfd4444d85aa10111ef66ba85ed72e08ba0855d8"
+ integrity sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==
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==
-npm-run-path@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-6.0.0.tgz#25cfdc4eae04976f3349c0b1afc089052c362537"
- integrity sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==
- dependencies:
- path-key "^4.0.0"
- unicorn-magic "^0.3.0"
-
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
-obug@^2.1.1:
- version "2.1.4"
- resolved "https://registry.yarnpkg.com/obug/-/obug-2.1.4.tgz#9090d8a548a522517915d2aa6aae907197ac6cf8"
- integrity sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==
+obug@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/obug/-/obug-3.0.0.tgz#2ddfc17d1c1dd8c825eebd127099da307c919299"
+ integrity sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==
parent-module@^1.0.0:
version "1.0.1"
@@ -2325,17 +2311,12 @@ parse-numeric-range@^1.3.0:
resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3"
integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==
-parse5@^7.0.0:
- version "7.3.0"
- resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05"
- integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==
+parse5@^8.0.0:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-8.1.1.tgz#d73eb52282024c921e9c5a19b870f53492e631b2"
+ integrity sha512-EcR/6WDcU7b2N3vQSxKALCk9lXGtUMQxXio008OEMBE0kavu/7JLfBEeFPzi43nMqVDSbEn3QCK+BMCc9zO//w==
dependencies:
- entities "^6.0.0"
-
-path-key@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18"
- integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
+ entities "^9.0.0"
path-parse@^1.0.7:
version "1.0.7"
@@ -2347,11 +2328,6 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
-pathe@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716"
- integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
-
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -2362,7 +2338,7 @@ 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, picomatch@^4.0.4, picomatch@^4.0.7:
+picomatch@^4.0.4, picomatch@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f"
integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==
@@ -2372,14 +2348,14 @@ postcss-simple-vars@^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.28:
- version "8.5.28"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.28.tgz#da4563a99a06e62d6c1cd1acae363224bcaed6e9"
- integrity sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==
+postcss@^8.5.32:
+ version "8.5.32"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.32.tgz#fbccef8388a0c77508c713bfd37728d646595803"
+ integrity sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==
dependencies:
- nanoid "^3.3.18"
+ nanoid "^3.3.20"
picocolors "^1.1.1"
- source-map-js "^1.2.1"
+ source-map-js "^1.2.2"
pretty-format@^27.0.2:
version "27.5.1"
@@ -2407,15 +2383,6 @@ 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"
@@ -2431,14 +2398,13 @@ qrcode-generator@^2.0.4:
resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-2.0.4.tgz#e8b3f30922577eba52078aa9c0d5a2a74fe1fd94"
integrity sha512-mZSiP6RnbHl4xL2Ap5HfkjLnmxfKcPWpWe/c+5XxCuetEenqmNFf1FH/ftXPCtFG5/TDobjsjz6sSNL0Sr8Z9g==
-query-string@^9.5.1:
- version "9.5.1"
- resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.5.1.tgz#aecdc091d3dc7ce293eed83957e220d523a3d0f7"
- integrity sha512-/zO3RwuRCMTIcEgq6YMv4OrtEE1XzBG7w5N6zc6ydYnkWYOWsnLI/5894hYEzfESfMOT4cHCsRTIdxsSl1KjGg==
+query-string@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/query-string/-/query-string-10.0.0.tgz#fbc0743e6a0e0c1a600f2d1d399ce42521e17b0d"
+ integrity sha512-QYodx/Ywk8lCcOmsyupXVN91yWPAdjq0xVqE2FE2BHFYnma71C8txR8JCBptjzoUpkfAK7Qjua9VjYRTru4LYg==
dependencies:
- decode-uri-component "^0.5.0"
- filter-obj "^5.1.0"
- split-on-first "^3.0.0"
+ filter-obj "^6.1.0"
+ split-on-first "^4.0.0"
raf@^3.4.1:
version "3.4.1"
@@ -2455,18 +2421,18 @@ randombytes@^2.0.5:
safe-buffer "^5.1.0"
react-aria@^3.48.0:
- version "3.51.0"
- resolved "https://registry.yarnpkg.com/react-aria/-/react-aria-3.51.0.tgz#b8129df769eda51a0f92d256fddcc105badf6ed7"
- integrity sha512-AyWLw0XR38cFPwBu/ErgGaVrc5dupLEKmRlMXTGvFKOtbaGRQ2+yQJkjVhpdHhoRhU4+G+tJDFeHDTS8tK3bfQ==
+ version "3.53.1"
+ resolved "https://registry.yarnpkg.com/react-aria/-/react-aria-3.53.1.tgz#8c78b8e1a2c680d404c1aabe75f31c98d2444b10"
+ integrity sha512-dikisyOjJd3D2fFrIDWmVWzqiLz5B86GzZv1zxs7xI4dVc0JIz/1Wa52I20gYvVL2WXeFXlygYsle/QcOMhtcg==
dependencies:
- "@internationalized/date" "^3.12.3"
- "@internationalized/number" "^3.6.7"
+ "@internationalized/date" "^3.12.4"
+ "@internationalized/number" "^3.6.9"
"@internationalized/string" "^3.2.10"
- "@react-types/shared" "^3.36.1"
+ "@react-types/shared" "^3.37.0"
"@swc/helpers" "^0.5.0"
aria-hidden "^1.2.3"
clsx "^2.0.0"
- react-stately "3.49.0"
+ react-stately "3.51.0"
use-sync-external-store "^1.6.0"
react-bootstrap@^2.10.10:
@@ -2488,10 +2454,10 @@ react-bootstrap@^2.10.10:
uncontrollable "^7.2.1"
warning "^4.0.3"
-react-dom@^19.3.0:
- version "19.3.0"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.3.0.tgz#a0ff19161d7f4e9be5469a07278b0c06e00f9001"
- integrity sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==
+react-dom@^19.3.1:
+ version "19.3.1"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.3.1.tgz#b384c53cfcbe9c2a5d09d863b98455de9d68afe2"
+ integrity sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==
dependencies:
scheduler "^0.28.0"
@@ -2500,14 +2466,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@^10.2.0:
- version "10.2.0"
- resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-10.2.0.tgz#576783362ada9bc9a8ced594b1d8ff1225a08f2f"
- integrity sha512-b7wiqmo/fWQZ5Et7f8Qsh58rD+6STV/KrX4NuVjmpGFAxk2vqgEwGKDlW0uRfWG2mg4zt7sWnxvOBfLPMDkMYA==
+react-intl@^12.1.7:
+ version "12.1.7"
+ resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-12.1.7.tgz#fb24e2fe107acb070903a44a6dbb68cb7e749974"
+ integrity sha512-DKPrDYGQfQpyW8g8dbEPd6abvinwHPw85LcAT6nmd8+kzexea5egLQu6KZTEGDgP+3ap8dn+sX3JTuS7v0xD4g==
dependencies:
- "@formatjs/icu-messageformat-parser" "3.5.17"
- "@formatjs/intl" "4.2.0"
- intl-messageformat "11.2.14"
+ "@formatjs/icu-messageformat-parser" "3.5.23"
+ "@formatjs/intl" "6.1.5"
+ intl-messageformat "12.1.5"
react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0:
version "16.13.1"
@@ -2549,17 +2515,17 @@ react-qr-code@^2.2.0:
prop-types "^15.8.1"
qrcode-generator "^2.0.4"
-react-router-dom@^7.18.4:
- version "7.18.4"
- resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.18.4.tgz#07b72b8aab2d80af847425107f966f1688c703d0"
- integrity sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==
+react-router-dom@^7.18.5:
+ version "7.18.5"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.18.5.tgz#25e19a13396de166785c1199227a64bcd6815a8d"
+ integrity sha512-9HcfDMU6JJ/fp6gJZYGwNE6RknVGS4kBjH1HkNw959mCJi70TVwdipQWv2FEol9uGpi+SZTFtVpJPtEQfayeOw==
dependencies:
- react-router "7.18.4"
+ react-router "7.18.5"
-react-router@7.18.4:
- version "7.18.4"
- resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.18.4.tgz#dc30cbb27e936f06d9e1d75ab648b317deb698a4"
- integrity sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==
+react-router@7.18.5:
+ version "7.18.5"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.18.5.tgz#808611f9f8675fe4506699815168822f4d9f3ee3"
+ integrity sha512-dTgvcXDOrLPrlLHrcZAqWveiK1hMSqN+rGAJzegP8HtmNkfRxOoshGWBNX3ZJ0cZjOHkRdlUJtaIhZMLLyGyPw==
dependencies:
cookie "^1.0.1"
set-cookie-parser "^2.6.0"
@@ -2579,22 +2545,22 @@ react-select@^5.10.2:
react-transition-group "^4.3.0"
use-isomorphic-layout-effect "^1.2.0"
-react-stately@3.49.0:
- version "3.49.0"
- resolved "https://registry.yarnpkg.com/react-stately/-/react-stately-3.49.0.tgz#bc7fda7d0245e2a1239a8f0451950b27639d0e17"
- integrity sha512-13iNq2KzBrRAzxRc+n53hgROfIistiYY/sPtIhCw1qUB7/kmo+X1xEU2uiS5zcCIrc55AUPwoHqOIIpKWSwB9A==
+react-stately@3.51.0:
+ version "3.51.0"
+ resolved "https://registry.yarnpkg.com/react-stately/-/react-stately-3.51.0.tgz#115925ddfdd4838baedc7d1123ffcb5f937230fc"
+ integrity sha512-YJcmkAXu2XOQP97hO/qz/cC3qv9kni3oVhwBlxWr358BHePJUpY15XwL3YSRMKxYt4EkslPqAz11JRirMfANGw==
dependencies:
- "@internationalized/date" "^3.12.3"
- "@internationalized/number" "^3.6.7"
+ "@internationalized/date" "^3.12.4"
+ "@internationalized/number" "^3.6.9"
"@internationalized/string" "^3.2.10"
- "@react-types/shared" "^3.36.1"
+ "@react-types/shared" "^3.37.0"
"@swc/helpers" "^0.5.0"
use-sync-external-store "^1.6.0"
-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==
+react-toastify@^11.2.0:
+ version "11.2.0"
+ resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-11.2.0.tgz#9035cbf6549c68fca0c150a9591cbc9fe1a41596"
+ integrity sha512-lznu1N00MfEv4p9vGWNVqCfDQ3DxScDAZTFk6STMsEcLcRZKT4PCO0JxsNduGN7LJPBSkXXs/YuLydjh650T7Q==
dependencies:
clsx "^2.1.1"
@@ -2608,12 +2574,12 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.5:
loose-envify "^1.4.0"
prop-types "^15.6.2"
-react@^19.3.0:
- version "19.3.0"
- resolved "https://registry.yarnpkg.com/react/-/react-19.3.0.tgz#e20aa3a14687075ce863351b5878b0608c3fbfb2"
- integrity sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==
+react@^19.3.1:
+ version "19.3.1"
+ resolved "https://registry.yarnpkg.com/react/-/react-19.3.1.tgz#26e9523fc36ea06be5050ebb780249a133260ad7"
+ integrity sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==
-readdirp@^5.0.0:
+readdirp@^5.0.0, readdirp@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-5.1.1.tgz#520bca06f9d1ae1b96cc0800dbe84b983d19422c"
integrity sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==
@@ -2708,43 +2674,38 @@ resolve-from@^4.0.0:
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve@^1.19.0:
- version "1.22.12"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f"
- integrity sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==
+ version "1.22.14"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.14.tgz#302388cd2d7731a100db1dedce7b050f74ba38f7"
+ integrity sha512-1ZjYvR9sbkiwC5+9QHetqW5HmA2pHRMaoupD01t2HlYnV/vydC2zPwLixhsMieSWVZt0JJRg5LVIk0Ow1D/TrA==
dependencies:
es-errors "^1.3.0"
- is-core-module "^2.16.1"
+ is-core-module "^2.18.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
-retry@^0.12.0:
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
- integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==
-
-rolldown@~1.2.6:
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.2.9.tgz#b449d73b7aeaf0097adfc25967a76fa1bf304c56"
- integrity sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==
+rolldown@~1.2.16:
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.2.16.tgz#6c99aafa32361d578fbd945aeb43e533bb8f4c28"
+ integrity sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==
dependencies:
- "@oxc-project/types" "=0.150.0"
+ "@oxc-project/types" "=0.156.0"
"@rolldown/pluginutils" "^1.0.0"
optionalDependencies:
- "@rolldown/binding-android-arm-eabi" "1.2.9"
- "@rolldown/binding-android-arm64" "1.2.9"
- "@rolldown/binding-darwin-arm64" "1.2.9"
- "@rolldown/binding-darwin-x64" "1.2.9"
- "@rolldown/binding-freebsd-x64" "1.2.9"
- "@rolldown/binding-linux-arm-gnueabihf" "1.2.9"
- "@rolldown/binding-linux-arm64-gnu" "1.2.9"
- "@rolldown/binding-linux-arm64-musl" "1.2.9"
- "@rolldown/binding-linux-ppc64-gnu" "1.2.9"
- "@rolldown/binding-linux-s390x-gnu" "1.2.9"
- "@rolldown/binding-linux-x64-gnu" "1.2.9"
- "@rolldown/binding-linux-x64-musl" "1.2.9"
- "@rolldown/binding-openharmony-arm64" "1.2.9"
- "@rolldown/binding-win32-arm64-msvc" "1.2.9"
- "@rolldown/binding-win32-x64-msvc" "1.2.9"
+ "@rolldown/binding-android-arm-eabi" "1.2.16"
+ "@rolldown/binding-android-arm64" "1.2.16"
+ "@rolldown/binding-darwin-arm64" "1.2.16"
+ "@rolldown/binding-darwin-x64" "1.2.16"
+ "@rolldown/binding-freebsd-x64" "1.2.16"
+ "@rolldown/binding-linux-arm-gnueabihf" "1.2.16"
+ "@rolldown/binding-linux-arm64-gnu" "1.2.16"
+ "@rolldown/binding-linux-arm64-musl" "1.2.16"
+ "@rolldown/binding-linux-ppc64-gnu" "1.2.16"
+ "@rolldown/binding-linux-s390x-gnu" "1.2.16"
+ "@rolldown/binding-linux-x64-gnu" "1.2.16"
+ "@rolldown/binding-linux-x64-musl" "1.2.16"
+ "@rolldown/binding-openharmony-arm64" "1.2.16"
+ "@rolldown/binding-win32-arm64-msvc" "1.2.16"
+ "@rolldown/binding-win32-x64-msvc" "1.2.16"
rooks@^9.9.0:
version "9.9.0"
@@ -2763,10 +2724,10 @@ safe-buffer@^5.1.0:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-sass@^1.104.1:
- version "1.104.1"
- resolved "https://registry.yarnpkg.com/sass/-/sass-1.104.1.tgz#29a4bdb33c48a8bb656e300a33afcafbfcbc8491"
- integrity sha512-yDA+1aIG3EHgN4V/BvuhCvu61FF6hEd4e+9DxikUm9U0CAGuvdIZ/UYy7qbOxjhbbWQraoyLlMuzdRGOSV5Bmw==
+sass@^1.108.1:
+ version "1.108.1"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.108.1.tgz#169e456df9ad50bac4593435a207106a59b645a1"
+ integrity sha512-sIYgSvOPeD9wABwDS/ofNjHASSck+7h3jLkk7wG1NPFL3XeS6vOQjBlAFSmRV09kZr1yt/Tuayt6kKtBbLjHNQ==
dependencies:
chokidar "^5.0.0"
immutable "^5.1.5"
@@ -2779,39 +2740,29 @@ scheduler@^0.28.0:
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.28.0.tgz#03d3ca348aeaf8b7502f64468f217b27523f9695"
integrity sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==
-seroval-plugins@~1.5.4:
- version "1.5.6"
- resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.5.6.tgz#7bca495aceb5b2428b9cb44b4b09e67c2de1c948"
- integrity sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ==
+seroval-plugins@~1.6.8:
+ version "1.6.12"
+ resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.6.12.tgz#a8f6013fc0fb68f797f53cde12deb25c3e9fbfee"
+ integrity sha512-+reiwp/lYxiyctSrdvX7OQ1YLrcbWYgBiZrhSFL+V43b0/DGRLNVSDyZINbQe+tWplPOI2fw2dPrdv6hcc09kQ==
-seroval@~1.5.4:
- version "1.5.6"
- resolved "https://registry.yarnpkg.com/seroval/-/seroval-1.5.6.tgz#68d4f6a05c3bde25daaaea6b7220146ff42353ca"
- integrity sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA==
+seroval@~1.6.11:
+ version "1.6.12"
+ resolved "https://registry.yarnpkg.com/seroval/-/seroval-1.6.12.tgz#d6ad056e44e52e666a7ca7919a67294eb13ecd3b"
+ integrity sha512-CqbIVfk/7GL3ITwZI55lcPbpQbjHZ2DD5cUj4iH7E9YsCfHNJV1mx7yNp86Z84w5IpmkZZS/2s5vPOWmVWSdQg==
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"
- integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==
+ version "2.7.3"
+ resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.3.tgz#ded8e573414ceb6f5752f610dc9751f4eda0295d"
+ integrity sha512-irZHtTqurLna57KQG/s+fzOwv/J6XSH4cs8M/vsrY3uCaQ3S6+QJguAH0WdN47SXlk82voMZ4naWMQf8fdEoIg==
-siginfo@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
- integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
-
-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==
-
-solid-js@^1.9.7:
- version "1.9.15"
- resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.9.15.tgz#209cbd4e0c7108d1e9bd1537ea7f91e03277a763"
- integrity sha512-EeiY2xfpZJqPLjXspVEKjAII4yv8NyG//NxZ3IpOFHdUNnnTyL0uJOeS9LWGvA7cFCz5y94cjFwYlmw5Luncsg==
+solid-js@^1.9.15:
+ version "1.9.18"
+ resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.9.18.tgz#a26f33066c8877fabf7094dea092138eb0ae9b63"
+ integrity sha512-CHGO5YFk6Y9m/1YSxVtKnXfrW7JD3Tmof8HmR4z6TmCPsS8efo8LnzIR8CQh3cUvffqYm6DWyBFfk8dQw1CNQQ==
dependencies:
csstype "^3.1.0"
- seroval "~1.5.4"
- seroval-plugins "~1.5.4"
+ seroval "~1.6.11"
+ seroval-plugins "~1.6.8"
solid-presence@^0.2.0:
version "0.2.0"
@@ -2835,10 +2786,10 @@ solid-transition-group@^0.2.3:
"@solid-primitives/refs" "^1.0.5"
"@solid-primitives/transition-group" "^1.0.2"
-"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
- integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.2.tgz#79fb88d97535a21cb27162cb05d211b67acea4ab"
+ integrity sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==
source-map@^0.5.7:
version "0.5.7"
@@ -2850,20 +2801,15 @@ space-separated-tokens@^2.0.0:
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f"
integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
-split-on-first@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7"
- integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==
+split-on-first@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-4.0.0.tgz#f49436d62be0d529cef522e2fd4bd08725de2ff3"
+ integrity sha512-pyrgNqiEBHA9KlEtg9quYGagtrlTyukL3ryQ+RmjRn9SYmcNQmfa+sWIsI+hlcL4WrmFdrt7IfM6o8ktBAooDQ==
-stackback@0.0.2:
- version "0.0.2"
- resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b"
- integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==
-
-std-env@^4.0.0-rc.1:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/std-env/-/std-env-4.2.0.tgz#8ebe0ec60485668ab47227b312f4254cdf80c9d3"
- integrity sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==
+std-env@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/std-env/-/std-env-4.3.0.tgz#4e7231b61fd83219aa7f76f71fa8818b07e41321"
+ integrity sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==
stringify-entities@^4.0.0:
version "4.0.4"
@@ -2880,26 +2826,19 @@ strip-indent@^3.0.0:
dependencies:
min-indent "^1.0.0"
-style-to-js@^1.0.0:
- version "1.1.21"
- resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.21.tgz#2908941187f857e79e28e9cd78008b9a0b3e0e8d"
- integrity sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==
+style-to-object@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-2.0.2.tgz#f14fc7b1ec0fc7d8e88208a0e09fdd20b5e362e6"
+ integrity sha512-QmbXPUylqU80HAgTOJeEOdbA40W9KtxmjMhQy+tq+b8F2aoh3aQLuy82hh2jm2qsC9hR9eI0+eQidTMydKcqRA==
dependencies:
- style-to-object "1.0.14"
-
-style-to-object@1.0.14:
- version "1.0.14"
- resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.14.tgz#1d22f0e7266bb8c6d8cae5caf4ec4f005e08f611"
- integrity sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==
- dependencies:
- inline-style-parser "0.2.7"
+ inline-style-parser "0.2.9"
stylis@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
-superjson@^2.2.2:
+superjson@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/superjson/-/superjson-2.2.6.tgz#a223a3a988172a5f9656e2063fe5f733af40d099"
integrity sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==
@@ -2911,27 +2850,22 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
-tiny-invariant@^1.3.3:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
- integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
-
tiny-warning@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
-tinybench@^2.9.0:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
- integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
+tinybench@^6.2.2:
+ version "6.2.3"
+ resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-6.2.3.tgz#2e560dfbf17f83ddbe0be0681f8ed0553d2136d5"
+ integrity sha512-Al01C2JIBnO2CXCAPbpAl6sUiOZHeThGtHYNTa51JRw9PyWOCdr+9Ong05R4qg/Sk3+H645E8ov1h2c58ityNw==
-tinyexec@^1.0.2:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.3.0.tgz#aacc1dbb1d4e93e6ad8dd64944e09f9ad147a474"
- integrity sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==
+tinyexec@^1.2.4, tinyexec@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.3.1.tgz#16a2e3c6e23fafce72640e678e651db36145b9c6"
+ integrity sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==
-tinyglobby@^0.2.15, tinyglobby@^0.2.17:
+tinyglobby@^0.2.17:
version "0.2.17"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631"
integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==
@@ -2939,11 +2873,6 @@ tinyglobby@^0.2.15, tinyglobby@^0.2.17:
fdir "^6.5.0"
picomatch "^4.0.4"
-tinyrainbow@^3.1.0:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-3.1.1.tgz#c0168387d3d8d70b6b3c2c0936de5fee738cea20"
- integrity sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==
-
tmp@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.7.tgz#26f4db11d1601ce8012dcb8a798ece1c06a99059"
@@ -2959,10 +2888,10 @@ trough@^2.0.0:
resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f"
integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==
-tslib@^2.0.0, tslib@^2.8.0:
- version "2.8.1"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
- integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+tslib@^2.0.0:
+ version "2.9.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.9.1.tgz#8986213ba2f17f38ba39a7c43d0e565a9134ff5f"
+ integrity sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==
typescript@7.0.2:
version "7.0.2"
@@ -3010,11 +2939,6 @@ undici-types@~8.9.0:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-8.9.0.tgz#e240d97c8b5d85e5347ce73d25865c7906c1ec9f"
integrity sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==
-unicorn-magic@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz#4efd45c85a69e0dd576d25532fbfa22aa5c8a104"
- integrity sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==
-
unified@^11.0.0:
version "11.0.5"
resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1"
@@ -3058,22 +2982,22 @@ unist-util-stringify-position@^4.0.0:
dependencies:
"@types/unist" "^3.0.0"
-unist-util-visit-parents@^6.0.0:
- version "6.0.2"
- resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz#777df7fb98652ce16b4b7cd999d0a1a40efa3a02"
- integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==
+unist-util-visit-parents@^6.0.0, unist-util-visit-parents@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.1.0.tgz#eff74226782b378957731bcf62cf5e9d8a080606"
+ integrity sha512-vKXxz6O38sKDj1mtx/k2cWvkkFBuYGx6uGrlzBkVeWCRF/F8sdI0j3Wbkz/5rVNRRuVc8UrgRdbCxNhmYXgP3w==
dependencies:
"@types/unist" "^3.0.0"
unist-util-is "^6.0.0"
unist-util-visit@^5.0.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468"
- integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.2.0.tgz#914256bc5e1801e280c12970fad4328352645974"
+ integrity sha512-fpsYs/VVoZxfGY9/j3FQpZmN8wdLJaV1Pt38AaJNZu9uKIHcFU3halCdBveEq1HGKO1OEfJY65w1zvoFV1b3qQ==
dependencies:
"@types/unist" "^3.0.0"
unist-util-is "^6.0.0"
- unist-util-visit-parents "^6.0.0"
+ unist-util-visit-parents "^6.1.0"
use-isomorphic-layout-effect@^1.2.0:
version "1.2.1"
@@ -3081,22 +3005,22 @@ use-isomorphic-layout-effect@^1.2.0:
integrity sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==
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==
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz#6dcb66ef569e02f186af6b3d575f414ce746e18f"
+ integrity sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==
vfile-location@^5.0.0:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3"
- integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.4.tgz#7e101a62d77ed528deb33c88c9839fb3be25b895"
+ integrity sha512-vEvxatYZCIpNvzGk2UD+u4I/sZkSKoOsdRCbkOIsN73QpJbriJ1qvvBAL1PeI0y55Leoqf7pORbaSPpG/smmOg==
dependencies:
"@types/unist" "^3.0.0"
vfile "^6.0.0"
vfile-message@^4.0.0:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4"
- integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.4.tgz#24dd2080867c336e121a2b7446c247d935022565"
+ integrity sha512-66ycRswIkNmHH9PHku7ADtv5RVwgIrOcuv3J8Svz6h4qgGRrKVa+9/neuKyJZUXyOpYwLTvS+whZ4R2urHHgQg==
dependencies:
"@types/unist" "^3.0.0"
unist-util-stringify-position "^4.0.0"
@@ -3109,57 +3033,48 @@ vfile@^6.0.0:
"@types/unist" "^3.0.0"
vfile-message "^4.0.0"
-vite-plugin-checker@^0.14.5:
- version "0.14.5"
- resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.14.5.tgz#b809a65bbec0195d932bfee4bb3a49cdd2907640"
- integrity sha512-c9lQ92eisUO+F7Fd93aelojmiOS+NQpPgQ1XR2LTQHox1/laZf4yAoQj+L3RA9Vgh10e2nFd9b8r2LLyYZsbpA==
+vite-plugin-checker@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-1.0.0.tgz#8027a347e19cdaa0e7d841cd5ae5eed9f59b5b9e"
+ integrity sha512-3Ef78GTs13BUSTCqUCnl5kh8/Ev00G3cTt4z2mQacCODCdx6oq4B/TgXiCvGJ1D7/Hhbd84QKz691+8gbn1uog==
dependencies:
- "@babel/code-frame" "^7.29.0"
- chokidar "^5.0.0"
- npm-run-path "^6.0.0"
- picocolors "^1.1.1"
+ "@babel/code-frame" "^8.0.0"
+ chokidar "^6.0.0"
+ clickable-path "^0.1.1"
picomatch "^4.0.4"
- proper-lockfile "^4.1.2"
- tiny-invariant "^1.3.3"
+ tinyexec "^1.2.4"
-"vite@^6.0.0 || ^7.0.0 || ^8.0.0", vite@^8.3.0:
- version "8.3.0"
- resolved "https://registry.yarnpkg.com/vite/-/vite-8.3.0.tgz#f9565cfd4879d58d28fa64d3c7aff3b372d2ccc8"
- integrity sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==
+vite@^8.3.6:
+ version "8.3.6"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-8.3.6.tgz#ff2eb6b0b18e910c08a79c59a80faebecd422808"
+ integrity sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA==
dependencies:
lightningcss "^1.33.0"
picomatch "^4.0.7"
- postcss "^8.5.28"
- rolldown "~1.2.6"
+ postcss "^8.5.32"
+ rolldown "~1.2.16"
tinyglobby "^0.2.17"
optionalDependencies:
fsevents "~2.3.3"
-vitest@^4.1.11:
- version "4.1.11"
- resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.1.11.tgz#1653c1521ae917f960d9b21877797c47dfd8bf21"
- integrity sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==
+vitest@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/vitest/-/vitest-5.1.0.tgz#5c6d94179dcb84bb153e87c3fc4e73cf7f29f9a0"
+ integrity sha512-MzmNFnrwa8v8lF1bLxw3UJ4EREwVOVb75VqJZmUDRjJ6EluK/x9th7qTbaeDk41WxE1GxWqmTXcdsfobfH5sOA==
dependencies:
- "@vitest/expect" "4.1.11"
- "@vitest/mocker" "4.1.11"
- "@vitest/pretty-format" "4.1.11"
- "@vitest/runner" "4.1.11"
- "@vitest/snapshot" "4.1.11"
- "@vitest/spy" "4.1.11"
- "@vitest/utils" "4.1.11"
- 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 "^4.0.0-rc.1"
- tinybench "^2.9.0"
- tinyexec "^1.0.2"
- tinyglobby "^0.2.15"
- tinyrainbow "^3.1.0"
- vite "^6.0.0 || ^7.0.0 || ^8.0.0"
- why-is-node-running "^2.3.0"
+ "@types/chai" "^5.2.2"
+ "@vitest/mocker" "5.1.0"
+ chai "^6.3.0"
+ es-module-lexer "^2.3.2"
+ expect-type "^1.4.0"
+ magic-string "^1.4.3"
+ obug "^3.0.0"
+ picomatch "^4.0.7"
+ std-env "^4.3.0"
+ tinybench "^6.2.2"
+ tinyexec "^1.3.1"
+ tinyglobby "^0.2.17"
+ why-is-node-running "3.2.1"
warning@^4.0.0, warning@^4.0.3:
version "4.0.3"
@@ -3178,18 +3093,15 @@ whatwg-mimetype@^3.0.0:
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7"
integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==
-why-is-node-running@^2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04"
- integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==
- dependencies:
- siginfo "^2.0.0"
- stackback "0.0.2"
+why-is-node-running@3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-3.2.1.tgz#f66f0d61f861e4d3965a7ad63645a953f550830c"
+ integrity sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==
ws@^8.21.0:
- version "8.21.3"
- resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.3.tgz#660b4faddb6a3e575c86e078126919961f4de4fc"
- integrity sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==
+ version "8.22.2"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.22.2.tgz#b1762f4e4ac87ab44aa358f680cdf5d08b166d37"
+ integrity sha512-/rEZ2ejown2N5gxVTHUu9bVVC7ttu0/Tgzf5hbTOixR3Fs7mbbXgn2DbdbcKgJOmpQMtrIUWivtYuVMnp/W/yA==
yaml@^1.10.0:
version "1.10.3"
diff --git a/scripts/ci/test-and-build b/scripts/ci/test-and-build
index a0de34140..c7886718b 100755
--- a/scripts/ci/test-and-build
+++ b/scripts/ci/test-and-build
@@ -12,7 +12,7 @@ docker run --rm \
-v "$(pwd)/backend:/app" \
-w /app \
"${TESTING_IMAGE}" \
- sh -c 'yarn install && yarn lint . && rm -rf node_modules'
+ sh -c 'yarn install && yarn lint . && yarn test && rm -rf node_modules'
echo -e "${BLUE}❯ ${GREEN}Testing Complete${RESET}"
# Build
@@ -28,3 +28,5 @@ docker build --pull --no-cache --compress \
--build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \
.
echo -e "${BLUE}❯ ${GREEN}Building Complete${RESET}"
+
+python3 test/host-header-smoke.py "${IMAGE:-nginx-proxy-manager}:${BRANCH_LOWER:-unknown}-ci-${BUILD_NUMBER:-0000}"
diff --git a/test/README.md b/test/README.md
index 63a2453b7..20be040e7 100644
--- a/test/README.md
+++ b/test/README.md
@@ -1,5 +1,17 @@
# Cypress Test Suite
+## Host Header Regression Test
+
+Run `python3 test/host-header-smoke.py ` from the repository root.
+The backend CI build runs this check against the image it builds. It creates a
+disposable NPM container and renders a proxy host through the backend's normal
+configuration generator, including a custom location. A synthetic HTTPS upstream
+checks that the forwarded Host matches Origin and Referer, and echoes the request
+method, path and body. Coverage includes HTTP and HTTPS, explicit default and
+nonstandard ports, mixed-case names, IPv6 authorities, and HTTP/1.0 without Host.
+All test traffic and published Docker ports use loopback; the container and its
+anonymous volumes are removed on completion.
+
## Running Locally
```
@@ -44,3 +56,14 @@ The ESLint extension only works on JavaScript files by default, so add the follo
"editor.formatOnSave": false,
},
```
+
+## Forwarded Host image test
+
+Run `python3 test/host-header-smoke.py ` from the repository root.
+The test waits for the manager, generated HTTP/HTTPS proxy host, both location
+paths and its synthetic upstream. It verifies Host/Origin/Referer consistency,
+request bodies and HTTP/1.0 fallback, then uses a dynamic upstream counter to
+prove cache hits and isolation between client authorities on ports 232 and 233.
+Requests run through `docker exec`, so a remote Docker daemon or containerized
+CI agent needs no shared loopback interface or published ports. The CI image
+build script invokes this test against the image it just built.
diff --git a/test/cypress/Dockerfile b/test/cypress/Dockerfile
index cf071bafd..10fda2b1f 100644
--- a/test/cypress/Dockerfile
+++ b/test/cypress/Dockerfile
@@ -1,4 +1,4 @@
-FROM cypress/included:15.15.0
+FROM cypress/included:16.2.1
# Disable Cypress CLI colors
ENV FORCE_COLOR=0
@@ -18,6 +18,7 @@ RUN wget "https://github.com/testssl/testssl.sh/archive/refs/tags/v3.2rc4.tar.gz
COPY --chown=1000 ./test /test
WORKDIR /test
-RUN yarn install && yarn cache clean
+RUN npm i -g yarn && yarn install && yarn cache clean
ENTRYPOINT []
CMD ["cypress", "run"]
+
diff --git a/test/cypress/config/ci.mjs b/test/cypress/config/ci.mjs
index 86c0a80df..b1e208190 100644
--- a/test/cypress/config/ci.mjs
+++ b/test/cypress/config/ci.mjs
@@ -7,6 +7,7 @@ export default defineConfig({
allowCypressEnv: false,
requestTimeout: 30000,
defaultCommandTimeout: 20000,
+ taskTimeout: 90000,
reporter: "cypress-multi-reporters",
reporterOptions: {
configFile: "multi-reporter.json"
diff --git a/test/cypress/e2e/api/AccessListHostToggle.cy.js b/test/cypress/e2e/api/AccessListHostToggle.cy.js
new file mode 100644
index 000000000..46e01fb3a
--- /dev/null
+++ b/test/cypress/e2e/api/AccessListHostToggle.cy.js
@@ -0,0 +1,210 @@
+///
+
+// Regression tests for #5919, #4286 and #5710: access list rules must survive
+// every path that re-renders a proxy host config, not just the initial save.
+describe('Access Lists across Proxy Host disable/enable', () => {
+ const domain = 'website4.example.com';
+
+ const creds = {
+ username: 'toggle-user',
+ password: 'toggle-pass',
+ };
+
+ let token;
+ let authListId;
+ let clientListId;
+ let hostId;
+
+ /**
+ * Requests a path on the proxy host directly (bypassing squid)
+ * and yields the HTTP status code
+ *
+ * @param {object} [auth]
+ * @param {string} auth.username
+ * @param {string} auth.password
+ */
+ const request = (auth) => {
+ const authArg = auth ? `-u '${auth.username}:${auth.password}'` : '';
+ return cy.task('exec', { command: `curl --noproxy '*' -s -o /dev/null -w '%{http_code}' ${authArg} http://${domain}/` }, { log: false })
+ .then((result) => {
+ expect(result.exitCode).to.eq(0);
+ return result.stdout.trim();
+ });
+ };
+
+ // nginx reloads are signalled and return immediately, so the old
+ // config can still be served for a moment after an API change.
+ // Retry until the expected status is seen.
+ const expectStatus = (auth, status) => {
+ let last = null;
+ cy.waitUntil(() => request(auth).then((res) => {
+ last = res;
+ return res === status;
+ }), {
+ timeout: 15000,
+ interval: 500,
+ errorMsg: () => `${domain} did not return ${status}, last status: ${last}`,
+ });
+ };
+
+ const createAccessList = (data) => {
+ return cy.task('backendApiPost', {
+ token: token,
+ path: '/api/nginx/access-lists',
+ data: {
+ satisfy_any: false,
+ pass_auth: false,
+ items: [],
+ clients: [],
+ ...data,
+ }
+ }).then((res) => {
+ cy.validateSwaggerSchema('post', 201, '/nginx/access-lists', res);
+ expect(res).to.have.property('id');
+ expect(res.id).to.be.greaterThan(0);
+ return cy.wrap(res.id);
+ });
+ };
+
+ const setHostAccessList = (accessListId) => {
+ cy.task('backendApiPut', {
+ token: token,
+ path: `/api/nginx/proxy-hosts/${hostId}`,
+ data: {
+ access_list_id: accessListId,
+ }
+ }).then((data) => {
+ // No swagger validation here: with a host access list set, the expanded
+ // access_list in the response has no proxy_host_count, which the schema requires
+ expect(data).to.have.property('access_list_id', accessListId);
+ });
+ };
+
+ const toggleHost = () => {
+ cy.task('backendApiPost', {
+ token: token,
+ path: `/api/nginx/proxy-hosts/${hostId}/disable`,
+ }).then((data) => {
+ cy.validateSwaggerSchema('post', 200, '/nginx/proxy-hosts/{hostID}/disable', data);
+ expect(data).to.be.equal(true);
+ });
+
+ cy.task('backendApiPost', {
+ token: token,
+ path: `/api/nginx/proxy-hosts/${hostId}/enable`,
+ }).then((data) => {
+ cy.validateSwaggerSchema('post', 200, '/nginx/proxy-hosts/{hostID}/enable', data);
+ expect(data).to.be.equal(true);
+ });
+ };
+
+ before(() => {
+ cy.resetUsers();
+ cy.getToken().then((tok) => {
+ token = tok;
+ });
+ });
+
+ it('Should be able to create an auth access list and a client access list', () => {
+ createAccessList({
+ name: 'Toggle Auth',
+ items: [creds],
+ }).then((id) => {
+ authListId = id;
+ });
+
+ // The test runner is never in this range, so it must always be denied
+ createAccessList({
+ name: 'Toggle Clients',
+ clients: [
+ {
+ directive: 'allow',
+ address: '203.0.113.0/24',
+ }
+ ],
+ }).then((id) => {
+ clientListId = id;
+ });
+ });
+
+ it('Should be able to create a proxy host without an access list', () => {
+ cy.task('backendApiPost', {
+ token: token,
+ path: '/api/nginx/proxy-hosts',
+ data: {
+ domain_names: [domain],
+ forward_scheme: 'http',
+ forward_host: 'examplesite',
+ forward_port: 80,
+ access_list_id: 0,
+ certificate_id: 0,
+ meta: {
+ dns_challenge: false
+ },
+ advanced_config: '',
+ locations: [],
+ block_exploits: false,
+ caching_enabled: false,
+ allow_websocket_upgrade: false,
+ http2_support: false,
+ hsts_enabled: false,
+ hsts_subdomains: false,
+ ssl_forced: false
+ }
+ }).then((data) => {
+ cy.validateSwaggerSchema('post', 201, '/nginx/proxy-hosts', data);
+ expect(data).to.have.property('id');
+ expect(data.id).to.be.greaterThan(0);
+ hostId = data.id;
+ });
+
+ expectStatus(null, '200');
+ });
+
+ it('Should enforce an auth access list applied to an existing host', () => {
+ setHostAccessList(authListId);
+ expectStatus(null, '401');
+ expectStatus(creds, '200');
+ });
+
+ it('Should still enforce the auth access list after the host is disabled and enabled', () => {
+ toggleHost();
+ expectStatus(null, '401');
+ expectStatus(creds, '200');
+ });
+
+ it('Should enforce a client access list applied to an existing host', () => {
+ setHostAccessList(clientListId);
+ expectStatus(null, '403');
+ });
+
+ it('Should still enforce the client access list after the host is disabled and enabled', () => {
+ toggleHost();
+ expectStatus(null, '403');
+ });
+
+ it('Should be able to delete the proxy host and access lists', () => {
+ cy.task('backendApiDelete', {
+ token: token,
+ path: `/api/nginx/proxy-hosts/${hostId}`,
+ }).then((data) => {
+ cy.validateSwaggerSchema('delete', 200, '/nginx/proxy-hosts/{hostID}', data);
+ expect(data).to.be.equal(true);
+ });
+
+ // Each delete signals an nginx reload and returns immediately. A request
+ // made while the workers are being swapped can be reset by the nginx
+ // serving the API (502 from squid), so let each reload settle first.
+ [authListId, clientListId].forEach((id) => {
+ cy.wait(1500);
+ cy.task('backendApiDelete', {
+ token: token,
+ path: `/api/nginx/access-lists/${id}`,
+ }).then((data) => {
+ cy.validateSwaggerSchema('delete', 200, '/nginx/access-lists/{listID}', data);
+ expect(data).to.be.equal(true);
+ });
+ });
+ });
+
+});
diff --git a/test/cypress/e2e/api/AccessListLocations.cy.js b/test/cypress/e2e/api/AccessListLocations.cy.js
index 2ea2742ef..a30366a0f 100644
--- a/test/cypress/e2e/api/AccessListLocations.cy.js
+++ b/test/cypress/e2e/api/AccessListLocations.cy.js
@@ -31,7 +31,7 @@ describe('Per-path Access Lists', () => {
*/
const request = (path, creds) => {
const auth = creds ? `-u '${creds.username}:${creds.password}'` : '';
- return cy.exec(`curl --noproxy '*' -s -w '\n%{http_code}' ${auth} http://${domain}${path}`)
+ return cy.task('exec', { command: `curl --noproxy '*' -s -w '\n%{http_code}' ${auth} http://${domain}${path}` }, { log: false })
.then((result) => {
expect(result.exitCode).to.eq(0);
const lines = result.stdout.trim().split('\n');
diff --git a/test/cypress/e2e/api/Certificates.cy.js b/test/cypress/e2e/api/Certificates.cy.js
index 25d1b8c41..3469fac4a 100644
--- a/test/cypress/e2e/api/Certificates.cy.js
+++ b/test/cypress/e2e/api/Certificates.cy.js
@@ -35,6 +35,63 @@ describe('Certificates endpoints', () => {
});
});
+ it('Unauthenticated user cannot validate custom certificate', () => {
+ cy.task('backendApiPostFiles', {
+ path: '/api/nginx/certificates/validate',
+ files: {
+ certificate: certFile,
+ certificate_key: keyFile,
+ },
+ returnOnError: true,
+ }).then((data) => {
+ expect(data.error.code).to.equal(403);
+ });
+ });
+
+ it('User with certificates view permission cannot validate custom certificate', () => {
+ const viewer = {
+ email: 'viewer@example.com',
+ secret: 'changeme',
+ };
+
+ cy.task('backendApiPost', {
+ token: token,
+ path: '/api/users',
+ data: {
+ name: 'Certificate Viewer',
+ nickname: 'Viewer',
+ email: viewer.email,
+ roles: ['user'],
+ auth: {
+ type: 'password',
+ secret: viewer.secret,
+ },
+ },
+ }).then((user) => {
+ cy.task('backendApiPut', {
+ token: token,
+ path: `/api/users/${user.id}/permissions`,
+ data: {
+ certificates: 'view',
+ },
+ });
+
+ cy.getToken(null, {identity: viewer.email, secret: viewer.secret}).then((viewerToken) => {
+ cy.task('backendApiPostFiles', {
+ token: viewerToken,
+ path: '/api/nginx/certificates/validate',
+ files: {
+ certificate: certFile,
+ certificate_key: keyFile,
+ },
+ returnOnError: true,
+ }).then((data) => {
+ expect(data.error.code).to.equal(403);
+ });
+ });
+ });
+ });
+
it('Custom certificate lifecycle', () => {
// Create custom cert
cy.task('backendApiPost', {
@@ -69,6 +126,9 @@ describe('Certificates endpoints', () => {
}).then((data) => {
cy.validateSwaggerSchema('get', 200, '/nginx/certificates', data);
expect(data.length).to.be.greaterThan(0);
+ const cert = data.find((c) => c.id === certID);
+ expect(cert.meta).to.have.property('certificate');
+ expect(cert.meta).to.not.have.property('certificate_key');
// Delete cert
cy.task('backendApiDelete', {
@@ -83,6 +143,79 @@ describe('Certificates endpoints', () => {
});
});
+ it('Certificate in use by a host cannot be deleted', () => {
+ cy.task('backendApiPost', {
+ token: token,
+ path: '/api/nginx/certificates',
+ data: {
+ provider: 'other',
+ nice_name: 'In Use Certificate',
+ },
+ }).then((cert) => {
+ cy.task('backendApiPostFiles', {
+ token: token,
+ path: `/api/nginx/certificates/${cert.id}/upload`,
+ files: {
+ certificate: certFile,
+ certificate_key: keyFile,
+ },
+ }).then(() => {
+ cy.task('backendApiPost', {
+ token: token,
+ path: '/api/nginx/proxy-hosts',
+ data: {
+ domain_names: ['cert-in-use.example.com'],
+ forward_scheme: 'http',
+ forward_host: '1.1.1.1',
+ forward_port: 80,
+ access_list_id: '0',
+ certificate_id: cert.id,
+ meta: { dns_challenge: false },
+ advanced_config: '',
+ locations: [],
+ block_exploits: false,
+ caching_enabled: false,
+ allow_websocket_upgrade: false,
+ http2_support: false,
+ hsts_enabled: false,
+ hsts_subdomains: false,
+ ssl_forced: false,
+ },
+ }).then((host) => {
+ cy.task('backendApiDelete', {
+ token: token,
+ path: `/api/nginx/certificates/${cert.id}`,
+ returnOnError: true,
+ }).then((data) => {
+ cy.validateSwaggerSchema('delete', 400, '/nginx/certificates/{certID}', data);
+ expect(data.error.code).to.equal(400);
+ expect(data.error.message).to.contain('in use by 1 proxy host');
+ });
+
+ // Still there
+ cy.task('backendApiGet', {
+ token: token,
+ path: `/api/nginx/certificates/${cert.id}`,
+ }).then((data) => {
+ expect(data.id).to.equal(cert.id);
+ });
+
+ // Once the host no longer uses it, it can be deleted
+ cy.task('backendApiDelete', {
+ token: token,
+ path: `/api/nginx/proxy-hosts/${host.id}`,
+ });
+ cy.task('backendApiDelete', {
+ token: token,
+ path: `/api/nginx/certificates/${cert.id}`,
+ }).then((data) => {
+ expect(data).to.be.equal(true);
+ });
+ });
+ });
+ });
+ });
+
it('Request Certificate - CVE-2024-46256/CVE-2024-46257', () => {
cy.task('backendApiPost', {
token: token,
diff --git a/test/cypress/e2e/api/NginxValues.cy.js b/test/cypress/e2e/api/NginxValues.cy.js
new file mode 100644
index 000000000..7f1749764
--- /dev/null
+++ b/test/cypress/e2e/api/NginxValues.cy.js
@@ -0,0 +1,217 @@
+///
+
+// Values a non-admin can set are written into nginx config as-is. None of them may add
+// directives (that is what advanced_config is for, and only admins may set it), but the valid
+// forms people use, such as regex locations, must still produce a config nginx accepts.
+describe('Values rendered into nginx config', () => {
+ const user = {
+ email: 'nginx-values@example.com',
+ secret: 'changeme',
+ };
+
+ let token;
+ let userToken;
+
+ const location = (overrides) => Object.assign({
+ path: '/api',
+ forward_scheme: 'http',
+ forward_host: '127.0.0.1',
+ forward_port: 8080,
+ advanced_config: '',
+ }, overrides);
+
+ const proxyHost = (domain, overrides) => Object.assign({
+ domain_names: [domain],
+ forward_scheme: 'http',
+ forward_host: '127.0.0.1',
+ forward_port: 80,
+ access_list_id: '0',
+ certificate_id: 0,
+ meta: { dns_challenge: false },
+ advanced_config: '',
+ locations: [],
+ block_exploits: false,
+ caching_enabled: false,
+ allow_websocket_upgrade: false,
+ http2_support: false,
+ hsts_enabled: false,
+ hsts_subdomains: false,
+ ssl_forced: false,
+ }, overrides || {});
+
+ const expectRejected = (path, data) => {
+ cy.task('backendApiPost', {
+ token: userToken,
+ path: path,
+ data: data,
+ returnOnError: true,
+ }).then((res) => {
+ expect(res, JSON.stringify(data)).to.have.property('error');
+ expect(res.error.code, JSON.stringify(data)).to.equal(400);
+ });
+ };
+
+ const expectOnline = (path, id) => {
+ cy.task('backendApiGet', {
+ token: userToken,
+ path: `${path}/${id}`,
+ }).then((row) => {
+ expect(row.meta.nginx_err || null).to.equal(null);
+ expect(row.meta.nginx_online).to.equal(true);
+ });
+ };
+
+ before(() => {
+ cy.resetUsers();
+ cy.getToken().then((tok) => {
+ token = tok;
+ cy.task('backendApiPost', {
+ token: token,
+ path: '/api/users',
+ data: {
+ name: 'Nginx Values',
+ nickname: 'Values',
+ email: user.email,
+ roles: [],
+ auth: { type: 'password', secret: user.secret },
+ },
+ }).then((created) => {
+ cy.task('backendApiPut', {
+ token: token,
+ path: `/api/users/${created.id}/permissions`,
+ data: {
+ visibility: 'user',
+ proxy_hosts: 'manage',
+ redirection_hosts: 'manage',
+ dead_hosts: 'manage',
+ streams: 'manage',
+ access_lists: 'manage',
+ certificates: 'manage',
+ },
+ });
+ cy.getToken(null, { identity: user.email, secret: user.secret }).then((tok2) => {
+ userToken = tok2;
+ });
+ });
+ });
+ });
+
+ it('Location paths and forward hosts that nginx accepts still work', () => {
+ cy.task('backendApiPost', {
+ token: userToken,
+ path: '/api/nginx/proxy-hosts',
+ data: proxyHost('nginx-values-ok.example.com', {
+ locations: [
+ location({ path: '= /exact' }),
+ location({ path: '~* \\.(jpg|png|gif)$' }),
+ location({ path: '^~ /static/' }),
+ location({ path: '~ "^/v[0-9]{1,2}/"' }),
+ location({ path: '/sub', forward_host: '127.0.0.1/sub/' }),
+ ],
+ }),
+ }).then((host) => {
+ expectOnline('/api/nginx/proxy-hosts', host.id);
+ });
+ });
+
+ it('Redirect targets with a port and path still work', () => {
+ cy.task('backendApiPost', {
+ token: userToken,
+ path: '/api/nginx/redirection-hosts',
+ data: {
+ domain_names: ['nginx-values-redirect.example.com'],
+ forward_scheme: 'https',
+ forward_http_code: 301,
+ forward_domain_name: 'example.org:8443/landing',
+ preserve_path: true,
+ certificate_id: 0,
+ meta: {},
+ advanced_config: '',
+ block_exploits: false,
+ http2_support: false,
+ hsts_enabled: false,
+ hsts_subdomains: false,
+ ssl_forced: false,
+ },
+ }).then((host) => {
+ expectOnline('/api/nginx/redirection-hosts', host.id);
+ });
+ });
+
+ it('Location path cannot add directives', () => {
+ [
+ '/x { return 200; } location /y',
+ '/a; } server { listen 8080',
+ '/a\nreturn 200 x;\nlocation /b',
+ '/a {',
+ '~',
+ ].forEach((path) => {
+ expectRejected('/api/nginx/proxy-hosts', proxyHost('nginx-values-bad.example.com', {
+ locations: [location({ path })],
+ }));
+ });
+ });
+
+ it('Forward hosts cannot add directives', () => {
+ [
+ 'a; return 200; #',
+ 'a"; return 200; set $x "',
+ 'a:80; } location /x { return 200',
+ '$http_host',
+ ].forEach((forward_host) => {
+ expectRejected('/api/nginx/proxy-hosts', proxyHost('nginx-values-bad.example.com', { forward_host }));
+ expectRejected('/api/nginx/proxy-hosts', proxyHost('nginx-values-bad.example.com', {
+ locations: [location({ forward_host })],
+ }));
+ });
+ expectRejected('/api/nginx/proxy-hosts', proxyHost('nginx-values-bad.example.com', {
+ locations: [location({ forward_path: '/x; return 200' })],
+ }));
+ });
+
+ it('Redirect target cannot add directives', () => {
+ ['example.com; return 200 x; #', 'a.com { return 200; }'].forEach((forward_domain_name) => {
+ expectRejected('/api/nginx/redirection-hosts', {
+ domain_names: ['nginx-values-bad-redirect.example.com'],
+ forward_scheme: 'http',
+ forward_http_code: 301,
+ forward_domain_name,
+ });
+ });
+ });
+
+ it('Stream forwarding host cannot add directives', () => {
+ ['a.com:80; } server { listen 9999', 'a.com/x'].forEach((forwarding_host) => {
+ expectRejected('/api/nginx/streams', {
+ incoming_port: 1600,
+ forwarding_host,
+ forwarding_port: 80,
+ tcp_forwarding: true,
+ udp_forwarding: false,
+ });
+ });
+ });
+
+ it('Domain names cannot add directives', () => {
+ ['a.example.com\nreturn 200 x', 'a\tb.example.com'].forEach((domain) => {
+ expectRejected('/api/nginx/dead-hosts', { domain_names: [domain] });
+ });
+ });
+
+ it('Access list entries cannot add directives or corrupt the password file', () => {
+ ['::1%x; allow all', '1.2.3.4; allow all', 'fe80::1%eth0'].forEach((address) => {
+ expectRejected('/api/nginx/access-lists', {
+ name: 'Bad client',
+ items: [],
+ clients: [{ directive: 'deny', address }],
+ });
+ });
+ ['a:b', 'a\nroot:$apr1$x$y'].forEach((username) => {
+ expectRejected('/api/nginx/access-lists', {
+ name: 'Bad user',
+ items: [{ username, password: 'pw' }],
+ clients: [],
+ });
+ });
+ });
+});
diff --git a/test/cypress/e2e/api/NonAdminHosts.cy.js b/test/cypress/e2e/api/NonAdminHosts.cy.js
new file mode 100644
index 000000000..c05eca842
--- /dev/null
+++ b/test/cypress/e2e/api/NonAdminHosts.cy.js
@@ -0,0 +1,184 @@
+///
+
+describe('Non-admin host and permission rules', () => {
+ const ADMIN_CONFIG = 'add_header X-Admin-Only "1";';
+ const nonAdmin = {
+ email: 'nonadmin-hosts@example.com',
+ secret: 'changeme',
+ };
+
+ let token;
+ let userId;
+ let userToken;
+ let hostId;
+
+ const hostPayload = (overrides) => Object.assign({
+ domain_names: ['nonadmin-hosts.example.com'],
+ forward_scheme: 'http',
+ forward_host: '1.1.1.1',
+ forward_port: 80,
+ access_list_id: '0',
+ certificate_id: 0,
+ meta: { dns_challenge: false },
+ advanced_config: '',
+ locations: [],
+ block_exploits: false,
+ caching_enabled: false,
+ allow_websocket_upgrade: false,
+ http2_support: false,
+ hsts_enabled: false,
+ hsts_subdomains: false,
+ ssl_forced: false,
+ }, overrides || {});
+
+ const location = (path, advancedConfig) => ({
+ path: path,
+ forward_scheme: 'http',
+ forward_host: '1.1.1.1',
+ forward_port: 8080,
+ advanced_config: advancedConfig,
+ });
+
+ const getPermissions = () => cy.task('backendApiGet', {
+ token: token,
+ path: `/api/users/${userId}?expand=permissions`,
+ }).then((user) => user.permissions);
+
+ before(() => {
+ cy.resetUsers();
+ cy.getToken().then((tok) => {
+ token = tok;
+
+ // Admin-owned host with an admin-written location config
+ cy.task('backendApiPost', {
+ token: token,
+ path: '/api/nginx/proxy-hosts',
+ data: hostPayload({ locations: [location('/api', ADMIN_CONFIG)] }),
+ }).then((host) => {
+ hostId = host.id;
+
+ cy.task('backendApiPost', {
+ token: token,
+ path: '/api/users',
+ data: {
+ name: 'Non Admin',
+ nickname: 'NonAdmin',
+ email: nonAdmin.email,
+ roles: [],
+ auth: { type: 'password', secret: nonAdmin.secret },
+ },
+ }).then((user) => {
+ userId = user.id;
+ cy.task('backendApiPut', {
+ token: token,
+ path: `/api/users/${userId}/permissions`,
+ data: {
+ visibility: 'user',
+ proxy_hosts: 'manage',
+ certificates: 'manage',
+ meta: { proxy_host_ids: [hostId] },
+ },
+ });
+ cy.getToken(null, { identity: nonAdmin.email, secret: nonAdmin.secret }).then((tok2) => {
+ userToken = tok2;
+ });
+ });
+ });
+ });
+ });
+
+ it('Refused advanced config change does not request a certificate first', () => {
+ cy.task('backendApiGet', { token: token, path: '/api/nginx/certificates' }).then((before) => {
+ cy.task('backendApiPut', {
+ token: userToken,
+ path: `/api/nginx/proxy-hosts/${hostId}`,
+ data: { certificate_id: 'new', advanced_config: 'return 418;' },
+ returnOnError: true,
+ }).then((data) => {
+ expect(data).to.have.property('error');
+ expect(data.error.code).to.equal(403);
+ cy.task('backendApiGet', { token: token, path: '/api/nginx/certificates' }).then((after) => {
+ expect(after.length).to.equal(before.length);
+ });
+ });
+ });
+ });
+
+ it('Non-admin cannot add new location advanced config', () => {
+ cy.task('backendApiPut', {
+ token: userToken,
+ path: `/api/nginx/proxy-hosts/${hostId}`,
+ data: { locations: [location('/api', ADMIN_CONFIG), location('/new', 'return 418;')] },
+ returnOnError: true,
+ }).then((data) => {
+ expect(data).to.have.property('error');
+ expect(data.error.code).to.equal(403);
+ });
+ });
+
+ it('Non-admin can rename a location and keep its admin config', () => {
+ cy.task('backendApiPut', {
+ token: userToken,
+ path: `/api/nginx/proxy-hosts/${hostId}`,
+ data: { locations: [location('/v2', ADMIN_CONFIG), location('/api', '')] },
+ }).then((data) => {
+ const byPath = Object.fromEntries(data.locations.map((l) => [l.path, l.advanced_config]));
+ expect(byPath['/v2']).to.equal(ADMIN_CONFIG);
+ expect(byPath['/api']).to.equal('');
+ });
+ });
+
+ it('Non-admin can delete and re-add a location at the same path', () => {
+ // The location form sends advanced_config "" for a new row
+ cy.task('backendApiPut', {
+ token: userToken,
+ path: `/api/nginx/proxy-hosts/${hostId}`,
+ data: { locations: [location('/v2', '')] },
+ }).then((data) => {
+ expect(data.locations).to.have.length(1);
+ expect(data.locations[0].advanced_config).to.equal('');
+ });
+ });
+
+ it('Proxy host grants are cleared when proxy hosts are hidden', () => {
+ // Unknown ids are dropped
+ cy.task('backendApiPut', {
+ token: token,
+ path: `/api/users/${userId}/permissions`,
+ data: { proxy_hosts: 'manage', meta: { proxy_host_ids: [hostId, 999999] } },
+ });
+ getPermissions().then((perms) => {
+ expect(perms.meta.proxy_host_ids).to.deep.equal([hostId]);
+ });
+
+ cy.task('backendApiPut', {
+ token: token,
+ path: `/api/users/${userId}/permissions`,
+ data: { proxy_hosts: 'hidden' },
+ });
+ getPermissions().then((perms) => {
+ expect(perms.meta.proxy_host_ids).to.deep.equal([]);
+ });
+
+ // Showing proxy hosts again does not bring the old grant back
+ cy.task('backendApiPut', {
+ token: token,
+ path: `/api/users/${userId}/permissions`,
+ data: { proxy_hosts: 'manage' },
+ });
+ getPermissions().then((perms) => {
+ expect(perms.meta.proxy_host_ids).to.deep.equal([]);
+ });
+ });
+
+ it('Proxy host grants are cleared when visibility is all', () => {
+ cy.task('backendApiPut', {
+ token: token,
+ path: `/api/users/${userId}/permissions`,
+ data: { visibility: 'all', proxy_hosts: 'manage', meta: { proxy_host_ids: [hostId] } },
+ });
+ getPermissions().then((perms) => {
+ expect(perms.meta.proxy_host_ids).to.deep.equal([]);
+ });
+ });
+});
diff --git a/test/cypress/e2e/api/Streams.cy.js b/test/cypress/e2e/api/Streams.cy.js
index 95c6dba13..0587629fa 100644
--- a/test/cypress/e2e/api/Streams.cy.js
+++ b/test/cypress/e2e/api/Streams.cy.js
@@ -31,7 +31,7 @@ describe('Streams', () => {
});
});
- cy.exec('rm -f /test/results/testssl.json');
+ cy.task('exec', { command: 'rm -f /test/results/testssl.json' });
});
it('Should be able to create TCP Stream', () => {
@@ -55,7 +55,7 @@ describe('Streams', () => {
expect(data).to.have.property('tcp_forwarding', true);
expect(data).to.have.property('udp_forwarding', false);
- cy.exec('curl --noproxy -- http://website1.example.com:1500').then((result) => {
+ cy.task('exec', { command: 'curl --noproxy -- http://website1.example.com:1500' }).then((result) => {
expect(result.exitCode).to.eq(0);
expect(result.stdout).to.contain('yay it works');
});
@@ -106,7 +106,7 @@ describe('Streams', () => {
expect(data).to.have.property('tcp_forwarding', true);
expect(data).to.have.property('udp_forwarding', true);
- cy.exec('curl --noproxy -- http://website1.example.com:1502').then((result) => {
+ cy.task('exec', { command: 'curl --noproxy -- http://website1.example.com:1502' }).then((result) => {
expect(result.exitCode).to.eq(0);
expect(result.stdout).to.contain('yay it works');
});
@@ -166,7 +166,9 @@ describe('Streams', () => {
// Check the ssl termination
cy.task('log', '[testssl.sh] Running ...');
- cy.exec('/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503', {
+ cy.task('exec', {
+ command: '/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503',
+ }, {
timeout: 120000, // 2 minutes
}).then((result) => {
cy.task('log', `[testssl.sh] ${result.stdout}`);
diff --git a/test/cypress/e2e/api/Tokens.cy.js b/test/cypress/e2e/api/Tokens.cy.js
new file mode 100644
index 000000000..c5bd55c41
--- /dev/null
+++ b/test/cypress/e2e/api/Tokens.cy.js
@@ -0,0 +1,61 @@
+///
+
+describe('Tokens endpoints', () => {
+ const user = {
+ email: 'cypress@example.com',
+ secret: 'changeme',
+ };
+ const disabledUser = {
+ email: 'disabled@example.com',
+ secret: 'changeme',
+ };
+ const unknownEmail = 'nobody@example.com';
+
+ const login = (identity, secret) => {
+ return cy.request({
+ method: 'POST',
+ url: '/api/tokens',
+ body: {identity, secret},
+ failOnStatusCode: false,
+ });
+ };
+
+ before(() => {
+ cy.resetUsers();
+ cy.getToken().then((token) => {
+ cy.task('backendApiPost', {
+ token: token,
+ path: '/api/users',
+ data: {
+ name: 'Disabled User',
+ nickname: 'Disabled',
+ email: disabledUser.email,
+ is_disabled: true,
+ auth: {
+ type: 'password',
+ secret: disabledUser.secret,
+ },
+ },
+ });
+ });
+ });
+
+ it('Should give the same answer for an unknown email, a disabled account and a wrong password', () => {
+ // Only the error is compared: CI runs the backend in debug mode, which adds a stack trace that a real
+ // client never gets.
+ login(user.email, 'not-the-password').then((wrongPassword) => {
+ expect(wrongPassword.status).to.equal(400);
+ expect(wrongPassword.body.error.message_i18n).to.equal('error.invalid-auth');
+
+ login(unknownEmail, user.secret).then((unknown) => {
+ expect(unknown.status).to.equal(wrongPassword.status);
+ expect(unknown.body.error).to.deep.equal(wrongPassword.body.error);
+ });
+
+ login(disabledUser.email, disabledUser.secret).then((disabled) => {
+ expect(disabled.status).to.equal(wrongPassword.status);
+ expect(disabled.body.error).to.deep.equal(wrongPassword.body.error);
+ });
+ });
+ });
+});
diff --git a/test/cypress/plugins/backendApi/client.mjs b/test/cypress/plugins/backendApi/client.mjs
index 78618abb2..5c0daa5c0 100644
--- a/test/cypress/plugins/backendApi/client.mjs
+++ b/test/cypress/plugins/backendApi/client.mjs
@@ -1,3 +1,4 @@
+import http from "node:http";
import axios from "axios";
import logger from "./logger.mjs";
@@ -8,6 +9,10 @@ const BackendApi = function (config, token) {
this.axios = axios.create({
baseURL: config.baseUrl,
timeout: 90000,
+ // Don't reuse sockets: a pooled keep-alive connection that squid or the
+ // backend has since closed fails with a 502 (ERR_READ_ERROR 104), which
+ // made tests flaky.
+ httpAgent: new http.Agent({ keepAlive: false }),
});
};
diff --git a/test/cypress/plugins/index.mjs b/test/cypress/plugins/index.mjs
index 6a2ff9ffb..ba0706886 100644
--- a/test/cypress/plugins/index.mjs
+++ b/test/cypress/plugins/index.mjs
@@ -1,5 +1,6 @@
import { plugin as cypressGrepPlugin } from "@cypress/grep/plugin";
import { SwaggerValidation } from "@jc21/cypress-swagger-validation";
+import { exec } from "node:child_process";
import chalk from "chalk";
import backendTask from "./backendApi/task.mjs";
@@ -25,6 +26,32 @@ export default (on, config) => {
return null;
},
});
+ on("task", {
+ /**
+ * Replacement for cy.exec(), removed in Cypress 16
+ *
+ * @param {object} options
+ * @param {string} options.command
+ * @param {bool} [options.failOnNonZeroExit] Defaults to true
+ * @returns {object} { exitCode, stdout, stderr }
+ */
+ exec({ command, failOnNonZeroExit = true }) {
+ return new Promise((resolve, reject) => {
+ exec(command, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
+ const result = {
+ exitCode: error ? (error.code ?? 1) : 0,
+ stdout: stdout.trimEnd(),
+ stderr: stderr.trimEnd(),
+ };
+ if (error && failOnNonZeroExit) {
+ reject(new Error(`Command failed with exit code ${result.exitCode}: ${command}\n${result.stderr}`));
+ return;
+ }
+ resolve(result);
+ });
+ });
+ },
+ });
on("task", {
getFixturesFolder() {
return config.fixturesFolder;
diff --git a/test/cypress/support/commands.mjs b/test/cypress/support/commands.mjs
index 03c5bf224..0526cf553 100644
--- a/test/cypress/support/commands.mjs
+++ b/test/cypress/support/commands.mjs
@@ -33,7 +33,7 @@ Cypress.Commands.add("validateSwaggerFile", (url, savePath) => {
return cy
.request(url)
.then((response) => cy.writeFile(savePath, response.body, { log: false }))
- .then(() => cy.exec(`yarn swagger-lint '${savePath}'`, { failOnNonZeroExit: false }))
+ .then(() => cy.task('exec', { command: `yarn swagger-lint '${savePath}'`, failOnNonZeroExit: false }))
.then((result) => cy.task('log', `Swagger Vacuum Results:\n${result.stdout || ''}`)
.then(() => expect(result.exitCode).to.eq(0)));
});
@@ -167,10 +167,10 @@ Cypress.Commands.add('createCustomCerts', ({domain, certFile, keyFile}) => {
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) => {
+ return cy.task('exec', { command: cmd }).then((result) => {
cy.log(`mkcert output:\n${JSON.stringify(result)}`);
expect(result.exitCode).to.eq(0);
- return cy.exec('mkcert -install').then((result2) => {
+ return cy.task('exec', { command: 'mkcert -install' }).then((result2) => {
cy.log(`mkcert install output:\n${JSON.stringify(result2)}`);
expect(result2.exitCode).to.eq(0);
}).then(() => {
diff --git a/test/docker/nginx/conf.d/website123.conf b/test/docker/nginx/conf.d/website123.conf
index 656f3ade4..eefb233bc 100644
--- a/test/docker/nginx/conf.d/website123.conf
+++ b/test/docker/nginx/conf.d/website123.conf
@@ -1,6 +1,6 @@
server {
listen 80;
- server_name website1.example.com website2.example.com website3.example.com;
+ server_name website1.example.com website2.example.com website3.example.com website4.example.com;
root /www;
index index.html;
diff --git a/test/host-header-smoke.py b/test/host-header-smoke.py
new file mode 100644
index 000000000..9e6b51b0b
--- /dev/null
+++ b/test/host-header-smoke.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python3
+"""Check forwarded Host headers against a real upstream in a disposable image."""
+
+import base64
+import json
+import subprocess
+import sys
+import time
+import uuid
+
+
+BACKEND = r'''
+import http.server, json, ssl
+from urllib.parse import urlsplit
+
+class Backend(http.server.BaseHTTPRequestHandler):
+ requests = 0
+
+ def do_GET(self):
+ Backend.requests += 1
+ request_number = Backend.requests
+ host = self.headers.get("Host")
+ origin = self.headers.get("Origin")
+ referer = self.headers.get("Referer")
+ body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
+ self.send_response(200)
+ if origin and (host != urlsplit(origin).netloc or host != urlsplit(referer).netloc):
+ self.send_header("Content-Type", "text/html")
+ self.end_headers()
+ self.wfile.write(b"")
+ else:
+ self.send_header("Content-Type", "application/json")
+ self.send_header("X-Upstream-Request", str(request_number))
+ self.end_headers()
+ response = {"host": host, "origin": origin, "referer": referer,
+ "method": self.command, "path": self.path, "body": body.decode()}
+ if self.path.endswith(".js"):
+ response["request_number"] = request_number
+ self.wfile.write(json.dumps(response).encode())
+
+ do_POST = do_GET
+
+ def log_message(self, *args):
+ pass
+
+context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+context.load_cert_chain("/data/custom_ssl/npm-999/fullchain.pem",
+ "/data/custom_ssl/npm-999/privkey.pem")
+server = http.server.ThreadingHTTPServer(("127.0.0.1", 19443), Backend)
+server.socket = context.wrap_socket(server.socket, server_side=True)
+server.serve_forever()
+'''
+
+RENDER = r'''
+import nginx from "./internal/nginx.js";
+
+const host = {
+ id: 999, enabled: true, domain_names: ["host-port.example.test", "[::1]"],
+ forward_scheme: "https", forward_host: "127.0.0.1", forward_port: 19443,
+ certificate_id: 999, certificate: {id: 999, provider: "other"}, access_list_id: 0,
+ ssl_forced: false, hsts_enabled: false, http2_support: false,
+ block_exploits: false, caching_enabled: true, allow_websocket_upgrade: false,
+ advanced_config: "", locations: [{path: "/custom/", advanced_config: "",
+ forward_scheme: "https", forward_host: "127.0.0.1", forward_port: 19443}]
+};
+await nginx.generateConfig("proxy_host", host);
+process.exit(0);
+'''
+
+
+def docker(*args, **kwargs):
+ return subprocess.check_output(["docker", *args], text=True, **kwargs).strip()
+
+
+# Keep all traffic on the tested container's loopback interface so this also
+# works when the CI agent talks to a Docker daemon on another host.
+REQUEST = r'''
+import base64, http.client, json, socket, ssl, sys
+args = json.load(sys.stdin)
+connection = None
+sock = None
+try:
+ if args["http10"]:
+ sock = socket.create_connection(("127.0.0.1", 80), timeout=5)
+ target = "http://host-port.example.test" + args["path"]
+ sock.sendall(f"GET {target} HTTP/1.0\r\n\r\n".encode())
+ response = http.client.HTTPResponse(sock)
+ response.begin()
+ else:
+ port = 443 if args["tls"] else 80
+ if args["admin"]:
+ port = 81
+ connection = http.client.HTTPConnection("127.0.0.1", port, timeout=5)
+ if args["tls"]:
+ connection.sock = ssl._create_unverified_context().wrap_socket(
+ socket.create_connection(("127.0.0.1", port), timeout=5),
+ server_hostname="host-port.example.test")
+ connection.request(args["method"], args["path"], headers=args["headers"], body=args["body"])
+ response = connection.getresponse()
+ print(json.dumps({"status": response.status, "headers": dict(response.getheaders()),
+ "body": base64.b64encode(response.read()).decode()}))
+finally:
+ if connection is not None:
+ connection.close()
+ if sock is not None:
+ sock.close()
+'''
+
+
+def request(container, tls=False, path="/", method="GET", headers=None, body=None, admin=False, http10=False):
+ payload = {"tls": tls, "path": path, "method": method, "headers": headers or {},
+ "body": body, "admin": admin, "http10": http10}
+ try:
+ result = json.loads(docker("exec", "-i", container, "python3", "-S", "-c", REQUEST,
+ input=json.dumps(payload), stderr=subprocess.DEVNULL, timeout=15))
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as error:
+ raise OSError("Request inside the tested container failed") from error
+ return result["status"], result["headers"], base64.b64decode(result["body"])
+
+
+def wait_for_generated_host(container):
+ deadline = time.monotonic() + 60
+ while True:
+ try:
+ for tls in (False, True):
+ for prefix in ("/", "/custom/"):
+ path = prefix + "readiness"
+ status, headers, body = request(container, tls, path,
+ headers={"Host": "host-port.example.test:233"})
+ data = json.loads(body)
+ assert status == 200 and headers.get("Content-Type") == "application/json"
+ assert data["host"] == "host-port.example.test:233" and data["path"] == path
+ return
+ except (OSError, ValueError, AssertionError, KeyError):
+ if time.monotonic() >= deadline:
+ raise AssertionError("Generated HTTP/HTTPS host and upstream did not become ready")
+ time.sleep(0.5)
+
+
+def check_image(image):
+ name = f"npm-host-header-smoke-{uuid.uuid4().hex[:10]}"
+ backend = None
+ checked = 0
+ try:
+ docker("run", "-d", "--name", name, "-e", "IP_RANGES_FETCH_ENABLED=false",
+ "--mount", "type=volume,destination=/etc/letsencrypt", image)
+ deadline = time.monotonic() + 120
+ while True:
+ try:
+ status, _, body = request(name, path="/api/", admin=True)
+ if status == 200 and json.loads(body).get("status") == "OK":
+ break
+ except (OSError, ValueError):
+ pass
+ if time.monotonic() >= deadline:
+ raise AssertionError("Manager health endpoint did not become ready")
+ time.sleep(1)
+ docker("exec", name, "mkdir", "-p", "/data/custom_ssl/npm-999")
+ docker("exec", name, "openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes",
+ "-keyout", "/data/custom_ssl/npm-999/privkey.pem",
+ "-out", "/data/custom_ssl/npm-999/fullchain.pem",
+ "-days", "1", "-subj", "/CN=host-port.example.test", stderr=subprocess.DEVNULL)
+ backend = subprocess.Popen(["docker", "exec", name, "python3", "-S", "-c", BACKEND],
+ stdout=subprocess.DEVNULL)
+ subprocess.run(["docker", "exec", "-i", "-w", "/app", name,
+ "node", "--input-type=module"], input=RENDER, text=True, check=True)
+ docker("exec", name, "nginx", "-t")
+ docker("exec", name, "nginx", "-s", "reload")
+ wait_for_generated_host(name)
+ for tls, authorities in [
+ (False, ["host-port.example.test", "host-port.example.test:80",
+ "host-port.example.test:232"]),
+ (True, ["host-port.example.test", "host-port.example.test:443",
+ "host-port.example.test:233", "HOST-PORT.EXAMPLE.TEST:233",
+ "[::1]:233"]),
+ ]:
+ scheme = "https" if tls else "http"
+ for authority in authorities:
+ for prefix in ("/", "/custom/"):
+ for method in ("GET", "POST"):
+ # nginx forwards its normalised (lowercased) $host, as browsers send
+ # in Host and Origin anyway
+ forwarded = authority.lower()
+ origin = f"{scheme}://{forwarded}"
+ referer = origin + "/settings"
+ path = prefix + "apply?one=1&two=2"
+ payload = "rule=example" if method == "POST" else None
+ try:
+ status, headers, body = request(
+ name, tls, path, method,
+ {"Host": authority, "Origin": origin, "Referer": referer}, payload)
+ except OSError as error:
+ raise AssertionError((authority, path, method, str(error))) from error
+ assert status == 200 and headers.get("Content-Type") == "application/json", (
+ authority, path, method, status, body)
+ assert json.loads(body) == {"host": forwarded, "origin": origin,
+ "referer": referer, "method": method, "path": path,
+ "body": payload or ""}, body
+ checked += 1
+ # HTTP/1.0 permits no Host. An absolute target selects our generated host.
+ for prefix in ("/", "/custom/"):
+ status, _, body = request(name, path=prefix + "fallback", http10=True)
+ assert status == 200, status
+ assert json.loads(body)["host"] == "host-port.example.test"
+ checked += 1
+
+ cache_cases = 0
+ for prefix in ("/", "/custom/"):
+ path = prefix + f"cache-{uuid.uuid4().hex}.js"
+ cached = {}
+ authorities = [(False, "host-port.example.test:232"), (True, "host-port.example.test:233")]
+ for _ in range(2):
+ for tls, authority in authorities:
+ origin = f"{'https' if tls else 'http'}://{authority}"
+ status, headers, body = request(name, tls, path,
+ headers={"Host": authority, "Origin": origin, "Referer": origin + "/settings"})
+ data = json.loads(body)
+ assert status == 200 and data["host"] == authority, ("cache", prefix, authority, status, data)
+ serial = headers.get("X-Upstream-Request")
+ assert serial and str(data["request_number"]) == serial, (headers, data)
+ if authority in cached:
+ # A dynamic upstream counter must not change on a real cache hit.
+ assert (serial, body) == cached[authority], ("cache hit", authority, cached, serial, body)
+ else:
+ cached[authority] = (serial, body)
+ cache_cases += 1
+ assert len({serial for serial, _ in cached.values()}) == 2, cached
+ checked += cache_cases
+ print(json.dumps({"image": image, "cases": checked, "nginx": "passed",
+ "default_and_custom_locations": "passed", "http_and_https": "passed",
+ "origin_and_referer_validation": "passed", "missing_host_fallback": "passed", "cached_asset_cases": cache_cases,
+ "cache_port_isolation_and_hits": "passed", "generated_host_readiness": "passed"}),
+ flush=True)
+ except Exception:
+ subprocess.run(["docker", "logs", "--tail", "30", name], check=False)
+ raise
+ finally:
+ subprocess.run(["docker", "rm", "-fv", name], stdout=subprocess.DEVNULL, check=False)
+ if backend is not None:
+ backend.wait(timeout=10)
+
+
+if __name__ == "__main__":
+ check_image(sys.argv[1])
diff --git a/test/package.json b/test/package.json
index 84062e892..af04a520d 100644
--- a/test/package.json
+++ b/test/package.json
@@ -6,19 +6,19 @@
"dependencies": {
"@cypress/grep": "^7.0.0",
"@jc21/cypress-swagger-validation": "^0.3.2",
- "@quobix/vacuum": "^0.26.4",
- "axios": "^1.19.0",
+ "@quobix/vacuum": "^0.36.2",
+ "axios": "^1.21.0",
"chalk": "^5.6.2",
- "cypress": "^16.0.0",
- "cypress-multi-reporters": "^2.0.5",
+ "cypress": "^16.2.1",
+ "cypress-multi-reporters": "^2.1.0",
"cypress-wait-until": "^3.0.2",
- "eslint": "^10.9.1",
+ "eslint": "^10.14.0",
"eslint-plugin-align-assignments": "^1.1.2",
"eslint-plugin-chai-friendly": "^1.2.1",
- "eslint-plugin-cypress": "^7.0.1",
+ "eslint-plugin-cypress": "^7.0.2",
"form-data": "^4.0.6",
"lodash": "^4.18.1",
- "mocha": "^12.0.0",
+ "mocha": "^12.0.3",
"mocha-junit-reporter": "^2.2.1"
},
"scripts": {
diff --git a/test/yarn.lock b/test/yarn.lock
index 4eb1a544f..2482b2268 100644
--- a/test/yarn.lock
+++ b/test/yarn.lock
@@ -48,10 +48,10 @@
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-string-parser@^7.29.11":
+ version "7.29.11"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz#59d76b6193f61e0303b40991cabbedfadcbd5b63"
+ integrity sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g==
"@babel/helper-validator-identifier@^7.29.7":
version "7.29.7"
@@ -59,11 +59,11 @@
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==
+ version "7.29.12"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.12.tgz#1932edf93ff6602b0665817067946c689b6fe423"
+ integrity sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg==
dependencies:
- "@babel/types" "^7.29.7"
+ "@babel/types" "^7.29.11"
"@babel/plugin-syntax-jsx@^7.27.1":
version "7.29.7"
@@ -72,14 +72,32 @@
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==
+"@babel/types@^7.29.11":
+ version "7.29.11"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.11.tgz#774cffe8cac2ca494f1334e4052002c96bf628fc"
+ integrity sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==
dependencies:
- "@babel/helper-string-parser" "^7.29.7"
+ "@babel/helper-string-parser" "^7.29.11"
"@babel/helper-validator-identifier" "^7.29.7"
+"@cacheable/memory@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@cacheable/memory/-/memory-2.2.0.tgz#72aeb8b051f6d597d10ac8595b94ad8302bd2239"
+ integrity sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==
+ dependencies:
+ "@cacheable/utils" "^2.5.0"
+ "@keyv/bigmap" "^1.3.1"
+ hookified "^1.15.1"
+ keyv "^5.6.0"
+
+"@cacheable/utils@^2.5.0":
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/@cacheable/utils/-/utils-2.5.0.tgz#534c91113aa48fe43baedb169550b6ee070ef303"
+ integrity sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==
+ dependencies:
+ hashery "^1.5.1"
+ keyv "^5.6.0"
+
"@cypress/grep@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@cypress/grep/-/grep-7.0.0.tgz#2cdc92a395017947dc18fe3623fa7334631887e1"
@@ -90,9 +108,9 @@
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==
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@cypress/request/-/request-4.0.1.tgz#c3f0eec41834e7590d21cd740a4c6f7e0326368d"
+ integrity sha512-y20e+e6dFYkOUUJLVUZTsJRuTiXZaUQ32WD+R/ux/HBybbTx4ge7cNINcua0pU8+SNkKuRbOF12mBmzuzM8n5w==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
@@ -107,7 +125,7 @@
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
performance-now "^2.1.0"
- qs "~6.14.1"
+ qs "^6.15.2"
safe-buffer "^5.1.2"
tough-cookie "^5.0.0"
tunnel-agent "^0.6.0"
@@ -121,9 +139,9 @@
lodash.once "^4.1.1"
"@eslint-community/eslint-utils@^4.8.0":
- version "4.9.1"
- resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595"
- integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==
+ version "4.10.1"
+ resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz#8911bd72b2c3640a543609e0400b8c4d2e7e7cb6"
+ integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==
dependencies:
eslint-visitor-keys "^3.4.3"
@@ -160,10 +178,10 @@
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.7.2":
- version "0.7.2"
- resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz#4b0962f3f2c7ce8bc98b3ecfe34525c09d2cb729"
- integrity sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==
+"@eslint/plugin-kit@^0.7.3":
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz#cc7268cc36405b331ef92db1bc37971f21d66fe1"
+ integrity sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==
dependencies:
"@eslint/core" "^1.2.1"
levn "^0.4.1"
@@ -175,7 +193,7 @@
dependencies:
"@humanfs/types" "^0.15.0"
-"@humanfs/node@^0.16.6":
+"@humanfs/node@^0.16.8":
version "0.16.8"
resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.8.tgz#8f800cccc13f4f8cd3116e2d9c0a94939da3e3ed"
integrity sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==
@@ -227,6 +245,19 @@
resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796"
integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==
+"@keyv/bigmap@^1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@keyv/bigmap/-/bigmap-1.3.1.tgz#fc82fa83947e7ff68c6798d08907db842771ef2c"
+ integrity sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==
+ dependencies:
+ hashery "^1.4.0"
+ hookified "^1.15.0"
+
+"@keyv/serialize@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.1.1.tgz#0c01dd3a3483882af7cf3878d4e71d505c81fc4a"
+ integrity sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==
+
"@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"
@@ -248,14 +279,13 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
-"@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==
+"@quobix/vacuum@^0.36.2":
+ version "0.36.2"
+ resolved "https://registry.yarnpkg.com/@quobix/vacuum/-/vacuum-0.36.2.tgz#229e3aaffdb3391f5a6957d47277dd59452fac6b"
+ integrity sha512-rvHYm44AAN+qXjARBKXRHCOFIDWCFUj0iLxuSB2XmC0sHJcUJJzEoELK3v2ntUWkZiVJFF+3wyVlgMzew36AOA==
dependencies:
- https-proxy-agent "^7.0.6"
- node-fetch "^3.3.2"
- tar "^7.5.13"
+ https-proxy-agent "^9.1.0"
+ tar "^7.5.22"
"@types/esrecurse@^4.3.1":
version "4.3.1"
@@ -263,9 +293,9 @@
integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==
"@types/estree@^1.0.6", "@types/estree@^1.0.8":
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
- integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.10.tgz#8a4a266065af8c2fcf5983153cd97c11b313d0c6"
+ integrity sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==
"@types/json-schema@^7.0.15":
version "7.0.15"
@@ -300,9 +330,9 @@ acorn-walk@^8.2.0:
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==
+ version "8.19.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.19.0.tgz#6b26fd97ce1cc9dc50bdf7ad305e9f1384d4f28a"
+ integrity sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==
agent-base@6:
version "6.0.2"
@@ -311,10 +341,10 @@ agent-base@6:
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==
+agent-base@9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-9.0.0.tgz#ec9efb08314e1e75b0852d74aabf9a387f99834e"
+ integrity sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==
ajv-draft-04@^1.0.0:
version "1.0.0"
@@ -329,9 +359,9 @@ ajv-formats@^3.0.1:
ajv "^8.0.0"
ajv@^6.14.0:
- version "6.14.0"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
- integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==
+ version "6.15.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492"
+ integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
@@ -339,9 +369,9 @@ ajv@^6.14.0:
uri-js "^4.2.2"
ajv@^8.0.0, ajv@^8.17.1:
- version "8.18.0"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc"
- integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==
+ version "8.20.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.20.0.tgz#304b3636add88ba7d936760dd50ece006dea95f9"
+ integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==
dependencies:
fast-deep-equal "^3.1.3"
fast-uri "^3.0.1"
@@ -361,9 +391,9 @@ ansi-regex@^5.0.1:
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
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==
+ version "6.5.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.5.0.tgz#83f070e17628d0031eb33eb2315a72d8bb11d2a0"
+ integrity sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==
ansi-styles@^4.1.0:
version "4.3.0"
@@ -387,6 +417,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==
+argparse@^3.0.3:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-3.0.4.tgz#ba802dd75fdc3d5bdcb4339b713d62c6c5d4565d"
+ integrity sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA==
+
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
@@ -424,10 +459,10 @@ 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.19.0, axios@^1.7.7:
- version "1.20.0"
- resolved "https://registry.yarnpkg.com/axios/-/axios-1.20.0.tgz#515513445aa60e71d04b6521ca6210829ccb4786"
- integrity sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==
+axios@^1.21.0, axios@^1.7.7:
+ version "1.21.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.21.0.tgz#30c363a2652d9649e8b4d2645b34c64ca541ff67"
+ integrity sha512-YCzXMB53Q9PZBsh7yN7wtapit4WepwcHS2fP8QhGsBxSz5YtgYobV7GGlWlAJi+RxfU77GQTQi2DSLUma42EYA==
dependencies:
follow-redirects "^1.16.0"
form-data "^4.0.6"
@@ -435,9 +470,9 @@ axios@^1.19.0, axios@^1.7.7:
proxy-from-env "^2.1.0"
balanced-match@^4.0.2:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.3.tgz#6337a2f23e0604a30481423432f99eac603599f9"
- integrity sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a"
+ integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==
base64-js@^1.3.1:
version "1.5.1"
@@ -462,9 +497,9 @@ bluebird@^3.7.2:
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
brace-expansion@^5.0.8:
- version "5.0.9"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.9.tgz#7c72438809b5fa5babf54199a1f1c281a6984fcf"
- integrity sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==
+ version "5.0.12"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.12.tgz#995fbb4750a77c4d16a7dc942ff2ca6ef8e675ec"
+ integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==
dependencies:
balanced-match "^4.0.2"
@@ -480,11 +515,6 @@ browser-stdout@^1.3.1:
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
-buffer-crc32@~0.2.3:
- version "0.2.13"
- resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
- integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
-
buffer@^5.7.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
@@ -493,6 +523,17 @@ buffer@^5.7.1:
base64-js "^1.3.1"
ieee754 "^1.1.13"
+cacheable@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/cacheable/-/cacheable-2.5.0.tgz#d142d41043e5a865f6053cc70ef4e3ad068bd5ce"
+ integrity sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==
+ dependencies:
+ "@cacheable/memory" "^2.2.0"
+ "@cacheable/utils" "^2.5.0"
+ hookified "^1.15.0"
+ keyv "^5.6.0"
+ qified "^0.10.1"
+
cachedir@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d"
@@ -654,10 +695,10 @@ crypt@0.0.2:
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
-cypress-multi-reporters@^2.0.5:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-2.0.5.tgz#2ef058ac7ea1f03adf368eee47c61d7d4f2644db"
- integrity sha512-5ReXlNE7C/9/rpDI3z0tAJbPXsTHK7P3ogvUtBntQlmctRQ+sSMts7dIQY5MTb0XfBSge3CuwvNvaoqtw90KSQ==
+cypress-multi-reporters@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-2.1.0.tgz#c664fe4ab18840d44e8398710481f053eeb997cc"
+ integrity sha512-QCxLVZPkIRjAykSY8EfWWlVhLVeGh0iAsZpXJhsHbP7mmHN6GGVUPUEhZfLy3FO5kd5bGhmjGsW/YY9h3qADbw==
dependencies:
debug "^4.4.0"
lodash "^4.17.21"
@@ -668,10 +709,10 @@ 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@^16.0.0:
- version "16.0.0"
- resolved "https://registry.yarnpkg.com/cypress/-/cypress-16.0.0.tgz#22afcb0dbc9dda87188b05b0d7ac058215b8c35e"
- integrity sha512-/H3zsayGwIGboFIiLWaXxy6ADmCdWy5QjjDBZgslCvjIgYAf1DkZerwAM7wXMPSpfpQ4w+lGjFlmwneRBpg27g==
+cypress@^16.2.1:
+ version "16.2.1"
+ resolved "https://registry.yarnpkg.com/cypress/-/cypress-16.2.1.tgz#98993dd2e6669e303f4ec3391c380b2c59e3b005"
+ integrity sha512-3Vqf1Krr92hKww/Owob9JrDO7TgBCR2ct+OQrvBOFGSnoGsikL4YF5arqDrJb+SM8Fzi7xXIeVmCeaOqJKPhRA==
dependencies:
"@cypress/request" "^4.0.0"
"@cypress/xvfb" "^1.2.4"
@@ -720,11 +761,6 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
-data-uri-to-buffer@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
- integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
-
dayjs@^1.11.23:
version "1.11.23"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.23.tgz#b0a363506dde5f36cf5075e42ebe8115165a8c79"
@@ -816,9 +852,9 @@ es-errors@^1.3.0:
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
- integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b"
+ integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==
dependencies:
es-errors "^1.3.0"
@@ -858,12 +894,12 @@ eslint-plugin-chai-friendly@^1.2.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-1.2.1.tgz#141392762aa8626ac3d25780a502e625ce74922b"
integrity sha512-mV3EOJLDr8+L+LS8uCkP711fnNHz+4PsmPyz18xwkvjJwfLRlnx0Eu6CFnb5B+dW5ahoav2jVer2KFYsmIuv3A==
-eslint-plugin-cypress@^7.0.1:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-7.0.1.tgz#eb80500478533204ad5ebd2e49944224b0e47941"
- integrity sha512-Jn2sV9rt8iiUYZY+iGY8fvV8iseJ6Tl1jngnLeSRyuHvYgxEbDUUFwNhwj1cr9gI4xCzTDh4mgXJ3YY7+dEMVg==
+eslint-plugin-cypress@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-7.0.2.tgz#9260295baa503b13eb1b82b6b2daa9c98dee4f44"
+ integrity sha512-bJ46P9NYdXpqiO5Tn7rB5qQJ4r0TwFq7r9ZsOhUTK7wqF8MwinrNj6gB9Tt0xkJdKQE5FI1w3KY8VmPShje3MA==
dependencies:
- globals "^17.11.0"
+ globals "^17.12.0"
eslint-scope@^9.1.2:
version "9.1.2"
@@ -885,18 +921,18 @@ 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.9.1:
- version "10.9.1"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.9.1.tgz#409da5c41a5536d5a849f8555a18ca7ef1eb963b"
- integrity sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==
+eslint@^10.14.0:
+ version "10.14.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.14.0.tgz#b7574df08d11d9eaa04222f09606674a0ea51db9"
+ integrity sha512-T0pNsxkb9IKmtQjhpK3d7nWOpFv+YHuMbTJnSVMoQblF0onvXfErrtp4V1jHtsQPan7dKaGVD0q1WCXnzMF31A==
dependencies:
"@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.2"
"@eslint/config-array" "^0.23.5"
"@eslint/config-helpers" "^0.7.0"
"@eslint/core" "^1.2.1"
- "@eslint/plugin-kit" "^0.7.2"
- "@humanfs/node" "^0.16.6"
+ "@eslint/plugin-kit" "^0.7.3"
+ "@humanfs/node" "^0.16.8"
"@humanwhocodes/module-importer" "^1.0.1"
"@humanwhocodes/retry" "^0.4.2"
"@types/estree" "^1.0.6"
@@ -910,14 +946,14 @@ eslint@^10.9.1:
esquery "^1.7.0"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
- file-entry-cache "^8.0.0"
+ file-entry-cache "11.1.5 || >11.1.6 <12"
find-up "^5.0.0"
glob-parent "^6.0.2"
ignore "^5.2.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
json-stable-stringify-without-jsonify "^1.0.1"
- minimatch "^10.2.5"
+ minimatch "^10.2.6"
natural-compare "^1.4.0"
optionator "^0.9.3"
@@ -1038,14 +1074,14 @@ fast-levenshtein@^2.0.6:
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fast-uri@^3.0.1:
- version "3.1.6"
- resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.6.tgz#bfdd308ef763f835fed059f3c6c925f708e33e3a"
- integrity sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==
+ version "3.1.9"
+ resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.9.tgz#f1deba822b1d6ac79da2f860e872a63470cff203"
+ integrity sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==
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==
+ version "1.20.3"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.3.tgz#7ab8731e647b56abcfeee997dae333ca3ee1d04d"
+ integrity sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==
dependencies:
reusify "^1.0.4"
@@ -1054,20 +1090,12 @@ fdir@^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"
- resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9"
- integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
+"file-entry-cache@11.1.5 || >11.1.6 <12":
+ version "11.1.5"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-11.1.5.tgz#c8210eb055de63e68685ccfb6a017e386d4577d0"
+ integrity sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==
dependencies:
- node-domexception "^1.0.0"
- web-streams-polyfill "^3.0.3"
-
-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"
- integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
- dependencies:
- flat-cache "^4.0.0"
+ flat-cache "^6.1.23"
fill-range@^7.1.1:
version "7.1.1"
@@ -1088,6 +1116,11 @@ find-test-names@^1.28.18:
simple-bin-help "^1.8.0"
tinyglobby "^0.2.13"
+find-up-simple@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/find-up-simple/-/find-up-simple-1.0.1.tgz#18fb90ad49e45252c4d7fca56baade04fa3fca1e"
+ integrity sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==
+
find-up@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
@@ -1096,23 +1129,24 @@ find-up@^5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"
-flat-cache@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c"
- integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
+flat-cache@^6.1.23:
+ version "6.1.23"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-6.1.23.tgz#735dc888c271868d7301b3bbb8edba5028afb61d"
+ integrity sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==
dependencies:
- flatted "^3.2.9"
- keyv "^4.5.4"
+ cacheable "^2.5.0"
+ flatted "^3.4.2"
+ hookified "^1.15.0"
-flatted@^3.2.9:
- version "3.4.2"
- resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726"
- integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==
+flatted@^3.4.2:
+ version "3.4.4"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.4.tgz#aeeca2a506303f0cee61c59e6c9f2a88d2f29fc6"
+ integrity sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==
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==
+ version "1.16.2"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.2.tgz#a233160dd1bc9b16cbbab43709e6adc7cc941c8c"
+ integrity sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==
forever-agent@~0.6.1:
version "0.6.1"
@@ -1130,13 +1164,6 @@ form-data@^4.0.6, form-data@~4.0.4:
hasown "^2.0.4"
mime-types "^2.1.35"
-formdata-polyfill@^4.0.10:
- version "4.0.10"
- resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
- integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
- dependencies:
- fetch-blob "^3.1.2"
-
fs-extra@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
@@ -1153,9 +1180,9 @@ function-bind@^1.1.2:
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
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==
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz#7ee99f0bd5ab7c622002deeabba232923726d1d4"
+ integrity sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==
get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0:
version "1.3.0"
@@ -1225,10 +1252,10 @@ global-dirs@^3.0.0:
dependencies:
ini "2.0.0"
-globals@^17.11.0:
- version "17.11.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-17.11.0.tgz#d643485bb30220d7751e511cf4f68c73d3870d87"
- integrity sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==
+globals@^17.12.0:
+ version "17.14.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-17.14.0.tgz#5bbbe515622ba24229bd94d4a7c441740e1b50dd"
+ integrity sha512-UtdkTVyAJePQv0J2M9Xe4L0ruNSDV314XiIJsADI2Zf40bcdAonE/D/OhAU8AT99oij9mhLByb+NvA3QWZYgCg==
globby@^11.0.4:
version "11.1.0"
@@ -1277,20 +1304,30 @@ hasha@5.2.2:
is-stream "^2.0.0"
type-fest "^0.8.0"
-hasown@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
- integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+hashery@^1.4.0, hashery@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/hashery/-/hashery-1.5.1.tgz#4ba82ad54911ac617467870845d57a9fe508a400"
+ integrity sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==
dependencies:
- function-bind "^1.1.2"
+ hookified "^1.15.0"
-hasown@^2.0.4:
+hasown@^2.0.2, hasown@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003"
integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==
dependencies:
function-bind "^1.1.2"
+hookified@^1.15.0, hookified@^1.15.1:
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/hookified/-/hookified-1.15.1.tgz#b1fafeaa5489cdc29cb85546a8f837ed4ffbbcb6"
+ integrity sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==
+
+hookified@^2.1.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/hookified/-/hookified-2.2.0.tgz#1d024ac1668973dd5bcc4a96ab9ccdb7639ef8d4"
+ integrity sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==
+
http-signature@~1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.4.0.tgz#dee5a9ba2bf49416abc544abd6d967f6a94c8c3f"
@@ -1308,13 +1345,14 @@ https-proxy-agent@^5.0.1:
agent-base "6"
debug "4"
-https-proxy-agent@^7.0.6:
- version "7.0.6"
- resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
- integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
+https-proxy-agent@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz#a9f60f79bc1578c37b166dd7f58b6bcb5c57e44b"
+ integrity sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==
dependencies:
- agent-base "^7.1.2"
- debug "4"
+ agent-base "9.0.0"
+ debug "^4.3.4"
+ proxy-agent-negotiate "1.1.0"
human-signals@^1.1.1:
version "1.1.1"
@@ -1383,11 +1421,16 @@ is-number@^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:
+is-path-inside@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+is-path-inside@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-4.0.0.tgz#805aeb62c47c1b12fc3fd13bfb3ed1e7430071db"
+ integrity sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==
+
is-stream@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
@@ -1414,29 +1457,24 @@ isstream@~0.1.2:
integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
js-yaml@^4.1.0:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.1.tgz#01216c001d67f48e2cd560d708c7af21090a3848"
- integrity sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.2.tgz#8e44fb14a2643c59726bb15787b5f1512cb3d3fb"
+ integrity sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==
dependencies:
argparse "^2.0.1"
js-yaml@^5.0.0:
- version "5.4.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-5.4.1.tgz#4629d91d4b4551f300435f0e4127899710f816fb"
- integrity sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-5.5.0.tgz#ec843f09ffb2a54b6758a41eee109a1815d16442"
+ integrity sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg==
dependencies:
- argparse "^2.0.1"
+ argparse "^3.0.3"
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
-json-buffer@3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
- integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
-
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -1463,18 +1501,18 @@ json-stringify-safe@~5.0.1:
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
jsonfile@^6.0.1:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62"
- integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.1.tgz#b6e31717f22cc37330b081ce0051ed5de53af2f6"
+ integrity sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
jsonpath@^1.1.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/jsonpath/-/jsonpath-1.2.1.tgz#2b74a4bcc78948e43e33ac971138ce0c68bce701"
- integrity sha512-Jl6Jhk0jG+kP3yk59SSeGq7LFPR4JQz1DU0K+kXTysUhMostbhU3qh5mjTuf0PqFcXpAT7kvmMt9WxV10NyIgQ==
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/jsonpath/-/jsonpath-1.3.0.tgz#623197970fb433845c68024bf9e2b864f5376ab2"
+ integrity sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==
dependencies:
esprima "1.2.5"
static-eval "2.1.1"
@@ -1490,12 +1528,12 @@ jsprim@^2.0.2:
json-schema "0.4.0"
verror "1.10.0"
-keyv@^4.5.4:
- version "4.5.4"
- resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
- integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+keyv@^5.6.0:
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-5.6.0.tgz#03044074c6b4d072d0a62c7b9fa649537baf0105"
+ integrity sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==
dependencies:
- json-buffer "3.0.1"
+ "@keyv/serialize" "^1.1.1"
levn@^0.4.1:
version "0.4.1"
@@ -1554,9 +1592,9 @@ log-update@^6.1.0:
wrap-ansi "^9.0.0"
lru-cache@^11.0.0:
- version "11.5.2"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.5.2.tgz#00e16665c90c620fba14a3c368732a976493f760"
- integrity sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==
+ version "11.5.3"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.5.3.tgz#a7767b4034e35269562d2b9da2b185b5df70c421"
+ integrity sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==
math-intrinsics@^1.1.0:
version "1.1.0"
@@ -1612,7 +1650,7 @@ mimic-function@^5.0.0:
resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076"
integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==
-minimatch@^10.2.2, minimatch@^10.2.4, minimatch@^10.2.5:
+minimatch@^10.2.2, minimatch@^10.2.4, minimatch@^10.2.6:
version "10.2.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef"
integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==
@@ -1652,24 +1690,24 @@ mocha-junit-reporter@^2.2.1:
strip-ansi "^6.0.1"
xml "^1.0.1"
-mocha@^12.0.0:
- version "12.0.0"
- resolved "https://registry.yarnpkg.com/mocha/-/mocha-12.0.0.tgz#6284d68e66fac4597e8411b0fd62ab4c6717023c"
- integrity sha512-NYNh5IFt6WYqm9bi4601m7vix8MZdXC0DwS4gY6WhXO2RgJWhivhISVmq1oklCif18OSI9l+vx5Mdm5oh1XGiQ==
+mocha@^12.0.3:
+ version "12.0.3"
+ resolved "https://registry.yarnpkg.com/mocha/-/mocha-12.0.3.tgz#f3022f5684f5a8306323dd5ab4d0f881a90cd031"
+ integrity sha512-beammieDdiWdkjANWeWPpkxUROYGi5FN88fPOkLiticz7HTg3Y+Pho3EYE6SUdcuWnZ3h2WD8EXCA55JF4o6wg==
dependencies:
browser-stdout "^1.3.1"
chokidar "^5.0.0"
debug "^4.3.5"
diff "^9.0.0"
- find-up "^5.0.0"
+ find-up-simple "^1.0.1"
glob "^13.0.0"
- is-path-inside "^3.0.3"
+ is-path-inside "^4.0.0"
is-unicode-supported "^0.1.0"
js-yaml "^5.0.0"
minimatch "^10.2.2"
ms "^2.1.3"
picocolors "^1.1.1"
- serialize-javascript "^7.0.2"
+ serialize-javascript "^7.1.1"
strip-json-comments "^5.0.3"
supports-color "^8.1.1"
workerpool "^10.0.0"
@@ -1684,20 +1722,6 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
-node-domexception@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
- integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
-
-node-fetch@^3.3.2:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b"
- integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==
- dependencies:
- data-uri-to-buffer "^4.0.0"
- fetch-blob "^3.1.4"
- formdata-polyfill "^4.0.10"
-
npm-run-path@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
@@ -1705,7 +1729,7 @@ npm-run-path@^4.0.0:
dependencies:
path-key "^3.0.0"
-object-inspect@^1.13.3:
+object-inspect@^1.13.3, object-inspect@^1.13.4:
version "1.13.4"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
@@ -1811,9 +1835,9 @@ picomatch@^2.3.1:
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==
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f"
+ integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==
pify@^2.2.0:
version "2.3.0"
@@ -1835,6 +1859,11 @@ process@^0.11.10:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
+proxy-agent-negotiate@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz#de7d37aede9d71e74346125d6f484fac8092b615"
+ integrity sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==
+
proxy-from-env@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee"
@@ -1846,9 +1875,9 @@ proxy-from-env@^2.1.0:
integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==
pump@^3.0.0:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d"
- integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.4.tgz#1f313430527fa8b905622ebd22fe1444e757ab3c"
+ integrity sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
@@ -1858,12 +1887,20 @@ punycode@^2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
-qs@~6.14.1:
- version "6.14.2"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c"
- integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==
+qified@^0.10.1:
+ version "0.10.1"
+ resolved "https://registry.yarnpkg.com/qified/-/qified-0.10.1.tgz#0640bf21bbe6ca540db290ad8f5fde4d870b8bda"
+ integrity sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==
dependencies:
- side-channel "^1.1.0"
+ hookified "^2.1.1"
+
+qs@^6.15.2:
+ version "6.17.1"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.17.1.tgz#43ac71d29ca0e9d0eed16a218e35b6cde5628e81"
+ integrity sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==
+ dependencies:
+ es-define-property "^1.0.1"
+ side-channel "^1.1.1"
queue-microtask@^1.2.2:
version "1.2.3"
@@ -1923,14 +1960,14 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
semver@^7.6.3:
- version "7.7.4"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
- integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
+ version "7.8.6"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.6.tgz#67c0cda133a3ceb6f70b803e67625bf969000424"
+ integrity sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==
-serialize-javascript@^7.0.2:
- version "7.1.1"
- resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.1.1.tgz#c3b0a7ac13df6cf2fcda71cbb142ba9392a710b9"
- integrity sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ==
+serialize-javascript@^7.1.1:
+ version "7.1.4"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.1.4.tgz#477af55bdec0d831fa3b209245f673217fd27380"
+ integrity sha512-FqlEuoTq/zRmA4Lg6aOqyH3lCHbUtCB0bmHCeFniX2GBdDqMJM3FwdvEhlE5Pim1dlt2FfBdVPmUNNiGhhKqpw==
shebang-command@^2.0.0:
version "2.0.0"
@@ -1944,13 +1981,13 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
-side-channel-list@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
- integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==
+side-channel-list@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127"
+ integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==
dependencies:
es-errors "^1.3.0"
- object-inspect "^1.13.3"
+ object-inspect "^1.13.4"
side-channel-map@^1.0.1:
version "1.0.1"
@@ -1973,14 +2010,14 @@ side-channel-weakmap@^1.0.2:
object-inspect "^1.13.3"
side-channel-map "^1.0.1"
-side-channel@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
- integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
+side-channel@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab"
+ integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==
dependencies:
es-errors "^1.3.0"
- object-inspect "^1.13.3"
- side-channel-list "^1.0.0"
+ object-inspect "^1.13.4"
+ side-channel-list "^1.0.1"
side-channel-map "^1.0.1"
side-channel-weakmap "^1.0.2"
@@ -2066,9 +2103,9 @@ string-width@^7.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==
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-8.3.0.tgz#f1e85ca6d276ed9226c979c3651fb7abf548fbbe"
+ integrity sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==
dependencies:
get-east-asian-width "^1.5.0"
strip-ansi "^7.1.2"
@@ -2112,11 +2149,11 @@ supports-color@^8.1.1:
has-flag "^4.0.0"
systeminformation@^5.31.1:
- version "5.31.17"
- resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.31.17.tgz#60bd1cdaeb34212b5ad2410608ee47d41d2d8560"
- integrity sha512-TvFA9iwDWlMjqZVlKIJ0Cy+Zgm9ttlMx0SMRwJDMNKyhlEKWBMb3+WRwDi/3dvHdWbexpos4Osp4U49p5WjB5g==
+ version "5.33.19"
+ resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.33.19.tgz#b956e75ff6d7bdc6019ca4fc13c78b01f47cc415"
+ integrity sha512-Zm7wGeCG7PqfvzegMJIzrxICopWqYCeLATVmvxOOci1isLcNVKgY4Fr0hhYQzvc7cgtK3377jQVChTFAnznOiA==
-tar@^7.5.13:
+tar@^7.5.22:
version "7.5.22"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.22.tgz#a696f998136e71487dc3f869a85bba2c67971ba9"
integrity sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==
@@ -2133,9 +2170,9 @@ throttleit@^1.0.0:
integrity sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==
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==
+ version "0.2.17"
+ resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631"
+ integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==
dependencies:
fdir "^6.5.0"
picomatch "^4.0.4"
@@ -2231,11 +2268,6 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
-web-streams-polyfill@^3.0.3:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"
- integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
-
which@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
@@ -2283,11 +2315,10 @@ yallist@^5.0.0:
integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==
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==
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-3.4.0.tgz#88b2a21455f37ca7dccf2eeb33bacb4392322719"
+ integrity sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==
dependencies:
- buffer-crc32 "~0.2.3"
pend "~1.2.0"
yocto-queue@^0.1.0: