Compare commits

..

3 Commits

Author SHA1 Message Date
Jamie Curnow
c303b69649 Update deps, the safe ones 2025-10-26 00:39:06 +10:00
Jamie Curnow
bb6c9c8daf Certificates section react work 2025-10-26 00:28:39 +10:00
Jamie Curnow
5b7013b8d5 Moved certrbot plugin list to backend
frontend doesn't include when building in react version
adds swagger for existing dns-providers endpoint
2025-10-26 00:28:03 +10:00
46 changed files with 1533 additions and 744 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_Store .DS_Store
.idea .idea
.qodo
._* ._*
.vscode .vscode
certbot-help.txt certbot-help.txt

View File

@@ -1,4 +1,4 @@
# certbot-dns-plugins # Certbot dns-plugins
This file contains info about available Certbot DNS plugins. This file contains info about available Certbot DNS plugins.
This only works for plugins which use the standard argument structure, so: This only works for plugins which use the standard argument structure, so:

View File

@@ -1,11 +1,11 @@
import fs from "node:fs"; import fs from "node:fs";
import https from "node:https"; import https from "node:https";
import path from "path";
import archiver from "archiver"; import archiver from "archiver";
import _ from "lodash"; import _ from "lodash";
import moment from "moment"; import moment from "moment";
import path from "path";
import tempWrite from "temp-write"; import tempWrite from "temp-write";
import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" };
import { installPlugin } from "../lib/certbot.js"; import { installPlugin } from "../lib/certbot.js";
import { useLetsencryptServer, useLetsencryptStaging } from "../lib/config.js"; import { useLetsencryptServer, useLetsencryptStaging } from "../lib/config.js";
import error from "../lib/error.js"; import error from "../lib/error.js";
@@ -26,7 +26,11 @@ const omissions = () => {
}; };
const internalCertificate = { const internalCertificate = {
allowedSslFiles: ["certificate", "certificate_key", "intermediate_certificate"], allowedSslFiles: [
"certificate",
"certificate_key",
"intermediate_certificate",
],
intervalTimeout: 1000 * 60 * 60, // 1 hour intervalTimeout: 1000 * 60 * 60, // 1 hour
interval: null, interval: null,
intervalProcessing: false, intervalProcessing: false,
@@ -53,7 +57,10 @@ const internalCertificate = {
); );
const expirationThreshold = moment() const expirationThreshold = moment()
.add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]) .add(
internalCertificate.renewBeforeExpirationBy[0],
internalCertificate.renewBeforeExpirationBy[1],
)
.format("YYYY-MM-DD HH:mm:ss"); .format("YYYY-MM-DD HH:mm:ss");
// Fetch all the letsencrypt certs from the db that will expire within the configured threshold // Fetch all the letsencrypt certs from the db that will expire within the configured threshold
@@ -119,91 +126,115 @@ const internalCertificate = {
data.nice_name = data.domain_names.join(", "); data.nice_name = data.domain_names.join(", ");
} }
const certificate = await certificateModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); // this command really should clean up and delete the cert if it can't fully succeed
const certificate = await certificateModel
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
if (certificate.provider === "letsencrypt") { try {
// Request a new Cert from LE. Let the fun begin. if (certificate.provider === "letsencrypt") {
// Request a new Cert from LE. Let the fun begin.
// 1. Find out any hosts that are using any of the hostnames in this cert // 1. Find out any hosts that are using any of the hostnames in this cert
// 2. Disable them in nginx temporarily // 2. Disable them in nginx temporarily
// 3. Generate the LE config
// 4. Request cert
// 5. Remove LE config
// 6. Re-instate previously disabled hosts
// 1. Find out any hosts that are using any of the hostnames in this cert
const inUseResult = await internalHost.getHostsWithDomains(certificate.domain_names);
// 2. Disable them in nginx temporarily
await internalCertificate.disableInUseHosts(inUseResult);
const user = await userModel.query().where("is_deleted", 0).andWhere("id", data.owner_user_id).first();
if (!user || !user.email) {
throw new error.ValidationError(
"A valid email address must be set on your user account to use Let's Encrypt",
);
}
// With DNS challenge no config is needed, so skip 3 and 5.
if (certificate.meta?.dns_challenge) {
try {
await internalNginx.reload();
// 4. Request cert
await internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate, user.email);
await internalNginx.reload();
// 6. Re-instate previously disabled hosts
await internalCertificate.enableInUseHosts(inUseResult);
} catch (err) {
// In the event of failure, revert things and throw err back
await internalCertificate.enableInUseHosts(inUseResult);
await internalNginx.reload();
throw err;
}
} else {
// 3. Generate the LE config // 3. Generate the LE config
// 4. Request cert
// 5. Remove LE config
// 6. Re-instate previously disabled hosts
// 1. Find out any hosts that are using any of the hostnames in this cert
const inUseResult = await internalHost.getHostsWithDomains(
certificate.domain_names,
);
// 2. Disable them in nginx temporarily
await internalCertificate.disableInUseHosts(inUseResult);
const user = await userModel
.query()
.where("is_deleted", 0)
.andWhere("id", data.owner_user_id)
.first();
if (!user || !user.email) {
throw new error.ValidationError(
"A valid email address must be set on your user account to use Let's Encrypt",
);
}
// With DNS challenge no config is needed, so skip 3 and 5.
if (certificate.meta?.dns_challenge) {
try {
await internalNginx.reload();
// 4. Request cert
await internalCertificate.requestLetsEncryptSslWithDnsChallenge(
certificate,
user.email,
);
await internalNginx.reload();
// 6. Re-instate previously disabled hosts
await internalCertificate.enableInUseHosts(inUseResult);
} catch (err) {
// In the event of failure, revert things and throw err back
await internalCertificate.enableInUseHosts(inUseResult);
await internalNginx.reload();
throw err;
}
} else {
// 3. Generate the LE config
try {
await internalNginx.generateLetsEncryptRequestConfig(certificate);
await internalNginx.reload();
setTimeout(() => {}, 5000);
// 4. Request cert
await internalCertificate.requestLetsEncryptSsl(
certificate,
user.email,
);
// 5. Remove LE config
await internalNginx.deleteLetsEncryptRequestConfig(certificate);
await internalNginx.reload();
// 6. Re-instate previously disabled hosts
await internalCertificate.enableInUseHosts(inUseResult);
} catch (err) {
// In the event of failure, revert things and throw err back
await internalNginx.deleteLetsEncryptRequestConfig(certificate);
await internalCertificate.enableInUseHosts(inUseResult);
await internalNginx.reload();
throw err;
}
}
// At this point, the letsencrypt cert should exist on disk.
// Lets get the expiry date from the file and update the row silently
try { try {
await internalNginx.generateLetsEncryptRequestConfig(certificate); const certInfo = await internalCertificate.getCertificateInfoFromFile(
await internalNginx.reload(); `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`,
setTimeout(() => {}, 5000); );
// 4. Request cert const savedRow = await certificateModel
await internalCertificate.requestLetsEncryptSsl(certificate, user.email); .query()
// 5. Remove LE config .patchAndFetchById(certificate.id, {
await internalNginx.deleteLetsEncryptRequestConfig(certificate); expires_on: moment(certInfo.dates.to, "X").format(
await internalNginx.reload(); "YYYY-MM-DD HH:mm:ss",
// 6. Re-instate previously disabled hosts ),
await internalCertificate.enableInUseHosts(inUseResult); })
.then(utils.omitRow(omissions()));
// Add cert data for audit log
savedRow.meta = _.assign({}, savedRow.meta, {
letsencrypt_certificate: certInfo,
});
return savedRow;
} catch (err) { } catch (err) {
// In the event of failure, revert things and throw err back // Delete the certificate from the database if it was not created successfully
await internalNginx.deleteLetsEncryptRequestConfig(certificate); await certificateModel.query().deleteById(certificate.id);
await internalCertificate.enableInUseHosts(inUseResult);
await internalNginx.reload();
throw err; throw err;
} }
} }
} catch (err) {
// At this point, the letsencrypt cert should exist on disk. // Delete the certificate here. This is a hard delete, since it never existed properly
// Lets get the expiry date from the file and update the row silently await certificateModel.query().deleteById(certificate.id);
try { throw err;
const certInfo = await internalCertificate.getCertificateInfoFromFile(
`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`,
);
const savedRow = await certificateModel
.query()
.patchAndFetchById(certificate.id, {
expires_on: moment(certInfo.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"),
})
.then(utils.omitRow(omissions()));
// Add cert data for audit log
savedRow.meta = _.assign({}, savedRow.meta, {
letsencrypt_certificate: certInfo,
});
return savedRow;
} catch (err) {
// Delete the certificate from the database if it was not created successfully
await certificateModel.query().deleteById(certificate.id);
throw err;
}
} }
data.meta = _.assign({}, data.meta || {}, certificate.meta); data.meta = _.assign({}, data.meta || {}, certificate.meta);
@@ -313,7 +344,9 @@ const internalCertificate = {
if (certificate.provider === "letsencrypt") { if (certificate.provider === "letsencrypt") {
const zipDirectory = internalCertificate.getLiveCertPath(data.id); const zipDirectory = internalCertificate.getLiveCertPath(data.id);
if (!fs.existsSync(zipDirectory)) { if (!fs.existsSync(zipDirectory)) {
throw new error.ItemNotFoundError(`Certificate ${certificate.nice_name} does not exists`); throw new error.ItemNotFoundError(
`Certificate ${certificate.nice_name} does not exists`,
);
} }
const certFiles = fs const certFiles = fs
@@ -330,7 +363,9 @@ const internalCertificate = {
fileName: opName, fileName: opName,
}; };
} }
throw new error.ValidationError("Only Let'sEncrypt certificates can be downloaded"); throw new error.ValidationError(
"Only Let'sEncrypt certificates can be downloaded",
);
}, },
/** /**
@@ -435,7 +470,10 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
getCount: async (userId, visibility) => { getCount: async (userId, visibility) => {
const query = certificateModel.query().count("id as count").where("is_deleted", 0); const query = certificateModel
.query()
.count("id as count")
.where("is_deleted", 0);
if (visibility !== "all") { if (visibility !== "all") {
query.andWhere("owner_user_id", userId); query.andWhere("owner_user_id", userId);
@@ -483,13 +521,17 @@ const internalCertificate = {
}); });
}).then(() => { }).then(() => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.writeFile(`${dir}/privkey.pem`, certificate.meta.certificate_key, (err) => { fs.writeFile(
if (err) { `${dir}/privkey.pem`,
reject(err); certificate.meta.certificate_key,
} else { (err) => {
resolve(); if (err) {
} reject(err);
}); } else {
resolve();
}
},
);
}); });
}); });
}, },
@@ -562,7 +604,9 @@ const internalCertificate = {
upload: async (access, data) => { upload: async (access, data) => {
const row = await internalCertificate.get(access, { id: data.id }); const row = await internalCertificate.get(access, { id: data.id });
if (row.provider !== "other") { if (row.provider !== "other") {
throw new error.ValidationError("Cannot upload certificates for this type of provider"); throw new error.ValidationError(
"Cannot upload certificates for this type of provider",
);
} }
const validations = await internalCertificate.validate(data); const validations = await internalCertificate.validate(data);
@@ -578,7 +622,9 @@ const internalCertificate = {
const certificate = await internalCertificate.update(access, { const certificate = await internalCertificate.update(access, {
id: data.id, id: data.id,
expires_on: moment(validations.certificate.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), expires_on: moment(validations.certificate.dates.to, "X").format(
"YYYY-MM-DD HH:mm:ss",
),
domain_names: [validations.certificate.cn], domain_names: [validations.certificate.cn],
meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later
}); });
@@ -603,7 +649,9 @@ const internalCertificate = {
}, 10000); }, 10000);
try { try {
const result = await utils.exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `); const result = await utils.exec(
`openssl pkey -in ${filepath} -check -noout 2>&1 `,
);
clearTimeout(failTimeout); clearTimeout(failTimeout);
if (!result.toLowerCase().includes("key is valid")) { if (!result.toLowerCase().includes("key is valid")) {
throw new error.ValidationError(`Result Validation Error: ${result}`); throw new error.ValidationError(`Result Validation Error: ${result}`);
@@ -613,7 +661,10 @@ const internalCertificate = {
} catch (err) { } catch (err) {
clearTimeout(failTimeout); clearTimeout(failTimeout);
fs.unlinkSync(filepath); fs.unlinkSync(filepath);
throw new error.ValidationError(`Certificate Key is not valid (${err.message})`, err); throw new error.ValidationError(
`Certificate Key is not valid (${err.message})`,
err,
);
} }
}, },
@@ -627,7 +678,10 @@ const internalCertificate = {
getCertificateInfo: async (certificate, throwExpired) => { getCertificateInfo: async (certificate, throwExpired) => {
try { try {
const filepath = await tempWrite(certificate, "/tmp"); const filepath = await tempWrite(certificate, "/tmp");
const certData = await internalCertificate.getCertificateInfoFromFile(filepath, throwExpired); const certData = await internalCertificate.getCertificateInfoFromFile(
filepath,
throwExpired,
);
fs.unlinkSync(filepath); fs.unlinkSync(filepath);
return certData; return certData;
} catch (err) { } catch (err) {
@@ -647,7 +701,13 @@ const internalCertificate = {
const certData = {}; const certData = {};
try { try {
const result = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-subject", "-noout"]); const result = await utils.execFile("openssl", [
"x509",
"-in",
certificateFile,
"-subject",
"-noout",
]);
// Examples: // Examples:
// subject=CN = *.jc21.com // subject=CN = *.jc21.com
// subject=CN = something.example.com // subject=CN = something.example.com
@@ -657,7 +717,13 @@ const internalCertificate = {
certData.cn = match[1]; certData.cn = match[1];
} }
const result2 = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-issuer", "-noout"]); const result2 = await utils.execFile("openssl", [
"x509",
"-in",
certificateFile,
"-issuer",
"-noout",
]);
// Examples: // Examples:
// issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 // issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
// issuer=C = US, O = Let's Encrypt, CN = E5 // issuer=C = US, O = Let's Encrypt, CN = E5
@@ -668,7 +734,13 @@ const internalCertificate = {
certData.issuer = match2[1]; certData.issuer = match2[1];
} }
const result3 = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-dates", "-noout"]); const result3 = await utils.execFile("openssl", [
"x509",
"-in",
certificateFile,
"-dates",
"-noout",
]);
// notBefore=Jul 14 04:04:29 2018 GMT // notBefore=Jul 14 04:04:29 2018 GMT
// notAfter=Oct 12 04:04:29 2018 GMT // notAfter=Oct 12 04:04:29 2018 GMT
let validFrom = null; let validFrom = null;
@@ -680,7 +752,10 @@ const internalCertificate = {
const match = regex.exec(str.trim()); const match = regex.exec(str.trim());
if (match && typeof match[2] !== "undefined") { if (match && typeof match[2] !== "undefined") {
const date = Number.parseInt(moment(match[2], "MMM DD HH:mm:ss YYYY z").format("X"), 10); const date = Number.parseInt(
moment(match[2], "MMM DD HH:mm:ss YYYY z").format("X"),
10,
);
if (match[1].toLowerCase() === "notbefore") { if (match[1].toLowerCase() === "notbefore") {
validFrom = date; validFrom = date;
@@ -692,10 +767,15 @@ const internalCertificate = {
}); });
if (!validFrom || !validTo) { if (!validFrom || !validTo) {
throw new error.ValidationError(`Could not determine dates from certificate: ${result}`); throw new error.ValidationError(
`Could not determine dates from certificate: ${result}`,
);
} }
if (throw_expired && validTo < Number.parseInt(moment().format("X"), 10)) { if (
throw_expired &&
validTo < Number.parseInt(moment().format("X"), 10)
) {
throw new error.ValidationError("Certificate has expired"); throw new error.ValidationError("Certificate has expired");
} }
@@ -706,7 +786,10 @@ const internalCertificate = {
return certData; return certData;
} catch (err) { } catch (err) {
throw new error.ValidationError(`Certificate is not valid (${err.message})`, err); throw new error.ValidationError(
`Certificate is not valid (${err.message})`,
err,
);
} }
}, },
@@ -787,7 +870,11 @@ const internalCertificate = {
const credentialsLocation = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; const credentialsLocation = `/etc/letsencrypt/credentials/credentials-${certificate.id}`;
fs.mkdirSync("/etc/letsencrypt/credentials", { recursive: true }); fs.mkdirSync("/etc/letsencrypt/credentials", { recursive: true });
fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, { mode: 0o600 }); fs.writeFileSync(
credentialsLocation,
certificate.meta.dns_provider_credentials,
{ mode: 0o600 },
);
// Whether the plugin has a --<name>-credentials argument // Whether the plugin has a --<name>-credentials argument
const hasConfigArg = certificate.meta.dns_provider !== "route53"; const hasConfigArg = certificate.meta.dns_provider !== "route53";
@@ -812,7 +899,10 @@ const internalCertificate = {
]; ];
if (hasConfigArg) { if (hasConfigArg) {
args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation); args.push(
`--${dnsPlugin.full_plugin_name}-credentials`,
credentialsLocation,
);
} }
if (certificate.meta.propagation_seconds !== undefined) { if (certificate.meta.propagation_seconds !== undefined) {
args.push( args.push(
@@ -821,7 +911,10 @@ const internalCertificate = {
); );
} }
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); const adds = internalCertificate.getAdditionalCertbotArgs(
certificate.id,
certificate.meta.dns_provider,
);
args.push(...adds.args); args.push(...adds.args);
logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`);
@@ -857,9 +950,13 @@ const internalCertificate = {
`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`,
); );
const updatedCertificate = await certificateModel.query().patchAndFetchById(certificate.id, { const updatedCertificate = await certificateModel
expires_on: moment(certInfo.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), .query()
}); .patchAndFetchById(certificate.id, {
expires_on: moment(certInfo.dates.to, "X").format(
"YYYY-MM-DD HH:mm:ss",
),
});
// Add to audit log // Add to audit log
await internalAuditLog.add(access, { await internalAuditLog.add(access, {
@@ -869,7 +966,9 @@ const internalCertificate = {
meta: updatedCertificate, meta: updatedCertificate,
}); });
} else { } else {
throw new error.ValidationError("Only Let'sEncrypt certificates can be renewed"); throw new error.ValidationError(
"Only Let'sEncrypt certificates can be renewed",
);
} }
}, },
@@ -899,7 +998,10 @@ const internalCertificate = {
"--disable-hook-validation", "--disable-hook-validation",
]; ];
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); const adds = internalCertificate.getAdditionalCertbotArgs(
certificate.id,
certificate.meta.dns_provider,
);
args.push(...adds.args); args.push(...adds.args);
logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`);
@@ -938,7 +1040,10 @@ const internalCertificate = {
"--no-random-sleep-on-renew", "--no-random-sleep-on-renew",
]; ];
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); const adds = internalCertificate.getAdditionalCertbotArgs(
certificate.id,
certificate.meta.dns_provider,
);
args.push(...adds.args); args.push(...adds.args);
logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`);
@@ -978,7 +1083,9 @@ const internalCertificate = {
try { try {
const result = await utils.execFile(certbotCommand, args, adds.opts); const result = await utils.execFile(certbotCommand, args, adds.opts);
await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); await utils.exec(
`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`,
);
logger.info(result); logger.info(result);
return result; return result;
} catch (err) { } catch (err) {
@@ -995,7 +1102,10 @@ const internalCertificate = {
*/ */
hasLetsEncryptSslCerts: (certificate) => { hasLetsEncryptSslCerts: (certificate) => {
const letsencryptPath = internalCertificate.getLiveCertPath(certificate.id); const letsencryptPath = internalCertificate.getLiveCertPath(certificate.id);
return fs.existsSync(`${letsencryptPath}/fullchain.pem`) && fs.existsSync(`${letsencryptPath}/privkey.pem`); return (
fs.existsSync(`${letsencryptPath}/fullchain.pem`) &&
fs.existsSync(`${letsencryptPath}/privkey.pem`)
);
}, },
/** /**
@@ -1009,15 +1119,24 @@ const internalCertificate = {
disableInUseHosts: async (inUseResult) => { disableInUseHosts: async (inUseResult) => {
if (inUseResult?.total_count) { if (inUseResult?.total_count) {
if (inUseResult?.proxy_hosts.length) { if (inUseResult?.proxy_hosts.length) {
await internalNginx.bulkDeleteConfigs("proxy_host", inUseResult.proxy_hosts); await internalNginx.bulkDeleteConfigs(
"proxy_host",
inUseResult.proxy_hosts,
);
} }
if (inUseResult?.redirection_hosts.length) { if (inUseResult?.redirection_hosts.length) {
await internalNginx.bulkDeleteConfigs("redirection_host", inUseResult.redirection_hosts); await internalNginx.bulkDeleteConfigs(
"redirection_host",
inUseResult.redirection_hosts,
);
} }
if (inUseResult?.dead_hosts.length) { if (inUseResult?.dead_hosts.length) {
await internalNginx.bulkDeleteConfigs("dead_host", inUseResult.dead_hosts); await internalNginx.bulkDeleteConfigs(
"dead_host",
inUseResult.dead_hosts,
);
} }
} }
}, },
@@ -1033,50 +1152,73 @@ const internalCertificate = {
enableInUseHosts: async (inUseResult) => { enableInUseHosts: async (inUseResult) => {
if (inUseResult.total_count) { if (inUseResult.total_count) {
if (inUseResult.proxy_hosts.length) { if (inUseResult.proxy_hosts.length) {
await internalNginx.bulkGenerateConfigs("proxy_host", inUseResult.proxy_hosts); await internalNginx.bulkGenerateConfigs(
"proxy_host",
inUseResult.proxy_hosts,
);
} }
if (inUseResult.redirection_hosts.length) { if (inUseResult.redirection_hosts.length) {
await internalNginx.bulkGenerateConfigs("redirection_host", inUseResult.redirection_hosts); await internalNginx.bulkGenerateConfigs(
"redirection_host",
inUseResult.redirection_hosts,
);
} }
if (inUseResult.dead_hosts.length) { if (inUseResult.dead_hosts.length) {
await internalNginx.bulkGenerateConfigs("dead_host", inUseResult.dead_hosts); await internalNginx.bulkGenerateConfigs(
"dead_host",
inUseResult.dead_hosts,
);
} }
} }
}, },
testHttpsChallenge: async (access, domains) => { /**
*
* @param {Object} payload
* @param {string[]} payload.domains
* @returns
*/
testHttpsChallenge: async (access, payload) => {
await access.can("certificates:list"); await access.can("certificates:list");
if (!isArray(domains)) {
throw new error.InternalValidationError("Domains must be an array of strings");
}
if (domains.length === 0) {
throw new error.InternalValidationError("No domains provided");
}
// Create a test challenge file // Create a test challenge file
const testChallengeDir = "/data/letsencrypt-acme-challenge/.well-known/acme-challenge"; const testChallengeDir =
"/data/letsencrypt-acme-challenge/.well-known/acme-challenge";
const testChallengeFile = `${testChallengeDir}/test-challenge`; const testChallengeFile = `${testChallengeDir}/test-challenge`;
fs.mkdirSync(testChallengeDir, { recursive: true }); fs.mkdirSync(testChallengeDir, { recursive: true });
fs.writeFileSync(testChallengeFile, "Success", { encoding: "utf8" }); fs.writeFileSync(testChallengeFile, "Success", { encoding: "utf8" });
async function performTestForDomain(domain) { const results = {};
logger.info(`Testing http challenge for ${domain}`); for (const domain of payload.domains) {
const url = `http://${domain}/.well-known/acme-challenge/test-challenge`; results[domain] = await internalCertificate.performTestForDomain(domain);
const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`; }
const options = {
method: "POST",
headers: {
"User-Agent": "Mozilla/5.0",
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": Buffer.byteLength(formBody),
},
};
const result = await new Promise((resolve) => { // Remove the test challenge file
const req = https.request("https://www.site24x7.com/tools/restapi-tester", options, (res) => { fs.unlinkSync(testChallengeFile);
return results;
},
performTestForDomain: async (domain) => {
logger.info(`Testing http challenge for ${domain}`);
const url = `http://${domain}/.well-known/acme-challenge/test-challenge`;
const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`;
const options = {
method: "POST",
headers: {
"User-Agent": "Mozilla/5.0",
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": Buffer.byteLength(formBody),
},
};
const result = await new Promise((resolve) => {
const req = https.request(
"https://www.site24x7.com/tools/restapi-tester",
options,
(res) => {
let responseBody = ""; let responseBody = "";
res.on("data", (chunk) => { res.on("data", (chunk) => {
@@ -1107,69 +1249,66 @@ const internalCertificate = {
resolve(undefined); resolve(undefined);
} }
}); });
}); },
// Make sure to write the request body.
req.write(formBody);
req.end();
req.on("error", (e) => {
logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e);
resolve(undefined);
});
});
if (!result) {
// Some error occurred while trying to get the data
return "failed";
}
if (result.error) {
logger.info(
`HTTP challenge test failed for domain ${domain} because error was returned: ${result.error.msg}`,
);
return `other:${result.error.msg}`;
}
if (`${result.responsecode}` === "200" && result.htmlresponse === "Success") {
// Server exists and has responded with the correct data
return "ok";
}
if (`${result.responsecode}` === "200") {
// Server exists but has responded with wrong data
logger.info(
`HTTP challenge test failed for domain ${domain} because of invalid returned data:`,
result.htmlresponse,
);
return "wrong-data";
}
if (`${result.responsecode}` === "404") {
// Server exists but responded with a 404
logger.info(`HTTP challenge test failed for domain ${domain} because code 404 was returned`);
return "404";
}
if (
`${result.responsecode}` === "0" ||
(typeof result.reason === "string" && result.reason.toLowerCase() === "host unavailable")
) {
// Server does not exist at domain
logger.info(`HTTP challenge test failed for domain ${domain} the host was not found`);
return "no-host";
}
// Other errors
logger.info(
`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`,
); );
return `other:${result.responsecode}`;
// Make sure to write the request body.
req.write(formBody);
req.end();
req.on("error", (e) => {
logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e);
resolve(undefined);
});
});
if (!result) {
// Some error occurred while trying to get the data
return "failed";
} }
if (result.error) {
const results = {}; logger.info(
`HTTP challenge test failed for domain ${domain} because error was returned: ${result.error.msg}`,
for (const domain of domains) { );
results[domain] = await performTestForDomain(domain); return `other:${result.error.msg}`;
} }
if (
// Remove the test challenge file `${result.responsecode}` === "200" &&
fs.unlinkSync(testChallengeFile); result.htmlresponse === "Success"
) {
return results; // Server exists and has responded with the correct data
return "ok";
}
if (`${result.responsecode}` === "200") {
// Server exists but has responded with wrong data
logger.info(
`HTTP challenge test failed for domain ${domain} because of invalid returned data:`,
result.htmlresponse,
);
return "wrong-data";
}
if (`${result.responsecode}` === "404") {
// Server exists but responded with a 404
logger.info(
`HTTP challenge test failed for domain ${domain} because code 404 was returned`,
);
return "404";
}
if (
`${result.responsecode}` === "0" ||
(typeof result.reason === "string" &&
result.reason.toLowerCase() === "host unavailable")
) {
// Server does not exist at domain
logger.info(
`HTTP challenge test failed for domain ${domain} the host was not found`,
);
return "no-host";
}
// Other errors
logger.info(
`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`,
);
return `other:${result.responsecode}`;
}, },
getAdditionalCertbotArgs: (certificate_id, dns_provider) => { getAdditionalCertbotArgs: (certificate_id, dns_provider) => {

View File

@@ -1,5 +1,5 @@
import batchflow from "batchflow"; import batchflow from "batchflow";
import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" };
import { certbot as logger } from "../logger.js"; import { certbot as logger } from "../logger.js";
import errs from "./error.js"; import errs from "./error.js";
import utils from "./utils.js"; import utils from "./utils.js";
@@ -8,7 +8,7 @@ const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0
/** /**
* Installs a cerbot plugin given the key for the object from * Installs a cerbot plugin given the key for the object from
* ../global/certbot-dns-plugins.json * ../certbot/dns-plugins.json
* *
* @param {string} pluginKey * @param {string} pluginKey
* @returns {Object} * @returns {Object}

View File

@@ -24,16 +24,21 @@ const apiValidator = async (schema, payload /*, description*/) => {
throw new errs.ValidationError("Payload is undefined"); throw new errs.ValidationError("Payload is undefined");
} }
const validate = ajv.compile(schema); const validate = ajv.compile(schema);
const valid = validate(payload); const valid = validate(payload);
if (valid && !validate.errors) { if (valid && !validate.errors) {
return payload; return payload;
} }
const message = ajv.errorsText(validate.errors); const message = ajv.errorsText(validate.errors);
const err = new errs.ValidationError(message); const err = new errs.ValidationError(message);
err.debug = [validate.errors, payload]; err.debug = {validationErrors: validate.errors, payload};
throw err; throw err;
}; };

View File

@@ -26,19 +26,19 @@
"knex": "2.4.2", "knex": "2.4.2",
"liquidjs": "10.6.1", "liquidjs": "10.6.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.30.1",
"mysql2": "^3.11.1", "mysql2": "^3.15.3",
"node-rsa": "^1.0.8", "node-rsa": "^1.1.1",
"objection": "3.0.1", "objection": "3.0.1",
"path": "^0.12.7", "path": "^0.12.7",
"pg": "^8.13.1", "pg": "^8.16.3",
"signale": "1.4.0", "signale": "1.4.0",
"sqlite3": "5.1.6", "sqlite3": "^5.1.7",
"temp-write": "^4.0.0" "temp-write": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@apidevtools/swagger-parser": "^10.1.0", "@apidevtools/swagger-parser": "^10.1.0",
"@biomejs/biome": "^2.2.4", "@biomejs/biome": "^2.3.0",
"chalk": "4.1.2", "chalk": "4.1.2",
"nodemon": "^2.0.2" "nodemon": "^2.0.2"
}, },

View File

@@ -1,5 +1,5 @@
import express from "express"; import express from "express";
import dnsPlugins from "../../global/certbot-dns-plugins.json" with { type: "json" }; import dnsPlugins from "../../certbot/dns-plugins.json" with { type: "json" };
import internalCertificate from "../../internal/certificate.js"; import internalCertificate from "../../internal/certificate.js";
import errs from "../../lib/error.js"; import errs from "../../lib/error.js";
import jwtdecode from "../../lib/express/jwt-decode.js"; import jwtdecode from "../../lib/express/jwt-decode.js";
@@ -44,11 +44,18 @@ router
}, },
}, },
{ {
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, expand:
typeof req.query.expand === "string"
? req.query.expand.split(",")
: null,
query: typeof req.query.query === "string" ? req.query.query : null, query: typeof req.query.query === "string" ? req.query.query : null,
}, },
); );
const rows = await internalCertificate.getAll(res.locals.access, data.expand, data.query); const rows = await internalCertificate.getAll(
res.locals.access,
data.expand,
data.query,
);
res.status(200).send(rows); res.status(200).send(rows);
} catch (err) { } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
@@ -63,9 +70,15 @@ router
*/ */
.post(async (req, res, next) => { .post(async (req, res, next) => {
try { try {
const payload = await apiValidator(getValidationSchema("/nginx/certificates", "post"), req.body); const payload = await apiValidator(
getValidationSchema("/nginx/certificates", "post"),
req.body,
);
req.setTimeout(900000); // 15 minutes timeout req.setTimeout(900000); // 15 minutes timeout
const result = await internalCertificate.create(res.locals.access, payload); const result = await internalCertificate.create(
res.locals.access,
payload,
);
res.status(201).send(result); res.status(201).send(result);
} catch (err) { } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
@@ -120,20 +133,21 @@ router
.all(jwtdecode()) .all(jwtdecode())
/** /**
* GET /api/nginx/certificates/test-http * POST /api/nginx/certificates/test-http
* *
* Test HTTP challenge for domains * Test HTTP challenge for domains
*/ */
.get(async (req, res, next) => { .post(async (req, res, next) => {
if (req.query.domains === undefined) {
next(new errs.ValidationError("Domains are required as query parameters"));
return;
}
try { try {
const payload = await apiValidator(
getValidationSchema("/nginx/certificates/test-http", "post"),
req.body,
);
req.setTimeout(60000); // 1 minute timeout
const result = await internalCertificate.testHttpsChallenge( const result = await internalCertificate.testHttpsChallenge(
res.locals.access, res.locals.access,
JSON.parse(req.query.domains), payload,
); );
res.status(200).send(result); res.status(200).send(result);
} catch (err) { } catch (err) {
@@ -142,7 +156,6 @@ router
} }
}); });
/** /**
* Validate Certs before saving * Validate Certs before saving
* *
@@ -211,7 +224,10 @@ router
}, },
{ {
certificate_id: req.params.certificate_id, certificate_id: req.params.certificate_id,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, expand:
typeof req.query.expand === "string"
? req.query.expand.split(",")
: null,
}, },
); );
const row = await internalCertificate.get(res.locals.access, { const row = await internalCertificate.get(res.locals.access, {

View File

@@ -65,7 +65,7 @@ router
const result = await internalProxyHost.create(res.locals.access, payload); const result = await internalProxyHost.create(res.locals.access, payload);
res.status(201).send(result); res.status(201).send(result);
} catch (err) { } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err} ${JSON.stringify(err.debug, null, 2)}`);
next(err); next(err);
} }
}); });

View File

@@ -0,0 +1,23 @@
{
"type": "array",
"description": "DNS Providers list",
"items": {
"type": "object",
"required": ["id", "name", "credentials"],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the DNS provider, matching the python package"
},
"name": {
"type": "string",
"description": "Human-readable name of the DNS provider"
},
"credentials": {
"type": "string",
"description": "Instructions on how to format the credentials for this DNS provider"
}
}
}
}

View File

@@ -0,0 +1,52 @@
{
"operationId": "getDNSProviders",
"summary": "Get DNS Providers for Certificates",
"tags": [
"Certificates"
],
"security": [
{
"BearerAuth": [
"certificates"
]
}
],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": [
{
"id": "vultr",
"name": "Vultr",
"credentials": "dns_vultr_key = YOUR_VULTR_API_KEY"
},
{
"id": "websupport",
"name": "Websupport.sk",
"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>"
},
{
"id": "wedos",
"name": "Wedos",
"credentials": "dns_wedos_user = <wedos_registration>\ndns_wedos_auth = <wapi_password>"
},
{
"id": "zoneedit",
"name": "ZoneEdit",
"credentials": "dns_zoneedit_user = <login-user-id>\ndns_zoneedit_token = <dyn-authentication-token>"
}
]
}
},
"schema": {
"$ref": "../../../../components/dns-providers-list.json"
}
}
}
}
}
}

View File

@@ -7,18 +7,24 @@
"BearerAuth": ["certificates"] "BearerAuth": ["certificates"]
} }
], ],
"parameters": [ "requestBody": {
{ "description": "Test Payload",
"in": "query", "required": true,
"name": "domains", "content": {
"description": "Expansions", "application/json": {
"required": true, "schema": {
"schema": { "type": "object",
"type": "string", "additionalProperties": false,
"example": "[\"test.example.ord\",\"test.example.com\",\"nonexistent.example.com\"]" "required": ["domains"],
"properties": {
"domains": {
"$ref": "../../../../common.json#/properties/domain_names"
}
}
}
} }
} }
], },
"responses": { "responses": {
"200": { "200": {
"description": "200 response", "description": "200 response",

View File

@@ -61,14 +61,19 @@
"$ref": "./paths/nginx/certificates/post.json" "$ref": "./paths/nginx/certificates/post.json"
} }
}, },
"/nginx/certificates/dns-providers": {
"get": {
"$ref": "./paths/nginx/certificates/dns-providers/get.json"
}
},
"/nginx/certificates/validate": { "/nginx/certificates/validate": {
"post": { "post": {
"$ref": "./paths/nginx/certificates/validate/post.json" "$ref": "./paths/nginx/certificates/validate/post.json"
} }
}, },
"/nginx/certificates/test-http": { "/nginx/certificates/test-http": {
"get": { "post": {
"$ref": "./paths/nginx/certificates/test-http/get.json" "$ref": "./paths/nginx/certificates/test-http/post.json"
} }
}, },
"/nginx/certificates/{certID}": { "/nginx/certificates/{certID}": {

View File

@@ -1,7 +1,7 @@
#!/usr/bin/node #!/usr/bin/node
// Usage: // Usage:
// Install all plugins defined in `certbot-dns-plugins.json`: // Install all plugins defined in `../certbot/dns-plugins.json`:
// ./install-certbot-plugins // ./install-certbot-plugins
// Install one or more specific plugins: // Install one or more specific plugins:
// ./install-certbot-plugins route53 cloudflare // ./install-certbot-plugins route53 cloudflare
@@ -10,20 +10,21 @@
// docker exec npm_core /command/s6-setuidgid 1000:1000 bash -c "/app/scripts/install-certbot-plugins" // docker exec npm_core /command/s6-setuidgid 1000:1000 bash -c "/app/scripts/install-certbot-plugins"
// //
import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; import batchflow from "batchflow";
import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" };
import { installPlugin } from "../lib/certbot.js"; import { installPlugin } from "../lib/certbot.js";
import { certbot as logger } from "../logger.js"; import { certbot as logger } from "../logger.js";
import batchflow from "batchflow";
let hasErrors = false; let hasErrors = false;
let failingPlugins = []; const failingPlugins = [];
let pluginKeys = Object.keys(dnsPlugins); let pluginKeys = Object.keys(dnsPlugins);
if (process.argv.length > 2) { if (process.argv.length > 2) {
pluginKeys = process.argv.slice(2); pluginKeys = process.argv.slice(2);
} }
batchflow(pluginKeys).sequential() batchflow(pluginKeys)
.sequential()
.each((i, pluginKey, next) => { .each((i, pluginKey, next) => {
installPlugin(pluginKey) installPlugin(pluginKey)
.then(() => { .then(() => {
@@ -40,10 +41,14 @@ batchflow(pluginKeys).sequential()
}) })
.end(() => { .end(() => {
if (hasErrors) { if (hasErrors) {
logger.error('Some plugins failed to install. Please check the logs above. Failing plugins: ' + '\n - ' + failingPlugins.join('\n - ')); logger.error(
"Some plugins failed to install. Please check the logs above. Failing plugins: " +
"\n - " +
failingPlugins.join("\n - "),
);
process.exit(1); process.exit(1);
} else { } else {
logger.complete('Plugins installed successfully'); logger.complete("Plugins installed successfully");
process.exit(0); process.exit(0);
} }
}); });

View File

@@ -43,59 +43,59 @@
ajv-draft-04 "^1.0.0" ajv-draft-04 "^1.0.0"
call-me-maybe "^1.0.2" call-me-maybe "^1.0.2"
"@biomejs/biome@^2.2.4": "@biomejs/biome@^2.3.0":
version "2.2.4" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.2.4.tgz#184e4b83f89bd0d4151682a5aa3840df37748e17" resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.3.0.tgz#80030b68d94bd0a0761ac2cd22cc4f2c0f23f4f9"
integrity sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg== integrity sha512-shdUY5H3S3tJVUWoVWo5ua+GdPW5lRHf+b0IwZ4OC1o2zOKQECZ6l2KbU6t89FNhtd3Qx5eg5N7/UsQWGQbAFw==
optionalDependencies: optionalDependencies:
"@biomejs/cli-darwin-arm64" "2.2.4" "@biomejs/cli-darwin-arm64" "2.3.0"
"@biomejs/cli-darwin-x64" "2.2.4" "@biomejs/cli-darwin-x64" "2.3.0"
"@biomejs/cli-linux-arm64" "2.2.4" "@biomejs/cli-linux-arm64" "2.3.0"
"@biomejs/cli-linux-arm64-musl" "2.2.4" "@biomejs/cli-linux-arm64-musl" "2.3.0"
"@biomejs/cli-linux-x64" "2.2.4" "@biomejs/cli-linux-x64" "2.3.0"
"@biomejs/cli-linux-x64-musl" "2.2.4" "@biomejs/cli-linux-x64-musl" "2.3.0"
"@biomejs/cli-win32-arm64" "2.2.4" "@biomejs/cli-win32-arm64" "2.3.0"
"@biomejs/cli-win32-x64" "2.2.4" "@biomejs/cli-win32-x64" "2.3.0"
"@biomejs/cli-darwin-arm64@2.2.4": "@biomejs/cli-darwin-arm64@2.3.0":
version "2.2.4" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.4.tgz#9b50620c93501e370b7e6d5a8445f117f9946a0c" resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.0.tgz#78cef4d7415adbf0718c7854e7160e181d916652"
integrity sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA== integrity sha512-3cJVT0Z5pbTkoBmbjmDZTDFYxIkRcrs9sYVJbIBHU8E6qQxgXAaBfSVjjCreG56rfDuQBr43GzwzmaHPcu4vlw==
"@biomejs/cli-darwin-x64@2.2.4": "@biomejs/cli-darwin-x64@2.3.0":
version "2.2.4" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.4.tgz#343620c884fc8141155d114430e80e4eacfddc9e" resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.0.tgz#068baf1f0f748c01658ba9bb511d8d18461d922b"
integrity sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg== integrity sha512-6LIkhglh3UGjuDqJXsK42qCA0XkD1Ke4K/raFOii7QQPbM8Pia7Qj2Hji4XuF2/R78hRmEx7uKJH3t/Y9UahtQ==
"@biomejs/cli-linux-arm64-musl@2.2.4": "@biomejs/cli-linux-arm64-musl@2.3.0":
version "2.2.4" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.4.tgz#cabcdadce2bc88b697f4063374224266c6f8b6e5" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.0.tgz#9a1350184abcea8092957a9519098cac7629705a"
integrity sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ== integrity sha512-nDksoFdwZ2YrE7NiYDhtMhL2UgFn8Kb7Y0bYvnTAakHnqEdb4lKindtBc1f+xg2Snz0JQhJUYO7r9CDBosRU5w==
"@biomejs/cli-linux-arm64@2.2.4": "@biomejs/cli-linux-arm64@2.3.0":
version "2.2.4" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.4.tgz#55620f8f088145e62e1158eb85c568554d0c8673" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.0.tgz#f322daebb32fe0b18f7981c8cdbe84a06852bfee"
integrity sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw== integrity sha512-uhAsbXySX7xsXahegDg5h3CDgfMcRsJvWLFPG0pjkylgBb9lErbK2C0UINW52zhwg0cPISB09lxHPxCau4e2xA==
"@biomejs/cli-linux-x64-musl@2.2.4": "@biomejs/cli-linux-x64-musl@2.3.0":
version "2.2.4" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.4.tgz#6bfaea72505afdbda66a66c998d2d169a8b55f90" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.0.tgz#ce441d5c00eda977b74e4116f9723f2edc579485"
integrity sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg== integrity sha512-+i9UcJwl99uAhtRQDz9jUAh+Xkb097eekxs/D9j4deWDg5/yB/jPWzISe1nBHvlzTXsdUSj0VvB4Go2DSpKIMw==
"@biomejs/cli-linux-x64@2.2.4": "@biomejs/cli-linux-x64@2.3.0":
version "2.2.4" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.4.tgz#8c1ed61dcafb8a5939346c714ec122651f57e1db" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.0.tgz#da7ea904307b3211df62a4b42e5a022f8f583009"
integrity sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ== integrity sha512-uxa8reA2s1VgoH8MhbGlCmMOt3JuSE1vJBifkh1ulaPiuk0SPx8cCdpnm9NWnTe2x/LfWInWx4sZ7muaXTPGGw==
"@biomejs/cli-win32-arm64@2.2.4": "@biomejs/cli-win32-arm64@2.3.0":
version "2.2.4" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.4.tgz#b2528f6c436e753d6083d7779f0662e08786cedb" resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.0.tgz#cdc0f8bbf025fb28c5b03b326128cce363ecffa5"
integrity sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ== integrity sha512-ynjmsJLIKrAjC3CCnKMMhzcnNy8dbQWjKfSU5YA0mIruTxBNMbkAJp+Pr2iV7/hFou+66ZSD/WV8hmLEmhUaXA==
"@biomejs/cli-win32-x64@2.2.4": "@biomejs/cli-win32-x64@2.3.0":
version "2.2.4" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.4.tgz#c8e21413120fe073fa49b78fdd987022941ff66f" resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.0.tgz#10e1de6222e272a1e3e395b3d845ee66cb6febd8"
integrity sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg== integrity sha512-zOCYmCRVkWXc9v8P7OLbLlGGMxQTKMvi+5IC4v7O8DkjLCOHRzRVK/Lno2pGZNo0lzKM60pcQOhH8HVkXMQdFg==
"@gar/promisify@^1.0.1": "@gar/promisify@^1.0.1":
version "1.1.3" version "1.1.3"
@@ -107,7 +107,7 @@
resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796"
integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==
"@mapbox/node-pre-gyp@^1.0.0", "@mapbox/node-pre-gyp@^1.0.11": "@mapbox/node-pre-gyp@^1.0.11":
version "1.0.11" version "1.0.11"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==
@@ -346,6 +346,13 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
bindings@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
dependencies:
file-uri-to-path "1.0.0"
bl@^4.0.3: bl@^4.0.3:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
@@ -512,6 +519,11 @@ chokidar@^3.5.2:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chownr@^2.0.0: chownr@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
@@ -693,6 +705,18 @@ decamelize@^1.2.0:
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
decompress-response@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
dependencies:
mimic-response "^3.1.0"
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
delegates@^1.0.0: delegates@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@@ -766,7 +790,7 @@ encoding@^0.1.12:
dependencies: dependencies:
iconv-lite "^0.6.2" iconv-lite "^0.6.2"
end-of-stream@^1.4.1: end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.5" version "1.4.5"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c"
integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==
@@ -832,6 +856,11 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
expand-template@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
express-fileupload@^1.1.9: express-fileupload@^1.1.9:
version "1.5.2" version "1.5.2"
resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.5.2.tgz#4da70ba6f2ffd4c736eab0776445865a9dbd9bfa" resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.5.2.tgz#4da70ba6f2ffd4c736eab0776445865a9dbd9bfa"
@@ -893,6 +922,11 @@ figures@^2.0.0:
dependencies: dependencies:
escape-string-regexp "^1.0.5" escape-string-regexp "^1.0.5"
file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
fill-range@^7.1.1: fill-range@^7.1.1:
version "7.1.1" version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
@@ -1040,6 +1074,11 @@ getopts@2.3.0:
resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4"
integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==
github-from-package@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
glob-parent@~5.1.2: glob-parent@~5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -1210,6 +1249,11 @@ inherits@2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
ini@~1.3.0:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
interpret@^2.2.0: interpret@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
@@ -1553,6 +1597,11 @@ mime@1.6.0:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
mimic-response@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
minimatch@^3.1.1, minimatch@^3.1.2: minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -1567,6 +1616,11 @@ minimatch@^5.1.0:
dependencies: dependencies:
brace-expansion "^2.0.1" brace-expansion "^2.0.1"
minimist@^1.2.0, minimist@^1.2.3:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
minipass-collect@^1.0.2: minipass-collect@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
@@ -1626,12 +1680,17 @@ minizlib@^2.0.0, minizlib@^2.1.1:
minipass "^3.0.0" minipass "^3.0.0"
yallist "^4.0.0" yallist "^4.0.0"
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@^1.0.3, mkdirp@^1.0.4: mkdirp@^1.0.3, mkdirp@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moment@^2.29.4: moment@^2.30.1:
version "2.30.1" version "2.30.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
@@ -1651,10 +1710,10 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
mysql2@^3.11.1: mysql2@^3.15.3:
version "3.14.4" version "3.15.3"
resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.14.4.tgz#36e33a8d33820a299fb9e9221486310b1a4c8767" resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.15.3.tgz#f0348d9c7401bb98cb1f45ffc5a773b109f70808"
integrity sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w== integrity sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==
dependencies: dependencies:
aws-ssl-profiles "^1.1.1" aws-ssl-profiles "^1.1.1"
denque "^2.1.0" denque "^2.1.0"
@@ -1673,6 +1732,11 @@ named-placeholders@^1.1.3:
dependencies: dependencies:
lru-cache "^7.14.1" lru-cache "^7.14.1"
napi-build-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
negotiator@0.6.3: negotiator@0.6.3:
version "0.6.3" version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
@@ -1683,16 +1747,23 @@ negotiator@^0.6.2, negotiator@~0.6.4:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
node-addon-api@^4.2.0: node-abi@^3.3.0:
version "4.3.0" version "3.78.0"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.78.0.tgz#fd0ecbd0aa89857b98da06bd3909194abb0821ba"
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== integrity sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==
dependencies:
semver "^7.3.5"
node-addon-api@^5.0.0: node-addon-api@^5.0.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762"
integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==
node-addon-api@^7.0.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
node-fetch@^2.6.7: node-fetch@^2.6.7:
version "2.7.0" version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
@@ -1716,7 +1787,7 @@ node-gyp@8.x:
tar "^6.1.2" tar "^6.1.2"
which "^2.0.2" which "^2.0.2"
node-rsa@^1.0.8: node-rsa@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.1.1.tgz#efd9ad382097782f506153398496f79e4464434d" resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.1.1.tgz#efd9ad382097782f506153398496f79e4464434d"
integrity sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw== integrity sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==
@@ -1801,7 +1872,7 @@ on-headers@~1.1.0:
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65"
integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==
once@^1.3.0, once@^1.4.0: once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
@@ -1940,7 +2011,7 @@ pg-types@2.2.0:
postgres-date "~1.0.4" postgres-date "~1.0.4"
postgres-interval "^1.1.0" postgres-interval "^1.1.0"
pg@^8.13.1: pg@^8.16.3:
version "8.16.3" version "8.16.3"
resolved "https://registry.yarnpkg.com/pg/-/pg-8.16.3.tgz#160741d0b44fdf64680e45374b06d632e86c99fd" resolved "https://registry.yarnpkg.com/pg/-/pg-8.16.3.tgz#160741d0b44fdf64680e45374b06d632e86c99fd"
integrity sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw== integrity sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==
@@ -2000,6 +2071,24 @@ postgres-interval@^1.1.0:
dependencies: dependencies:
xtend "^4.0.0" xtend "^4.0.0"
prebuild-install@^7.1.1:
version "7.1.3"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
dependencies:
detect-libc "^2.0.0"
expand-template "^2.0.3"
github-from-package "0.0.0"
minimist "^1.2.3"
mkdirp-classic "^0.5.3"
napi-build-utils "^2.0.0"
node-abi "^3.3.0"
pump "^3.0.0"
rc "^1.2.7"
simple-get "^4.0.0"
tar-fs "^2.0.0"
tunnel-agent "^0.6.0"
process-nextick-args@~2.0.0: process-nextick-args@~2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@@ -2036,6 +2125,14 @@ pstree.remy@^1.1.8:
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
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==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
qs@6.13.0: qs@6.13.0:
version "6.13.0" version "6.13.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
@@ -2063,6 +2160,16 @@ raw-body@2.5.2:
iconv-lite "0.4.24" iconv-lite "0.4.24"
unpipe "1.0.0" unpipe "1.0.0"
rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
dependencies:
deep-extend "^0.6.0"
ini "~1.3.0"
minimist "^1.2.0"
strip-json-comments "~2.0.1"
readable-stream@^2.0.0, readable-stream@^2.0.5: readable-stream@^2.0.0, readable-stream@^2.0.5:
version "2.3.8" version "2.3.8"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
@@ -2280,6 +2387,20 @@ signale@1.4.0:
figures "^2.0.0" figures "^2.0.0"
pkg-conf "^2.1.0" pkg-conf "^2.1.0"
simple-concat@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
simple-get@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
dependencies:
decompress-response "^6.0.0"
once "^1.3.1"
simple-concat "^1.0.0"
simple-update-notifier@^1.0.7: simple-update-notifier@^1.0.7:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82"
@@ -2314,13 +2435,14 @@ split2@^4.1.0:
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
sqlite3@5.1.6: sqlite3@^5.1.7:
version "5.1.6" version "5.1.7"
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.6.tgz#1d4fbc90fe4fbd51e952e0a90fd8f6c2b9098e97" resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7"
integrity sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw== integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==
dependencies: dependencies:
"@mapbox/node-pre-gyp" "^1.0.0" bindings "^1.5.0"
node-addon-api "^4.2.0" node-addon-api "^7.0.0"
prebuild-install "^7.1.1"
tar "^6.1.11" tar "^6.1.11"
optionalDependencies: optionalDependencies:
node-gyp "8.x" node-gyp "8.x"
@@ -2382,6 +2504,11 @@ strip-bom@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
supports-color@^5.3.0, supports-color@^5.5.0: supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -2401,7 +2528,17 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
tar-stream@^2.2.0: tar-fs@^2.0.0:
version "2.1.4"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.4.tgz#800824dbf4ef06ded9afea4acafe71c67c76b930"
integrity sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.1.4"
tar-stream@^2.1.4, tar-stream@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
@@ -2472,6 +2609,13 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
dependencies:
safe-buffer "^5.0.1"
type-is@~1.6.18: type-is@~1.6.18:
version "1.6.18" version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"

View File

@@ -39,7 +39,6 @@ EXPOSE 80 81 443
COPY backend /app COPY backend /app
COPY frontend/dist /app/frontend COPY frontend/dist /app/frontend
COPY global /app/global
WORKDIR /app WORKDIR /app
RUN yarn install \ RUN yarn install \

View File

@@ -1,6 +1,5 @@
# WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production. # WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production.
services: services:
fullstack: fullstack:
image: npm2dev:core image: npm2dev:core
container_name: npm2dev.core container_name: npm2dev.core
@@ -23,9 +22,9 @@ services:
PGID: 1000 PGID: 1000
FORCE_COLOR: 1 FORCE_COLOR: 1
# specifically for dev: # specifically for dev:
DEBUG: 'true' DEBUG: "true"
DEVELOPMENT: 'true' DEVELOPMENT: "true"
LE_STAGING: 'true' LE_STAGING: "true"
# db: # db:
# DB_MYSQL_HOST: 'db' # DB_MYSQL_HOST: 'db'
# DB_MYSQL_PORT: '3306' # DB_MYSQL_PORT: '3306'
@@ -33,26 +32,25 @@ services:
# DB_MYSQL_PASSWORD: 'npm' # DB_MYSQL_PASSWORD: 'npm'
# DB_MYSQL_NAME: 'npm' # DB_MYSQL_NAME: 'npm'
# db-postgres: # db-postgres:
DB_POSTGRES_HOST: 'db-postgres' DB_POSTGRES_HOST: "db-postgres"
DB_POSTGRES_PORT: '5432' DB_POSTGRES_PORT: "5432"
DB_POSTGRES_USER: 'npm' DB_POSTGRES_USER: "npm"
DB_POSTGRES_PASSWORD: 'npmpass' DB_POSTGRES_PASSWORD: "npmpass"
DB_POSTGRES_NAME: 'npm' DB_POSTGRES_NAME: "npm"
# DB_SQLITE_FILE: "/data/database.sqlite" # DB_SQLITE_FILE: "/data/database.sqlite"
# DISABLE_IPV6: "true" # DISABLE_IPV6: "true"
# Required for DNS Certificate provisioning testing: # Required for DNS Certificate provisioning testing:
LE_SERVER: 'https://ca.internal/acme/acme/directory' LE_SERVER: "https://ca.internal/acme/acme/directory"
REQUESTS_CA_BUNDLE: '/etc/ssl/certs/NginxProxyManager.crt' REQUESTS_CA_BUNDLE: "/etc/ssl/certs/NginxProxyManager.crt"
volumes: volumes:
- npm_data:/data - npm_data:/data
- le_data:/etc/letsencrypt - le_data:/etc/letsencrypt
- './dev/resolv.conf:/etc/resolv.conf:ro' - "./dev/resolv.conf:/etc/resolv.conf:ro"
- ../backend:/app - ../backend:/app
- ../frontend:/app/frontend - ../frontend:/frontend
- ../global:/app/global - "/etc/localtime:/etc/localtime:ro"
- '/etc/localtime:/etc/localtime:ro'
healthcheck: healthcheck:
test: [ "CMD", "/usr/bin/check-health" ] test: ["CMD", "/usr/bin/check-health"]
interval: 10s interval: 10s
timeout: 3s timeout: 3s
depends_on: depends_on:
@@ -72,13 +70,13 @@ services:
- nginx_proxy_manager - nginx_proxy_manager
environment: environment:
TZ: "${TZ:-Australia/Brisbane}" TZ: "${TZ:-Australia/Brisbane}"
MYSQL_ROOT_PASSWORD: 'npm' MYSQL_ROOT_PASSWORD: "npm"
MYSQL_DATABASE: 'npm' MYSQL_DATABASE: "npm"
MYSQL_USER: 'npm' MYSQL_USER: "npm"
MYSQL_PASSWORD: 'npm' MYSQL_PASSWORD: "npm"
volumes: volumes:
- db_data:/var/lib/mysql - db_data:/var/lib/mysql
- '/etc/localtime:/etc/localtime:ro' - "/etc/localtime:/etc/localtime:ro"
db-postgres: db-postgres:
image: postgres:latest image: postgres:latest
@@ -86,9 +84,9 @@ services:
networks: networks:
- nginx_proxy_manager - nginx_proxy_manager
environment: environment:
POSTGRES_USER: 'npm' POSTGRES_USER: "npm"
POSTGRES_PASSWORD: 'npmpass' POSTGRES_PASSWORD: "npmpass"
POSTGRES_DB: 'npm' POSTGRES_DB: "npm"
volumes: volumes:
- psql_data:/var/lib/postgresql/data - psql_data:/var/lib/postgresql/data
- ./ci/postgres:/docker-entrypoint-initdb.d - ./ci/postgres:/docker-entrypoint-initdb.d
@@ -97,8 +95,8 @@ services:
image: jc21/testca image: jc21/testca
container_name: npm2dev.stepca container_name: npm2dev.stepca
volumes: volumes:
- './dev/resolv.conf:/etc/resolv.conf:ro' - "./dev/resolv.conf:/etc/resolv.conf:ro"
- '/etc/localtime:/etc/localtime:ro' - "/etc/localtime:/etc/localtime:ro"
networks: networks:
nginx_proxy_manager: nginx_proxy_manager:
aliases: aliases:
@@ -119,7 +117,7 @@ services:
- 3082:80 - 3082:80
environment: environment:
URL: "http://npm:81/api/schema" URL: "http://npm:81/api/schema"
PORT: '80' PORT: "80"
depends_on: depends_on:
- fullstack - fullstack
@@ -127,9 +125,9 @@ services:
image: ubuntu/squid image: ubuntu/squid
container_name: npm2dev.squid container_name: npm2dev.squid
volumes: volumes:
- './dev/squid.conf:/etc/squid/squid.conf:ro' - "./dev/squid.conf:/etc/squid/squid.conf:ro"
- './dev/resolv.conf:/etc/resolv.conf:ro' - "./dev/resolv.conf:/etc/resolv.conf:ro"
- '/etc/localtime:/etc/localtime:ro' - "/etc/localtime:/etc/localtime:ro"
networks: networks:
- nginx_proxy_manager - nginx_proxy_manager
ports: ports:
@@ -139,18 +137,18 @@ services:
image: pschiffe/pdns-mysql:4.8 image: pschiffe/pdns-mysql:4.8
container_name: npm2dev.pdns container_name: npm2dev.pdns
volumes: volumes:
- '/etc/localtime:/etc/localtime:ro' - "/etc/localtime:/etc/localtime:ro"
environment: environment:
PDNS_master: 'yes' PDNS_master: "yes"
PDNS_api: 'yes' PDNS_api: "yes"
PDNS_api_key: 'npm' PDNS_api_key: "npm"
PDNS_webserver: 'yes' PDNS_webserver: "yes"
PDNS_webserver_address: '0.0.0.0' PDNS_webserver_address: "0.0.0.0"
PDNS_webserver_password: 'npm' PDNS_webserver_password: "npm"
PDNS_webserver-allow-from: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8' PDNS_webserver-allow-from: "127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8"
PDNS_version_string: 'anonymous' PDNS_version_string: "anonymous"
PDNS_default_ttl: 1500 PDNS_default_ttl: 1500
PDNS_allow_axfr_ips: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8' PDNS_allow_axfr_ips: "127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8"
PDNS_gmysql_host: pdns-db PDNS_gmysql_host: pdns-db
PDNS_gmysql_port: 3306 PDNS_gmysql_port: 3306
PDNS_gmysql_user: pdns PDNS_gmysql_user: pdns
@@ -168,14 +166,14 @@ services:
image: mariadb image: mariadb
container_name: npm2dev.pdns-db container_name: npm2dev.pdns-db
environment: environment:
MYSQL_ROOT_PASSWORD: 'pdns' MYSQL_ROOT_PASSWORD: "pdns"
MYSQL_DATABASE: 'pdns' MYSQL_DATABASE: "pdns"
MYSQL_USER: 'pdns' MYSQL_USER: "pdns"
MYSQL_PASSWORD: 'pdns' MYSQL_PASSWORD: "pdns"
volumes: volumes:
- 'pdns_mysql:/var/lib/mysql' - "pdns_mysql:/var/lib/mysql"
- '/etc/localtime:/etc/localtime:ro' - "/etc/localtime:/etc/localtime:ro"
- './dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro' - "./dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro"
networks: networks:
- nginx_proxy_manager - nginx_proxy_manager
@@ -186,25 +184,25 @@ services:
context: ../ context: ../
dockerfile: test/cypress/Dockerfile dockerfile: test/cypress/Dockerfile
environment: environment:
HTTP_PROXY: 'squid:3128' HTTP_PROXY: "squid:3128"
HTTPS_PROXY: 'squid:3128' HTTPS_PROXY: "squid:3128"
volumes: volumes:
- '../test/results:/results' - "../test/results:/results"
- './dev/resolv.conf:/etc/resolv.conf:ro' - "./dev/resolv.conf:/etc/resolv.conf:ro"
- '/etc/localtime:/etc/localtime:ro' - "/etc/localtime:/etc/localtime:ro"
command: cypress run --browser chrome --config-file=cypress/config/ci.js command: cypress run --browser chrome --config-file=cypress/config/ci.js
networks: networks:
- nginx_proxy_manager - nginx_proxy_manager
authentik-redis: authentik-redis:
image: 'redis:alpine' image: "redis:alpine"
container_name: npm2dev.authentik-redis container_name: npm2dev.authentik-redis
command: --save 60 1 --loglevel warning command: --save 60 1 --loglevel warning
networks: networks:
- nginx_proxy_manager - nginx_proxy_manager
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: [ 'CMD-SHELL', 'redis-cli ping | grep PONG' ] test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s start_period: 20s
interval: 30s interval: 30s
retries: 5 retries: 5
@@ -246,9 +244,9 @@ services:
networks: networks:
- nginx_proxy_manager - nginx_proxy_manager
environment: environment:
AUTHENTIK_HOST: 'http://authentik:9000' AUTHENTIK_HOST: "http://authentik:9000"
AUTHENTIK_INSECURE: 'true' AUTHENTIK_INSECURE: "true"
AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp' AUTHENTIK_TOKEN: "wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp"
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
- authentik - authentik

View File

@@ -7,11 +7,11 @@ set -e
if [ "$DEVELOPMENT" = 'true' ]; then if [ "$DEVELOPMENT" = 'true' ]; then
. /usr/bin/common.sh . /usr/bin/common.sh
cd /app/frontend || exit 1 cd /frontend || exit 1
HOME=$NPMHOME HOME=$NPMHOME
export HOME export HOME
mkdir -p /app/frontend/dist mkdir -p /frontend/dist
chown -R "$PUID:$PGID" /app/frontend/dist chown -R "$PUID:$PGID" /frontend/dist
log_info 'Starting frontend ...' log_info 'Starting frontend ...'
s6-setuidgid "$PUID:$PGID" yarn install s6-setuidgid "$PUID:$PGID" yarn install

View File

@@ -18,7 +18,7 @@
"dependencies": { "dependencies": {
"@tabler/core": "^1.4.0", "@tabler/core": "^1.4.0",
"@tabler/icons-react": "^3.35.0", "@tabler/icons-react": "^3.35.0",
"@tanstack/react-query": "^5.90.3", "@tanstack/react-query": "^5.90.5",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"@uiw/react-textarea-code-editor": "^3.1.1", "@uiw/react-textarea-code-editor": "^3.1.1",
"classnames": "^2.5.1", "classnames": "^2.5.1",
@@ -39,7 +39,7 @@
"rooks": "^9.3.0" "rooks": "^9.3.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.2.6", "@biomejs/biome": "^2.3.0",
"@formatjs/cli": "^6.7.4", "@formatjs/cli": "^6.7.4",
"@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-query-devtools": "^5.90.2",
"@testing-library/dom": "^10.4.1", "@testing-library/dom": "^10.4.1",
@@ -50,16 +50,16 @@
"@types/react": "^19.2.2", "@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2", "@types/react-dom": "^19.2.2",
"@types/react-table": "^7.7.20", "@types/react-table": "^7.7.20",
"@vitejs/plugin-react": "^5.0.4", "@vitejs/plugin-react": "^5.1.0",
"happy-dom": "^20.0.2", "happy-dom": "^20.0.8",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",
"sass": "^1.93.2", "sass": "^1.93.2",
"tmp": "^0.2.5", "tmp": "^0.2.5",
"typescript": "5.9.3", "typescript": "5.9.3",
"vite": "^7.1.10", "vite": "^7.1.12",
"vite-plugin-checker": "^0.11.0", "vite-plugin-checker": "^0.11.0",
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4" "vitest": "^4.0.3"
} }
} }

View File

@@ -4,7 +4,6 @@ import type { Certificate } from "./models";
export async function createCertificate(item: Certificate): Promise<Certificate> { export async function createCertificate(item: Certificate): Promise<Certificate> {
return await api.post({ return await api.post({
url: "/nginx/certificates", url: "/nginx/certificates",
// todo: only use whitelist of fields for this data
data: item, data: item,
}); });
} }

View File

@@ -1,10 +1,10 @@
import * as api from "./base"; import * as api from "./base";
export async function testHttpCertificate(domains: string[]): Promise<Record<string, string>> { export async function testHttpCertificate(domains: string[]): Promise<Record<string, string>> {
return await api.get({ return await api.post({
url: "/nginx/certificates/test-http", url: "/nginx/certificates/test-http",
params: { data: {
domains: domains.join(","), domains,
}, },
}); });
} }

View File

@@ -58,7 +58,7 @@ export function AccessField({ name = "accessListId", label = "access-list", id =
options?.unshift({ options?.unshift({
value: 0, value: 0,
label: intl.formatMessage({ id: "access-list.public" }), label: intl.formatMessage({ id: "access-list.public" }),
subLabel: "No basic auth required", subLabel: intl.formatMessage({ id: "access-list.public.subtitle" }),
icon: <IconLockOpen2 size={14} className="text-red" />, icon: <IconLockOpen2 size={14} className="text-red" />,
}); });

View File

@@ -1,8 +1,10 @@
import { IconAlertTriangle } from "@tabler/icons-react";
import { Field, useFormikContext } from "formik"; import { Field, useFormikContext } from "formik";
import { useState } from "react"; import { useState } from "react";
import Select, { type ActionMeta } from "react-select"; import Select, { type ActionMeta } from "react-select";
import type { DNSProvider } from "src/api/backend"; import type { DNSProvider } from "src/api/backend";
import { useDnsProviders } from "src/hooks"; import { useDnsProviders } from "src/hooks";
import { T } from "src/locale";
import styles from "./DNSProviderFields.module.css"; import styles from "./DNSProviderFields.module.css";
interface DNSProviderOption { interface DNSProviderOption {
@@ -10,7 +12,11 @@ interface DNSProviderOption {
readonly label: string; readonly label: string;
readonly credentials: string; readonly credentials: string;
} }
export function DNSProviderFields() {
interface Props {
showBoundaryBox?: boolean;
}
export function DNSProviderFields({ showBoundaryBox = false }: Props) {
const { values, setFieldValue } = useFormikContext(); const { values, setFieldValue } = useFormikContext();
const { data: dnsProviders, isLoading } = useDnsProviders(); const { data: dnsProviders, isLoading } = useDnsProviders();
const [dnsProviderId, setDnsProviderId] = useState<string | null>(null); const [dnsProviderId, setDnsProviderId] = useState<string | null>(null);
@@ -31,17 +37,17 @@ export function DNSProviderFields() {
})) || []; })) || [];
return ( return (
<div className={styles.dnsChallengeWarning}> <div className={showBoundaryBox ? styles.dnsChallengeWarning : undefined}>
<p className="text-info"> <p className="text-warning">
This section requires some knowledge about Certbot and DNS plugins. Please consult the respective <IconAlertTriangle size={16} className="me-1" />
plugins documentation. <T id="certificates.dns.warning" />
</p> </p>
<Field name="meta.dnsProvider"> <Field name="meta.dnsProvider">
{({ field }: any) => ( {({ field }: any) => (
<div className="row"> <div className="row">
<label htmlFor="dnsProvider" className="form-label"> <label htmlFor="dnsProvider" className="form-label">
DNS Provider <T id="certificates.dns.provider" />
</label> </label>
<Select <Select
className="react-select-container" className="react-select-container"
@@ -66,7 +72,7 @@ export function DNSProviderFields() {
{({ field }: any) => ( {({ field }: any) => (
<div className="mt-3"> <div className="mt-3">
<label htmlFor="dnsProviderCredentials" className="form-label"> <label htmlFor="dnsProviderCredentials" className="form-label">
Credentials File Content <T id="certificates.dns.credentials" />
</label> </label>
<textarea <textarea
id="dnsProviderCredentials" id="dnsProviderCredentials"
@@ -78,13 +84,12 @@ export function DNSProviderFields() {
/> />
<div> <div>
<small className="text-muted"> <small className="text-muted">
This plugin requires a configuration file containing an API token or other <T id="certificates.dns.credentials-note" />
credentials to your provider
</small> </small>
</div> </div>
<div> <div>
<small className="text-danger"> <small className="text-danger">
This data will be stored as plaintext in the database and in a file! <T id="certificates.dns.credentials-warning" />
</small> </small>
</div> </div>
</div> </div>
@@ -94,20 +99,18 @@ export function DNSProviderFields() {
{({ field }: any) => ( {({ field }: any) => (
<div className="mt-3"> <div className="mt-3">
<label htmlFor="propagationSeconds" className="form-label"> <label htmlFor="propagationSeconds" className="form-label">
Propagation Seconds <T id="certificates.dns.propagation-seconds" />
</label> </label>
<input <input
id="propagationSeconds" id="propagationSeconds"
type="number" type="number"
x
className="form-control" className="form-control"
min={0} min={0}
max={600} max={600}
{...field} {...field}
/> />
<small className="text-muted"> <small className="text-muted">
Leave empty to use the plugins default value. Number of seconds to wait for DNS <T id="certificates.dns.propagation-seconds-note" />
propagation.
</small> </small>
</div> </div>
)} )}

View File

@@ -18,14 +18,16 @@ interface Props {
dnsProviderWildcardSupported?: boolean; dnsProviderWildcardSupported?: boolean;
name?: string; name?: string;
label?: string; label?: string;
onChange?: (domains: string[]) => void;
} }
export function DomainNamesField({ export function DomainNamesField({
name = "domainNames", name = "domainNames",
label = "domain-names", label = "domain-names",
id = "domainNames", id = "domainNames",
maxDomains, maxDomains,
isWildcardPermitted = true, isWildcardPermitted = false,
dnsProviderWildcardSupported = true, dnsProviderWildcardSupported = false,
onChange,
}: Props) { }: Props) {
const { setFieldValue } = useFormikContext(); const { setFieldValue } = useFormikContext();
@@ -34,6 +36,7 @@ export function DomainNamesField({
return i.value; return i.value;
}); });
setFieldValue(name, doms); setFieldValue(name, doms);
onChange?.(doms);
}; };
const helperTexts: ReactNode[] = []; const helperTexts: ReactNode[] = [];

View File

@@ -11,7 +11,7 @@ interface Props {
initialValues: ProxyLocation[]; initialValues: ProxyLocation[];
name?: string; name?: string;
} }
export function LocationsFields({ initialValues, name = "items" }: Props) { export function LocationsFields({ initialValues, name = "locations" }: Props) {
const [values, setValues] = useState<ProxyLocation[]>(initialValues || []); const [values, setValues] = useState<ProxyLocation[]>(initialValues || []);
const { setFieldValue } = useFormikContext(); const { setFieldValue } = useFormikContext();
const [advVisible, setAdvVisible] = useState<number[]>([]); const [advVisible, setAdvVisible] = useState<number[]>([]);

View File

@@ -30,6 +30,7 @@ export function NginxConfigField({
fontFamily: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace", fontFamily: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
borderRadius: "0.3rem", borderRadius: "0.3rem",
minHeight: "200px", minHeight: "200px",
backgroundColor: "var(--tblr-bg-surface-dark)",
}} }}
{...field} {...field}
/> />

View File

@@ -3,7 +3,7 @@ import { Field, useFormikContext } from "formik";
import Select, { type ActionMeta, components, type OptionProps } from "react-select"; import Select, { type ActionMeta, components, type OptionProps } from "react-select";
import type { Certificate } from "src/api/backend"; import type { Certificate } from "src/api/backend";
import { useCertificates } from "src/hooks"; import { useCertificates } from "src/hooks";
import { DateTimeFormat, T } from "src/locale"; import { DateTimeFormat, intl, T } from "src/locale";
interface CertOption { interface CertOption {
readonly value: number | "new"; readonly value: number | "new";
@@ -75,9 +75,7 @@ export function SSLCertificateField({
data?.map((cert: Certificate) => ({ data?.map((cert: Certificate) => ({
value: cert.id, value: cert.id,
label: cert.niceName, label: cert.niceName,
subLabel: `${cert.provider === "letsencrypt" ? "Let's Encrypt" : cert.provider} &mdash; Expires: ${ subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} &mdash; ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A" })}`,
cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A"
}`,
icon: <IconShield size={14} className="text-pink" />, icon: <IconShield size={14} className="text-pink" />,
})) || []; })) || [];
@@ -85,8 +83,8 @@ export function SSLCertificateField({
if (allowNew) { if (allowNew) {
options?.unshift({ options?.unshift({
value: "new", value: "new",
label: "Request a new Certificate", label: intl.formatMessage({ id: "certificates.request.title" }),
subLabel: "with Let's Encrypt", subLabel: intl.formatMessage({ id: "certificates.request.subtitle" }),
icon: <IconShield size={14} className="text-lime" />, icon: <IconShield size={14} className="text-lime" />,
}); });
} }
@@ -95,8 +93,10 @@ export function SSLCertificateField({
if (!required) { if (!required) {
options?.unshift({ options?.unshift({
value: 0, value: 0,
label: "None", label: intl.formatMessage({ id: "certificate.none.title" }),
subLabel: forHttp ? "This host will not use HTTPS" : "No certificate assigned", subLabel: forHttp
? intl.formatMessage({ id: "certificate.none.subtitle.for-http" })
: intl.formatMessage({ id: "certificate.none.subtitle" }),
icon: <IconShield size={14} className="text-red" />, icon: <IconShield size={14} className="text-red" />,
}); });
} }

View File

@@ -136,8 +136,8 @@ export function SSLOptionsFields({ forHttp = true, forceDNSForNew, requireDomain
</label> </label>
)} )}
</Field> </Field>
{requireDomainNames ? <DomainNamesField /> : null} {requireDomainNames ? <DomainNamesField isWildcardPermitted dnsProviderWildcardSupported /> : null}
{dnsChallenge ? <DNSProviderFields /> : null} {dnsChallenge ? <DNSProviderFields showBoundaryBox /> : null}
</> </>
) : null} ) : null}
</div> </div>

View File

@@ -64,7 +64,7 @@ export function EventFormatter({ row }: Props) {
<div className="flex-fill"> <div className="flex-fill">
<div className="font-weight-medium"> <div className="font-weight-medium">
{getIcon(row)} {getIcon(row)}
<T id={`event.${row.action}-${row.objectType}`} /> <T id={`object.event.${row.action}`} tData={{ object: row.objectType }} />
&mdash; <span className="badge">{getEventValue(row)}</span> &mdash; <span className="badge">{getEventValue(row)}</span>
</div> </div>
<div className="text-secondary mt-1">{DateTimeFormat(row.createdOn)}</div> <div className="text-secondary mt-1">{DateTimeFormat(row.createdOn)}</div>

View File

@@ -6,6 +6,7 @@
"access-list.help.rules-order": "Note that the allow and deny directives will be applied in the order they are defined.", "access-list.help.rules-order": "Note that the allow and deny directives will be applied in the order they are defined.",
"access-list.pass-auth": "Pass Auth to Upstream", "access-list.pass-auth": "Pass Auth to Upstream",
"access-list.public": "Publicly Accessible", "access-list.public": "Publicly Accessible",
"access-list.public.subtitle": "No basic auth required",
"access-list.satisfy-any": "Satisfy Any", "access-list.satisfy-any": "Satisfy Any",
"access-list.subtitle": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Rule} other {Rules}} - Created: {date}", "access-list.subtitle": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Rule} other {Rules}} - Created: {date}",
"access-lists": "Access Lists", "access-lists": "Access Lists",
@@ -21,8 +22,28 @@
"auditlogs": "Audit Logs", "auditlogs": "Audit Logs",
"cancel": "Cancel", "cancel": "Cancel",
"certificate": "Certificate", "certificate": "Certificate",
"certificate.none.subtitle": "No certificate assigned",
"certificate.none.subtitle.for-http": "This host will not use HTTPS",
"certificate.none.title": "None",
"certificates": "Certificates", "certificates": "Certificates",
"certificates.custom": "Custom Certificate", "certificates.custom": "Custom Certificate",
"certificates.dns.credentials": "Credentials File Content",
"certificates.dns.credentials-note": "This plugin requires a configuration file containing an API token or other credentials for your provider",
"certificates.dns.credentials-warning": "This data will be stored as plaintext in the database and in a file!",
"certificates.dns.propagation-seconds": "Propagation Seconds",
"certificates.dns.propagation-seconds-note": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.",
"certificates.dns.provider": "DNS Provider",
"certificates.dns.warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.",
"certificates.http.reachability-404": "There is a server found at this domain but it does not seem to be Nginx Proxy Manager. Please make sure your domain points to the IP where your NPM instance is running.",
"certificates.http.reachability-failed-to-check": "Failed to check the reachability due to a communication error with site24x7.com.",
"certificates.http.reachability-not-resolved": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router.",
"certificates.http.reachability-ok": "Your server is reachable and creating certificates should be possible.",
"certificates.http.reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
"certificates.http.reachability-wrong-data": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
"certificates.http.test-results": "Test Results",
"certificates.http.warning": "These domains must be already configured to point to this installation.",
"certificates.request.subtitle": "with Let's Encrypt",
"certificates.request.title": "Request a new Certificate",
"column.access": "Access", "column.access": "Access",
"column.authorization": "Authorization", "column.authorization": "Authorization",
"column.authorizations": "Authorizations", "column.authorizations": "Authorizations",
@@ -74,22 +95,7 @@
"error.max-domains": "Too many domains, max is {max}", "error.max-domains": "Too many domains, max is {max}",
"error.passwords-must-match": "Passwords must match", "error.passwords-must-match": "Passwords must match",
"error.required": "This is required", "error.required": "This is required",
"event.created-access-list": "Created Access List", "expires.on": "Expires: {date}",
"event.created-dead-host": "Created 404 Host",
"event.created-redirection-host": "Created Redirection Host",
"event.created-stream": "Created Stream",
"event.created-user": "Created User",
"event.deleted-dead-host": "Deleted 404 Host",
"event.deleted-stream": "Deleted Stream",
"event.deleted-user": "Deleted User",
"event.disabled-dead-host": "Disabled 404 Host",
"event.disabled-redirection-host": "Disabled Redirection Host",
"event.disabled-stream": "Disabled Stream",
"event.enabled-dead-host": "Enabled 404 Host",
"event.enabled-redirection-host": "Enabled Redirection Host",
"event.enabled-stream": "Enabled Stream",
"event.updated-redirection-host": "Updated Redirection Host",
"event.updated-user": "Updated User",
"footer.github-fork": "Fork me on Github", "footer.github-fork": "Fork me on Github",
"host.flags.block-exploits": "Block Common Exploits", "host.flags.block-exploits": "Block Common Exploits",
"host.flags.cache-assets": "Cache Assets", "host.flags.cache-assets": "Cache Assets",
@@ -101,6 +107,8 @@
"hosts": "Hosts", "hosts": "Hosts",
"http-only": "HTTP Only", "http-only": "HTTP Only",
"lets-encrypt": "Let's Encrypt", "lets-encrypt": "Let's Encrypt",
"lets-encrypt-via-dns": "Let's Encrypt via DNS",
"lets-encrypt-via-http": "Let's Encrypt via HTTP",
"loading": "Loading…", "loading": "Loading…",
"login.title": "Login to your account", "login.title": "Login to your account",
"nginx-config.label": "Custom Nginx Configuration", "nginx-config.label": "Custom Nginx Configuration",
@@ -121,6 +129,11 @@
"object.delete.content": "Are you sure you want to delete this {object}?", "object.delete.content": "Are you sure you want to delete this {object}?",
"object.edit": "Edit {object}", "object.edit": "Edit {object}",
"object.empty": "There are no {objects}", "object.empty": "There are no {objects}",
"object.event.created": "Created {object}",
"object.event.deleted": "Deleted {object}",
"object.event.disabled": "Disabled {object}",
"object.event.enabled": "Enabled {object}",
"object.event.updated": "Updated {object}",
"offline": "Offline", "offline": "Offline",
"online": "Online", "online": "Online",
"options": "Options", "options": "Options",
@@ -158,6 +171,7 @@
"streams.count": "{count} {count, plural, one {Stream} other {Streams}}", "streams.count": "{count} {count, plural, one {Stream} other {Streams}}",
"streams.tcp": "TCP", "streams.tcp": "TCP",
"streams.udp": "UDP", "streams.udp": "UDP",
"test": "Test",
"user": "User", "user": "User",
"user.change-password": "Change Password", "user.change-password": "Change Password",
"user.confirm-password": "Confirm Password", "user.confirm-password": "Confirm Password",

View File

@@ -20,6 +20,9 @@
"access-list.public": { "access-list.public": {
"defaultMessage": "Publicly Accessible" "defaultMessage": "Publicly Accessible"
}, },
"access-list.public.subtitle": {
"defaultMessage": "No basic auth required"
},
"access-list.satisfy-any": { "access-list.satisfy-any": {
"defaultMessage": "Satisfy Any" "defaultMessage": "Satisfy Any"
}, },
@@ -65,12 +68,72 @@
"certificate": { "certificate": {
"defaultMessage": "Certificate" "defaultMessage": "Certificate"
}, },
"certificate.none.subtitle": {
"defaultMessage": "No certificate assigned"
},
"certificate.none.subtitle.for-http": {
"defaultMessage": "This host will not use HTTPS"
},
"certificate.none.title": {
"defaultMessage": "None"
},
"certificates": { "certificates": {
"defaultMessage": "Certificates" "defaultMessage": "Certificates"
}, },
"certificates.custom": { "certificates.custom": {
"defaultMessage": "Custom Certificate" "defaultMessage": "Custom Certificate"
}, },
"certificates.dns.credentials": {
"defaultMessage": "Credentials File Content"
},
"certificates.dns.credentials-note": {
"defaultMessage": "This plugin requires a configuration file containing an API token or other credentials for your provider"
},
"certificates.dns.credentials-warning": {
"defaultMessage": "This data will be stored as plaintext in the database and in a file!"
},
"certificates.dns.propagation-seconds": {
"defaultMessage": "Propagation Seconds"
},
"certificates.dns.propagation-seconds-note": {
"defaultMessage": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation."
},
"certificates.dns.provider": {
"defaultMessage": "DNS Provider"
},
"certificates.dns.warning": {
"defaultMessage": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation."
},
"certificates.http.reachability-404": {
"defaultMessage": "There is a server found at this domain but it does not seem to be Nginx Proxy Manager. Please make sure your domain points to the IP where your NPM instance is running."
},
"certificates.http.reachability-failed-to-check": {
"defaultMessage": "Failed to check the reachability due to a communication error with site24x7.com."
},
"certificates.http.reachability-not-resolved": {
"defaultMessage": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router."
},
"certificates.http.reachability-ok": {
"defaultMessage": "Your server is reachable and creating certificates should be possible."
},
"certificates.http.reachability-other": {
"defaultMessage": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running."
},
"certificates.http.reachability-wrong-data": {
"defaultMessage": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running."
},
"certificates.http.test-results": {
"defaultMessage": "Test Results"
},
"certificates.http.warning": {
"defaultMessage": "These domains must be already configured to point to this installation."
},
"certificates.request.subtitle": {
"defaultMessage": "with Let's Encrypt"
},
"certificates.request.title": {
"defaultMessage": "Request a new Certificate"
},
"column.access": { "column.access": {
"defaultMessage": "Access" "defaultMessage": "Access"
}, },
@@ -224,53 +287,8 @@
"error.required": { "error.required": {
"defaultMessage": "This is required" "defaultMessage": "This is required"
}, },
"event.created-access-list": { "expires.on": {
"defaultMessage": "Created Access List" "defaultMessage": "Expires: {date}"
},
"event.created-dead-host": {
"defaultMessage": "Created 404 Host"
},
"event.created-redirection-host": {
"defaultMessage": "Created Redirection Host"
},
"event.created-stream": {
"defaultMessage": "Created Stream"
},
"event.created-user": {
"defaultMessage": "Created User"
},
"event.deleted-dead-host": {
"defaultMessage": "Deleted 404 Host"
},
"event.deleted-stream": {
"defaultMessage": "Deleted Stream"
},
"event.deleted-user": {
"defaultMessage": "Deleted User"
},
"event.disabled-dead-host": {
"defaultMessage": "Disabled 404 Host"
},
"event.disabled-redirection-host": {
"defaultMessage": "Disabled Redirection Host"
},
"event.disabled-stream": {
"defaultMessage": "Disabled Stream"
},
"event.enabled-dead-host": {
"defaultMessage": "Enabled 404 Host"
},
"event.enabled-redirection-host": {
"defaultMessage": "Enabled Redirection Host"
},
"event.enabled-stream": {
"defaultMessage": "Enabled Stream"
},
"event.updated-redirection-host": {
"defaultMessage": "Updated Redirection Host"
},
"event.updated-user": {
"defaultMessage": "Updated User"
}, },
"footer.github-fork": { "footer.github-fork": {
"defaultMessage": "Fork me on Github" "defaultMessage": "Fork me on Github"
@@ -305,6 +323,12 @@
"lets-encrypt": { "lets-encrypt": {
"defaultMessage": "Let's Encrypt" "defaultMessage": "Let's Encrypt"
}, },
"lets-encrypt-via-dns": {
"defaultMessage": "Let's Encrypt via DNS"
},
"lets-encrypt-via-http": {
"defaultMessage": "Let's Encrypt via HTTP"
},
"loading": { "loading": {
"defaultMessage": "Loading…" "defaultMessage": "Loading…"
}, },
@@ -365,6 +389,21 @@
"object.empty": { "object.empty": {
"defaultMessage": "There are no {objects}" "defaultMessage": "There are no {objects}"
}, },
"object.event.created": {
"defaultMessage": "Created {object}"
},
"object.event.deleted": {
"defaultMessage": "Deleted {object}"
},
"object.event.disabled": {
"defaultMessage": "Disabled {object}"
},
"object.event.enabled": {
"defaultMessage": "Enabled {object}"
},
"object.event.updated": {
"defaultMessage": "Updated {object}"
},
"offline": { "offline": {
"defaultMessage": "Offline" "defaultMessage": "Offline"
}, },
@@ -476,6 +515,9 @@
"streams.udp": { "streams.udp": {
"defaultMessage": "UDP" "defaultMessage": "UDP"
}, },
"test": {
"defaultMessage": "Test"
},
"user": { "user": {
"defaultMessage": "User" "defaultMessage": "User"
}, },

View File

@@ -0,0 +1,88 @@
import EasyModal, { type InnerModalProps } from "ez-modal-react";
import { Form, Formik } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import { Button, DomainNamesField } from "src/components";
import { useSetProxyHost } from "src/hooks";
import { T } from "src/locale";
import { showObjectSuccess } from "src/notifications";
const showCustomCertificateModal = () => {
EasyModal.show(CustomCertificateModal);
};
const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModalProps) => {
const { mutate: setProxyHost } = useSetProxyHost();
const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const onSubmit = async (values: any, { setSubmitting }: any) => {
if (isSubmitting) return;
setIsSubmitting(true);
setErrorMsg(null);
setProxyHost(values, {
onError: (err: any) => setErrorMsg(<T id={err.message} />),
onSuccess: () => {
showObjectSuccess("certificate", "saved");
remove();
},
onSettled: () => {
setIsSubmitting(false);
setSubmitting(false);
},
});
};
return (
<Modal show={visible} onHide={remove}>
<Formik
initialValues={
{
domainNames: [],
} as any
}
onSubmit={onSubmit}
>
{() => (
<Form>
<Modal.Header closeButton>
<Modal.Title>
<T id="object.add" tData={{ object: "certificate" }} />
</Modal.Title>
</Modal.Header>
<Modal.Body className="p-0">
<Alert variant="danger" show={!!errorMsg} onClose={() => setErrorMsg(null)} dismissible>
{errorMsg}
</Alert>
<div className="card m-0 border-0">
<div className="card-header">asd</div>
<div className="card-body">
<DomainNamesField />
</div>
</div>
</Modal.Body>
<Modal.Footer>
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
<T id="cancel" />
</Button>
<Button
type="submit"
actionType="primary"
className="ms-auto bg-lime"
data-bs-dismiss="modal"
isLoading={isSubmitting}
disabled={isSubmitting}
>
<T id="save" />
</Button>
</Modal.Footer>
</Form>
)}
</Formik>
</Modal>
);
});
export { showCustomCertificateModal };

View File

@@ -0,0 +1,89 @@
import { useQueryClient } from "@tanstack/react-query";
import EasyModal, { type InnerModalProps } from "ez-modal-react";
import { Form, Formik } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import { createCertificate } from "src/api/backend";
import { Button, DNSProviderFields, DomainNamesField } from "src/components";
import { T } from "src/locale";
import { showObjectSuccess } from "src/notifications";
const showDNSCertificateModal = () => {
EasyModal.show(DNSCertificateModal);
};
const DNSCertificateModal = EasyModal.create(({ visible, remove }: InnerModalProps) => {
const queryClient = useQueryClient();
const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const onSubmit = async (values: any, { setSubmitting }: any) => {
if (isSubmitting) return;
setIsSubmitting(true);
setErrorMsg(null);
try {
await createCertificate(values);
showObjectSuccess("certificate", "saved");
remove();
} catch (err: any) {
setErrorMsg(<T id={err.message} />);
}
queryClient.invalidateQueries({ queryKey: ["certificates"] });
setIsSubmitting(false);
setSubmitting(false);
};
return (
<Modal show={visible} onHide={remove}>
<Formik
initialValues={
{
domainNames: [],
provider: "letsencrypt",
} as any
}
onSubmit={onSubmit}
>
{() => (
<Form>
<Modal.Header closeButton>
<Modal.Title>
<T id="object.add" tData={{ object: "lets-encrypt-via-dns" }} />
</Modal.Title>
</Modal.Header>
<Modal.Body className="p-0">
<Alert variant="danger" show={!!errorMsg} onClose={() => setErrorMsg(null)} dismissible>
{errorMsg}
</Alert>
<div className="card m-0 border-0">
<div className="card-body">
<DomainNamesField isWildcardPermitted dnsProviderWildcardSupported />
<DNSProviderFields />
</div>
</div>
</Modal.Body>
<Modal.Footer>
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
<T id="cancel" />
</Button>
<Button
type="submit"
actionType="primary"
className="ms-auto bg-pink"
data-bs-dismiss="modal"
isLoading={isSubmitting}
disabled={isSubmitting}
>
<T id="save" />
</Button>
</Modal.Footer>
</Form>
)}
</Formik>
</Modal>
);
});
export { showDNSCertificateModal };

View File

@@ -131,7 +131,7 @@ const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
<div className="card-body"> <div className="card-body">
<div className="tab-content"> <div className="tab-content">
<div className="tab-pane active show" id="tab-details" role="tabpanel"> <div className="tab-pane active show" id="tab-details" role="tabpanel">
<DomainNamesField isWildcardPermitted /> <DomainNamesField isWildcardPermitted dnsProviderWildcardSupported />
</div> </div>
<div className="tab-pane" id="tab-ssl" role="tabpanel"> <div className="tab-pane" id="tab-ssl" role="tabpanel">
<SSLCertificateField <SSLCertificateField

View File

@@ -1,3 +1,4 @@
import CodeEditor from "@uiw/react-textarea-code-editor";
import EasyModal, { type InnerModalProps } from "ez-modal-react"; import EasyModal, { type InnerModalProps } from "ez-modal-react";
import { Alert } from "react-bootstrap"; import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal"; import Modal from "react-bootstrap/Modal";
@@ -39,9 +40,22 @@ const EventDetailsModal = EasyModal.create(({ id, visible, remove }: Props) => {
<EventFormatter row={data} /> <EventFormatter row={data} />
</div> </div>
<hr className="mt-4 mb-3" /> <hr className="mt-4 mb-3" />
<pre> <CodeEditor
<code>{JSON.stringify(data.meta, null, 2)}</code> language="json"
</pre> padding={15}
data-color-mode="dark"
minHeight={200}
indentWidth={2}
style={{
fontFamily:
"ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
borderRadius: "0.3rem",
minHeight: "200px",
backgroundColor: "var(--tblr-bg-surface-dark)",
}}
readOnly
value={JSON.stringify(data.meta, null, 2)}
/>
</div> </div>
</Modal.Body> </Modal.Body>
<Modal.Footer> <Modal.Footer>

View File

@@ -0,0 +1,188 @@
import { IconAlertTriangle } from "@tabler/icons-react";
import EasyModal, { type InnerModalProps } from "ez-modal-react";
import { Form, Formik } from "formik";
import { type ReactNode, useState } from "react";
import { Alert } from "react-bootstrap";
import Modal from "react-bootstrap/Modal";
import { createCertificate, testHttpCertificate } from "src/api/backend";
import { Button, DomainNamesField } from "src/components";
import { T } from "src/locale";
import { showObjectSuccess } from "src/notifications";
const showHTTPCertificateModal = () => {
EasyModal.show(HTTPCertificateModal);
};
const HTTPCertificateModal = EasyModal.create(({ visible, remove }: InnerModalProps) => {
const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [domains, setDomains] = useState([] as string[]);
const [isTesting, setIsTesting] = useState(false);
const [testResults, setTestResults] = useState(null as Record<string, string> | null);
const onSubmit = async (values: any, { setSubmitting }: any) => {
if (isSubmitting) return;
setIsSubmitting(true);
setErrorMsg(null);
try {
await createCertificate(values);
showObjectSuccess("certificate", "saved");
remove();
} catch (err: any) {
setErrorMsg(<T id={err.message} />);
}
setIsSubmitting(false);
setSubmitting(false);
};
const handleTest = async () => {
setIsTesting(true);
setErrorMsg(null);
setTestResults(null);
try {
const result = await testHttpCertificate(domains);
setTestResults(result);
} catch (err: any) {
setErrorMsg(<T id={err.message} />);
}
setIsTesting(false);
};
const parseTestResults = () => {
const elms = [];
for (const domain in testResults) {
const status = testResults[domain];
if (status === "ok") {
elms.push(
<p>
<strong>{domain}:</strong> <T id="certificates.http.reachability-ok" />
</p>,
);
} else {
if (status === "no-host") {
elms.push(
<p>
<strong>{domain}:</strong> <T id="certificates.http.reachability-not-resolved" />
</p>,
);
} else if (status === "failed") {
elms.push(
<p>
<strong>{domain}:</strong> <T id="certificates.http.reachability-failed-to-check" />
</p>,
);
} else if (status === "404") {
elms.push(
<p>
<strong>{domain}:</strong> <T id="certificates.http.reachability-404" />
</p>,
);
} else if (status === "wrong-data") {
elms.push(
<p>
<strong>{domain}:</strong> <T id="certificates.http.reachability-wrong-data" />
</p>,
);
} else if (status.startsWith("other:")) {
const code = status.substring(6);
elms.push(
<p>
<strong>{domain}:</strong> <T id="certificates.http.reachability-other" data={{ code }} />
</p>,
);
} else {
// This should never happen
elms.push(
<p>
<strong>{domain}:</strong> ?
</p>,
);
}
}
}
return <>{elms}</>;
};
return (
<Modal show={visible} onHide={remove}>
<Formik
initialValues={
{
domainNames: [],
provider: "letsencrypt",
} as any
}
onSubmit={onSubmit}
>
{() => (
<Form>
<Modal.Header closeButton>
<Modal.Title>
<T id="object.add" tData={{ object: "lets-encrypt-via-http" }} />
</Modal.Title>
</Modal.Header>
<Modal.Body className="p-0">
<Alert variant="danger" show={!!errorMsg} onClose={() => setErrorMsg(null)} dismissible>
{errorMsg}
</Alert>
<div className="card m-0 border-0">
<div className="card-body">
<p className="text-warning">
<IconAlertTriangle size={16} className="me-1" />
<T id="certificates.http.warning" />
</p>
<DomainNamesField
onChange={(doms) => {
setDomains(doms);
setTestResults(null);
}}
/>
</div>
{testResults ? (
<div className="card-footer">
<h5>
<T id="certificates.http.test-results" />
</h5>
{parseTestResults()}
</div>
) : null}
</div>
</Modal.Body>
<Modal.Footer>
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting || isTesting}>
<T id="cancel" />
</Button>
<div className="ms-auto">
<Button
type="button"
actionType="secondary"
className="me-3"
data-bs-dismiss="modal"
isLoading={isTesting}
disabled={isSubmitting || domains.length === 0}
onClick={handleTest}
>
<T id="test" />
</Button>
<Button
type="submit"
actionType="primary"
className="bg-pink"
data-bs-dismiss="modal"
isLoading={isSubmitting}
disabled={isSubmitting || isTesting}
>
<T id="save" />
</Button>
</div>
</Modal.Footer>
</Form>
)}
</Formik>
</Modal>
);
});
export { showHTTPCertificateModal };

View File

@@ -159,7 +159,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
<div className="card-body"> <div className="card-body">
<div className="tab-content"> <div className="tab-content">
<div className="tab-pane active show" id="tab-details" role="tabpanel"> <div className="tab-pane active show" id="tab-details" role="tabpanel">
<DomainNamesField isWildcardPermitted /> <DomainNamesField isWildcardPermitted dnsProviderWildcardSupported />
<div className="row"> <div className="row">
<div className="col-md-3"> <div className="col-md-3">
<Field name="forwardScheme"> <Field name="forwardScheme">

View File

@@ -144,7 +144,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
<div className="card-body"> <div className="card-body">
<div className="tab-content"> <div className="tab-content">
<div className="tab-pane active show" id="tab-details" role="tabpanel"> <div className="tab-pane active show" id="tab-details" role="tabpanel">
<DomainNamesField isWildcardPermitted /> <DomainNamesField isWildcardPermitted dnsProviderWildcardSupported />
<div className="row"> <div className="row">
<div className="col-md-4"> <div className="col-md-4">
<Field name="forwardScheme"> <Field name="forwardScheme">

View File

@@ -1,8 +1,11 @@
export * from "./AccessListModal"; export * from "./AccessListModal";
export * from "./ChangePasswordModal"; export * from "./ChangePasswordModal";
export * from "./CustomCertificateModal";
export * from "./DeadHostModal"; export * from "./DeadHostModal";
export * from "./DeleteConfirmModal"; export * from "./DeleteConfirmModal";
export * from "./DNSCertificateModal";
export * from "./EventDetailsModal"; export * from "./EventDetailsModal";
export * from "./HTTPCertificateModal";
export * from "./PermissionsModal"; export * from "./PermissionsModal";
export * from "./ProxyHostModal"; export * from "./ProxyHostModal";
export * from "./RedirectionHostModal"; export * from "./RedirectionHostModal";

View File

@@ -5,6 +5,7 @@ import type { Certificate } from "src/api/backend";
import { DomainsFormatter, EmptyData, GravatarFormatter } from "src/components"; import { DomainsFormatter, EmptyData, GravatarFormatter } from "src/components";
import { TableLayout } from "src/components/Table/TableLayout"; import { TableLayout } from "src/components/Table/TableLayout";
import { intl, T } from "src/locale"; import { intl, T } from "src/locale";
import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals";
interface Props { interface Props {
data: Certificate[]; data: Certificate[];
@@ -121,17 +122,28 @@ export default function Table({ data, isFetching }: Props) {
href="#" href="#"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
// onNew(); showHTTPCertificateModal();
}} }}
> >
<T id="lets-encrypt" /> <T id="lets-encrypt-via-http" />
</a> </a>
<a <a
className="dropdown-item" className="dropdown-item"
href="#" href="#"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
// onNewCustom(); showDNSCertificateModal();
}}
>
<T id="lets-encrypt-via-dns" />
</a>
<div className="dropdown-divider" />
<a
className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault();
showCustomCertificateModal();
}} }}
> >
<T id="certificates.custom" /> <T id="certificates.custom" />

View File

@@ -3,6 +3,7 @@ import Alert from "react-bootstrap/Alert";
import { LoadingPage } from "src/components"; import { LoadingPage } from "src/components";
import { useCertificates } from "src/hooks"; import { useCertificates } from "src/hooks";
import { T } from "src/locale"; import { T } from "src/locale";
import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals";
import Table from "./Table"; import Table from "./Table";
export default function TableWrapper() { export default function TableWrapper() {
@@ -54,10 +55,35 @@ export default function TableWrapper() {
<T id="object.add" tData={{ object: "certificate" }} /> <T id="object.add" tData={{ object: "certificate" }} />
</button> </button>
<div className="dropdown-menu"> <div className="dropdown-menu">
<a className="dropdown-item" href="#"> <a
<T id="lets-encrypt" /> className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault();
showHTTPCertificateModal();
}}
>
<T id="lets-encrypt-via-http" />
</a> </a>
<a className="dropdown-item" href="#"> <a
className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault();
showDNSCertificateModal();
}}
>
<T id="lets-encrypt-via-dns" />
</a>
<div className="dropdown-divider" />
<a
className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault();
showCustomCertificateModal();
}}
>
<T id="certificates.custom" /> <T id="certificates.custom" />
</a> </a>
</div> </div>

View File

@@ -118,15 +118,9 @@ const Dashboard = () => {
- check mobile - check mobile
- add help docs for host types - add help docs for host types
- REDO SCREENSHOTS in docs folder - REDO SCREENSHOTS in docs folder
- translations for:
- src/components/Form/AccessField.tsx
- src/components/Form/SSLCertificateField.tsx
- src/components/Form/DNSProviderFields.tsx
- search codebase for "TODO" - search codebase for "TODO"
- update documentation to add development notes for translations - update documentation to add development notes for translations
- use syntax highligting for audit logs json output
- double check output of access field selection on proxy host dialog, after access lists are completed - double check output of access field selection on proxy host dialog, after access lists are completed
- proxy host custom locations dialog
- check permissions in all places - check permissions in all places
More for api, then implement here: More for api, then implement here:

View File

@@ -37,13 +37,12 @@ export default function TableWrapper() {
let filtered = null; let filtered = null;
if (search && data) { if (search && data) {
filtered = data?.filter((_item) => { filtered = data?.filter(
return true; (item) =>
// TODO item.domainNames.some((domain: string) => domain.toLowerCase().includes(search)) ||
// item.domainNames.some((domain: string) => domain.toLowerCase().includes(search)) || item.forwardHost.toLowerCase().includes(search) ||
// item.forwardDomainName.toLowerCase().includes(search) `${item.forwardPort}`.includes(search),
// ); );
});
} else if (search !== "") { } else if (search !== "") {
// this can happen if someone deletes the last item while searching // this can happen if someone deletes the last item while searching
setSearch(""); setSearch("");

View File

@@ -203,59 +203,59 @@
"@babel/helper-string-parser" "^7.27.1" "@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1"
"@biomejs/biome@^2.2.6": "@biomejs/biome@^2.3.0":
version "2.2.6" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.2.6.tgz#76d8afbdd609a5dbace84bc982ae974a24b70b62" resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.3.0.tgz#80030b68d94bd0a0761ac2cd22cc4f2c0f23f4f9"
integrity sha512-yKTCNGhek0rL5OEW1jbLeZX8LHaM8yk7+3JRGv08my+gkpmtb5dDE+54r2ZjZx0ediFEn1pYBOJSmOdDP9xtFw== integrity sha512-shdUY5H3S3tJVUWoVWo5ua+GdPW5lRHf+b0IwZ4OC1o2zOKQECZ6l2KbU6t89FNhtd3Qx5eg5N7/UsQWGQbAFw==
optionalDependencies: optionalDependencies:
"@biomejs/cli-darwin-arm64" "2.2.6" "@biomejs/cli-darwin-arm64" "2.3.0"
"@biomejs/cli-darwin-x64" "2.2.6" "@biomejs/cli-darwin-x64" "2.3.0"
"@biomejs/cli-linux-arm64" "2.2.6" "@biomejs/cli-linux-arm64" "2.3.0"
"@biomejs/cli-linux-arm64-musl" "2.2.6" "@biomejs/cli-linux-arm64-musl" "2.3.0"
"@biomejs/cli-linux-x64" "2.2.6" "@biomejs/cli-linux-x64" "2.3.0"
"@biomejs/cli-linux-x64-musl" "2.2.6" "@biomejs/cli-linux-x64-musl" "2.3.0"
"@biomejs/cli-win32-arm64" "2.2.6" "@biomejs/cli-win32-arm64" "2.3.0"
"@biomejs/cli-win32-x64" "2.2.6" "@biomejs/cli-win32-x64" "2.3.0"
"@biomejs/cli-darwin-arm64@2.2.6": "@biomejs/cli-darwin-arm64@2.3.0":
version "2.2.6" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.6.tgz#b232a7d92c0b28884c1cf99d4348e1a52f95931f" resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.0.tgz#78cef4d7415adbf0718c7854e7160e181d916652"
integrity sha512-UZPmn3M45CjTYulgcrFJFZv7YmK3pTxTJDrFYlNElT2FNnkkX4fsxjExTSMeWKQYoZjvekpH5cvrYZZlWu3yfA== integrity sha512-3cJVT0Z5pbTkoBmbjmDZTDFYxIkRcrs9sYVJbIBHU8E6qQxgXAaBfSVjjCreG56rfDuQBr43GzwzmaHPcu4vlw==
"@biomejs/cli-darwin-x64@2.2.6": "@biomejs/cli-darwin-x64@2.3.0":
version "2.2.6" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.6.tgz#3a2d1582037735f8ed5243aa6d3cc4f4082c5f42" resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.0.tgz#068baf1f0f748c01658ba9bb511d8d18461d922b"
integrity sha512-HOUIquhHVgh/jvxyClpwlpl/oeMqntlteL89YqjuFDiZ091P0vhHccwz+8muu3nTyHWM5FQslt+4Jdcd67+xWQ== integrity sha512-6LIkhglh3UGjuDqJXsK42qCA0XkD1Ke4K/raFOii7QQPbM8Pia7Qj2Hji4XuF2/R78hRmEx7uKJH3t/Y9UahtQ==
"@biomejs/cli-linux-arm64-musl@2.2.6": "@biomejs/cli-linux-arm64-musl@2.3.0":
version "2.2.6" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.6.tgz#2bfe0859c55c840c0648f2b307efccca2527d655" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.0.tgz#9a1350184abcea8092957a9519098cac7629705a"
integrity sha512-TjCenQq3N6g1C+5UT3jE1bIiJb5MWQvulpUngTIpFsL4StVAUXucWD0SL9MCW89Tm6awWfeXBbZBAhJwjyFbRQ== integrity sha512-nDksoFdwZ2YrE7NiYDhtMhL2UgFn8Kb7Y0bYvnTAakHnqEdb4lKindtBc1f+xg2Snz0JQhJUYO7r9CDBosRU5w==
"@biomejs/cli-linux-arm64@2.2.6": "@biomejs/cli-linux-arm64@2.3.0":
version "2.2.6" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.6.tgz#aa1817296d84bda6ea972a54b139d1664580c190" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.0.tgz#f322daebb32fe0b18f7981c8cdbe84a06852bfee"
integrity sha512-BpGtuMJGN+o8pQjvYsUKZ+4JEErxdSmcRD/JG3mXoWc6zrcA7OkuyGFN1mDggO0Q1n7qXxo/PcupHk8gzijt5g== integrity sha512-uhAsbXySX7xsXahegDg5h3CDgfMcRsJvWLFPG0pjkylgBb9lErbK2C0UINW52zhwg0cPISB09lxHPxCau4e2xA==
"@biomejs/cli-linux-x64-musl@2.2.6": "@biomejs/cli-linux-x64-musl@2.3.0":
version "2.2.6" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.6.tgz#b65f9191420e3384e287b21a8b1769795ee2c4c1" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.0.tgz#ce441d5c00eda977b74e4116f9723f2edc579485"
integrity sha512-1ZcBux8zVM3JhWN2ZCPaYf0+ogxXG316uaoXJdgoPZcdK/rmRcRY7PqHdAos2ExzvjIdvhQp72UcveI98hgOog== integrity sha512-+i9UcJwl99uAhtRQDz9jUAh+Xkb097eekxs/D9j4deWDg5/yB/jPWzISe1nBHvlzTXsdUSj0VvB4Go2DSpKIMw==
"@biomejs/cli-linux-x64@2.2.6": "@biomejs/cli-linux-x64@2.3.0":
version "2.2.6" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.6.tgz#5c5f07264bed31a436db60577c77ea9f64a62d2b" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.0.tgz#da7ea904307b3211df62a4b42e5a022f8f583009"
integrity sha512-1HaM/dpI/1Z68zp8ZdT6EiBq+/O/z97a2AiHMl+VAdv5/ELckFt9EvRb8hDHpk8hUMoz03gXkC7VPXOVtU7faA== integrity sha512-uxa8reA2s1VgoH8MhbGlCmMOt3JuSE1vJBifkh1ulaPiuk0SPx8cCdpnm9NWnTe2x/LfWInWx4sZ7muaXTPGGw==
"@biomejs/cli-win32-arm64@2.2.6": "@biomejs/cli-win32-arm64@2.3.0":
version "2.2.6" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.6.tgz#a47ef2d6bec694df088ba70ffd6e89839c9c2304" resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.0.tgz#cdc0f8bbf025fb28c5b03b326128cce363ecffa5"
integrity sha512-h3A88G8PGM1ryTeZyLlSdfC/gz3e95EJw9BZmA6Po412DRqwqPBa2Y9U+4ZSGUAXCsnSQE00jLV8Pyrh0d+jQw== integrity sha512-ynjmsJLIKrAjC3CCnKMMhzcnNy8dbQWjKfSU5YA0mIruTxBNMbkAJp+Pr2iV7/hFou+66ZSD/WV8hmLEmhUaXA==
"@biomejs/cli-win32-x64@2.2.6": "@biomejs/cli-win32-x64@2.3.0":
version "2.2.6" version "2.3.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.6.tgz#cec00e860d9f20d820a48db4a6c7ab3b35c83ac6" resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.0.tgz#10e1de6222e272a1e3e395b3d845ee66cb6febd8"
integrity sha512-yx0CqeOhPjYQ5ZXgPfu8QYkgBhVJyvWe36as7jRuPrKPO5ylVDfwVtPQ+K/mooNTADW0IhxOZm3aPu16dP8yNQ== integrity sha512-zOCYmCRVkWXc9v8P7OLbLlGGMxQTKMvi+5IC4v7O8DkjLCOHRzRVK/Lno2pGZNo0lzKM60pcQOhH8HVkXMQdFg==
"@emotion/babel-plugin@^11.13.5": "@emotion/babel-plugin@^11.13.5":
version "11.13.5" version "11.13.5"
@@ -716,10 +716,10 @@
uncontrollable "^8.0.4" uncontrollable "^8.0.4"
warning "^4.0.3" warning "^4.0.3"
"@rolldown/pluginutils@1.0.0-beta.38": "@rolldown/pluginutils@1.0.0-beta.43":
version "1.0.0-beta.38" version "1.0.0-beta.43"
resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz#95253608c4629eb2a5f3d656009ac9ba031eb292" resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz#fa8249860113711ad3c8053bc79cb07c79b77f62"
integrity sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw== integrity sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==
"@rollup/rollup-android-arm-eabi@4.50.0": "@rollup/rollup-android-arm-eabi@4.50.0":
version "4.50.0" version "4.50.0"
@@ -826,6 +826,11 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz#d81efe6a12060c7feddf9805e2a94c3ab0679f48" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz#d81efe6a12060c7feddf9805e2a94c3ab0679f48"
integrity sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg== integrity sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==
"@standard-schema/spec@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c"
integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==
"@swc/helpers@^0.5.0": "@swc/helpers@^0.5.0":
version "0.5.17" version "0.5.17"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971"
@@ -853,10 +858,10 @@
resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.35.0.tgz#6f35247e41baba2a1b0f4dff048bb1335d6c1075" resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.35.0.tgz#6f35247e41baba2a1b0f4dff048bb1335d6c1075"
integrity sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ== integrity sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ==
"@tanstack/query-core@5.90.3": "@tanstack/query-core@5.90.5":
version "5.90.3" version "5.90.5"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.90.3.tgz#461422437d30aad0a7618122c5e7568095d79cfb" resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.90.5.tgz#0175f9f517514906db8ab379589ed3f96694ecc4"
integrity sha512-HtPOnCwmx4dd35PfXU8jjkhwYrsHfuqgC8RCJIwWglmhIUIlzPP0ZcEkDAc+UtAWCiLm7T8rxeEfHZlz3hYMCA== integrity sha512-wLamYp7FaDq6ZnNehypKI5fNvxHPfTYylE0m/ZpuuzJfJqhR5Pxg9gvGBHZx4n7J+V5Rg5mZxHHTlv25Zt5u+w==
"@tanstack/query-devtools@5.90.1": "@tanstack/query-devtools@5.90.1":
version "5.90.1" version "5.90.1"
@@ -870,12 +875,12 @@
dependencies: dependencies:
"@tanstack/query-devtools" "5.90.1" "@tanstack/query-devtools" "5.90.1"
"@tanstack/react-query@^5.90.3": "@tanstack/react-query@^5.90.5":
version "5.90.3" version "5.90.5"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.90.3.tgz#670ed97948c5d4e3c075049f8a01e84d51e0bdc4" resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.90.5.tgz#545e61282c787bd87ac5785da9a4943462f78ef6"
integrity sha512-i/LRL6DtuhG6bjGzavIMIVuKKPWx2AnEBIsBfuMm3YoHne0a20nWmsatOCBcVSaT0/8/5YFjNkebHAPLVUSi0Q== integrity sha512-pN+8UWpxZkEJ/Rnnj2v2Sxpx1WFlaa9L6a4UO89p6tTQbeo+m0MS8oYDjbggrR8QcTyjKoYWKS3xJQGr3ExT8Q==
dependencies: dependencies:
"@tanstack/query-core" "5.90.3" "@tanstack/query-core" "5.90.5"
"@tanstack/react-table@^8.21.3": "@tanstack/react-table@^8.21.3":
version "8.21.3" version "8.21.3"
@@ -1102,78 +1107,75 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
"@vitejs/plugin-react@^5.0.4": "@vitejs/plugin-react@^5.1.0":
version "5.0.4" version "5.1.0"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz#d642058e89c5b712655c8cbd13482f5813519602" resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz#1f37671a227571437d6e324b824256dac157570e"
integrity sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA== integrity sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==
dependencies: dependencies:
"@babel/core" "^7.28.4" "@babel/core" "^7.28.4"
"@babel/plugin-transform-react-jsx-self" "^7.27.1" "@babel/plugin-transform-react-jsx-self" "^7.27.1"
"@babel/plugin-transform-react-jsx-source" "^7.27.1" "@babel/plugin-transform-react-jsx-source" "^7.27.1"
"@rolldown/pluginutils" "1.0.0-beta.38" "@rolldown/pluginutils" "1.0.0-beta.43"
"@types/babel__core" "^7.20.5" "@types/babel__core" "^7.20.5"
react-refresh "^0.17.0" react-refresh "^0.18.0"
"@vitest/expect@3.2.4": "@vitest/expect@4.0.3":
version "3.2.4" version "4.0.3"
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.0.3.tgz#04a3c7f8220fb7022dbca368697f16fe008f816b"
integrity sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig== integrity sha512-v3eSDx/bF25pzar6aEJrrdTXJduEBU3uSGXHslIdGIpJVP8tQQHV6x1ZfzbFQ/bLIomLSbR/2ZCfnaEGkWkiVQ==
dependencies: dependencies:
"@standard-schema/spec" "^1.0.0"
"@types/chai" "^5.2.2" "@types/chai" "^5.2.2"
"@vitest/spy" "3.2.4" "@vitest/spy" "4.0.3"
"@vitest/utils" "3.2.4" "@vitest/utils" "4.0.3"
chai "^5.2.0" chai "^6.0.1"
tinyrainbow "^2.0.0" tinyrainbow "^3.0.3"
"@vitest/mocker@3.2.4": "@vitest/mocker@4.0.3":
version "3.2.4" version "4.0.3"
resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-3.2.4.tgz#4471c4efbd62db0d4fa203e65cc6b058a85cabd3" resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.0.3.tgz#2a9665dc0966584dbdda5329fc7ed815ae6d6eef"
integrity sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ== integrity sha512-evZcRspIPbbiJEe748zI2BRu94ThCBE+RkjCpVF8yoVYuTV7hMe+4wLF/7K86r8GwJHSmAPnPbZhpXWWrg1qbA==
dependencies: dependencies:
"@vitest/spy" "3.2.4" "@vitest/spy" "4.0.3"
estree-walker "^3.0.3" estree-walker "^3.0.3"
magic-string "^0.30.17" magic-string "^0.30.19"
"@vitest/pretty-format@3.2.4", "@vitest/pretty-format@^3.2.4": "@vitest/pretty-format@4.0.3":
version "3.2.4" version "4.0.3"
resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz#3c102f79e82b204a26c7a5921bf47d534919d3b4" resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.0.3.tgz#0f7632c5100793eb1848efa1d826709639caf485"
integrity sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA== integrity sha512-N7gly/DRXzxa9w9sbDXwD9QNFYP2hw90LLLGDobPNwiWgyW95GMxsCt29/COIKKh3P7XJICR38PSDePenMBtsw==
dependencies: dependencies:
tinyrainbow "^2.0.0" tinyrainbow "^3.0.3"
"@vitest/runner@3.2.4": "@vitest/runner@4.0.3":
version "3.2.4" version "4.0.3"
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-3.2.4.tgz#5ce0274f24a971f6500f6fc166d53d8382430766" resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.0.3.tgz#77003feebb25c014c3c74748ca3647b3b90ef648"
integrity sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ== integrity sha512-1/aK6fPM0lYXWyGKwop2Gbvz1plyTps/HDbIIJXYtJtspHjpXIeB3If07eWpVH4HW7Rmd3Rl+IS/+zEAXrRtXA==
dependencies: dependencies:
"@vitest/utils" "3.2.4" "@vitest/utils" "4.0.3"
pathe "^2.0.3"
strip-literal "^3.0.0"
"@vitest/snapshot@3.2.4":
version "3.2.4"
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-3.2.4.tgz#40a8bc0346ac0aee923c0eefc2dc005d90bc987c"
integrity sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==
dependencies:
"@vitest/pretty-format" "3.2.4"
magic-string "^0.30.17"
pathe "^2.0.3" pathe "^2.0.3"
"@vitest/spy@3.2.4": "@vitest/snapshot@4.0.3":
version "3.2.4" version "4.0.3"
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-3.2.4.tgz#cc18f26f40f3f028da6620046881f4e4518c2599" resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.0.3.tgz#242d300dabd20de95a7e137906fe15a62301786d"
integrity sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw== integrity sha512-amnYmvZ5MTjNCP1HZmdeczAPLRD6iOm9+2nMRUGxbe/6sQ0Ymur0NnR9LIrWS8JA3wKE71X25D6ya/3LN9YytA==
dependencies: dependencies:
tinyspy "^4.0.3" "@vitest/pretty-format" "4.0.3"
magic-string "^0.30.19"
pathe "^2.0.3"
"@vitest/utils@3.2.4": "@vitest/spy@4.0.3":
version "3.2.4" version "4.0.3"
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-3.2.4.tgz#c0813bc42d99527fb8c5b138c7a88516bca46fea" resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.0.3.tgz#b29246bf8e685dafc96cb99ca389fed7a48463ec"
integrity sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA== integrity sha512-82vVL8Cqz7rbXaNUl35V2G7xeNMAjBdNOVaHbrzznT9BmiCiPOzhf0FhU3eP41nP1bLDm/5wWKZqkG4nyU95DQ==
"@vitest/utils@4.0.3":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.0.3.tgz#068fa94ac39fd596102f480b50be1103a7b9ca4f"
integrity sha512-qV6KJkq8W3piW6MDIbGOmn1xhvcW4DuA07alqaQ+vdx7YA49J85pnwnxigZVQFQw3tWnQNRKWwhz5wbP6iv/GQ==
dependencies: dependencies:
"@vitest/pretty-format" "3.2.4" "@vitest/pretty-format" "4.0.3"
loupe "^3.1.4" tinyrainbow "^3.0.3"
tinyrainbow "^2.0.0"
ansi-regex@^5.0.1: ansi-regex@^5.0.1:
version "5.0.1" version "5.0.1"
@@ -1197,11 +1199,6 @@ aria-query@^5.0.0:
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59"
integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
assertion-error@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7"
integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==
babel-plugin-macros@^3.1.0: babel-plugin-macros@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
@@ -1251,11 +1248,6 @@ buffer@^6.0.3:
base64-js "^1.3.1" base64-js "^1.3.1"
ieee754 "^1.2.1" ieee754 "^1.2.1"
cac@^6.7.14:
version "6.7.14"
resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959"
integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==
callsites@^3.0.0: callsites@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -1271,16 +1263,10 @@ ccount@^2.0.0:
resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
chai@^5.2.0: chai@^6.0.1:
version "5.3.3" version "6.2.0"
resolved "https://registry.yarnpkg.com/chai/-/chai-5.3.3.tgz#dd3da955e270916a4bd3f625f4b919996ada7e06" resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.0.tgz#181bca6a219cddb99c3eeefb82483800ffa550ce"
integrity sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw== integrity sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==
dependencies:
assertion-error "^2.0.1"
check-error "^2.1.1"
deep-eql "^5.0.1"
loupe "^3.1.0"
pathval "^2.0.0"
character-entities-html4@^2.0.0: character-entities-html4@^2.0.0:
version "2.1.0" version "2.1.0"
@@ -1302,11 +1288,6 @@ character-reference-invalid@^2.0.0:
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
check-error@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc"
integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==
chokidar@^4.0.0, chokidar@^4.0.3: chokidar@^4.0.0, chokidar@^4.0.3:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30"
@@ -1375,13 +1356,20 @@ date-fns@^4.1.0:
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14"
integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.4.1: debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
version "4.4.1" version "4.4.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
dependencies: dependencies:
ms "^2.1.3" ms "^2.1.3"
debug@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
decimal.js@^10.4.3: decimal.js@^10.4.3:
version "10.6.0" version "10.6.0"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a"
@@ -1399,11 +1387,6 @@ decode-uri-component@^0.4.1:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5"
integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ== integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==
deep-eql@^5.0.1:
version "5.0.2"
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341"
integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==
deepmerge@^2.1.1: deepmerge@^2.1.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
@@ -1515,7 +1498,7 @@ estree-walker@^3.0.3:
dependencies: dependencies:
"@types/estree" "^1.0.0" "@types/estree" "^1.0.0"
expect-type@^1.2.1: expect-type@^1.2.2:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.2.tgz#c030a329fb61184126c8447585bc75a7ec6fbff3" resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.2.tgz#c030a329fb61184126c8447585bc75a7ec6fbff3"
integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA== integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==
@@ -1599,10 +1582,10 @@ globrex@^0.1.2:
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
happy-dom@^20.0.2: happy-dom@^20.0.8:
version "20.0.2" version "20.0.8"
resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-20.0.2.tgz#da676a1e3d0742aff8ba4789db90445842538808" resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-20.0.8.tgz#9bece083d2675ae45c2fc23871a9a02bb162a366"
integrity sha512-pYOyu624+6HDbY+qkjILpQGnpvZOusItCk+rvF5/V+6NkcgTKnbOldpIy22tBnxoaLtlM9nXgoqAcW29/B7CIw== integrity sha512-TlYaNQNtzsZ97rNMBAm8U+e2cUQXNithgfCizkDgc11lgmN4j9CKMhO3FPGKWQYPwwkFcPpoXYF/CqEPLgzfOg==
dependencies: dependencies:
"@types/node" "^20.0.0" "@types/node" "^20.0.0"
"@types/whatwg-mimetype" "^3.0.2" "@types/whatwg-mimetype" "^3.0.2"
@@ -1827,11 +1810,6 @@ is-plain-obj@^4.0.0:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-tokens@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4"
integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==
jsesc@^3.0.2: jsesc@^3.0.2:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
@@ -1874,11 +1852,6 @@ loose-envify@^1.0.0, loose-envify@^1.4.0:
dependencies: dependencies:
js-tokens "^3.0.0 || ^4.0.0" js-tokens "^3.0.0 || ^4.0.0"
loupe@^3.1.0, loupe@^3.1.4:
version "3.2.1"
resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.2.1.tgz#0095cf56dc5b7a9a7c08ff5b1a8796ec8ad17e76"
integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==
lru-cache@^5.1.1: lru-cache@^5.1.1:
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -1891,10 +1864,10 @@ lz-string@^1.5.0:
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
magic-string@^0.30.17: magic-string@^0.30.19:
version "0.30.18" version "0.30.21"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.18.tgz#905bfbbc6aa5692703a93db26a9edcaa0007d2bb" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91"
integrity sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ== integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==
dependencies: dependencies:
"@jridgewell/sourcemap-codec" "^1.5.5" "@jridgewell/sourcemap-codec" "^1.5.5"
@@ -2058,11 +2031,6 @@ pathe@^2.0.3:
resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716"
integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
pathval@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d"
integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==
performance-now@^2.1.0: performance-now@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -2216,10 +2184,10 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-refresh@^0.17.0: react-refresh@^0.18.0:
version "0.17.0" version "0.18.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.18.0.tgz#2dce97f4fe932a4d8142fa1630e475c1729c8062"
integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ== integrity sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==
react-router-dom@^7.9.4: react-router-dom@^7.9.4:
version "7.9.4" version "7.9.4"
@@ -2471,13 +2439,6 @@ strip-indent@^3.0.0:
dependencies: dependencies:
min-indent "^1.0.0" min-indent "^1.0.0"
strip-literal@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-3.0.0.tgz#ce9c452a91a0af2876ed1ae4e583539a353df3fc"
integrity sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==
dependencies:
js-tokens "^9.0.1"
stylis@4.2.0: stylis@4.2.0:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
@@ -2524,20 +2485,10 @@ tinyglobby@^0.2.15:
fdir "^6.5.0" fdir "^6.5.0"
picomatch "^4.0.3" picomatch "^4.0.3"
tinypool@^1.1.1: tinyrainbow@^3.0.3:
version "1.1.1" version "3.0.3"
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.1.1.tgz#059f2d042bd37567fbc017d3d426bdd2a2612591" resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-3.0.3.tgz#984a5b1c1b25854a9b6bccbe77964d0593d1ea42"
integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== integrity sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==
tinyrainbow@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz#9509b2162436315e80e3eee0fcce4474d2444294"
integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==
tinyspy@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-4.0.3.tgz#d1d0f0602f4c15f1aae083a34d6d0df3363b1b52"
integrity sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==
tmp@^0.2.5: tmp@^0.2.5:
version "0.2.5" version "0.2.5"
@@ -2703,17 +2654,6 @@ vfile@^6.0.0:
"@types/unist" "^3.0.0" "@types/unist" "^3.0.0"
vfile-message "^4.0.0" vfile-message "^4.0.0"
vite-node@3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.2.4.tgz#f3676d94c4af1e76898c162c92728bca65f7bb07"
integrity sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==
dependencies:
cac "^6.7.14"
debug "^4.4.1"
es-module-lexer "^1.7.0"
pathe "^2.0.3"
vite "^5.0.0 || ^6.0.0 || ^7.0.0-0"
vite-plugin-checker@^0.11.0: vite-plugin-checker@^0.11.0:
version "0.11.0" version "0.11.0"
resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.11.0.tgz#063ce180c3751b790a6472e19c5d1a352b215070" resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.11.0.tgz#063ce180c3751b790a6472e19c5d1a352b215070"
@@ -2737,24 +2677,10 @@ vite-tsconfig-paths@^5.1.4:
globrex "^0.1.2" globrex "^0.1.2"
tsconfck "^3.0.3" tsconfck "^3.0.3"
"vite@^5.0.0 || ^6.0.0 || ^7.0.0-0": "vite@^6.0.0 || ^7.0.0", vite@^7.1.12:
version "7.1.4" version "7.1.12"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.4.tgz#354944affb55e1aff0157406b74e0d0a3232df9a" resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.12.tgz#8b29a3f61eba23bcb93fc9ec9af4a3a1e83eecdb"
integrity sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw== integrity sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==
dependencies:
esbuild "^0.25.0"
fdir "^6.5.0"
picomatch "^4.0.3"
postcss "^8.5.6"
rollup "^4.43.0"
tinyglobby "^0.2.14"
optionalDependencies:
fsevents "~2.3.3"
vite@^7.1.10:
version "7.1.10"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.10.tgz#47c9970f3b0fe9057bfbcfeff8cd370edd7bd41b"
integrity sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==
dependencies: dependencies:
esbuild "^0.25.0" esbuild "^0.25.0"
fdir "^6.5.0" fdir "^6.5.0"
@@ -2765,33 +2691,30 @@ vite@^7.1.10:
optionalDependencies: optionalDependencies:
fsevents "~2.3.3" fsevents "~2.3.3"
vitest@^3.2.4: vitest@^4.0.3:
version "3.2.4" version "4.0.3"
resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.2.4.tgz#0637b903ad79d1539a25bc34c0ed54b5c67702ea" resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.0.3.tgz#6dcd04df504bb2260e755402768857d556d42d4b"
integrity sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A== integrity sha512-IUSop8jgaT7w0g1yOM/35qVtKjr/8Va4PrjzH1OUb0YH4c3OXB2lCZDkMAB6glA8T5w8S164oJGsbcmAecr4sA==
dependencies: dependencies:
"@types/chai" "^5.2.2" "@vitest/expect" "4.0.3"
"@vitest/expect" "3.2.4" "@vitest/mocker" "4.0.3"
"@vitest/mocker" "3.2.4" "@vitest/pretty-format" "4.0.3"
"@vitest/pretty-format" "^3.2.4" "@vitest/runner" "4.0.3"
"@vitest/runner" "3.2.4" "@vitest/snapshot" "4.0.3"
"@vitest/snapshot" "3.2.4" "@vitest/spy" "4.0.3"
"@vitest/spy" "3.2.4" "@vitest/utils" "4.0.3"
"@vitest/utils" "3.2.4" debug "^4.4.3"
chai "^5.2.0" es-module-lexer "^1.7.0"
debug "^4.4.1" expect-type "^1.2.2"
expect-type "^1.2.1" magic-string "^0.30.19"
magic-string "^0.30.17"
pathe "^2.0.3" pathe "^2.0.3"
picomatch "^4.0.2" picomatch "^4.0.3"
std-env "^3.9.0" std-env "^3.9.0"
tinybench "^2.9.0" tinybench "^2.9.0"
tinyexec "^0.3.2" tinyexec "^0.3.2"
tinyglobby "^0.2.14" tinyglobby "^0.2.15"
tinypool "^1.1.1" tinyrainbow "^3.0.3"
tinyrainbow "^2.0.0" vite "^6.0.0 || ^7.0.0"
vite "^5.0.0 || ^6.0.0 || ^7.0.0-0"
vite-node "3.2.4"
why-is-node-running "^2.3.0" why-is-node-running "^2.3.0"
vscode-uri@^3.1.0: vscode-uri@^3.1.0:

View File

@@ -15,7 +15,6 @@ if hash docker 2>/dev/null; then
-e CI=true \ -e CI=true \
-e NODE_OPTIONS=--openssl-legacy-provider \ -e NODE_OPTIONS=--openssl-legacy-provider \
-v "$(pwd)/frontend:/app/frontend" \ -v "$(pwd)/frontend:/app/frontend" \
-v "$(pwd)/global:/app/global" \
-w /app/frontend "${DOCKER_IMAGE}" \ -w /app/frontend "${DOCKER_IMAGE}" \
sh -c "yarn install && yarn lint && yarn build && chown -R $(id -u):$(id -g) /app/frontend" sh -c "yarn install && yarn lint && yarn build && chown -R $(id -u):$(id -g) /app/frontend"

View File

@@ -10,7 +10,6 @@ docker pull "${TESTING_IMAGE}"
echo -e "${BLUE} ${CYAN}Testing backend ...${RESET}" echo -e "${BLUE} ${CYAN}Testing backend ...${RESET}"
docker run --rm \ docker run --rm \
-v "$(pwd)/backend:/app" \ -v "$(pwd)/backend:/app" \
-v "$(pwd)/global:/app/global" \
-w /app \ -w /app \
"${TESTING_IMAGE}" \ "${TESTING_IMAGE}" \
sh -c 'yarn install && yarn lint . && rm -rf node_modules' sh -c 'yarn install && yarn lint . && rm -rf node_modules'