From 8a6d815152e009a4d93f9bcb6c1d5eaf337ff9ae Mon Sep 17 00:00:00 2001 From: JMDirksen Date: Sat, 19 Jul 2025 09:16:35 +0200 Subject: [PATCH 01/82] Fix initial email with upper case --- backend/setup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/setup.js b/backend/setup.js index 6b9b8e78..0905a4f3 100644 --- a/backend/setup.js +++ b/backend/setup.js @@ -21,7 +21,7 @@ const setupDefaultUser = () => { .then((row) => { if (!row || !row.id) { // Create a new user and set password - const email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com'; + const email = (process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com').toLowerCase(); const password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme'; logger.info('Creating a new user: ' + email + ' with password: ' + password); From 076d14b5e47d0471f11251a0a8b792265f7e3155 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 17:12:10 +0000 Subject: [PATCH 02/82] Bump tmp from 0.2.3 to 0.2.4 in /test Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.3 to 0.2.4. - [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md) - [Commits](https://github.com/raszi/node-tmp/compare/v0.2.3...v0.2.4) --- updated-dependencies: - dependency-name: tmp dependency-version: 0.2.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- test/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/yarn.lock b/test/yarn.lock index 9b9d86fe..09734b3f 100644 --- a/test/yarn.lock +++ b/test/yarn.lock @@ -2220,9 +2220,9 @@ tldts@^6.1.32: tldts-core "^6.1.86" tmp@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" - integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== + version "0.2.4" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.4.tgz#c6db987a2ccc97f812f17137b36af2b6521b0d13" + integrity sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ== tough-cookie@^5.0.0: version "5.1.2" From 8c9d2745e2e82e69d6dd3b7750fca387c3b5ad41 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 20 Aug 2025 09:53:13 +1000 Subject: [PATCH 03/82] Fix remote execution bug where email address can contain malicious code also convert almost all cmd execs for certificates to properly escape arguments --- backend/internal/access-list.js | 58 +-- backend/internal/certificate.js | 336 ++++++++++-------- backend/internal/nginx.js | 62 ++-- backend/lib/utils.js | 12 +- backend/routes/nginx/certificates.js | 4 +- backend/schema/common.json | 5 + .../schema/components/certificate-object.json | 2 +- backend/setup.js | 24 +- test/cypress/e2e/api/Certificates.cy.js | 24 ++ 9 files changed, 305 insertions(+), 222 deletions(-) diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index f6043e18..2407a0ac 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -1,5 +1,5 @@ const _ = require('lodash'); -const fs = require('fs'); +const fs = require('node:fs'); const batchflow = require('batchflow'); const logger = require('../logger').access; const error = require('../lib/error'); @@ -38,7 +38,7 @@ const internalAccessList = { .then((row) => { data.id = row.id; - let promises = []; + const promises = []; // Now add the items data.items.map((item) => { @@ -116,7 +116,7 @@ const internalAccessList = { .then((row) => { if (row.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new error.InternalValidationError(`Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`); } }) .then(() => { @@ -135,10 +135,10 @@ const internalAccessList = { .then(() => { // Check for items and add/update/remove them if (typeof data.items !== 'undefined' && data.items) { - let promises = []; - let items_to_keep = []; + const promises = []; + const items_to_keep = []; - data.items.map(function (item) { + data.items.map((item) => { if (item.password) { promises.push(accessListAuthModel .query() @@ -154,7 +154,7 @@ const internalAccessList = { } }); - let query = accessListAuthModel + const query = accessListAuthModel .query() .delete() .where('access_list_id', data.id); @@ -175,9 +175,9 @@ const internalAccessList = { .then(() => { // Check for clients and add/update/remove them if (typeof data.clients !== 'undefined' && data.clients) { - let promises = []; + const promises = []; - data.clients.map(function (client) { + data.clients.map((client) => { if (client.address) { promises.push(accessListClientModel .query() @@ -190,7 +190,7 @@ const internalAccessList = { } }); - let query = accessListClientModel + const query = accessListClientModel .query() .delete() .where('access_list_id', data.id); @@ -249,7 +249,7 @@ const internalAccessList = { return access.can('access_lists:get', data.id) .then((access_data) => { - let query = accessListModel + const query = accessListModel .query() .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) .leftJoin('proxy_host', function() { @@ -267,7 +267,7 @@ const internalAccessList = { } if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + query.withGraphFetched(`[${data.expand.join(', ')}]`); } return query.then(utils.omitRow(omissions())); @@ -327,7 +327,7 @@ const internalAccessList = { // 3. reconfigure those hosts, then reload nginx // set the access_list_id to zero for these items - row.proxy_hosts.map(function (val, idx) { + row.proxy_hosts.map((_val, idx) => { row.proxy_hosts[idx].access_list_id = 0; }); @@ -340,11 +340,11 @@ const internalAccessList = { }) .then(() => { // delete the htpasswd file - let htpasswd_file = internalAccessList.getFilename(row); + const htpasswd_file = internalAccessList.getFilename(row); try { fs.unlinkSync(htpasswd_file); - } catch (err) { + } catch (_err) { // do nothing } }) @@ -374,7 +374,7 @@ const internalAccessList = { getAll: (access, expand, search_query) => { return access.can('access_lists:list') .then((access_data) => { - let query = accessListModel + const query = accessListModel .query() .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) .leftJoin('proxy_host', function() { @@ -393,19 +393,19 @@ const internalAccessList = { // Query is used for searching if (typeof search_query === 'string') { query.where(function () { - this.where('name', 'like', '%' + search_query + '%'); + this.where('name', 'like', `%${search_query}%`); }); } if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); + query.withGraphFetched(`[${expand.join(', ')}]`); } return query.then(utils.omitRows(omissions())); }) .then((rows) => { if (rows) { - rows.map(function (row, idx) { + rows.map((row, idx) => { if (typeof row.items !== 'undefined' && row.items) { rows[idx] = internalAccessList.maskItems(row); } @@ -424,7 +424,7 @@ const internalAccessList = { * @returns {Promise} */ getCount: (user_id, visibility) => { - let query = accessListModel + const query = accessListModel .query() .count('id as count') .where('is_deleted', 0); @@ -445,7 +445,7 @@ const internalAccessList = { */ maskItems: (list) => { if (list && typeof list.items !== 'undefined') { - list.items.map(function (val, idx) { + list.items.map((val, idx) => { let repeat_for = 8; let first_char = '*'; @@ -468,7 +468,7 @@ const internalAccessList = { * @returns {String} */ getFilename: (list) => { - return '/data/access/' + list.id; + return `/data/access/${list.id}`; }, /** @@ -479,15 +479,15 @@ const internalAccessList = { * @returns {Promise} */ build: (list) => { - logger.info('Building Access file #' + list.id + ' for: ' + list.name); + logger.info(`Building Access file #${list.id} for: ${list.name}`); return new Promise((resolve, reject) => { - let htpasswd_file = internalAccessList.getFilename(list); + const htpasswd_file = internalAccessList.getFilename(list); // 1. remove any existing access file try { fs.unlinkSync(htpasswd_file); - } catch (err) { + } catch (_err) { // do nothing } @@ -504,14 +504,14 @@ const internalAccessList = { if (list.items.length) { return new Promise((resolve, reject) => { batchflow(list.items).sequential() - .each((i, item, next) => { + .each((_i, item, next) => { if (typeof item.password !== 'undefined' && item.password.length) { - logger.info('Adding: ' + item.username); + logger.info(`Adding: ${item.username}`); utils.execFile('openssl', ['passwd', '-apr1', item.password]) .then((res) => { try { - fs.appendFileSync(htpasswd_file, item.username + ':' + res + '\n', {encoding: 'utf8'}); + fs.appendFileSync(htpasswd_file, `${item.username}:${res}\n`, {encoding: 'utf8'}); } catch (err) { reject(err); } @@ -528,7 +528,7 @@ const internalAccessList = { reject(err); }) .end((results) => { - logger.success('Built Access file #' + list.id + ' for: ' + list.name); + logger.success(`Built Access file #${list.id} for: ${list.name}`); resolve(results); }); }); diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index f2e845a2..55e74c3e 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -1,6 +1,6 @@ const _ = require('lodash'); -const fs = require('fs'); -const https = require('https'); +const fs = require('node:fs'); +const https = require('node:https'); const tempWrite = require('temp-write'); const moment = require('moment'); const archiver = require('archiver'); @@ -49,7 +49,7 @@ const internalCertificate = { processExpiringHosts: () => { if (!internalCertificate.intervalProcessing) { internalCertificate.intervalProcessing = true; - logger.info('Renewing SSL certs expiring within ' + internalCertificate.renewBeforeExpirationBy[0] + ' ' + internalCertificate.renewBeforeExpirationBy[1] + ' ...'); + logger.info(`Renewing SSL certs expiring within ${internalCertificate.renewBeforeExpirationBy[0]} ${internalCertificate.renewBeforeExpirationBy[1]} ...`); const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss'); @@ -70,7 +70,7 @@ const internalCertificate = { */ let sequence = Promise.resolve(); - certificates.forEach(function (certificate) { + certificates.forEach((certificate) => { sequence = sequence.then(() => internalCertificate .renew( @@ -202,7 +202,7 @@ const internalCertificate = { .then(() => { // At this point, the letsencrypt cert should exist on disk. // Lets get the expiry date from the file and update the row silently - return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem') + return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`) .then((cert_info) => { return certificateModel .query() @@ -263,7 +263,7 @@ const internalCertificate = { .then((row) => { if (row.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Certificate could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new error.InternalValidationError(`Certificate could not be updated, IDs do not match: ${row.id} !== ${data.id}`); } return certificateModel @@ -308,7 +308,7 @@ const internalCertificate = { return access.can('certificates:get', data.id) .then((access_data) => { - let query = certificateModel + const query = certificateModel .query() .where('is_deleted', 0) .andWhere('id', data.id) @@ -323,7 +323,7 @@ const internalCertificate = { } if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + query.withGraphFetched(`[${data.expand.join(', ')}]`); } return query.then(utils.omitRow(omissions())); @@ -354,17 +354,17 @@ const internalCertificate = { }) .then((certificate) => { if (certificate.provider === 'letsencrypt') { - const zipDirectory = '/etc/letsencrypt/live/npm-' + data.id; + const zipDirectory = internalCertificate.getLiveCertPath(data.id); 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`); } - let certFiles = fs.readdirSync(zipDirectory) + const certFiles = fs.readdirSync(zipDirectory) .filter((fn) => fn.endsWith('.pem')) .map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); - const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`; - const opName = '/tmp/' + downloadName; + const downloadName = `npm-${data.id}-${Date.now()}.zip`; + const opName = `/tmp/${downloadName}`; internalCertificate.zipFiles(certFiles, opName) .then(() => { logger.debug('zip completed : ', opName); @@ -392,7 +392,7 @@ const internalCertificate = { return new Promise((resolve, reject) => { source .map((fl) => { - let fileName = path.basename(fl); + const fileName = path.basename(fl); logger.debug(fl, 'added to certificate zip'); archive.file(fl, { name: fileName }); }); @@ -462,7 +462,7 @@ const internalCertificate = { getAll: (access, expand, search_query) => { return access.can('certificates:list') .then((access_data) => { - let query = certificateModel + const query = certificateModel .query() .where('is_deleted', 0) .groupBy('id') @@ -479,12 +479,12 @@ const internalCertificate = { // Query is used for searching if (typeof search_query === 'string') { query.where(function () { - this.where('nice_name', 'like', '%' + search_query + '%'); + this.where('nice_name', 'like', `%${search_query}%`); }); } if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); + query.withGraphFetched(`[${expand.join(', ')}]`); } return query.then(utils.omitRows(omissions())); @@ -499,7 +499,7 @@ const internalCertificate = { * @returns {Promise} */ getCount: (user_id, visibility) => { - let query = certificateModel + const query = certificateModel .query() .count('id as count') .where('is_deleted', 0); @@ -521,7 +521,7 @@ const internalCertificate = { writeCustomCert: (certificate) => { logger.info('Writing Custom Certificate:', certificate); - const dir = '/data/custom_ssl/npm-' + certificate.id; + const dir = `/data/custom_ssl/npm-${certificate.id}`; return new Promise((resolve, reject) => { if (certificate.provider === 'letsencrypt') { @@ -531,7 +531,7 @@ const internalCertificate = { let certData = certificate.meta.certificate; if (typeof certificate.meta.intermediate_certificate !== 'undefined') { - certData = certData + '\n' + certificate.meta.intermediate_certificate; + certData = `${certData}\n${certificate.meta.intermediate_certificate}`; } try { @@ -543,7 +543,7 @@ const internalCertificate = { return; } - fs.writeFile(dir + '/fullchain.pem', certData, function (err) { + fs.writeFile(`${dir}/fullchain.pem`, certData, (err) => { if (err) { reject(err); } else { @@ -553,7 +553,7 @@ const internalCertificate = { }) .then(() => { return new Promise((resolve, reject) => { - fs.writeFile(dir + '/privkey.pem', certificate.meta.certificate_key, function (err) { + fs.writeFile(`${dir}/privkey.pem`, certificate.meta.certificate_key, (err) => { if (err) { reject(err); } else { @@ -591,7 +591,7 @@ const internalCertificate = { validate: (data) => { return new Promise((resolve) => { // Put file contents into an object - let files = {}; + const files = {}; _.map(data.files, (file, name) => { if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { files[name] = file.data.toString(); @@ -603,7 +603,7 @@ const internalCertificate = { .then((files) => { // For each file, create a temp file and write the contents to it // Then test it depending on the file type - let promises = []; + const promises = []; _.map(files, (content, type) => { promises.push(new Promise((resolve) => { if (type === 'certificate_key') { @@ -688,11 +688,11 @@ const internalCertificate = { reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.')); }, 10000); utils - .exec('openssl pkey -in ' + filepath + ' -check -noout 2>&1 ') + .exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `) .then((result) => { clearTimeout(failTimeout); if (!result.toLowerCase().includes('key is valid')) { - reject(new error.ValidationError('Result Validation Error: ' + result)); + reject(new error.ValidationError(`Result Validation Error: ${result}`)); } fs.unlinkSync(filepath); resolve(true); @@ -700,7 +700,7 @@ const internalCertificate = { .catch((err) => { clearTimeout(failTimeout); fs.unlinkSync(filepath); - reject(new error.ValidationError('Certificate Key is not valid (' + err.message + ')', err)); + reject(new error.ValidationError(`Certificate Key is not valid (${err.message})`, err)); }); }); }); @@ -735,9 +735,9 @@ const internalCertificate = { * @param {Boolean} [throw_expired] Throw when the certificate is out of date */ getCertificateInfoFromFile: (certificate_file, throw_expired) => { - let certData = {}; + const certData = {}; - return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout') + return utils.execFile('openssl', ['x509', '-in', certificate_file, '-subject', '-noout']) .then((result) => { // Examples: // subject=CN = *.jc21.com @@ -745,11 +745,11 @@ const internalCertificate = { const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; const match = regex.exec(result); if (match && typeof match[1] !== 'undefined') { - certData['cn'] = match[1]; + certData.cn = match[1]; } }) .then(() => { - return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout'); + return utils.execFile('openssl', ['x509', '-in', certificate_file, '-issuer', '-noout']); }) .then((result) => { @@ -760,11 +760,11 @@ const internalCertificate = { const regex = /^(?:issuer=)?(.*)$/gim; const match = regex.exec(result); if (match && typeof match[1] !== 'undefined') { - certData['issuer'] = match[1]; + certData.issuer = match[1]; } }) .then(() => { - return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout'); + return utils.execFile('openssl', ['x509', '-in', certificate_file, '-dates', '-noout']); }) .then((result) => { // notBefore=Jul 14 04:04:29 2018 GMT @@ -773,7 +773,7 @@ const internalCertificate = { let validTo = null; const lines = result.split('\n'); - lines.map(function (str) { + lines.map((str) => { const regex = /^(\S+)=(.*)$/gim; const match = regex.exec(str.trim()); @@ -789,21 +789,21 @@ const internalCertificate = { }); 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 < parseInt(moment().format('X'), 10)) { throw new error.ValidationError('Certificate has expired'); } - certData['dates'] = { + certData.dates = { from: validFrom, to: validTo }; return certData; }).catch((err) => { - throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err); + throw new error.ValidationError(`Certificate is not valid (${err.message})`, err); }); }, @@ -814,7 +814,7 @@ const internalCertificate = { * @param {Boolean} [remove] * @returns {Object} */ - cleanMeta: function (meta, remove) { + cleanMeta: (meta, remove) => { internalCertificate.allowedSslFiles.map((key) => { if (typeof meta[key] !== 'undefined' && meta[key]) { if (remove) { @@ -834,24 +834,35 @@ const internalCertificate = { * @returns {Promise} */ requestLetsEncryptSsl: (certificate) => { - logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); + logger.info(`Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); - const cmd = `${certbotCommand} certonly ` + - `--config '${letsencryptConfig}' ` + - '--work-dir "/tmp/letsencrypt-lib" ' + - '--logs-dir "/tmp/letsencrypt-log" ' + - `--cert-name "npm-${certificate.id}" ` + - '--agree-tos ' + - '--authenticator webroot ' + - `--email '${certificate.meta.letsencrypt_email}' ` + - '--preferred-challenges "dns,http" ' + - `--domains "${certificate.domain_names.join(',')}" ` + - (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + - (letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); + const args = [ + 'certonly', + '--config', + letsencryptConfig, + '--work-dir', + '/tmp/letsencrypt-lib', + '--logs-dir', + '/tmp/letsencrypt-log', + '--cert-name', + `npm-${certificate.id}`, + '--agree-tos', + '--authenticator', + 'webroot', + '--email', + certificate.meta.letsencrypt_email, + '--preferred-challenges', + 'dns,http', + '--domains', + certificate.domain_names.join(','), + ]; - logger.info('Command:', cmd); + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); + args.push(...adds.args); - return utils.exec(cmd) + logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); + + return utils.execFile(certbotCommand, args, adds.opts) .then((result) => { logger.success(result); return result; @@ -868,50 +879,48 @@ const internalCertificate = { requestLetsEncryptSslWithDnsChallenge: async (certificate) => { await certbot.installPlugin(certificate.meta.dns_provider); const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; - logger.info(`Requesting Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); + logger.info(`Requesting LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); - const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id; + const credentialsLocation = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true }); fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600}); // Whether the plugin has a ---credentials argument const hasConfigArg = certificate.meta.dns_provider !== 'route53'; - let mainCmd = certbotCommand + ' certonly ' + - `--config '${letsencryptConfig}' ` + - '--work-dir "/tmp/letsencrypt-lib" ' + - '--logs-dir "/tmp/letsencrypt-log" ' + - `--cert-name 'npm-${certificate.id}' ` + - '--agree-tos ' + - `--email '${certificate.meta.letsencrypt_email}' ` + - `--domains '${certificate.domain_names.join(',')}' ` + - `--authenticator '${dnsPlugin.full_plugin_name}' ` + - ( - hasConfigArg - ? `--${dnsPlugin.full_plugin_name}-credentials '${credentialsLocation}' ` - : '' - ) + - ( - certificate.meta.propagation_seconds !== undefined - ? `--${dnsPlugin.full_plugin_name}-propagation-seconds '${certificate.meta.propagation_seconds}' ` - : '' - ) + - (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + - (letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); + const args = [ + 'certonly', + '--config', + letsencryptConfig, + '--work-dir', + '/tmp/letsencrypt-lib', + '--logs-dir', + '/tmp/letsencrypt-log', + '--cert-name', + `npm-${certificate.id}`, + '--agree-tos', + '--email', + certificate.meta.letsencrypt_email, + '--domains', + certificate.domain_names.join(','), + '--authenticator', + dnsPlugin.full_plugin_name, + ]; - // Prepend the path to the credentials file as an environment variable - if (certificate.meta.dns_provider === 'route53') { - mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd; + if (hasConfigArg) { + args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation); + } + if (certificate.meta.propagation_seconds !== undefined) { + args.push(`--${dnsPlugin.full_plugin_name}-propagation-seconds`, certificate.meta.propagation_seconds.toString()); } - if (certificate.meta.dns_provider === 'duckdns') { - mainCmd = mainCmd + ' --dns-duckdns-no-txt-restore'; - } + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); + args.push(...adds.args); - logger.info('Command:', mainCmd); + logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); try { - const result = await utils.exec(mainCmd); + const result = await utils.execFile(certbotCommand, args, adds.opts); logger.info(result); return result; } catch (err) { @@ -939,7 +948,7 @@ const internalCertificate = { return renewMethod(certificate) .then(() => { - return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem'); + return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`); }) .then((cert_info) => { return certificateModel @@ -971,22 +980,31 @@ const internalCertificate = { * @returns {Promise} */ renewLetsEncryptSsl: (certificate) => { - logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); + logger.info(`Renewing LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); - const cmd = certbotCommand + ' renew --force-renewal ' + - `--config '${letsencryptConfig}' ` + - '--work-dir "/tmp/letsencrypt-lib" ' + - '--logs-dir "/tmp/letsencrypt-log" ' + - `--cert-name 'npm-${certificate.id}' ` + - '--preferred-challenges "dns,http" ' + - '--no-random-sleep-on-renew ' + - '--disable-hook-validation ' + - (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + - (letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); + const args = [ + 'renew', + '--force-renewal', + '--config', + letsencryptConfig, + '--work-dir', + '/tmp/letsencrypt-lib', + '--logs-dir', + '/tmp/letsencrypt-log', + '--cert-name', + `npm-${certificate.id}`, + '--preferred-challenges', + 'dns,http', + '--no-random-sleep-on-renew', + '--disable-hook-validation', + ]; - logger.info('Command:', cmd); + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); + args.push(...adds.args); - return utils.exec(cmd) + logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); + + return utils.execFile(certbotCommand, args, adds.opts) .then((result) => { logger.info(result); return result; @@ -1004,27 +1022,29 @@ const internalCertificate = { throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); } - logger.info(`Renewing Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); + logger.info(`Renewing LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); - let mainCmd = certbotCommand + ' renew --force-renewal ' + - `--config "${letsencryptConfig}" ` + - '--work-dir "/tmp/letsencrypt-lib" ' + - '--logs-dir "/tmp/letsencrypt-log" ' + - `--cert-name 'npm-${certificate.id}' ` + - '--disable-hook-validation ' + - '--no-random-sleep-on-renew ' + - (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + - (letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); + const args = [ + 'renew', + '--force-renewal', + '--config', + letsencryptConfig, + '--work-dir', + '/tmp/letsencrypt-lib', + '--logs-dir', + '/tmp/letsencrypt-log', + '--cert-name', + `npm-${certificate.id}`, + '--disable-hook-validation', + '--no-random-sleep-on-renew', + ]; - // Prepend the path to the credentials file as an environment variable - if (certificate.meta.dns_provider === 'route53') { - const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id; - mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd; - } + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); + args.push(...adds.args); - logger.info('Command:', mainCmd); + logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); - return utils.exec(mainCmd) + return utils.execFile(certbotCommand, args, adds.opts) .then(async (result) => { logger.info(result); return result; @@ -1037,25 +1057,29 @@ const internalCertificate = { * @returns {Promise} */ revokeLetsEncryptSsl: (certificate, throw_errors) => { - logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); + logger.info(`Revoking LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); - const mainCmd = certbotCommand + ' revoke ' + - `--config '${letsencryptConfig}' ` + - '--work-dir "/tmp/letsencrypt-lib" ' + - '--logs-dir "/tmp/letsencrypt-log" ' + - `--cert-path '/etc/letsencrypt/live/npm-${certificate.id}/fullchain.pem' ` + - '--delete-after-revoke ' + - (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + - (letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); + const args = [ + 'revoke', + '--config', + letsencryptConfig, + '--work-dir', + '/tmp/letsencrypt-lib', + '--logs-dir', + '/tmp/letsencrypt-log', + '--cert-path', + `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, + '--delete-after-revoke', + ]; - // Don't fail command if file does not exist - const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`; + const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); + args.push(...adds.args); - logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd); + logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); - return utils.exec(mainCmd) + return utils.execFile(certbotCommand, args, adds.opts) .then(async (result) => { - await utils.exec(delete_credentialsCmd); + await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); logger.info(result); return result; }) @@ -1073,9 +1097,8 @@ const internalCertificate = { * @returns {Boolean} */ hasLetsEncryptSslCerts: (certificate) => { - const letsencryptPath = '/etc/letsencrypt/live/npm-' + certificate.id; - - return fs.existsSync(letsencryptPath + '/fullchain.pem') && fs.existsSync(letsencryptPath + '/privkey.pem'); + const letsencryptPath = internalCertificate.getLiveCertPath(certificate.id); + return fs.existsSync(`${letsencryptPath}/fullchain.pem`) && fs.existsSync(`${letsencryptPath}/privkey.pem`); }, /** @@ -1087,7 +1110,7 @@ const internalCertificate = { */ disableInUseHosts: (in_use_result) => { if (in_use_result.total_count) { - let promises = []; + const promises = []; if (in_use_result.proxy_hosts.length) { promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts)); @@ -1117,7 +1140,7 @@ const internalCertificate = { */ enableInUseHosts: (in_use_result) => { if (in_use_result.total_count) { - let promises = []; + const promises = []; if (in_use_result.proxy_hosts.length) { promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts)); @@ -1150,12 +1173,12 @@ const internalCertificate = { // Create a test challenge file 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.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'}); async function performTestForDomain (domain) { - logger.info('Testing http challenge for ' + 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 = { @@ -1169,13 +1192,16 @@ const internalCertificate = { const result = await new Promise((resolve) => { - const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) { + const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, (res) => { let responseBody = ''; - res.on('data', (chunk) => responseBody = responseBody + chunk); - res.on('end', function () { + res.on('data', (chunk) => { + responseBody = responseBody + chunk; + }); + + res.on('end', () => { try { - const parsedBody = JSON.parse(responseBody + ''); + const parsedBody = JSON.parse(`${responseBody}`); if (res.statusCode !== 200) { logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`); resolve(undefined); @@ -1196,7 +1222,7 @@ const internalCertificate = { // Make sure to write the request body. req.write(formBody); req.end(); - req.on('error', function (e) { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); + req.on('error', (e) => { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); resolve(undefined); }); }); @@ -1238,6 +1264,34 @@ const internalCertificate = { fs.unlinkSync(testChallengeFile); return results; + }, + + getAdditionalCertbotArgs: (certificate_id, dns_provider) => { + const args = []; + if (letsencryptServer !== null) { + args.push('--server', letsencryptServer); + } + if (letsencryptStaging && letsencryptServer === null) { + args.push('--staging'); + } + + // For route53, add the credentials file as an environment variable, + // inheriting the process env + const opts = {}; + if (certificate_id && dns_provider === 'route53') { + opts.env = process.env; + opts.env.AWS_CONFIG_FILE = `/etc/letsencrypt/credentials/credentials-${certificate_id}`; + } + + if (dns_provider === 'duckdns') { + args.push('--dns-duckdns-no-txt-restore'); + } + + return {args: args, opts: opts}; + }, + + getLiveCertPath: (certificate_id) => { + return `/etc/letsencrypt/live/npm-${certificate_id}`; } }; diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 5f802c00..59694d3c 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -1,5 +1,5 @@ const _ = require('lodash'); -const fs = require('fs'); +const fs = require('node:fs'); const logger = require('../logger').nginx; const config = require('../lib/config'); const utils = require('../lib/utils'); @@ -57,9 +57,9 @@ const internalNginx = { // It will always look like this: // nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address) - let valid_lines = []; - let err_lines = err.message.split('\n'); - err_lines.map(function (line) { + const valid_lines = []; + const err_lines = err.message.split('\n'); + err_lines.map((line) => { if (line.indexOf('/var/log/nginx/error.log') === -1) { valid_lines.push(line); } @@ -105,7 +105,7 @@ const internalNginx = { logger.info('Testing Nginx configuration'); } - return utils.exec('/usr/sbin/nginx -t -g "error_log off;"'); + return utils.execFile('/usr/sbin/nginx', ['-t', '-g', 'error_log off;']); }, /** @@ -115,7 +115,7 @@ const internalNginx = { return internalNginx.test() .then(() => { logger.info('Reloading Nginx'); - return utils.exec('/usr/sbin/nginx -s reload'); + return utils.execFile('/usr/sbin/nginx', ['-s', 'reload']); }); }, @@ -128,7 +128,7 @@ const internalNginx = { if (host_type === 'default') { return '/data/nginx/default_host/site.conf'; } - return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf'; + return `/data/nginx/${internalNginx.getFileFriendlyHostType(host_type)}/${host_id}.conf`; }, /** @@ -141,7 +141,7 @@ const internalNginx = { let template; try { - template = fs.readFileSync(__dirname + '/../templates/_location.conf', {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/_location.conf`, {encoding: 'utf8'}); } catch (err) { reject(new error.ConfigurationError(err.message)); return; @@ -152,7 +152,7 @@ const internalNginx = { const locationRendering = async () => { for (let i = 0; i < host.locations.length; i++) { - let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id}, + const locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id}, {ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits}, {allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support}, {hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list}, @@ -183,21 +183,21 @@ const internalNginx = { */ generateConfig: (host_type, host_row) => { // Prevent modifying the original object: - let host = JSON.parse(JSON.stringify(host_row)); + const host = JSON.parse(JSON.stringify(host_row)); const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); if (config.debug()) { - logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2)); + logger.info(`Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2)); } const renderEngine = utils.getRenderEngine(); return new Promise((resolve, reject) => { - let template = null; - let filename = internalNginx.getConfigName(nice_host_type, host.id); + let template = null; + const filename = internalNginx.getConfigName(nice_host_type, host.id); try { - template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/${nice_host_type}.conf`, {encoding: 'utf8'}); } catch (err) { reject(new error.ConfigurationError(err.message)); return; @@ -252,7 +252,7 @@ const internalNginx = { }) .catch((err) => { if (config.debug()) { - logger.warn('Could not write ' + filename + ':', err.message); + logger.warn(`Could not write ${filename}:`, err.message); } reject(new error.ConfigurationError(err.message)); @@ -277,11 +277,11 @@ const internalNginx = { const renderEngine = utils.getRenderEngine(); return new Promise((resolve, reject) => { - let template = null; - let filename = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf'; + let template = null; + const filename = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; try { - template = fs.readFileSync(__dirname + '/../templates/letsencrypt-request.conf', {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/letsencrypt-request.conf`, {encoding: 'utf8'}); } catch (err) { reject(new error.ConfigurationError(err.message)); return; @@ -302,7 +302,7 @@ const internalNginx = { }) .catch((err) => { if (config.debug()) { - logger.warn('Could not write ' + filename + ':', err.message); + logger.warn(`Could not write ${filename}:`, err.message); } reject(new error.ConfigurationError(err.message)); @@ -316,7 +316,7 @@ const internalNginx = { * @param {String} filename */ deleteFile: (filename) => { - logger.debug('Deleting file: ' + filename); + logger.debug(`Deleting file: ${filename}`); try { fs.unlinkSync(filename); } catch (err) { @@ -330,7 +330,7 @@ const internalNginx = { * @returns String */ getFileFriendlyHostType: (host_type) => { - return host_type.replace(new RegExp('-', 'g'), '_'); + return host_type.replace(/-/g, '_'); }, /** @@ -340,7 +340,7 @@ const internalNginx = { * @returns {Promise} */ deleteLetsEncryptRequestConfig: (certificate) => { - const config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf'; + const config_file = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; return new Promise((resolve/*, reject*/) => { internalNginx.deleteFile(config_file); resolve(); @@ -355,7 +355,7 @@ const internalNginx = { */ deleteConfig: (host_type, host, delete_err_file) => { const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); - const config_file_err = config_file + '.err'; + const config_file_err = `${config_file}.err`; return new Promise((resolve/*, reject*/) => { internalNginx.deleteFile(config_file); @@ -373,7 +373,7 @@ const internalNginx = { */ renameConfigAsError: (host_type, host) => { const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); - const config_file_err = config_file + '.err'; + const config_file_err = `${config_file}.err`; return new Promise((resolve/*, reject*/) => { fs.unlink(config_file, () => { @@ -392,8 +392,8 @@ const internalNginx = { * @returns {Promise} */ bulkGenerateConfigs: (host_type, hosts) => { - let promises = []; - hosts.map(function (host) { + const promises = []; + hosts.map((host) => { promises.push(internalNginx.generateConfig(host_type, host)); }); @@ -406,8 +406,8 @@ const internalNginx = { * @returns {Promise} */ bulkDeleteConfigs: (host_type, hosts) => { - let promises = []; - hosts.map(function (host) { + const promises = []; + hosts.map((host) => { promises.push(internalNginx.deleteConfig(host_type, host, true)); }); @@ -418,14 +418,12 @@ const internalNginx = { * @param {string} config * @returns {boolean} */ - advancedConfigHasDefaultLocation: function (cfg) { - return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im); - }, + advancedConfigHasDefaultLocation: (cfg) => !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im), /** * @returns {boolean} */ - ipv6Enabled: function () { + ipv6Enabled: () => { if (typeof process.env.DISABLE_IPV6 !== 'undefined') { const disabled = process.env.DISABLE_IPV6.toLowerCase(); return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes'); diff --git a/backend/lib/utils.js b/backend/lib/utils.js index 66f2dfd9..e2d60778 100644 --- a/backend/lib/utils.js +++ b/backend/lib/utils.js @@ -29,15 +29,19 @@ module.exports = { /** * @param {String} cmd * @param {Array} args + * @param {Object|undefined} options * @returns {Promise} */ - execFile: (cmd, args) => { - // logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : '')); + execFile: (cmd, args, options) => { + logger.debug(`CMD: ${cmd} ${args ? args.join(' ') : ''}`); + if (typeof options === 'undefined') { + options = {}; + } return new Promise((resolve, reject) => { - execFile(cmd, args, (err, stdout, /*stderr*/) => { + execFile(cmd, args, options, (err, stdout, stderr) => { if (err && typeof err === 'object') { - reject(err); + reject(new error.CommandError(stderr, 1, err)); } else { resolve(stdout.trim()); } diff --git a/backend/routes/nginx/certificates.js b/backend/routes/nginx/certificates.js index bf47c03f..4b10d137 100644 --- a/backend/routes/nginx/certificates.js +++ b/backend/routes/nginx/certificates.js @@ -6,7 +6,7 @@ const apiValidator = require('../../lib/validator/api'); const internalCertificate = require('../../internal/certificate'); const schema = require('../../schema'); -let router = express.Router({ +const router = express.Router({ caseSensitive: true, strict: true, mergeParams: true @@ -231,7 +231,7 @@ router */ router .route('/:certificate_id/download') - .options((req, res) => { + .options((_req, res) => { res.sendStatus(204); }) .all(jwtdecode()) diff --git a/backend/schema/common.json b/backend/schema/common.json index 83de0143..343f125e 100644 --- a/backend/schema/common.json +++ b/backend/schema/common.json @@ -110,6 +110,11 @@ "caching_enabled": { "description": "Should we cache assets", "type": "boolean" + }, + "email": { + "description": "Email address", + "type": "string", + "pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" } } } diff --git a/backend/schema/components/certificate-object.json b/backend/schema/components/certificate-object.json index b75dcf61..dcc2a834 100644 --- a/backend/schema/components/certificate-object.json +++ b/backend/schema/components/certificate-object.json @@ -69,7 +69,7 @@ "type": "object" }, "letsencrypt_email": { - "type": "string" + "$ref": "../common.json#/properties/email" }, "propagation_seconds": { "type": "integer", diff --git a/backend/setup.js b/backend/setup.js index 6b9b8e78..fcb4f59c 100644 --- a/backend/setup.js +++ b/backend/setup.js @@ -24,7 +24,7 @@ const setupDefaultUser = () => { const email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com'; const password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme'; - logger.info('Creating a new user: ' + email + ' with password: ' + password); + logger.info(`Creating a new user: ${email} with password: ${password}`); const data = { is_deleted: 0, @@ -113,20 +113,20 @@ const setupCertbotPlugins = () => { .andWhere('provider', 'letsencrypt') .then((certificates) => { if (certificates && certificates.length) { - let plugins = []; - let promises = []; + const plugins = []; + const promises = []; - certificates.map(function (certificate) { + certificates.map((certificate) => { if (certificate.meta && certificate.meta.dns_challenge === true) { if (plugins.indexOf(certificate.meta.dns_provider) === -1) { plugins.push(certificate.meta.dns_provider); } // Make sure credentials file exists - const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id; + const credentials_loc = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; // Escape single quotes and backslashes const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\'); - const credentials_cmd = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }'; + const credentials_cmd = `[ -f '${credentials_loc}' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo '${escapedCredentials}' > '${credentials_loc}' && chmod 600 '${credentials_loc}'; }`; promises.push(utils.exec(credentials_cmd)); } }); @@ -136,7 +136,7 @@ const setupCertbotPlugins = () => { if (promises.length) { return Promise.all(promises) .then(() => { - logger.info('Added Certbot plugins ' + plugins.join(', ')); + logger.info(`Added Certbot plugins ${plugins.join(', ')}`); }); } }); @@ -165,9 +165,7 @@ const setupLogrotation = () => { return runLogrotate(); }; -module.exports = function () { - return setupDefaultUser() - .then(setupDefaultSettings) - .then(setupCertbotPlugins) - .then(setupLogrotation); -}; +module.exports = () => setupDefaultUser() + .then(setupDefaultSettings) + .then(setupCertbotPlugins) + .then(setupLogrotation); diff --git a/test/cypress/e2e/api/Certificates.cy.js b/test/cypress/e2e/api/Certificates.cy.js index 9f47edcb..837bde96 100644 --- a/test/cypress/e2e/api/Certificates.cy.js +++ b/test/cypress/e2e/api/Certificates.cy.js @@ -96,4 +96,28 @@ describe('Certificates endpoints', () => { expect(data.error.message).to.contain('data/domain_names/0 must match pattern'); }); }); + + it('Request Certificate - LE Email Escaped', () => { + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/certificates', + data: { + domain_names: ['test.com"||echo hello-world||\\\\n test.com"'], + meta: { + dns_challenge: false, + letsencrypt_agree: true, + letsencrypt_email: "admin@example.com' --version;echo hello-world", + }, + provider: 'letsencrypt', + }, + returnOnError: true, + }).then((data) => { + cy.validateSwaggerSchema('post', 400, '/nginx/certificates', data); + expect(data).to.have.property('error'); + expect(data.error).to.have.property('message'); + expect(data.error).to.have.property('code'); + expect(data.error.code).to.equal(400); + expect(data.error.message).to.contain('data/meta/letsencrypt_email must match pattern'); + }); + }); }); From aff4182ab817e200522083dbac954ddd9fa10876 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:13:31 +0000 Subject: [PATCH 04/82] Bump cipher-base from 1.0.4 to 1.0.6 in /frontend Bumps [cipher-base](https://github.com/crypto-browserify/cipher-base) from 1.0.4 to 1.0.6. - [Changelog](https://github.com/browserify/cipher-base/blob/master/CHANGELOG.md) - [Commits](https://github.com/crypto-browserify/cipher-base/compare/v1.0.4...v1.0.6) --- updated-dependencies: - dependency-name: cipher-base dependency-version: 1.0.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontend/yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a7eca950..34bb59d1 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1982,12 +1982,12 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + version "1.0.6" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.6.tgz#8fe672437d01cd6c4561af5334e0cc50ff1955f7" + integrity sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + inherits "^2.0.4" + safe-buffer "^5.2.1" class-utils@^0.3.5: version "0.3.6" From c8adbdfc15a1e1650cdbdb1205e4b134bd787dd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:45:37 +0000 Subject: [PATCH 05/82] Bump sha.js from 2.4.11 to 2.4.12 in /frontend Bumps [sha.js](https://github.com/crypto-browserify/sha.js) from 2.4.11 to 2.4.12. - [Changelog](https://github.com/browserify/sha.js/blob/master/CHANGELOG.md) - [Commits](https://github.com/crypto-browserify/sha.js/compare/v2.4.11...v2.4.12) --- updated-dependencies: - dependency-name: sha.js dependency-version: 2.4.12 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontend/yarn.lock | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a7eca950..6311bc74 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -6253,12 +6253,13 @@ setimmediate@^1.0.4: integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + version "2.4.12" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f" + integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + inherits "^2.0.4" + safe-buffer "^5.2.1" + to-buffer "^1.2.0" shebang-command@^1.2.0: version "1.2.0" From 4397f57a517fdaa7e445524b98fe0832a9ee17de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 02:38:28 +0000 Subject: [PATCH 06/82] Bump @eslint/plugin-kit from 0.3.2 to 0.3.5 in /test Bumps [@eslint/plugin-kit](https://github.com/eslint/rewrite/tree/HEAD/packages/plugin-kit) from 0.3.2 to 0.3.5. - [Release notes](https://github.com/eslint/rewrite/releases) - [Changelog](https://github.com/eslint/rewrite/blob/main/packages/plugin-kit/CHANGELOG.md) - [Commits](https://github.com/eslint/rewrite/commits/plugin-kit-v0.3.5/packages/plugin-kit) --- updated-dependencies: - dependency-name: "@eslint/plugin-kit" dependency-version: 0.3.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- test/yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/yarn.lock b/test/yarn.lock index 09734b3f..56c06485 100644 --- a/test/yarn.lock +++ b/test/yarn.lock @@ -108,10 +108,10 @@ dependencies: "@types/json-schema" "^7.0.15" -"@eslint/core@^0.15.0": - version "0.15.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.0.tgz#8fc04709a7b9a179d9f7d93068fc000cb8c5603d" - integrity sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw== +"@eslint/core@^0.15.2": + version "0.15.2" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.2.tgz#59386327d7862cc3603ebc7c78159d2dcc4a868f" + integrity sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg== dependencies: "@types/json-schema" "^7.0.15" @@ -141,11 +141,11 @@ integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== "@eslint/plugin-kit@^0.3.1": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz#0cad96b134d23a653348e3342f485636b5ef4732" - integrity sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg== + version "0.3.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz#fd8764f0ee79c8ddab4da65460c641cefee017c5" + integrity sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w== dependencies: - "@eslint/core" "^0.15.0" + "@eslint/core" "^0.15.2" levn "^0.4.1" "@humanfs/core@^0.19.1": From 487fa6d31b10bf168b8654d698e5d36ee40f2f40 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 10 Sep 2025 10:38:21 +1000 Subject: [PATCH 07/82] Attempt to fix frontend build for node 22 replaced node-sass with sass --- frontend/package.json | 4 +- frontend/yarn.lock | 1221 ++++++----------------------------------- 2 files changed, 183 insertions(+), 1042 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 769e9036..7347faa0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,8 +26,8 @@ "messageformat": "^2.3.0", "messageformat-loader": "^0.8.1", "mini-css-extract-plugin": "^0.9.0", - "moment": "^2.29.4", - "node-sass": "^9.0.0", + "moment": "^2.30.1", + "sass": "^1.92.1", "nodemon": "^2.0.2", "numeral": "^2.0.6", "sass-loader": "^10.0.0", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 081913d6..d1522dbe 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -248,11 +248,6 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" - integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== - "@jridgewell/gen-mapping@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" @@ -306,37 +301,94 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" - integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== - dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" +"@parcel/watcher-android-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" + integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA== -"@npmcli/fs@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" - integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== - dependencies: - "@gar/promisify" "^1.1.3" - semver "^7.3.5" +"@parcel/watcher-darwin-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67" + integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw== -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" +"@parcel/watcher-darwin-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8" + integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg== -"@npmcli/move-file@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" - integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== +"@parcel/watcher-freebsd-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b" + integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ== + +"@parcel/watcher-linux-arm-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1" + integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA== + +"@parcel/watcher-linux-arm-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e" + integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q== + +"@parcel/watcher-linux-arm64-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30" + integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w== + +"@parcel/watcher-linux-arm64-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2" + integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg== + +"@parcel/watcher-linux-x64-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e" + integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A== + +"@parcel/watcher-linux-x64-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee" + integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== + +"@parcel/watcher-win32-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243" + integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw== + +"@parcel/watcher-win32-ia32@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6" + integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ== + +"@parcel/watcher-win32-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947" + integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA== + +"@parcel/watcher@^2.4.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200" + integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" + detect-libc "^1.0.3" + is-glob "^4.0.3" + micromatch "^4.0.5" + node-addon-api "^7.0.0" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.5.1" + "@parcel/watcher-darwin-arm64" "2.5.1" + "@parcel/watcher-darwin-x64" "2.5.1" + "@parcel/watcher-freebsd-x64" "2.5.1" + "@parcel/watcher-linux-arm-glibc" "2.5.1" + "@parcel/watcher-linux-arm-musl" "2.5.1" + "@parcel/watcher-linux-arm64-glibc" "2.5.1" + "@parcel/watcher-linux-arm64-musl" "2.5.1" + "@parcel/watcher-linux-x64-glibc" "2.5.1" + "@parcel/watcher-linux-x64-musl" "2.5.1" + "@parcel/watcher-win32-arm64" "2.5.1" + "@parcel/watcher-win32-ia32" "2.5.1" + "@parcel/watcher-win32-x64" "2.5.1" "@sindresorhus/is@^0.14.0": version "0.14.0" @@ -350,16 +402,6 @@ dependencies: defer-to-connect "^1.0.1" -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - -"@tootallnate/once@2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" - integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== - "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -385,21 +427,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/minimist@^1.2.0": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" - integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== - "@types/node@*": version "14.0.27" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g== -"@types/normalize-package-data@^2.4.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" - integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== - "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -632,28 +664,6 @@ acorn@^7.0.0, acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== -agent-base@6, agent-base@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -agentkeepalive@^4.1.3, agentkeepalive@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" - integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== - dependencies: - humanize-ms "^1.2.1" - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" @@ -732,11 +742,6 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -773,24 +778,11 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -"aproba@^1.0.3 || ^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" - integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== - aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -are-we-there-yet@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" - integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== - dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -835,11 +827,6 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -892,11 +879,6 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-foreach@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" - integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= - async@~0.2.6: version "0.2.10" resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" @@ -1564,13 +1546,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" @@ -1594,6 +1569,13 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -1721,54 +1703,6 @@ cacache@^12.0.2, cacache@^12.0.3: unique-filename "^1.1.1" y18n "^4.0.0" -cacache@^15.2.0: - version "15.3.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" - integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - -cacache@^16.1.0: - version "16.1.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" - integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== - dependencies: - "@npmcli/fs" "^2.1.0" - "@npmcli/move-file" "^2.0.0" - chownr "^2.0.0" - fs-minipass "^2.1.0" - glob "^8.0.1" - infer-owner "^1.0.4" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - mkdirp "^1.0.4" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^9.0.0" - tar "^6.1.11" - unique-filename "^2.0.0" - cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -1844,15 +1778,6 @@ camel-case@^4.1.1: pascal-case "^3.1.1" tslib "^1.10.0" -camelcase-keys@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" - integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== - dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.1" - camelcase@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" @@ -1912,14 +1837,6 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -1959,16 +1876,18 @@ chokidar@^3.2.2, chokidar@^3.4.1: optionalDependencies: fsevents "~2.1.2" +chokidar@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" @@ -2006,11 +1925,6 @@ clean-css@4.2.x, clean-css@^4.2.3: dependencies: source-map "~0.6.0" -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - cli-boxes@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" @@ -2055,15 +1969,6 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" @@ -2103,11 +2008,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== - commander@2.17.x: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" @@ -2190,11 +2090,6 @@ console-browserify@^1.1.0: resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -console-control-strings@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== - constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -2309,15 +2204,6 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -2389,13 +2275,6 @@ d3@^3.5.6: resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" integrity sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g= -debug@4, debug@^4.3.3: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -2417,15 +2296,7 @@ debug@^4.0.1, debug@^4.1.0: dependencies: ms "^2.1.1" -decamelize-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= - dependencies: - decamelize "^1.1.0" - map-obj "^1.0.0" - -decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.2.0: +decamelize@^1.0.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -2500,11 +2371,6 @@ defined@^1.0.0: resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - des.js@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" @@ -2525,6 +2391,11 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + detective@^4.3.1: version "4.7.1" resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" @@ -2741,13 +2612,6 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -encoding@^0.1.12, encoding@^0.1.13: - version "0.1.13" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" - integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== - dependencies: - iconv-lite "^0.6.2" - end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -2774,11 +2638,6 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== -env-paths@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - envify@^3.0.0: version "3.4.1" resolved "https://registry.yarnpkg.com/envify/-/envify-3.4.1.tgz#d7122329e8df1688ba771b12501917c9ce5cbce8" @@ -2787,11 +2646,6 @@ envify@^3.0.0: jstransform "^11.0.3" through "~2.3.4" -err-code@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" - integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== - errno@^0.1.3, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" @@ -2799,13 +2653,6 @@ errno@^0.1.3, errno@~0.1.7: dependencies: prr "~1.0.1" -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: version "1.17.6" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" @@ -2849,11 +2696,6 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - escape-goat@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" @@ -3153,6 +2995,13 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -3236,13 +3085,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-minipass@^2.0.0, fs-minipass@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -3286,33 +3128,12 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gauge@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" - integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== - dependencies: - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.3" - console-control-strings "^1.1.0" - has-unicode "^2.0.1" - signal-exit "^3.0.7" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.5" - -gaze@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" - integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== - dependencies: - globule "^1.0.0" - gensync@^1.0.0-beta.1: version "1.0.0-beta.1" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== -get-caller-file@^2.0.1, get-caller-file@^2.0.5: +get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -3341,11 +3162,6 @@ get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -3391,7 +3207,7 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1: +glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -3403,17 +3219,6 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - global-dirs@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" @@ -3498,15 +3303,6 @@ globby@^7.1.1: pify "^3.0.0" slash "^1.0.0" -globule@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.2.tgz#d8bdd9e9e4eef8f96e245999a5dee7eb5d8529c4" - integrity sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA== - dependencies: - glob "~7.1.1" - lodash "~4.17.10" - minimatch "~3.0.2" - gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" @@ -3534,16 +3330,6 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.2.6: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -hard-rejection@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" - integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -3585,11 +3371,6 @@ has-tostringtag@^1.0.2: dependencies: has-symbols "^1.0.3" -has-unicode@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== - has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -3693,18 +3474,6 @@ homedir-polyfill@^1.0.1: dependencies: parse-passwd "^1.0.0" -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -hosted-git-info@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" - integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== - dependencies: - lru-cache "^6.0.0" - html-minifier-terser@^5.0.1: version "5.1.1" resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" @@ -3758,49 +3527,16 @@ htmlparser2@^3.3.0: inherits "^2.0.1" readable-stream "^3.1.1" -http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: +http-cache-semantics@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -http-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" - integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== - dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" - https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== - dependencies: - agent-base "6" - debug "4" - -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - iconv-lite@^0.4.24, iconv-lite@^0.4.5: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -3808,13 +3544,6 @@ iconv-lite@^0.4.24, iconv-lite@^0.4.5: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - icss-utils@^4.0.0, icss-utils@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" @@ -3852,6 +3581,11 @@ ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +immutable@^5.0.2: + version "5.1.3" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.3.tgz#e6486694c8b76c37c063cca92399fa64098634d4" + integrity sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg== + import-fresh@^3.0.0: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" @@ -3886,17 +3620,12 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= -infer-owner@^1.0.3, infer-owner@^1.0.4: +infer-owner@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== @@ -3960,11 +3689,6 @@ invariant@^2.2.2: dependencies: loose-envify "^1.0.0" -ip@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" - integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== - is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -3979,11 +3703,6 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -4020,13 +3739,6 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.5.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz#3c0ef7d31b4acfc574f80c58409d568a836848e3" - integrity sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ== - dependencies: - has "^1.0.3" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -4110,6 +3822,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-installed-globally@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" @@ -4118,11 +3837,6 @@ is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" -is-lambda@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" - integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== - is-npm@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" @@ -4150,7 +3864,7 @@ is-path-inside@^3.0.1: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== -is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: +is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= @@ -4245,11 +3959,6 @@ jquery@^3.5.0: resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== -js-base64@^2.4.9: - version "2.6.4" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" - integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -4293,11 +4002,6 @@ json-parse-better-errors@^1.0.2: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -4364,7 +4068,7 @@ kind-of@^5.0.0: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== -kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: +kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -4394,11 +4098,6 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -4447,7 +4146,7 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@~4.17.10: +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4493,18 +4192,6 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -lru-cache@^7.7.1: - version "7.18.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" - integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== - make-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -4520,50 +4207,6 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-fetch-happen@^10.0.4: - version "10.2.1" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" - integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== - dependencies: - agentkeepalive "^4.2.1" - cacache "^16.1.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-fetch "^2.0.3" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.3" - promise-retry "^2.0.1" - socks-proxy-agent "^7.0.0" - ssri "^9.0.0" - -make-fetch-happen@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" - integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== - dependencies: - agentkeepalive "^4.1.3" - cacache "^15.2.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^6.0.0" - minipass "^3.1.3" - minipass-collect "^1.0.2" - minipass-fetch "^1.3.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.2" - promise-retry "^2.0.1" - socks-proxy-agent "^6.0.0" - ssri "^8.0.0" - make-plural@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-4.3.0.tgz#f23de08efdb0cac2e0c9ba9f315b0dff6b4c2735" @@ -4576,16 +4219,6 @@ map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= -map-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - -map-obj@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" - integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== - map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -4635,24 +4268,6 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -meow@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" - integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== - dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize "^1.2.0" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "4.1.0" - normalize-package-data "^3.0.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.18.0" - yargs-parser "^20.2.3" - merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -4726,6 +4341,14 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" +micromatch@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -4744,11 +4367,6 @@ mimic-response@^1.0.0, mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - mini-css-extract-plugin@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" @@ -4769,111 +4387,18 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimist-options@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" - integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== - dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - kind-of "^6.0.3" - minimist@^1.2.0, minimist@^1.2.5: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-fetch@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" - integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== - dependencies: - minipass "^3.1.0" - minipass-sized "^1.0.3" - minizlib "^2.0.0" - optionalDependencies: - encoding "^0.1.12" - -minipass-fetch@^2.0.3: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" - integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== - dependencies: - minipass "^3.1.6" - minipass-sized "^1.0.3" - minizlib "^2.1.2" - optionalDependencies: - encoding "^0.1.13" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" - integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== - dependencies: - minipass "^3.0.0" - -minipass-sized@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" - integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== - dependencies: - minipass "^3.0.0" - -minipass@^3.0.0: - version "3.1.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732" - integrity sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw== - dependencies: - yallist "^4.0.0" - -minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3, minipass@^3.1.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== - -minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -4905,15 +4430,10 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3: dependencies: minimist "^1.2.5" -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -moment@^2.29.4: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== +moment@^2.30.1: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== move-concurrently@^1.0.1: version "1.0.1" @@ -4932,16 +4452,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@2.1.2, ms@^2.1.1: +ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - mute-stream@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -4952,11 +4467,6 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== -nan@^2.17.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" - integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== - nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -4979,11 +4489,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -negotiator@^0.6.2, negotiator@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" @@ -5009,21 +4514,10 @@ no-case@^3.0.3: lower-case "^2.0.1" tslib "^1.10.0" -node-gyp@^8.4.1: - version "8.4.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" - integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^9.1.0" - nopt "^5.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" +node-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-libs-browser@^2.2.1: version "2.2.1" @@ -5054,26 +4548,6 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-sass@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-9.0.0.tgz#c21cd17bd9379c2d09362b3baf2cbf089bce08ed" - integrity sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg== - dependencies: - async-foreach "^0.1.3" - chalk "^4.1.2" - cross-spawn "^7.0.3" - gaze "^1.0.0" - get-stdin "^4.0.1" - glob "^7.0.3" - lodash "^4.17.15" - make-fetch-happen "^10.0.4" - meow "^9.0.0" - nan "^2.17.0" - node-gyp "^8.4.1" - sass-graph "^4.0.1" - stdout-stream "^1.4.0" - "true-case-path" "^2.2.1" - nodemon@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" @@ -5090,13 +4564,6 @@ nodemon@^2.0.2: undefsafe "^2.0.2" update-notifier "^4.0.0" -nopt@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" - integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== - dependencies: - abbrev "1" - nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -5104,26 +4571,6 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" - integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== - dependencies: - hosted-git-info "^4.0.1" - is-core-module "^2.5.0" - semver "^7.3.4" - validate-npm-package-license "^3.0.1" - normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -5151,16 +4598,6 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== -npmlog@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" - integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== - dependencies: - are-we-there-yet "^3.0.0" - console-control-strings "^1.1.0" - gauge "^4.0.3" - set-blocking "^2.0.0" - nth-check@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" @@ -5301,13 +4738,6 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -5382,16 +4812,6 @@ parse-asn1@^5.1.6: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -5440,11 +4860,6 @@ path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - path-parse@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -5479,6 +4894,11 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" @@ -5610,14 +5030,6 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -promise-retry@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" - integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== - dependencies: - err-code "^2.0.2" - retry "^0.12.0" - promise@^7.0.3: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -5717,11 +5129,6 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -quick-lru@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" - integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== - randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -5765,25 +5172,6 @@ read-input@^0.3.1: resolved "https://registry.yarnpkg.com/read-input/-/read-input-0.3.1.tgz#5b3169308013464ffda6ec92e58d2d3cea948df1" integrity sha1-WzFpMIATRk/9puyS5Y0tPOqUjfE= -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -5824,6 +5212,11 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + readdirp@~3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" @@ -5841,14 +5234,6 @@ recast@^0.11.17: private "~0.1.5" source-map "~0.5.0" -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" - regenerate@^1.2.1: version "1.4.1" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" @@ -5994,7 +5379,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.10.0, resolve@^1.3.2: +resolve@^1.3.2: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -6021,11 +5406,6 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -6059,13 +5439,6 @@ rimraf@^2.5.4, rimraf@^2.6.3: dependencies: glob "^7.1.3" -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - ripemd160@=2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" @@ -6123,21 +5496,11 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-graph@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-4.0.1.tgz#2ff8ca477224d694055bf4093f414cf6cfad1d2e" - integrity sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA== - dependencies: - glob "^7.0.0" - lodash "^4.17.11" - scss-tokenizer "^0.4.3" - yargs "^17.2.1" - sass-loader@^10.0.0: version "10.5.2" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.5.2.tgz#1ca30534fff296417b853c7597ca3b0bbe8c37d0" @@ -6149,6 +5512,17 @@ sass-loader@^10.0.0: schema-utils "^3.0.0" semver "^7.3.2" +sass@^1.92.1: + version "1.92.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.92.1.tgz#07fb1fec5647d7b712685d1090628bf52456fe86" + integrity sha512-ffmsdbwqb3XeyR8jJR6KelIXARM9bFQe8A6Q3W4Klmwy5Ckd5gz7jgUNHo4UOqutU5Sk1DtKLbpDP0nLCg1xqQ== + dependencies: + chokidar "^4.0.0" + immutable "^5.0.2" + source-map-js ">=0.6.2 <2.0.0" + optionalDependencies: + "@parcel/watcher" "^2.4.1" + schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -6176,14 +5550,6 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -scss-tokenizer@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz#1058400ee7d814d71049c29923d2b25e61dc026c" - integrity sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw== - dependencies: - js-base64 "^2.4.9" - source-map "^0.7.3" - semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" @@ -6191,7 +5557,7 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: +semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -6201,12 +5567,10 @@ semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" +semver@^7.3.2: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== serialize-javascript@^2.1.2: version "2.1.2" @@ -6268,33 +5632,16 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" @@ -6314,11 +5661,6 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -smart-buffer@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -6349,32 +5691,6 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -socks-proxy-agent@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" - integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== - dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" - -socks-proxy-agent@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" - integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== - dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" - -socks@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== - dependencies: - ip "^2.0.0" - smart-buffer "^4.2.0" - sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" @@ -6387,6 +5703,11 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== +"source-map-js@>=0.6.2 <2.0.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -6440,32 +5761,6 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== - split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -6485,20 +5780,6 @@ ssri@^6.0.1: dependencies: figgy-pudding "^3.5.1" -ssri@^8.0.0, ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - -ssri@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" - integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== - dependencies: - minipass "^3.1.1" - static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -6507,13 +5788,6 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -stdout-stream@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" - integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== - dependencies: - readable-stream "^2.0.1" - stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -6551,15 +5825,6 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -6629,20 +5894,6 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - strip-json-comments@^3.0.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -6715,18 +5966,6 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: - version "6.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" - integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^5.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - term-size@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" @@ -6856,21 +6095,11 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -trim-newlines@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" - integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== - trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= -"true-case-path@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" - integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== - tslib@^1.10.0, tslib@^1.9.0: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" @@ -6893,16 +6122,6 @@ type-fest@^0.11.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== -type-fest@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" - integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -6991,13 +6210,6 @@ unique-filename@^1.1.1: dependencies: unique-slug "^2.0.0" -unique-filename@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" - integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== - dependencies: - unique-slug "^3.0.0" - unique-slug@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" @@ -7005,13 +6217,6 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unique-slug@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" - integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== - dependencies: - imurmurhash "^0.1.4" - unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -7130,14 +6335,6 @@ v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" @@ -7263,20 +6460,6 @@ which@^1.2.14, which@^1.2.9, which@^1.3.1: dependencies: isexe "^2.0.0" -which@^2.0.1, which@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" - integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== - dependencies: - string-width "^1.0.2 || 2 || 3 || 4" - widest-line@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" @@ -7324,15 +6507,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -7370,21 +6544,11 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - yaml@^1.6.0: version "1.10.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" @@ -7406,16 +6570,6 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.3: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" @@ -7449,19 +6603,6 @@ yargs@^15.0.0: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^17.2.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" From c9aba0c928517d942e0a8d114e564ba37923a0f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:18:55 +0000 Subject: [PATCH 08/82] Bump axios from 1.10.0 to 1.12.0 in /test Bumps [axios](https://github.com/axios/axios) from 1.10.0 to 1.12.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.10.0...v1.12.0) --- updated-dependencies: - dependency-name: axios dependency-version: 1.12.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- test/yarn.lock | 48 +++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/test/yarn.lock b/test/yarn.lock index 09734b3f..6102c710 100644 --- a/test/yarn.lock +++ b/test/yarn.lock @@ -423,12 +423,12 @@ aws4@^1.8.0: integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A== axios@^1.7.7, axios@^1.7.9: - version "1.10.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.10.0.tgz#af320aee8632eaf2a400b6a1979fa75856f38d54" - integrity sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw== + version "1.12.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.0.tgz#11248459be05a5ee493485628fa0e4323d0abfc3" + integrity sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg== dependencies: follow-redirects "^1.15.6" - form-data "^4.0.0" + form-data "^4.0.4" proxy-from-env "^1.1.0" balanced-match@^1.0.0: @@ -856,6 +856,16 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + escalade@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" @@ -1169,22 +1179,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@^4.0.0, form-data@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" - integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -form-data@~4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== +form-data@^4.0.1, form-data@^4.0.4, form-data@~4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" + integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" mime-types "^2.1.12" fs-extra@^9.1.0: @@ -1207,7 +1210,7 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: +get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -1303,11 +1306,18 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.1.0: +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" From 330993f028c19ec6942c1751ec66b552284208fa Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Tue, 2 Sep 2025 21:43:00 +1000 Subject: [PATCH 09/82] Convert backend to ESM - About 5 years overdue - Remove eslint, use bomejs instead --- backend/.eslintrc.json | 73 -- backend/.prettierrc | 11 - backend/app.js | 88 +- backend/biome.json | 91 ++ backend/db.js | 35 +- backend/index.js | 50 +- backend/internal/access-list.js | 435 +++---- backend/internal/audit-log.js | 71 +- backend/internal/certificate.js | 1031 +++++++++-------- backend/internal/dead-host.js | 364 +++--- backend/internal/host.js | 241 ++-- backend/internal/ip_ranges.js | 119 +- backend/internal/nginx.js | 197 ++-- backend/internal/proxy-host.js | 372 +++--- backend/internal/redirection-host.js | 373 +++--- backend/internal/report.js | 35 +- backend/internal/setting.js | 78 +- backend/internal/stream.js | 307 ++--- backend/internal/token.js | 185 ++- backend/internal/user.js | 397 +++---- backend/lib/access.js | 341 +++--- backend/lib/certbot.js | 154 +-- backend/lib/config.js | 284 ++--- backend/lib/error.js | 97 +- backend/lib/express/cors.js | 15 +- backend/lib/express/jwt-decode.js | 12 +- backend/lib/express/jwt.js | 10 +- backend/lib/express/pagination.js | 32 +- backend/lib/express/user-id-from-me.js | 5 +- backend/lib/helpers.js | 112 +- backend/lib/migrate_template.js | 46 +- backend/lib/utils.js | 208 ++-- backend/lib/validator/api.js | 32 +- backend/lib/validator/index.js | 36 +- backend/logger.js | 28 +- backend/migrate.js | 24 +- backend/migrations/20180618015850_initial.js | 267 ++--- .../migrations/20180929054513_websockets.js | 29 +- .../migrations/20181019052346_forward_host.js | 28 +- .../20181113041458_http2_support.js | 37 +- .../20181213013211_forward_scheme.js | 26 +- backend/migrations/20190104035154_disabled.js | 44 +- .../20190215115310_customlocations.js | 26 +- backend/migrations/20190218060101_hsts.js | 44 +- backend/migrations/20190227065017_settings.js | 19 +- .../20200410143839_access_list_client.js | 55 +- .../20200410143840_access_list_client_fix.js | 26 +- .../migrations/20201014143841_pass_auth.js | 36 +- .../20210210154702_redirection_scheme.js | 40 +- .../20210210154703_redirection_status_code.js | 40 +- .../20210423103500_stream_domain.js | 63 +- .../20211108145214_regenerate_default_host.js | 52 +- .../migrations/20240427161436_stream_ssl.js | 37 +- backend/models/access_list.js | 107 +- backend/models/access_list_auth.js | 49 +- backend/models/access_list_client.js | 49 +- backend/models/audit-log.js | 44 +- backend/models/auth.js | 78 +- backend/models/certificate.js | 125 +- backend/models/dead_host.js | 87 +- backend/models/now_helper.js | 13 +- backend/models/proxy_host.js | 112 +- backend/models/redirection_host.js | 95 +- backend/models/setting.js | 6 +- backend/models/stream.js | 81 +- backend/models/token.js | 103 +- backend/models/user.js | 60 +- backend/models/user_permission.js | 8 +- backend/package.json | 29 +- backend/routes/audit-log.js | 50 +- backend/routes/main.js | 70 +- backend/routes/nginx/access_lists.js | 114 +- backend/routes/nginx/certificates.js | 171 +-- backend/routes/nginx/dead_hosts.js | 130 ++- backend/routes/nginx/proxy_hosts.js | 132 +-- backend/routes/nginx/redirection_hosts.js | 136 +-- backend/routes/nginx/streams.js | 132 +-- backend/routes/reports.js | 22 +- backend/routes/schema.js | 28 +- backend/routes/settings.js | 67 +- backend/routes/tokens.js | 37 +- backend/routes/users.js | 138 +-- backend/schema/index.js | 77 +- backend/scripts/install-certbot-plugins | 10 +- backend/setup.js | 128 +- backend/validate-schema.js | 19 +- backend/yarn.lock | 537 +-------- scripts/ci/frontend-build | 2 +- scripts/ci/test-and-build | 2 +- 89 files changed, 4799 insertions(+), 5107 deletions(-) delete mode 100644 backend/.eslintrc.json delete mode 100644 backend/.prettierrc create mode 100644 backend/biome.json mode change 100644 => 100755 backend/validate-schema.js diff --git a/backend/.eslintrc.json b/backend/.eslintrc.json deleted file mode 100644 index 6d6172a4..00000000 --- a/backend/.eslintrc.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "env": { - "node": true, - "es6": true - }, - "extends": [ - "eslint:recommended" - ], - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module" - }, - "plugins": [ - "align-assignments" - ], - "rules": { - "arrow-parens": [ - "error", - "always" - ], - "indent": [ - "error", - "tab" - ], - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "error", - "single" - ], - "semi": [ - "error", - "always" - ], - "key-spacing": [ - "error", - { - "align": "value" - } - ], - "comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "func-call-spacing": [ - "error", - "never" - ], - "keyword-spacing": [ - "error", - { - "before": true - } - ], - "no-irregular-whitespace": "error", - "no-unused-expressions": 0, - "align-assignments/align-assignments": [ - 2, - { - "requiresOnly": false - } - ] - } -} \ No newline at end of file diff --git a/backend/.prettierrc b/backend/.prettierrc deleted file mode 100644 index fefbcfa6..00000000 --- a/backend/.prettierrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "printWidth": 320, - "tabWidth": 4, - "useTabs": true, - "semi": true, - "singleQuote": true, - "bracketSpacing": true, - "jsxBracketSameLine": true, - "trailingComma": "all", - "proseWrap": "always" -} diff --git a/backend/app.js b/backend/app.js index 59f7def2..857db882 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1,9 +1,12 @@ -const express = require('express'); -const bodyParser = require('body-parser'); -const fileUpload = require('express-fileupload'); -const compression = require('compression'); -const config = require('./lib/config'); -const log = require('./logger').express; +import bodyParser from "body-parser"; +import compression from "compression"; +import express from "express"; +import fileUpload from "express-fileupload"; +import { isDebugMode } from "./lib/config.js"; +import cors from "./lib/express/cors.js"; +import jwt from "./lib/express/jwt.js"; +import { express as logger } from "./logger.js"; +import mainRoutes from "./routes/main.js"; /** * App @@ -11,7 +14,7 @@ const log = require('./logger').express; const app = express(); app.use(fileUpload()); app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({extended: true})); +app.use(bodyParser.urlencoded({ extended: true })); // Gzip app.use(compression()); @@ -20,71 +23,70 @@ app.use(compression()); * General Logging, BEFORE routes */ -app.disable('x-powered-by'); -app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); -app.enable('strict routing'); +app.disable("x-powered-by"); +app.enable("trust proxy", ["loopback", "linklocal", "uniquelocal"]); +app.enable("strict routing"); // pretty print JSON when not live -if (config.debug()) { - app.set('json spaces', 2); +if (isDebugMode()) { + app.set("json spaces", 2); } // CORS for everything -app.use(require('./lib/express/cors')); +app.use(cors); // General security/cache related headers + server header -app.use(function (req, res, next) { - let x_frame_options = 'DENY'; +app.use((_, res, next) => { + let x_frame_options = "DENY"; - if (typeof process.env.X_FRAME_OPTIONS !== 'undefined' && process.env.X_FRAME_OPTIONS) { + if (typeof process.env.X_FRAME_OPTIONS !== "undefined" && process.env.X_FRAME_OPTIONS) { x_frame_options = process.env.X_FRAME_OPTIONS; } res.set({ - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Frame-Options': x_frame_options, - 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', - Pragma: 'no-cache', - Expires: 0 + "X-XSS-Protection": "1; mode=block", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": x_frame_options, + "Cache-Control": "no-cache, no-store, max-age=0, must-revalidate", + Pragma: "no-cache", + Expires: 0, }); next(); }); -app.use(require('./lib/express/jwt')()); -app.use('/', require('./routes/main')); +app.use(jwt()); +app.use("/", mainRoutes); // production error handler // no stacktraces leaked to user -// eslint-disable-next-line -app.use(function (err, req, res, next) { - - let payload = { +app.use((err, req, res, _) => { + const payload = { error: { - code: err.status, - message: err.public ? err.message : 'Internal Error' - } + code: err.status, + message: err.public ? err.message : "Internal Error", + }, }; - if (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) { + if (typeof err.message_i18n !== "undefined") { + payload.error.message_i18n = err.message_i18n; + } + + if (isDebugMode() || (req.baseUrl + req.path).includes("nginx/certificates")) { payload.debug = { - stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null, - previous: err.previous + stack: typeof err.stack !== "undefined" && err.stack ? err.stack.split("\n") : null, + previous: err.previous, }; } // Not every error is worth logging - but this is good for now until it gets annoying. - if (typeof err.stack !== 'undefined' && err.stack) { - if (config.debug()) { - log.debug(err.stack); - } else if (typeof err.public == 'undefined' || !err.public) { - log.warn(err.message); + if (typeof err.stack !== "undefined" && err.stack) { + logger.debug(err.stack); + if (typeof err.public === "undefined" || !err.public) { + logger.warn(err.message); } } - res - .status(err.status || 500) - .send(payload); + res.status(err.status || 500).send(payload); }); -module.exports = app; +export default app; diff --git a/backend/biome.json b/backend/biome.json new file mode 100644 index 00000000..a7d7539f --- /dev/null +++ b/backend/biome.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false, + "includes": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "!**/dist/**/*" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "indentWidth": 4, + "lineWidth": 120, + "formatWithErrors": true + }, + "assist": { + "actions": { + "source": { + "organizeImports": { + "level": "on", + "options": { + "groups": [ + ":BUN:", + ":NODE:", + [ + "npm:*", + "npm:*/**" + ], + ":PACKAGE_WITH_PROTOCOL:", + ":URL:", + ":PACKAGE:", + [ + "/src/*", + "/src/**" + ], + [ + "/**" + ], + [ + "#*", + "#*/**" + ], + ":PATH:" + ] + } + } + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "useUniqueElementIds": "off" + }, + "suspicious": { + "noExplicitAny": "off" + }, + "performance": { + "noDelete": "off" + }, + "nursery": "off", + "a11y": { + "useSemanticElements": "off", + "useValidAnchor": "off" + }, + "style": { + "noParameterAssign": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSelfClosingElements": "error", + "useSingleVarDeclarator": "error", + "noUnusedTemplateLiteral": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error", + "noUselessElse": "error" + } + } + } +} diff --git a/backend/db.js b/backend/db.js index 1a8b1634..6fe47b6f 100644 --- a/backend/db.js +++ b/backend/db.js @@ -1,27 +1,32 @@ -const config = require('./lib/config'); +import knex from "knex"; +import {configGet, configHas} from "./lib/config.js"; -if (!config.has('database')) { - throw new Error('Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/'); -} +const generateDbConfig = () => { + if (!configHas("database")) { + throw new Error( + "Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/", + ); + } -function generateDbConfig() { - const cfg = config.get('database'); - if (cfg.engine === 'knex-native') { + const cfg = configGet("database"); + + if (cfg.engine === "knex-native") { return cfg.knex; } + return { - client: cfg.engine, + client: cfg.engine, connection: { - host: cfg.host, - user: cfg.user, + host: cfg.host, + user: cfg.user, password: cfg.password, database: cfg.name, - port: cfg.port + port: cfg.port, }, migrations: { - tableName: 'migrations' - } + tableName: "migrations", + }, }; -} +}; -module.exports = require('knex')(generateDbConfig()); +export default knex(generateDbConfig()); diff --git a/backend/index.js b/backend/index.js index d334a7c2..00285667 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,48 +1,47 @@ #!/usr/bin/env node -const schema = require('./schema'); -const logger = require('./logger').global; +import app from "./app.js"; +import internalCertificate from "./internal/certificate.js"; +import internalIpRanges from "./internal/ip_ranges.js"; +import { global as logger } from "./logger.js"; +import { migrateUp } from "./migrate.js"; +import { getCompiledSchema } from "./schema/index.js"; +import setup from "./setup.js"; -const IP_RANGES_FETCH_ENABLED = process.env.IP_RANGES_FETCH_ENABLED !== 'false'; +const IP_RANGES_FETCH_ENABLED = process.env.IP_RANGES_FETCH_ENABLED !== "false"; -async function appStart () { - const migrate = require('./migrate'); - const setup = require('./setup'); - const app = require('./app'); - const internalCertificate = require('./internal/certificate'); - const internalIpRanges = require('./internal/ip_ranges'); - - return migrate.latest() +async function appStart() { + return migrateUp() .then(setup) - .then(schema.getCompiledSchema) + .then(getCompiledSchema) .then(() => { - if (IP_RANGES_FETCH_ENABLED) { - logger.info('IP Ranges fetch is enabled'); - return internalIpRanges.fetch().catch((err) => { - logger.error('IP Ranges fetch failed, continuing anyway:', err.message); - }); - } else { - logger.info('IP Ranges fetch is disabled by environment variable'); + if (!IP_RANGES_FETCH_ENABLED) { + logger.info("IP Ranges fetch is disabled by environment variable"); + return; } + logger.info("IP Ranges fetch is enabled"); + return internalIpRanges.fetch().catch((err) => { + logger.error("IP Ranges fetch failed, continuing anyway:", err.message); + }); }) .then(() => { internalCertificate.initTimer(); internalIpRanges.initTimer(); const server = app.listen(3000, () => { - logger.info('Backend PID ' + process.pid + ' listening on port 3000 ...'); + logger.info(`Backend PID ${process.pid} listening on port 3000 ...`); - process.on('SIGTERM', () => { - logger.info('PID ' + process.pid + ' received SIGTERM'); + process.on("SIGTERM", () => { + logger.info(`PID ${process.pid} received SIGTERM`); server.close(() => { - logger.info('Stopping.'); + logger.info("Stopping."); process.exit(0); }); }); }); }) .catch((err) => { - logger.error(err.message, err); + logger.error(`Startup Error: ${err.message}`, err); setTimeout(appStart, 1000); }); } @@ -50,7 +49,6 @@ async function appStart () { try { appStart(); } catch (err) { - logger.error(err.message, err); + logger.fatal(err); process.exit(1); } - diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 2407a0ac..c2bd1f61 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -1,37 +1,37 @@ -const _ = require('lodash'); -const fs = require('node:fs'); -const batchflow = require('batchflow'); -const logger = require('../logger').access; -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const accessListModel = require('../models/access_list'); -const accessListAuthModel = require('../models/access_list_auth'); -const accessListClientModel = require('../models/access_list_client'); -const proxyHostModel = require('../models/proxy_host'); -const internalAuditLog = require('./audit-log'); -const internalNginx = require('./nginx'); +import fs from "node:fs"; +import batchflow from "batchflow"; +import _ from "lodash"; +import errs from "../lib/error.js"; +import utils from "../lib/utils.js"; +import { access as logger } from "../logger.js"; +import accessListModel from "../models/access_list.js"; +import accessListAuthModel from "../models/access_list_auth.js"; +import accessListClientModel from "../models/access_list_client.js"; +import proxyHostModel from "../models/proxy_host.js"; +import internalAuditLog from "./audit-log.js"; +import internalNginx from "./nginx.js"; -function omissions () { - return ['is_deleted']; -} +const omissions = () => { + return ["is_deleted"]; +}; const internalAccessList = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ create: (access, data) => { - return access.can('access_lists:create', data) + return access + .can("access_lists:create", data) .then((/*access_data*/) => { return accessListModel .query() .insertAndFetch({ - name: data.name, - satisfy_any: data.satisfy_any, - pass_auth: data.pass_auth, - owner_user_id: access.token.getUserId(1) + name: data.name, + satisfy_any: data.satisfy_any, + pass_auth: data.pass_auth, + owner_user_id: access.token.getUserId(1), }) .then(utils.omitRow(omissions())); }) @@ -42,27 +42,27 @@ const internalAccessList = { // Now add the items data.items.map((item) => { - promises.push(accessListAuthModel - .query() - .insert({ + promises.push( + accessListAuthModel.query().insert({ access_list_id: row.id, - username: item.username, - password: item.password - }) + username: item.username, + password: item.password, + }), ); + return true; }); // Now add the clients - if (typeof data.clients !== 'undefined' && data.clients) { + if (typeof data.clients !== "undefined" && data.clients) { data.clients.map((client) => { - promises.push(accessListClientModel - .query() - .insert({ + promises.push( + accessListClientModel.query().insert({ access_list_id: row.id, - address: client.address, - directive: client.directive - }) + address: client.address, + directive: client.directive, + }), ); + return true; }); } @@ -70,28 +70,33 @@ const internalAccessList = { }) .then(() => { // re-fetch with expansions - return internalAccessList.get(access, { - id: data.id, - expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]'] - }, true /* <- skip masking */); + return internalAccessList.get( + access, + { + id: data.id, + expand: ["owner", "items", "clients", "proxy_hosts.access_list.[clients,items]"], + }, + true /* <- skip masking */, + ); }) .then((row) => { // Audit log data.meta = _.assign({}, data.meta || {}, row.meta); - return internalAccessList.build(row) + return internalAccessList + .build(row) .then(() => { - if (parseInt(row.proxy_host_count, 10)) { - return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); + if (Number.parseInt(row.proxy_host_count, 10)) { + return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); } }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'created', - object_type: 'access-list', - object_id: row.id, - meta: internalAccessList.maskItems(data) + action: "created", + object_type: "access-list", + object_id: row.id, + meta: internalAccessList.maskItems(data), }); }) .then(() => { @@ -109,124 +114,122 @@ const internalAccessList = { * @return {Promise} */ update: (access, data) => { - return access.can('access_lists:update', data.id) + return access + .can("access_lists:update", data.id) .then((/*access_data*/) => { - return internalAccessList.get(access, {id: data.id}); + return internalAccessList.get(access, { id: data.id }); }) .then((row) => { if (row.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError(`Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`); + throw new errs.InternalValidationError( + `Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); } }) .then(() => { // patch name if specified - if (typeof data.name !== 'undefined' && data.name) { - return accessListModel - .query() - .where({id: data.id}) - .patch({ - name: data.name, - satisfy_any: data.satisfy_any, - pass_auth: data.pass_auth, - }); + if (typeof data.name !== "undefined" && data.name) { + return accessListModel.query().where({ id: data.id }).patch({ + name: data.name, + satisfy_any: data.satisfy_any, + pass_auth: data.pass_auth, + }); } }) .then(() => { // Check for items and add/update/remove them - if (typeof data.items !== 'undefined' && data.items) { - const promises = []; + if (typeof data.items !== "undefined" && data.items) { + const promises = []; const items_to_keep = []; data.items.map((item) => { if (item.password) { - promises.push(accessListAuthModel - .query() - .insert({ + promises.push( + accessListAuthModel.query().insert({ access_list_id: data.id, - username: item.username, - password: item.password - }) + username: item.username, + password: item.password, + }), ); } else { // This was supplied with an empty password, which means keep it but don't change the password items_to_keep.push(item.username); } + return true; }); - const query = accessListAuthModel - .query() - .delete() - .where('access_list_id', data.id); + const query = accessListAuthModel.query().delete().where("access_list_id", data.id); if (items_to_keep.length) { - query.andWhere('username', 'NOT IN', items_to_keep); + query.andWhere("username", "NOT IN", items_to_keep); } - return query - .then(() => { - // Add new items - if (promises.length) { - return Promise.all(promises); - } - }); + return query.then(() => { + // Add new items + if (promises.length) { + return Promise.all(promises); + } + }); } }) .then(() => { // Check for clients and add/update/remove them - if (typeof data.clients !== 'undefined' && data.clients) { + if (typeof data.clients !== "undefined" && data.clients) { const promises = []; data.clients.map((client) => { if (client.address) { - promises.push(accessListClientModel - .query() - .insert({ + promises.push( + accessListClientModel.query().insert({ access_list_id: data.id, - address: client.address, - directive: client.directive - }) + address: client.address, + directive: client.directive, + }), ); } + return true; }); - const query = accessListClientModel - .query() - .delete() - .where('access_list_id', data.id); + const query = accessListClientModel.query().delete().where("access_list_id", data.id); - return query - .then(() => { - // Add new items - if (promises.length) { - return Promise.all(promises); - } - }); + return query.then(() => { + // Add new items + if (promises.length) { + return Promise.all(promises); + } + }); } }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'updated', - object_type: 'access-list', - object_id: data.id, - meta: internalAccessList.maskItems(data) + action: "updated", + object_type: "access-list", + object_id: data.id, + meta: internalAccessList.maskItems(data), }); }) .then(() => { // re-fetch with expansions - return internalAccessList.get(access, { - id: data.id, - expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]'] - }, true /* <- skip masking */); + return internalAccessList.get( + access, + { + id: data.id, + expand: ["owner", "items", "clients", "proxy_hosts.[certificate,access_list.[clients,items]]"], + }, + true /* <- skip masking */, + ); }) .then((row) => { - return internalAccessList.build(row) + return internalAccessList + .build(row) .then(() => { - if (parseInt(row.proxy_host_count, 10)) { - return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); + if (Number.parseInt(row.proxy_host_count, 10)) { + return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); } - }).then(internalNginx.reload) + }) + .then(internalNginx.reload) .then(() => { return internalAccessList.maskItems(row); }); @@ -243,47 +246,50 @@ const internalAccessList = { * @return {Promise} */ get: (access, data, skip_masking) => { - if (typeof data === 'undefined') { - data = {}; - } + const thisData = data || {}; - return access.can('access_lists:get', data.id) - .then((access_data) => { + return access + .can("access_lists:get", thisData.id) + .then((accessData) => { const query = accessListModel .query() - .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) - .leftJoin('proxy_host', function() { - this.on('proxy_host.access_list_id', '=', 'access_list.id') - .andOn('proxy_host.is_deleted', '=', 0); + .select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) + .leftJoin("proxy_host", function () { + this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( + "proxy_host.is_deleted", + "=", + 0, + ); }) - .where('access_list.is_deleted', 0) - .andWhere('access_list.id', data.id) - .groupBy('access_list.id') - .allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]') + .where("access_list.is_deleted", 0) + .andWhere("access_list.id", thisData.id) + .groupBy("access_list.id") + .allowGraph("[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]") .first(); - if (access_data.permission_visibility !== 'all') { - query.andWhere('access_list.owner_user_id', access.token.getUserId(1)); + if (accessData.permission_visibility !== "all") { + query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched(`[${data.expand.join(', ')}]`); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { + let thisRow = row; if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(thisData.id); } - if (!skip_masking && typeof row.items !== 'undefined' && row.items) { - row = internalAccessList.maskItems(row); + if (!skip_masking && typeof thisRow.items !== "undefined" && thisRow.items) { + thisRow = internalAccessList.maskItems(thisRow); } // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof data.omit !== "undefined" && data.omit !== null) { + thisRow = _.omit(thisRow, data.omit); } - return row; + return thisRow; }); }, @@ -295,13 +301,14 @@ const internalAccessList = { * @returns {Promise} */ delete: (access, data) => { - return access.can('access_lists:delete', data.id) + return access + .can("access_lists:delete", data.id) .then(() => { - return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']}); + return internalAccessList.get(access, { id: data.id, expand: ["proxy_hosts", "items", "clients"] }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(data.id); } // 1. update row to be deleted @@ -312,26 +319,27 @@ const internalAccessList = { // 1. update row to be deleted return accessListModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // 2. update any proxy hosts that were using it (ignoring permissions) if (row.proxy_hosts) { return proxyHostModel .query() - .where('access_list_id', '=', row.id) - .patch({access_list_id: 0}) + .where("access_list_id", "=", row.id) + .patch({ access_list_id: 0 }) .then(() => { // 3. reconfigure those hosts, then reload nginx // set the access_list_id to zero for these items row.proxy_hosts.map((_val, idx) => { row.proxy_hosts[idx].access_list_id = 0; + return true; }); - return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); + return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); }) .then(() => { return internalNginx.reload(); @@ -351,10 +359,10 @@ const internalAccessList = { .then(() => { // 4. audit log return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'access-list', - object_id: row.id, - meta: _.omit(internalAccessList.maskItems(row), ['is_deleted', 'proxy_hosts']) + action: "deleted", + object_type: "access-list", + object_id: row.id, + meta: _.omit(internalAccessList.maskItems(row), ["is_deleted", "proxy_hosts"]), }); }); }) @@ -372,33 +380,37 @@ const internalAccessList = { * @returns {Promise} */ getAll: (access, expand, search_query) => { - return access.can('access_lists:list') + return access + .can("access_lists:list") .then((access_data) => { const query = accessListModel .query() - .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) - .leftJoin('proxy_host', function() { - this.on('proxy_host.access_list_id', '=', 'access_list.id') - .andOn('proxy_host.is_deleted', '=', 0); + .select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) + .leftJoin("proxy_host", function () { + this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( + "proxy_host.is_deleted", + "=", + 0, + ); }) - .where('access_list.is_deleted', 0) - .groupBy('access_list.id') - .allowGraph('[owner,items,clients]') - .orderBy('access_list.name', 'ASC'); + .where("access_list.is_deleted", 0) + .groupBy("access_list.id") + .allowGraph("[owner,items,clients]") + .orderBy("access_list.name", "ASC"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('access_list.owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); } // Query is used for searching - if (typeof search_query === 'string') { + if (typeof search_query === "string") { query.where(function () { - this.where('name', 'like', `%${search_query}%`); + this.where("name", "like", `%${search_query}%`); }); } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched(`[${expand.join(', ')}]`); + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); } return query.then(utils.omitRows(omissions())); @@ -406,9 +418,10 @@ const internalAccessList = { .then((rows) => { if (rows) { rows.map((row, idx) => { - if (typeof row.items !== 'undefined' && row.items) { + if (typeof row.items !== "undefined" && row.items) { rows[idx] = internalAccessList.maskItems(row); } + return true; }); } @@ -424,19 +437,15 @@ const internalAccessList = { * @returns {Promise} */ getCount: (user_id, visibility) => { - const query = accessListModel - .query() - .count('id as count') - .where('is_deleted', 0); + const query = accessListModel.query().count("id as count").where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", user_id); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); + return query.first().then((row) => { + return Number.parseInt(row.count, 10); + }); }, /** @@ -444,18 +453,19 @@ const internalAccessList = { * @returns {Object} */ maskItems: (list) => { - if (list && typeof list.items !== 'undefined') { + if (list && typeof list.items !== "undefined") { list.items.map((val, idx) => { let repeat_for = 8; - let first_char = '*'; + let first_char = "*"; - if (typeof val.password !== 'undefined' && val.password) { + if (typeof val.password !== "undefined" && val.password) { repeat_for = val.password.length - 1; first_char = val.password.charAt(0); } - list.items[idx].hint = first_char + ('*').repeat(repeat_for); - list.items[idx].password = ''; + list.items[idx].hint = first_char + "*".repeat(repeat_for); + list.items[idx].password = ""; + return true; }); } @@ -493,48 +503,51 @@ const internalAccessList = { // 2. create empty access file try { - fs.writeFileSync(htpasswd_file, '', {encoding: 'utf8'}); + fs.writeFileSync(htpasswd_file, "", { encoding: "utf8" }); resolve(htpasswd_file); } catch (err) { reject(err); } - }) - .then((htpasswd_file) => { - // 3. generate password for each user - if (list.items.length) { - return new Promise((resolve, reject) => { - batchflow(list.items).sequential() - .each((_i, item, next) => { - if (typeof item.password !== 'undefined' && item.password.length) { - logger.info(`Adding: ${item.username}`); + }).then((htpasswd_file) => { + // 3. generate password for each user + if (list.items.length) { + return new Promise((resolve, reject) => { + batchflow(list.items) + .sequential() + .each((_i, item, next) => { + if (typeof item.password !== "undefined" && item.password.length) { + logger.info(`Adding: ${item.username}`); - utils.execFile('openssl', ['passwd', '-apr1', item.password]) - .then((res) => { - try { - fs.appendFileSync(htpasswd_file, `${item.username}:${res}\n`, {encoding: 'utf8'}); - } catch (err) { - reject(err); - } - next(); - }) - .catch((err) => { - logger.error(err); - next(err); - }); - } - }) - .error((err) => { - logger.error(err); - reject(err); - }) - .end((results) => { - logger.success(`Built Access file #${list.id} for: ${list.name}`); - resolve(results); - }); - }); - } - }); - } + utils + .execFile("openssl", ["passwd", "-apr1", item.password]) + .then((res) => { + try { + fs.appendFileSync(htpasswd_file, `${item.username}:${res}\n`, { + encoding: "utf8", + }); + } catch (err) { + reject(err); + } + next(); + }) + .catch((err) => { + logger.error(err); + next(err); + }); + } + }) + .error((err) => { + logger.error(err); + reject(err); + }) + .end((results) => { + logger.success(`Built Access file #${list.id} for: ${list.name}`); + resolve(results); + }); + }); + } + }); + }, }; -module.exports = internalAccessList; +export default internalAccessList; diff --git a/backend/internal/audit-log.js b/backend/internal/audit-log.js index 60bdd2ef..5a81976b 100644 --- a/backend/internal/audit-log.js +++ b/backend/internal/audit-log.js @@ -1,6 +1,6 @@ -const error = require('../lib/error'); -const auditLogModel = require('../models/audit-log'); -const {castJsonIfNeed} = require('../lib/helpers'); +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import auditLogModel from "../models/audit-log.js"; const internalAuditLog = { @@ -13,28 +13,27 @@ const internalAuditLog = { * @returns {Promise} */ getAll: (access, expand, search_query) => { - return access.can('auditlog:list') - .then(() => { - let query = auditLogModel - .query() - .orderBy('created_on', 'DESC') - .orderBy('id', 'DESC') - .limit(100) - .allowGraph('[user]'); + return access.can("auditlog:list").then(() => { + const query = auditLogModel + .query() + .orderBy("created_on", "DESC") + .orderBy("id", "DESC") + .limit(100) + .allowGraph("[user]"); - // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { - query.where(function () { - this.where(castJsonIfNeed('meta'), 'like', '%' + search_query + '%'); - }); - } + // Query is used for searching + if (typeof search_query === "string" && search_query.length > 0) { + query.where(function () { + this.where(castJsonIfNeed("meta"), "like", `%${search_query}`); + }); + } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } - return query; - }); + return query; + }); }, /** @@ -54,26 +53,26 @@ const internalAuditLog = { add: (access, data) => { return new Promise((resolve, reject) => { // Default the user id - if (typeof data.user_id === 'undefined' || !data.user_id) { + if (typeof data.user_id === "undefined" || !data.user_id) { data.user_id = access.token.getUserId(1); } - if (typeof data.action === 'undefined' || !data.action) { - reject(new error.InternalValidationError('Audit log entry must contain an Action')); + if (typeof data.action === "undefined" || !data.action) { + reject(new errs.InternalValidationError("Audit log entry must contain an Action")); } else { // Make sure at least 1 of the IDs are set and action - resolve(auditLogModel - .query() - .insert({ - user_id: data.user_id, - action: data.action, - object_type: data.object_type || '', - object_id: data.object_id || 0, - meta: data.meta || {} - })); + resolve( + auditLogModel.query().insert({ + user_id: data.user_id, + action: data.action, + object_type: data.object_type || "", + object_id: data.object_id || 0, + meta: data.meta || {}, + }), + ); } }); - } + }, }; -module.exports = internalAuditLog; +export default internalAuditLog; diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 55e74c3e..f5a93635 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -1,44 +1,42 @@ -const _ = require('lodash'); -const fs = require('node:fs'); -const https = require('node:https'); -const tempWrite = require('temp-write'); -const moment = require('moment'); -const archiver = require('archiver'); -const path = require('path'); -const { isArray } = require('lodash'); -const logger = require('../logger').ssl; -const config = require('../lib/config'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const certbot = require('../lib/certbot'); -const certificateModel = require('../models/certificate'); -const tokenModel = require('../models/token'); -const dnsPlugins = require('../global/certbot-dns-plugins.json'); -const internalAuditLog = require('./audit-log'); -const internalNginx = require('./nginx'); -const internalHost = require('./host'); +import fs from "node:fs"; +import https from "node:https"; +import path from "path"; +import archiver from "archiver"; +import _ from "lodash"; +import moment from "moment"; +import tempWrite from "temp-write"; +import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; +import { installPlugin } from "../lib/certbot.js"; +import { useLetsencryptServer, useLetsencryptStaging } from "../lib/config.js"; +import error from "../lib/error.js"; +import utils from "../lib/utils.js"; +import { ssl as logger } from "../logger.js"; +import certificateModel from "../models/certificate.js"; +import tokenModel from "../models/token.js"; +import internalAuditLog from "./audit-log.js"; +import internalHost from "./host.js"; +import internalNginx from "./nginx.js"; +const letsencryptConfig = "/etc/letsencrypt.ini"; +const certbotCommand = "certbot"; -const letsencryptStaging = config.useLetsencryptStaging(); -const letsencryptServer = config.useLetsencryptServer(); -const letsencryptConfig = '/etc/letsencrypt.ini'; -const certbotCommand = 'certbot'; - -function omissions() { - return ['is_deleted', 'owner.is_deleted']; -} +const omissions = () => { + return ["is_deleted", "owner.is_deleted"]; +}; const internalCertificate = { - - allowedSslFiles: ['certificate', 'certificate_key', 'intermediate_certificate'], - intervalTimeout: 1000 * 60 * 60, // 1 hour - interval: null, - intervalProcessing: false, - renewBeforeExpirationBy: [30, 'days'], + allowedSslFiles: ["certificate", "certificate_key", "intermediate_certificate"], + intervalTimeout: 1000 * 60 * 60, // 1 hour + interval: null, + intervalProcessing: false, + renewBeforeExpirationBy: [30, "days"], initTimer: () => { - logger.info('Let\'s Encrypt Renewal Timer initialized'); - internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.intervalTimeout); + logger.info("Let's Encrypt Renewal Timer initialized"); + internalCertificate.interval = setInterval( + internalCertificate.processExpiringHosts, + internalCertificate.intervalTimeout, + ); // And do this now as well internalCertificate.processExpiringHosts(); }, @@ -49,16 +47,20 @@ const internalCertificate = { processExpiringHosts: () => { if (!internalCertificate.intervalProcessing) { internalCertificate.intervalProcessing = true; - logger.info(`Renewing SSL certs expiring within ${internalCertificate.renewBeforeExpirationBy[0]} ${internalCertificate.renewBeforeExpirationBy[1]} ...`); + logger.info( + `Renewing SSL certs expiring within ${internalCertificate.renewBeforeExpirationBy[0]} ${internalCertificate.renewBeforeExpirationBy[1]} ...`, + ); - const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss'); + const expirationThreshold = moment() + .add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]) + .format("YYYY-MM-DD HH:mm:ss"); // Fetch all the letsencrypt certs from the db that will expire within the configured threshold certificateModel .query() - .where('is_deleted', 0) - .andWhere('provider', 'letsencrypt') - .andWhere('expires_on', '<', expirationThreshold) + .where("is_deleted", 0) + .andWhere("provider", "letsencrypt") + .andWhere("expires_on", "<", expirationThreshold) .then((certificates) => { if (!certificates || !certificates.length) { return null; @@ -77,7 +79,7 @@ const internalCertificate = { { can: () => Promise.resolve({ - permission_visibility: 'all', + permission_visibility: "all", }), token: new tokenModel(), }, @@ -93,7 +95,7 @@ const internalCertificate = { return sequence; }) .then(() => { - logger.info('Completed SSL cert renew process'); + logger.info("Completed SSL cert renew process"); internalCertificate.intervalProcessing = false; }) .catch((err) => { @@ -109,21 +111,19 @@ const internalCertificate = { * @returns {Promise} */ create: (access, data) => { - return access.can('certificates:create', data) + return access + .can("certificates:create", data) .then(() => { data.owner_user_id = access.token.getUserId(1); - if (data.provider === 'letsencrypt') { - data.nice_name = data.domain_names.join(', '); + if (data.provider === "letsencrypt") { + data.nice_name = data.domain_names.join(", "); } - return certificateModel - .query() - .insertAndFetch(data) - .then(utils.omitRow(omissions())); + return certificateModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); }) .then((certificate) => { - if (certificate.provider === 'letsencrypt') { + 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 @@ -134,49 +134,22 @@ const internalCertificate = { // 6. Re-instate previously disabled hosts // 1. Find out any hosts that are using any of the hostnames in this cert - return internalHost.getHostsWithDomains(certificate.domain_names) + return internalHost + .getHostsWithDomains(certificate.domain_names) .then((in_use_result) => { // 2. Disable them in nginx temporarily - return internalCertificate.disableInUseHosts(in_use_result) - .then(() => { - return in_use_result; - }); + return internalCertificate.disableInUseHosts(in_use_result).then(() => { + return in_use_result; + }); }) .then((in_use_result) => { // With DNS challenge no config is needed, so skip 3 and 5. if (certificate.meta.dns_challenge) { - return internalNginx.reload().then(() => { - // 4. Request cert - return internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate); - }) - .then(internalNginx.reload) - .then(() => { - // 6. Re-instate previously disabled hosts - return internalCertificate.enableInUseHosts(in_use_result); - }) - .then(() => { - return certificate; - }) - .catch((err) => { - // In the event of failure, revert things and throw err back - return internalCertificate.enableInUseHosts(in_use_result) - .then(internalNginx.reload) - .then(() => { - throw err; - }); - }); - } else { - // 3. Generate the LE config - return internalNginx.generateLetsEncryptRequestConfig(certificate) - .then(internalNginx.reload) - .then(async() => await new Promise((r) => setTimeout(r, 5000))) + return internalNginx + .reload() .then(() => { // 4. Request cert - return internalCertificate.requestLetsEncryptSsl(certificate); - }) - .then(() => { - // 5. Remove LE config - return internalNginx.deleteLetsEncryptRequestConfig(certificate); + return internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate); }) .then(internalNginx.reload) .then(() => { @@ -188,59 +161,92 @@ const internalCertificate = { }) .catch((err) => { // In the event of failure, revert things and throw err back - return internalNginx.deleteLetsEncryptRequestConfig(certificate) - .then(() => { - return internalCertificate.enableInUseHosts(in_use_result); - }) + return internalCertificate + .enableInUseHosts(in_use_result) .then(internalNginx.reload) .then(() => { throw err; }); }); } + // 3. Generate the LE config + return internalNginx + .generateLetsEncryptRequestConfig(certificate) + .then(internalNginx.reload) + .then(async () => await new Promise((r) => setTimeout(r, 5000))) + .then(() => { + // 4. Request cert + return internalCertificate.requestLetsEncryptSsl(certificate); + }) + .then(() => { + // 5. Remove LE config + return internalNginx.deleteLetsEncryptRequestConfig(certificate); + }) + .then(internalNginx.reload) + .then(() => { + // 6. Re-instate previously disabled hosts + return internalCertificate.enableInUseHosts(in_use_result); + }) + .then(() => { + return certificate; + }) + .catch((err) => { + // In the event of failure, revert things and throw err back + return internalNginx + .deleteLetsEncryptRequestConfig(certificate) + .then(() => { + return internalCertificate.enableInUseHosts(in_use_result); + }) + .then(internalNginx.reload) + .then(() => { + throw err; + }); + }); }) .then(() => { // At this point, the letsencrypt cert should exist on disk. // Lets get the expiry date from the file and update the row silently - return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`) + return internalCertificate + .getCertificateInfoFromFile( + `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, + ) .then((cert_info) => { return certificateModel .query() .patchAndFetchById(certificate.id, { - expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss') + expires_on: moment(cert_info.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), }) .then(utils.omitRow(omissions())) .then((saved_row) => { // Add cert data for audit log saved_row.meta = _.assign({}, saved_row.meta, { - letsencrypt_certificate: cert_info + letsencrypt_certificate: cert_info, }); return saved_row; }); }); - }).catch(async (error) => { + }) + .catch(async (error) => { // Delete the certificate from the database if it was not created successfully - await certificateModel - .query() - .deleteById(certificate.id); + await certificateModel.query().deleteById(certificate.id); throw error; }); - } else { - return certificate; } - }).then((certificate) => { - + return certificate; + }) + .then((certificate) => { data.meta = _.assign({}, data.meta || {}, certificate.meta); // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'certificate', - object_id: certificate.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "created", + object_type: "certificate", + object_id: certificate.id, + meta: data, + }) .then(() => { return certificate; }); @@ -256,14 +262,17 @@ const internalCertificate = { * @return {Promise} */ update: (access, data) => { - return access.can('certificates:update', data.id) + return access + .can("certificates:update", data.id) .then((/*access_data*/) => { - return internalCertificate.get(access, {id: data.id}); + return internalCertificate.get(access, { id: data.id }); }) .then((row) => { if (row.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError(`Certificate could not be updated, IDs do not match: ${row.id} !== ${data.id}`); + throw new error.InternalValidationError( + `Certificate could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); } return certificateModel @@ -272,20 +281,21 @@ const internalCertificate = { .then(utils.omitRow(omissions())) .then((saved_row) => { saved_row.meta = internalCertificate.cleanMeta(saved_row.meta); - data.meta = internalCertificate.cleanMeta(data.meta); + data.meta = internalCertificate.cleanMeta(data.meta); // Add row.nice_name for custom certs - if (saved_row.provider === 'other') { + if (saved_row.provider === "other") { data.nice_name = saved_row.nice_name; } // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'certificate', - object_id: row.id, - meta: _.omit(data, ['expires_on']) // this prevents json circular reference because expires_on might be raw - }) + return internalAuditLog + .add(access, { + action: "updated", + object_type: "certificate", + object_id: row.id, + meta: _.omit(data, ["expires_on"]), // this prevents json circular reference because expires_on might be raw + }) .then(() => { return saved_row; }); @@ -302,39 +312,38 @@ const internalCertificate = { * @return {Promise} */ get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } + const thisData = data || {}; - return access.can('certificates:get', data.id) - .then((access_data) => { + return access + .can("certificates:get", thisData.id) + .then((accessData) => { const query = certificateModel .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[owner]') - .allowGraph('[proxy_hosts]') - .allowGraph('[redirection_hosts]') - .allowGraph('[dead_hosts]') + .where("is_deleted", 0) + .andWhere("id", thisData.id) + .allowGraph("[owner]") + .allowGraph("[proxy_hosts]") + .allowGraph("[redirection_hosts]") + .allowGraph("[dead_hosts]") .first(); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched(`[${data.expand.join(', ')}]`); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new error.ItemNotFoundError(thisData.id); } // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { + return _.omit(row, thisData.omit); } return row; }); @@ -348,59 +357,61 @@ const internalCertificate = { */ download: (access, data) => { return new Promise((resolve, reject) => { - access.can('certificates:get', data) + access + .can("certificates:get", data) .then(() => { return internalCertificate.get(access, data); }) .then((certificate) => { - if (certificate.provider === 'letsencrypt') { + if (certificate.provider === "letsencrypt") { const zipDirectory = internalCertificate.getLiveCertPath(data.id); if (!fs.existsSync(zipDirectory)) { throw new error.ItemNotFoundError(`Certificate ${certificate.nice_name} does not exists`); } - const certFiles = fs.readdirSync(zipDirectory) - .filter((fn) => fn.endsWith('.pem')) + const certFiles = fs + .readdirSync(zipDirectory) + .filter((fn) => fn.endsWith(".pem")) .map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); const downloadName = `npm-${data.id}-${Date.now()}.zip`; - const opName = `/tmp/${downloadName}`; - internalCertificate.zipFiles(certFiles, opName) + const opName = `/tmp/${downloadName}`; + internalCertificate + .zipFiles(certFiles, opName) .then(() => { - logger.debug('zip completed : ', opName); + logger.debug("zip completed : ", opName); const resp = { - fileName: opName + fileName: opName, }; resolve(resp); - }).catch((err) => reject(err)); + }) + .catch((err) => reject(err)); } else { - throw new error.ValidationError('Only Let\'sEncrypt certificates can be downloaded'); + throw new error.ValidationError("Only Let'sEncrypt certificates can be downloaded"); } - }).catch((err) => reject(err)); + }) + .catch((err) => reject(err)); }); }, /** - * @param {String} source - * @param {String} out - * @returns {Promise} - */ + * @param {String} source + * @param {String} out + * @returns {Promise} + */ zipFiles(source, out) { - const archive = archiver('zip', { zlib: { level: 9 } }); - const stream = fs.createWriteStream(out); + const archive = archiver("zip", { zlib: { level: 9 } }); + const stream = fs.createWriteStream(out); return new Promise((resolve, reject) => { - source - .map((fl) => { - const fileName = path.basename(fl); - logger.debug(fl, 'added to certificate zip'); - archive.file(fl, { name: fileName }); - }); - archive - .on('error', (err) => reject(err)) - .pipe(stream); - - stream.on('close', () => resolve()); + source.map((fl) => { + const fileName = path.basename(fl); + logger.debug(fl, "added to certificate zip"); + archive.file(fl, { name: fileName }); + return true; + }); + archive.on("error", (err) => reject(err)).pipe(stream); + stream.on("close", () => resolve()); archive.finalize(); }); }, @@ -413,9 +424,10 @@ const internalCertificate = { * @returns {Promise} */ delete: (access, data) => { - return access.can('certificates:delete', data.id) + return access + .can("certificates:delete", data.id) .then(() => { - return internalCertificate.get(access, {id: data.id}); + return internalCertificate.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { @@ -424,23 +436,23 @@ const internalCertificate = { return certificateModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // Add to audit log row.meta = internalCertificate.cleanMeta(row.meta); return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'certificate', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "deleted", + object_type: "certificate", + object_id: row.id, + meta: _.omit(row, omissions()), }); }) .then(() => { - if (row.provider === 'letsencrypt') { + if (row.provider === "letsencrypt") { // Revoke the cert return internalCertificate.revokeLetsEncryptSsl(row); } @@ -460,35 +472,34 @@ const internalCertificate = { * @returns {Promise} */ getAll: (access, expand, search_query) => { - return access.can('certificates:list') - .then((access_data) => { - const query = certificateModel - .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[owner]') - .allowGraph('[proxy_hosts]') - .allowGraph('[redirection_hosts]') - .allowGraph('[dead_hosts]') - .orderBy('nice_name', 'ASC'); + return access.can("certificates:list").then((access_data) => { + const query = certificateModel + .query() + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner]") + .allowGraph("[proxy_hosts]") + .allowGraph("[redirection_hosts]") + .allowGraph("[dead_hosts]") + .orderBy("nice_name", "ASC"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('nice_name', 'like', `%${search_query}%`); - }); - } + // Query is used for searching + if (typeof search_query === "string") { + query.where(function () { + this.where("nice_name", "like", `%${search_query}%`); + }); + } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched(`[${expand.join(', ')}]`); - } + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } - return query.then(utils.omitRows(omissions())); - }); + return query.then(utils.omitRows(omissions())); + }); }, /** @@ -499,19 +510,15 @@ const internalCertificate = { * @returns {Promise} */ getCount: (user_id, 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') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", user_id); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); + return query.first().then((row) => { + return Number.parseInt(row.count, 10); + }); }, /** @@ -519,18 +526,18 @@ const internalCertificate = { * @returns {Promise} */ writeCustomCert: (certificate) => { - logger.info('Writing Custom Certificate:', certificate); + logger.info("Writing Custom Certificate:", certificate); const dir = `/data/custom_ssl/npm-${certificate.id}`; return new Promise((resolve, reject) => { - if (certificate.provider === 'letsencrypt') { - reject(new Error('Refusing to write letsencrypt certs here')); + if (certificate.provider === "letsencrypt") { + reject(new Error("Refusing to write letsencrypt certs here")); return; } let certData = certificate.meta.certificate; - if (typeof certificate.meta.intermediate_certificate !== 'undefined') { + if (typeof certificate.meta.intermediate_certificate !== "undefined") { certData = `${certData}\n${certificate.meta.intermediate_certificate}`; } @@ -550,18 +557,17 @@ const internalCertificate = { resolve(); } }); - }) - .then(() => { - return new Promise((resolve, reject) => { - fs.writeFile(`${dir}/privkey.pem`, certificate.meta.certificate_key, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); + }).then(() => { + return new Promise((resolve, reject) => { + fs.writeFile(`${dir}/privkey.pem`, certificate.meta.certificate_key, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } }); }); + }); }, /** @@ -574,9 +580,9 @@ const internalCertificate = { */ createQuickCertificate: (access, data) => { return internalCertificate.create(access, { - provider: 'letsencrypt', + provider: "letsencrypt", domain_names: data.domain_names, - meta: data.meta + meta: data.meta, }); }, @@ -599,35 +605,35 @@ const internalCertificate = { }); resolve(files); - }) - .then((files) => { - // For each file, create a temp file and write the contents to it - // Then test it depending on the file type - const promises = []; - _.map(files, (content, type) => { - promises.push(new Promise((resolve) => { - if (type === 'certificate_key') { + }).then((files) => { + // For each file, create a temp file and write the contents to it + // Then test it depending on the file type + const promises = []; + _.map(files, (content, type) => { + promises.push( + new Promise((resolve) => { + if (type === "certificate_key") { resolve(internalCertificate.checkPrivateKey(content)); } else { // this should handle `certificate` and intermediate certificate resolve(internalCertificate.getCertificateInfo(content, true)); } }).then((res) => { - return {[type]: res}; - })); + return { [type]: res }; + }), + ); + }); + + return Promise.all(promises).then((files) => { + let data = {}; + + _.each(files, (file) => { + data = _.assign({}, data, file); }); - return Promise.all(promises) - .then((files) => { - let data = {}; - - _.each(files, (file) => { - data = _.assign({}, data, file); - }); - - return data; - }); + return data; }); + }); }, /** @@ -638,40 +644,41 @@ const internalCertificate = { * @returns {Promise} */ upload: (access, data) => { - return internalCertificate.get(access, {id: data.id}) - .then((row) => { - if (row.provider !== 'other') { - throw new error.ValidationError('Cannot upload certificates for this type of provider'); - } + return internalCertificate.get(access, { id: data.id }).then((row) => { + if (row.provider !== "other") { + throw new error.ValidationError("Cannot upload certificates for this type of provider"); + } - return internalCertificate.validate(data) - .then((validations) => { - if (typeof validations.certificate === 'undefined') { - throw new error.ValidationError('Certificate file was not provided'); + return internalCertificate + .validate(data) + .then((validations) => { + if (typeof validations.certificate === "undefined") { + throw new error.ValidationError("Certificate file was not provided"); + } + + _.map(data.files, (file, name) => { + if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { + row.meta[name] = file.data.toString(); } - - _.map(data.files, (file, name) => { - if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { - row.meta[name] = file.data.toString(); - } - }); - - // TODO: This uses a mysql only raw function that won't translate to postgres - return internalCertificate.update(access, { - id: data.id, - expires_on: moment(validations.certificate.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'), - domain_names: [validations.certificate.cn], - meta: _.clone(row.meta) // Prevent the update method from changing this value that we'll use later - }) - .then((certificate) => { - certificate.meta = row.meta; - return internalCertificate.writeCustomCert(certificate); - }); - }) - .then(() => { - return _.pick(row.meta, internalCertificate.allowedSslFiles); }); - }); + + // TODO: This uses a mysql only raw function that won't translate to postgres + return internalCertificate + .update(access, { + id: data.id, + expires_on: moment(validations.certificate.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), + domain_names: [validations.certificate.cn], + meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later + }) + .then((certificate) => { + certificate.meta = row.meta; + return internalCertificate.writeCustomCert(certificate); + }); + }) + .then(() => { + return _.pick(row.meta, internalCertificate.allowedSslFiles); + }); + }); }, /** @@ -681,29 +688,32 @@ const internalCertificate = { * @param {String} private_key This is the entire key contents as a string */ checkPrivateKey: (private_key) => { - return tempWrite(private_key, '/tmp') - .then((filepath) => { - return new Promise((resolve, reject) => { - const failTimeout = setTimeout(() => { - reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.')); - }, 10000); - utils - .exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `) - .then((result) => { - clearTimeout(failTimeout); - if (!result.toLowerCase().includes('key is valid')) { - reject(new error.ValidationError(`Result Validation Error: ${result}`)); - } - fs.unlinkSync(filepath); - resolve(true); - }) - .catch((err) => { - clearTimeout(failTimeout); - fs.unlinkSync(filepath); - reject(new error.ValidationError(`Certificate Key is not valid (${err.message})`, err)); - }); - }); + return tempWrite(private_key, "/tmp").then((filepath) => { + return new Promise((resolve, reject) => { + const failTimeout = setTimeout(() => { + reject( + new error.ValidationError( + "Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.", + ), + ); + }, 10000); + utils + .exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `) + .then((result) => { + clearTimeout(failTimeout); + if (!result.toLowerCase().includes("key is valid")) { + reject(new error.ValidationError(`Result Validation Error: ${result}`)); + } + fs.unlinkSync(filepath); + resolve(true); + }) + .catch((err) => { + clearTimeout(failTimeout); + fs.unlinkSync(filepath); + reject(new error.ValidationError(`Certificate Key is not valid (${err.message})`, err)); + }); }); + }); }, /** @@ -714,17 +724,18 @@ const internalCertificate = { * @param {Boolean} [throw_expired] Throw when the certificate is out of date */ getCertificateInfo: (certificate, throw_expired) => { - return tempWrite(certificate, '/tmp') - .then((filepath) => { - return internalCertificate.getCertificateInfoFromFile(filepath, throw_expired) - .then((certData) => { - fs.unlinkSync(filepath); - return certData; - }).catch((err) => { - fs.unlinkSync(filepath); - throw err; - }); - }); + return tempWrite(certificate, "/tmp").then((filepath) => { + return internalCertificate + .getCertificateInfoFromFile(filepath, throw_expired) + .then((certData) => { + fs.unlinkSync(filepath); + return certData; + }) + .catch((err) => { + fs.unlinkSync(filepath); + throw err; + }); + }); }, /** @@ -737,19 +748,20 @@ const internalCertificate = { getCertificateInfoFromFile: (certificate_file, throw_expired) => { const certData = {}; - return utils.execFile('openssl', ['x509', '-in', certificate_file, '-subject', '-noout']) + return utils + .execFile("openssl", ["x509", "-in", certificate_file, "-subject", "-noout"]) .then((result) => { // Examples: // subject=CN = *.jc21.com // subject=CN = something.example.com const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; const match = regex.exec(result); - if (match && typeof match[1] !== 'undefined') { + if (match && typeof match[1] !== "undefined") { certData.cn = match[1]; } }) .then(() => { - return utils.execFile('openssl', ['x509', '-in', certificate_file, '-issuer', '-noout']); + return utils.execFile("openssl", ["x509", "-in", certificate_file, "-issuer", "-noout"]); }) .then((result) => { @@ -759,50 +771,52 @@ const internalCertificate = { // issuer=O = NginxProxyManager, CN = NginxProxyManager Intermediate CA","O = NginxProxyManager, CN = NginxProxyManager Intermediate CA const regex = /^(?:issuer=)?(.*)$/gim; const match = regex.exec(result); - if (match && typeof match[1] !== 'undefined') { + if (match && typeof match[1] !== "undefined") { certData.issuer = match[1]; } }) .then(() => { - return utils.execFile('openssl', ['x509', '-in', certificate_file, '-dates', '-noout']); + return utils.execFile("openssl", ["x509", "-in", certificate_file, "-dates", "-noout"]); }) .then((result) => { // notBefore=Jul 14 04:04:29 2018 GMT // notAfter=Oct 12 04:04:29 2018 GMT let validFrom = null; - let validTo = null; + let validTo = null; - const lines = result.split('\n'); + const lines = result.split("\n"); lines.map((str) => { const regex = /^(\S+)=(.*)$/gim; const match = regex.exec(str.trim()); - if (match && typeof match[2] !== 'undefined') { - const date = parseInt(moment(match[2], 'MMM DD HH:mm:ss YYYY z').format('X'), 10); + if (match && typeof match[2] !== "undefined") { + const date = Number.parseInt(moment(match[2], "MMM DD HH:mm:ss YYYY z").format("X"), 10); - if (match[1].toLowerCase() === 'notbefore') { + if (match[1].toLowerCase() === "notbefore") { validFrom = date; - } else if (match[1].toLowerCase() === 'notafter') { + } else if (match[1].toLowerCase() === "notafter") { validTo = date; } } + return true; }); if (!validFrom || !validTo) { throw new error.ValidationError(`Could not determine dates from certificate: ${result}`); } - if (throw_expired && validTo < parseInt(moment().format('X'), 10)) { - throw new error.ValidationError('Certificate has expired'); + if (throw_expired && validTo < Number.parseInt(moment().format("X"), 10)) { + throw new error.ValidationError("Certificate has expired"); } certData.dates = { from: validFrom, - to: validTo + to: validTo, }; return certData; - }).catch((err) => { + }) + .catch((err) => { throw new error.ValidationError(`Certificate is not valid (${err.message})`, err); }); }, @@ -816,13 +830,14 @@ const internalCertificate = { */ cleanMeta: (meta, remove) => { internalCertificate.allowedSslFiles.map((key) => { - if (typeof meta[key] !== 'undefined' && meta[key]) { + if (typeof meta[key] !== "undefined" && meta[key]) { if (remove) { delete meta[key]; } else { meta[key] = true; } } + return true; }); return meta; @@ -834,39 +849,40 @@ const internalCertificate = { * @returns {Promise} */ requestLetsEncryptSsl: (certificate) => { - logger.info(`Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); + logger.info( + `Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); const args = [ - 'certonly', - '--config', + "certonly", + "--config", letsencryptConfig, - '--work-dir', - '/tmp/letsencrypt-lib', - '--logs-dir', - '/tmp/letsencrypt-log', - '--cert-name', + "--work-dir", + "/tmp/letsencrypt-lib", + "--logs-dir", + "/tmp/letsencrypt-log", + "--cert-name", `npm-${certificate.id}`, - '--agree-tos', - '--authenticator', - 'webroot', - '--email', + "--agree-tos", + "--authenticator", + "webroot", + "--email", certificate.meta.letsencrypt_email, - '--preferred-challenges', - 'dns,http', - '--domains', - certificate.domain_names.join(','), + "--preferred-challenges", + "dns,http", + "--domains", + certificate.domain_names.join(","), ]; const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); args.push(...adds.args); - logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - return utils.execFile(certbotCommand, args, adds.opts) - .then((result) => { - logger.success(result); - return result; - }); + return utils.execFile(certbotCommand, args, adds.opts).then((result) => { + logger.success(result); + return result; + }); }, /** @@ -877,33 +893,35 @@ const internalCertificate = { * @returns {Promise} */ requestLetsEncryptSslWithDnsChallenge: async (certificate) => { - await certbot.installPlugin(certificate.meta.dns_provider); + await installPlugin(certificate.meta.dns_provider); const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; - logger.info(`Requesting LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); + logger.info( + `Requesting LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); const credentialsLocation = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; - fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true }); - fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600}); + fs.mkdirSync("/etc/letsencrypt/credentials", { recursive: true }); + fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, { mode: 0o600 }); // Whether the plugin has a ---credentials argument - const hasConfigArg = certificate.meta.dns_provider !== 'route53'; + const hasConfigArg = certificate.meta.dns_provider !== "route53"; const args = [ - 'certonly', - '--config', + "certonly", + "--config", letsencryptConfig, - '--work-dir', - '/tmp/letsencrypt-lib', - '--logs-dir', - '/tmp/letsencrypt-log', - '--cert-name', + "--work-dir", + "/tmp/letsencrypt-lib", + "--logs-dir", + "/tmp/letsencrypt-log", + "--cert-name", `npm-${certificate.id}`, - '--agree-tos', - '--email', + "--agree-tos", + "--email", certificate.meta.letsencrypt_email, - '--domains', - certificate.domain_names.join(','), - '--authenticator', + "--domains", + certificate.domain_names.join(","), + "--authenticator", dnsPlugin.full_plugin_name, ]; @@ -911,13 +929,16 @@ const internalCertificate = { args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation); } if (certificate.meta.propagation_seconds !== undefined) { - args.push(`--${dnsPlugin.full_plugin_name}-propagation-seconds`, certificate.meta.propagation_seconds.toString()); + args.push( + `--${dnsPlugin.full_plugin_name}-propagation-seconds`, + certificate.meta.propagation_seconds.toString(), + ); } const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); args.push(...adds.args); - logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); try { const result = await utils.execFile(certbotCommand, args, adds.opts); @@ -930,7 +951,6 @@ const internalCertificate = { } }, - /** * @param {Access} access * @param {Object} data @@ -938,40 +958,43 @@ const internalCertificate = { * @returns {Promise} */ renew: (access, data) => { - return access.can('certificates:update', data) + return access + .can("certificates:update", data) .then(() => { return internalCertificate.get(access, data); }) .then((certificate) => { - if (certificate.provider === 'letsencrypt') { - const renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl; + if (certificate.provider === "letsencrypt") { + const renewMethod = certificate.meta.dns_challenge + ? internalCertificate.renewLetsEncryptSslWithDnsChallenge + : internalCertificate.renewLetsEncryptSsl; return renewMethod(certificate) .then(() => { - return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`); + return internalCertificate.getCertificateInfoFromFile( + `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, + ); }) .then((cert_info) => { - return certificateModel - .query() - .patchAndFetchById(certificate.id, { - expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss') - }); + return certificateModel.query().patchAndFetchById(certificate.id, { + expires_on: moment(cert_info.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), + }); }) .then((updated_certificate) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'renewed', - object_type: 'certificate', - object_id: updated_certificate.id, - meta: updated_certificate - }) + return internalAuditLog + .add(access, { + action: "renewed", + object_type: "certificate", + object_id: updated_certificate.id, + meta: updated_certificate, + }) .then(() => { return updated_certificate; }); }); - } else { - throw new error.ValidationError('Only Let\'sEncrypt certificates can be renewed'); } + throw new error.ValidationError("Only Let'sEncrypt certificates can be renewed"); }); }, @@ -980,35 +1003,36 @@ const internalCertificate = { * @returns {Promise} */ renewLetsEncryptSsl: (certificate) => { - logger.info(`Renewing LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); + logger.info( + `Renewing LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); const args = [ - 'renew', - '--force-renewal', - '--config', + "renew", + "--force-renewal", + "--config", letsencryptConfig, - '--work-dir', - '/tmp/letsencrypt-lib', - '--logs-dir', - '/tmp/letsencrypt-log', - '--cert-name', + "--work-dir", + "/tmp/letsencrypt-lib", + "--logs-dir", + "/tmp/letsencrypt-log", + "--cert-name", `npm-${certificate.id}`, - '--preferred-challenges', - 'dns,http', - '--no-random-sleep-on-renew', - '--disable-hook-validation', + "--preferred-challenges", + "dns,http", + "--no-random-sleep-on-renew", + "--disable-hook-validation", ]; const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); args.push(...adds.args); - logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - return utils.execFile(certbotCommand, args, adds.opts) - .then((result) => { - logger.info(result); - return result; - }); + return utils.execFile(certbotCommand, args, adds.opts).then((result) => { + logger.info(result); + return result; + }); }, /** @@ -1022,33 +1046,34 @@ const internalCertificate = { throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); } - logger.info(`Renewing LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); + logger.info( + `Renewing LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); const args = [ - 'renew', - '--force-renewal', - '--config', + "renew", + "--force-renewal", + "--config", letsencryptConfig, - '--work-dir', - '/tmp/letsencrypt-lib', - '--logs-dir', - '/tmp/letsencrypt-log', - '--cert-name', + "--work-dir", + "/tmp/letsencrypt-lib", + "--logs-dir", + "/tmp/letsencrypt-log", + "--cert-name", `npm-${certificate.id}`, - '--disable-hook-validation', - '--no-random-sleep-on-renew', + "--disable-hook-validation", + "--no-random-sleep-on-renew", ]; const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); args.push(...adds.args); - logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - return utils.execFile(certbotCommand, args, adds.opts) - .then(async (result) => { - logger.info(result); - return result; - }); + return utils.execFile(certbotCommand, args, adds.opts).then(async (result) => { + logger.info(result); + return result; + }); }, /** @@ -1057,27 +1082,30 @@ const internalCertificate = { * @returns {Promise} */ revokeLetsEncryptSsl: (certificate, throw_errors) => { - logger.info(`Revoking LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); + logger.info( + `Revoking LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, + ); const args = [ - 'revoke', - '--config', + "revoke", + "--config", letsencryptConfig, - '--work-dir', - '/tmp/letsencrypt-lib', - '--logs-dir', - '/tmp/letsencrypt-log', - '--cert-path', + "--work-dir", + "/tmp/letsencrypt-lib", + "--logs-dir", + "/tmp/letsencrypt-log", + "--cert-path", `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, - '--delete-after-revoke', + "--delete-after-revoke", ]; const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); args.push(...adds.args); - logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); + logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - return utils.execFile(certbotCommand, args, adds.opts) + return utils + .execFile(certbotCommand, args, adds.opts) .then(async (result) => { await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); logger.info(result); @@ -1113,22 +1141,20 @@ const internalCertificate = { const promises = []; if (in_use_result.proxy_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts)); + promises.push(internalNginx.bulkDeleteConfigs("proxy_host", in_use_result.proxy_hosts)); } if (in_use_result.redirection_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs('redirection_host', in_use_result.redirection_hosts)); + promises.push(internalNginx.bulkDeleteConfigs("redirection_host", in_use_result.redirection_hosts)); } if (in_use_result.dead_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs('dead_host', in_use_result.dead_hosts)); + promises.push(internalNginx.bulkDeleteConfigs("dead_host", in_use_result.dead_hosts)); } return Promise.all(promises); - - } else { - return Promise.resolve(); } + return Promise.resolve(); }, /** @@ -1143,76 +1169,79 @@ const internalCertificate = { const promises = []; if (in_use_result.proxy_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts)); + promises.push(internalNginx.bulkGenerateConfigs("proxy_host", in_use_result.proxy_hosts)); } if (in_use_result.redirection_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs('redirection_host', in_use_result.redirection_hosts)); + promises.push(internalNginx.bulkGenerateConfigs("redirection_host", in_use_result.redirection_hosts)); } if (in_use_result.dead_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs('dead_host', in_use_result.dead_hosts)); + promises.push(internalNginx.bulkGenerateConfigs("dead_host", in_use_result.dead_hosts)); } return Promise.all(promises); - - } else { - return Promise.resolve(); } + return Promise.resolve(); }, testHttpsChallenge: async (access, domains) => { - await access.can('certificates:list'); + await access.can("certificates:list"); if (!isArray(domains)) { - throw new error.InternalValidationError('Domains must be an array of strings'); + throw new error.InternalValidationError("Domains must be an array of strings"); } if (domains.length === 0) { - throw new error.InternalValidationError('No domains provided'); + throw new error.InternalValidationError("No domains provided"); } // 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`; - fs.mkdirSync(testChallengeDir, {recursive: true}); - fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'}); + fs.mkdirSync(testChallengeDir, { recursive: true }); + fs.writeFileSync(testChallengeFile, "Success", { encoding: "utf8" }); - async function performTestForDomain (domain) { + async function performTestForDomain(domain) { logger.info(`Testing http challenge for ${domain}`); - const url = `http://${domain}/.well-known/acme-challenge/test-challenge`; + 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', + const options = { + method: "POST", headers: { - 'User-Agent': 'Mozilla/5.0', - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': Buffer.byteLength(formBody) - } + "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 = ""; - const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, (res) => { - let responseBody = ''; - - res.on('data', (chunk) => { + res.on("data", (chunk) => { responseBody = responseBody + chunk; }); - res.on('end', () => { + res.on("end", () => { try { const parsedBody = JSON.parse(`${responseBody}`); if (res.statusCode !== 200) { - logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`); + logger.warn( + `Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`, + ); resolve(undefined); } else { resolve(parsedBody); } } catch (err) { if (res.statusCode !== 200) { - logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned`); + logger.warn( + `Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned`, + ); } else { - logger.warn(`Failed to test HTTP challenge for domain ${domain} because response failed to be parsed: ${err.message}`); + logger.warn( + `Failed to test HTTP challenge for domain ${domain} because response failed to be parsed: ${err.message}`, + ); } resolve(undefined); } @@ -1222,41 +1251,57 @@ const internalCertificate = { // 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); }); + req.on("error", (e) => { + logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); + resolve(undefined); + }); }); if (!result) { // Some error occurred while trying to get the data - return 'failed'; - } else if (result.error) { - logger.info(`HTTP challenge test failed for domain ${domain} because error was returned: ${result.error.msg}`); + return "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}`; - } else if (`${result.responsecode}` === '200' && result.htmlresponse === 'Success') { + } + if (`${result.responsecode}` === "200" && result.htmlresponse === "Success") { // Server exists and has responded with the correct data - return 'ok'; - } else if (`${result.responsecode}` === '200') { + 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'; - } else if (`${result.responsecode}` === '404') { + 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'; - } else if (`${result.responsecode}` === '0' || (typeof result.reason === 'string' && result.reason.toLowerCase() === 'host unavailable')) { + 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'; - } else { - // Other errors - logger.info(`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`); - return `other:${result.responsecode}`; + return "no-host"; } + // Other errors + logger.info( + `HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`, + ); + return `other:${result.responsecode}`; } const results = {}; - for (const domain of domains){ + for (const domain of domains) { results[domain] = await performTestForDomain(domain); } @@ -1268,31 +1313,31 @@ const internalCertificate = { getAdditionalCertbotArgs: (certificate_id, dns_provider) => { const args = []; - if (letsencryptServer !== null) { - args.push('--server', letsencryptServer); + if (useLetsencryptServer() !== null) { + args.push("--server", useLetsencryptServer()); } - if (letsencryptStaging && letsencryptServer === null) { - args.push('--staging'); + if (useLetsencryptStaging() && useLetsencryptServer() === null) { + args.push("--staging"); } // For route53, add the credentials file as an environment variable, // inheriting the process env const opts = {}; - if (certificate_id && dns_provider === 'route53') { - opts.env = process.env; + if (certificate_id && dns_provider === "route53") { + opts.env = process.env; opts.env.AWS_CONFIG_FILE = `/etc/letsencrypt/credentials/credentials-${certificate_id}`; } - if (dns_provider === 'duckdns') { - args.push('--dns-duckdns-no-txt-restore'); + if (dns_provider === "duckdns") { + args.push("--dns-duckdns-no-txt-restore"); } - return {args: args, opts: opts}; + return { args: args, opts: opts }; }, getLiveCertPath: (certificate_id) => { return `/etc/letsencrypt/live/npm-${certificate_id}`; - } + }, }; -module.exports = internalCertificate; +export default internalCertificate; diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js index 6bbdf61b..8b8573ca 100644 --- a/backend/internal/dead-host.js +++ b/backend/internal/dead-host.js @@ -1,106 +1,104 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const deadHostModel = require('../models/dead_host'); -const internalHost = require('./host'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); -const {castJsonIfNeed} = require('../lib/helpers'); +import _ from "lodash"; +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import utils from "../lib/utils.js"; +import deadHostModel from "../models/dead_host.js"; +import internalAuditLog from "./audit-log.js"; +import internalCertificate from "./certificate.js"; +import internalHost from "./host.js"; +import internalNginx from "./nginx.js"; -function omissions () { - return ['is_deleted']; -} +const omissions = () => { + return ["is_deleted"]; +}; const internalDeadHost = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ create: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + const createCertificate = data.certificate_id === "new"; - if (create_certificate) { + if (createCertificate) { delete data.certificate_id; } - return access.can('dead_hosts:create', data) + return access + .can("dead_hosts:create", data) .then((/*access_data*/) => { // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + const domain_name_check_promises = []; - data.domain_names.map(function (domain_name) { + data.domain_names.map((domain_name) => { domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); + return true; }); - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + return Promise.all(domain_name_check_promises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; }); + }); }) .then(() => { // At this point the domains should have been checked data.owner_user_id = access.token.getUserId(1); - data = internalHost.cleanSslHstsData(data); + const thisData = internalHost.cleanSslHstsData(data); // Fix for db field not having a default value // for this optional field. - if (typeof data.advanced_config === 'undefined') { - data.advanced_config = ''; + if (typeof data.advanced_config === "undefined") { + thisData.advanced_config = ""; } - return deadHostModel - .query() - .insertAndFetch(data) - .then(utils.omitRow(omissions())); + return deadHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); }) .then((row) => { - if (create_certificate) { - return internalCertificate.createQuickCertificate(access, data) + if (createCertificate) { + return internalCertificate + .createQuickCertificate(access, data) .then((cert) => { // update host with cert id return internalDeadHost.update(access, { - id: row.id, - certificate_id: cert.id + id: row.id, + certificate_id: cert.id, }); }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // re-fetch with cert return internalDeadHost.get(access, { - id: row.id, - expand: ['certificate', 'owner'] + id: row.id, + expand: ["certificate", "owner"], }); }) .then((row) => { // Configure nginx - return internalNginx.configure(deadHostModel, 'dead_host', row) - .then(() => { - return row; - }); + return internalNginx.configure(deadHostModel, "dead_host", row).then(() => { + return row; + }); }) .then((row) => { data.meta = _.assign({}, data.meta || {}, row.meta); // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'dead-host', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "created", + object_type: "dead-host", + object_id: row.id, + meta: data, + }) .then(() => { return row; }); @@ -114,95 +112,104 @@ const internalDeadHost = { * @return {Promise} */ update: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + let thisData = data; + const createCertificate = thisData.certificate_id === "new"; - if (create_certificate) { - delete data.certificate_id; + if (createCertificate) { + delete thisData.certificate_id; } - return access.can('dead_hosts:update', data.id) + return access + .can("dead_hosts:update", thisData.id) .then((/*access_data*/) => { // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + const domain_name_check_promises = []; - if (typeof data.domain_names !== 'undefined') { - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'dead', data.id)); + if (typeof thisData.domain_names !== "undefined") { + thisData.domain_names.map((domain_name) => { + domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, "dead", data.id)); + return true; }); - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + return Promise.all(domain_name_check_promises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; }); + }); } }) .then(() => { - return internalDeadHost.get(access, {id: data.id}); + return internalDeadHost.get(access, { id: thisData.id }); }) .then((row) => { - if (row.id !== data.id) { + if (row.id !== thisData.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('404 Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `404 Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`, + ); } - if (create_certificate) { - return internalCertificate.createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta) - }) + if (createCertificate) { + return internalCertificate + .createQuickCertificate(access, { + domain_names: thisData.domain_names || row.domain_names, + meta: _.assign({}, row.meta, thisData.meta), + }) .then((cert) => { // update host with cert id - data.certificate_id = cert.id; + thisData.certificate_id = cert.id; }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign({}, { - domain_names: row.domain_names - }, data); + thisData = _.assign( + {}, + { + domain_names: row.domain_names, + }, + data, + ); - data = internalHost.cleanSslHstsData(data, row); + thisData = internalHost.cleanSslHstsData(thisData, row); return deadHostModel .query() - .where({id: data.id}) - .patch(data) + .where({ id: thisData.id }) + .patch(thisData) .then((saved_row) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'dead-host', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "updated", + object_type: "dead-host", + object_id: row.id, + meta: thisData, + }) .then(() => { return _.omit(saved_row, omissions()); }); }); }) .then(() => { - return internalDeadHost.get(access, { - id: data.id, - expand: ['owner', 'certificate'] - }) + return internalDeadHost + .get(access, { + id: thisData.id, + expand: ["owner", "certificate"], + }) .then((row) => { // Configure nginx - return internalNginx.configure(deadHostModel, 'dead_host', row) - .then((new_meta) => { - row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); - }); + return internalNginx.configure(deadHostModel, "dead_host", row).then((new_meta) => { + row.meta = new_meta; + return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); + }); }); }); }, @@ -216,36 +223,35 @@ const internalDeadHost = { * @return {Promise} */ get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } + const thisData = data || {}; - return access.can('dead_hosts:get', data.id) + return access + .can("dead_hosts:get", thisData.id) .then((access_data) => { - let query = deadHostModel + const query = deadHostModel .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[owner,certificate]') + .where("is_deleted", 0) + .andWhere("id", dthisDataata.id) + .allowGraph("[owner,certificate]") .first(); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${data.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(thisData.id); } // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { + return _.omit(row, thisData.omit); } return row; }); @@ -259,35 +265,35 @@ const internalDeadHost = { * @returns {Promise} */ delete: (access, data) => { - return access.can('dead_hosts:delete', data.id) + return access + .can("dead_hosts:delete", data.id) .then(() => { - return internalDeadHost.get(access, {id: data.id}); + return internalDeadHost.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(data.id); } return deadHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('dead_host', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("dead_host", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'dead-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "deleted", + object_type: "dead-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -304,39 +310,41 @@ const internalDeadHost = { * @returns {Promise} */ enable: (access, data) => { - return access.can('dead_hosts:update', data.id) + return access + .can("dead_hosts:update", data.id) .then(() => { return internalDeadHost.get(access, { - id: data.id, - expand: ['certificate', 'owner'] + id: data.id, + expand: ["certificate", "owner"], }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Host is already enabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Host is already enabled"); } row.enabled = 1; return deadHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 1 + enabled: 1, }) .then(() => { // Configure nginx - return internalNginx.configure(deadHostModel, 'dead_host', row); + return internalNginx.configure(deadHostModel, "dead_host", row); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'dead-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "enabled", + object_type: "dead-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -353,39 +361,40 @@ const internalDeadHost = { * @returns {Promise} */ disable: (access, data) => { - return access.can('dead_hosts:update', data.id) + return access + .can("dead_hosts:update", data.id) .then(() => { - return internalDeadHost.get(access, {id: data.id}); + return internalDeadHost.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Host is already disabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (!row.enabled) { + throw new errs.ValidationError("Host is already disabled"); } row.enabled = 0; return deadHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 0 + enabled: 0, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('dead_host', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("dead_host", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'dead-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "disabled", + object_type: "dead-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -403,34 +412,35 @@ const internalDeadHost = { * @returns {Promise} */ getAll: (access, expand, search_query) => { - return access.can('dead_hosts:list') + return access + .can("dead_hosts:list") .then((access_data) => { - let query = deadHostModel + const query = deadHostModel .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[owner,certificate]') - .orderBy(castJsonIfNeed('domain_names'), 'ASC'); + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,certificate]") + .orderBy(castJsonIfNeed("domain_names"), "ASC"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { + if (typeof search_query === "string" && search_query.length > 0) { query.where(function () { - this.where(castJsonIfNeed('domain_names'), 'like', '%' + search_query + '%'); + this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`); }); } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); } return query.then(utils.omitRows(omissions())); }) .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { + if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { return internalHost.cleanAllRowsCertificateMeta(rows); } @@ -446,20 +456,16 @@ const internalDeadHost = { * @returns {Promise} */ getCount: (user_id, visibility) => { - let query = deadHostModel - .query() - .count('id as count') - .where('is_deleted', 0); + const query = deadHostModel.query().count("id as count").where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", user_id); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); - } + return query.first().then((row) => { + return Number.parseInt(row.count, 10); + }); + }, }; -module.exports = internalDeadHost; +export default internalDeadHost; diff --git a/backend/internal/host.js b/backend/internal/host.js index 52c6d2bd..a7f6f46a 100644 --- a/backend/internal/host.js +++ b/backend/internal/host.js @@ -1,11 +1,10 @@ -const _ = require('lodash'); -const proxyHostModel = require('../models/proxy_host'); -const redirectionHostModel = require('../models/redirection_host'); -const deadHostModel = require('../models/dead_host'); -const {castJsonIfNeed} = require('../lib/helpers'); +import _ from "lodash"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import deadHostModel from "../models/dead_host.js"; +import proxyHostModel from "../models/proxy_host.js"; +import redirectionHostModel from "../models/redirection_host.js"; const internalHost = { - /** * Makes sure that the ssl_* and hsts_* fields play nicely together. * ie: if there is no cert, then force_ssl is off. @@ -15,25 +14,23 @@ const internalHost = { * @param {object} [existing_data] * @returns {object} */ - cleanSslHstsData: function (data, existing_data) { - existing_data = existing_data === undefined ? {} : existing_data; + cleanSslHstsData: (data, existingData) => { + const combinedData = _.assign({}, existingData || {}, data); - const combined_data = _.assign({}, existing_data, data); - - if (!combined_data.certificate_id) { - combined_data.ssl_forced = false; - combined_data.http2_support = false; + if (!combinedData.certificate_id) { + combinedData.ssl_forced = false; + combinedData.http2_support = false; } - if (!combined_data.ssl_forced) { - combined_data.hsts_enabled = false; + if (!combinedData.ssl_forced) { + combinedData.hsts_enabled = false; } - if (!combined_data.hsts_enabled) { - combined_data.hsts_subdomains = false; + if (!combinedData.hsts_enabled) { + combinedData.hsts_subdomains = false; } - return combined_data; + return combinedData; }, /** @@ -42,11 +39,12 @@ const internalHost = { * @param {Array} rows * @returns {Array} */ - cleanAllRowsCertificateMeta: function (rows) { - rows.map(function (row, idx) { - if (typeof rows[idx].certificate !== 'undefined' && rows[idx].certificate) { + cleanAllRowsCertificateMeta: (rows) => { + rows.map((_, idx) => { + if (typeof rows[idx].certificate !== "undefined" && rows[idx].certificate) { rows[idx].certificate.meta = {}; } + return true; }); return rows; @@ -58,8 +56,8 @@ const internalHost = { * @param {Object} row * @returns {Object} */ - cleanRowCertificateMeta: function (row) { - if (typeof row.certificate !== 'undefined' && row.certificate) { + cleanRowCertificateMeta: (row) => { + if (typeof row.certificate !== "undefined" && row.certificate) { row.certificate.meta = {}; } @@ -73,48 +71,44 @@ const internalHost = { * @param {Array} domain_names * @returns {Promise} */ - getHostsWithDomains: function (domain_names) { + getHostsWithDomains: (domain_names) => { const promises = [ - proxyHostModel - .query() - .where('is_deleted', 0), - redirectionHostModel - .query() - .where('is_deleted', 0), - deadHostModel - .query() - .where('is_deleted', 0) + proxyHostModel.query().where("is_deleted", 0), + redirectionHostModel.query().where("is_deleted", 0), + deadHostModel.query().where("is_deleted", 0), ]; - return Promise.all(promises) - .then((promises_results) => { - let response_object = { - total_count: 0, - dead_hosts: [], - proxy_hosts: [], - redirection_hosts: [] - }; + return Promise.all(promises).then((promises_results) => { + const response_object = { + total_count: 0, + dead_hosts: [], + proxy_hosts: [], + redirection_hosts: [], + }; - if (promises_results[0]) { - // Proxy Hosts - response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names); - response_object.total_count += response_object.proxy_hosts.length; - } + if (promises_results[0]) { + // Proxy Hosts + response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names); + response_object.total_count += response_object.proxy_hosts.length; + } - if (promises_results[1]) { - // Redirection Hosts - response_object.redirection_hosts = internalHost._getHostsWithDomains(promises_results[1], domain_names); - response_object.total_count += response_object.redirection_hosts.length; - } + if (promises_results[1]) { + // Redirection Hosts + response_object.redirection_hosts = internalHost._getHostsWithDomains( + promises_results[1], + domain_names, + ); + response_object.total_count += response_object.redirection_hosts.length; + } - if (promises_results[2]) { - // Dead Hosts - response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names); - response_object.total_count += response_object.dead_hosts.length; - } + if (promises_results[2]) { + // Dead Hosts + response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names); + response_object.total_count += response_object.dead_hosts.length; + } - return response_object; - }); + return response_object; + }); }, /** @@ -125,112 +119,133 @@ const internalHost = { * @param {Integer} [ignore_id] Must be supplied if type was also supplied * @returns {Promise} */ - isHostnameTaken: function (hostname, ignore_type, ignore_id) { + isHostnameTaken: (hostname, ignore_type, ignore_id) => { const promises = [ proxyHostModel .query() - .where('is_deleted', 0) - .andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'), + .where("is_deleted", 0) + .andWhere(castJsonIfNeed("domain_names"), "like", `%${hostname}%`), redirectionHostModel .query() - .where('is_deleted', 0) - .andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'), + .where("is_deleted", 0) + .andWhere(castJsonIfNeed("domain_names"), "like", `%${hostname}%`), deadHostModel .query() - .where('is_deleted', 0) - .andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%') + .where("is_deleted", 0) + .andWhere(castJsonIfNeed("domain_names"), "like", `%${hostname}%`), ]; - return Promise.all(promises) - .then((promises_results) => { - let is_taken = false; + return Promise.all(promises).then((promises_results) => { + let is_taken = false; - if (promises_results[0]) { - // Proxy Hosts - if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[0], ignore_type === 'proxy' && ignore_id ? ignore_id : 0)) { - is_taken = true; - } + if (promises_results[0]) { + // Proxy Hosts + if ( + internalHost._checkHostnameRecordsTaken( + hostname, + promises_results[0], + ignore_type === "proxy" && ignore_id ? ignore_id : 0, + ) + ) { + is_taken = true; } + } - if (promises_results[1]) { - // Redirection Hosts - if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[1], ignore_type === 'redirection' && ignore_id ? ignore_id : 0)) { - is_taken = true; - } + if (promises_results[1]) { + // Redirection Hosts + if ( + internalHost._checkHostnameRecordsTaken( + hostname, + promises_results[1], + ignore_type === "redirection" && ignore_id ? ignore_id : 0, + ) + ) { + is_taken = true; } + } - if (promises_results[2]) { - // Dead Hosts - if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[2], ignore_type === 'dead' && ignore_id ? ignore_id : 0)) { - is_taken = true; - } + if (promises_results[2]) { + // Dead Hosts + if ( + internalHost._checkHostnameRecordsTaken( + hostname, + promises_results[2], + ignore_type === "dead" && ignore_id ? ignore_id : 0, + ) + ) { + is_taken = true; } + } - return { - hostname: hostname, - is_taken: is_taken - }; - }); + return { + hostname: hostname, + is_taken: is_taken, + }; + }); }, /** * Private call only * * @param {String} hostname - * @param {Array} existing_rows - * @param {Integer} [ignore_id] + * @param {Array} existingRows + * @param {Integer} [ignoreId] * @returns {Boolean} */ - _checkHostnameRecordsTaken: function (hostname, existing_rows, ignore_id) { - let is_taken = false; + _checkHostnameRecordsTaken: (hostname, existingRows, ignoreId) => { + let isTaken = false; - if (existing_rows && existing_rows.length) { - existing_rows.map(function (existing_row) { - existing_row.domain_names.map(function (existing_hostname) { + if (existingRows?.length) { + existingRows.map((existingRow) => { + existingRow.domain_names.map((existingHostname) => { // Does this domain match? - if (existing_hostname.toLowerCase() === hostname.toLowerCase()) { - if (!ignore_id || ignore_id !== existing_row.id) { - is_taken = true; + if (existingHostname.toLowerCase() === hostname.toLowerCase()) { + if (!ignoreId || ignoreId !== existingRow.id) { + isTaken = true; } } + return true; }); + return true; }); } - return is_taken; + return isTaken; }, /** * Private call only * * @param {Array} hosts - * @param {Array} domain_names + * @param {Array} domainNames * @returns {Array} */ - _getHostsWithDomains: function (hosts, domain_names) { - let response = []; + _getHostsWithDomains: (hosts, domainNames) => { + const response = []; - if (hosts && hosts.length) { - hosts.map(function (host) { - let host_matches = false; + if (hosts?.length) { + hosts.map((host) => { + let hostMatches = false; - domain_names.map(function (domain_name) { - host.domain_names.map(function (host_domain_name) { - if (domain_name.toLowerCase() === host_domain_name.toLowerCase()) { - host_matches = true; + domainNames.map((domainName) => { + host.domain_names.map((hostDomainName) => { + if (domainName.toLowerCase() === hostDomainName.toLowerCase()) { + hostMatches = true; } + return true; }); + return true; }); - if (host_matches) { + if (hostMatches) { response.push(host); } + return true; }); } return response; - } - + }, }; -module.exports = internalHost; +export default internalHost; diff --git a/backend/internal/ip_ranges.js b/backend/internal/ip_ranges.js index d34ee5a1..662da8ed 100644 --- a/backend/internal/ip_ranges.js +++ b/backend/internal/ip_ranges.js @@ -1,45 +1,51 @@ -const https = require('https'); -const fs = require('fs'); -const logger = require('../logger').ip_ranges; -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const internalNginx = require('./nginx'); +import fs from "node:fs"; +import https from "node:https"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import errs from "../lib/error.js"; +import utils from "../lib/utils.js"; +import { ipRanges as logger } from "../logger.js"; +import internalNginx from "./nginx.js"; -const CLOUDFRONT_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json'; -const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4'; -const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const CLOUDFRONT_URL = "https://ip-ranges.amazonaws.com/ip-ranges.json"; +const CLOUDFARE_V4_URL = "https://www.cloudflare.com/ips-v4"; +const CLOUDFARE_V6_URL = "https://www.cloudflare.com/ips-v6"; const regIpV4 = /^(\d+\.?){4}\/\d+/; const regIpV6 = /^(([\da-fA-F]+)?:)+\/\d+/; const internalIpRanges = { - - interval_timeout: 1000 * 60 * 60 * 6, // 6 hours - interval: null, + interval_timeout: 1000 * 60 * 60 * 6, // 6 hours + interval: null, interval_processing: false, - iteration_count: 0, + iteration_count: 0, initTimer: () => { - logger.info('IP Ranges Renewal Timer initialized'); + logger.info("IP Ranges Renewal Timer initialized"); internalIpRanges.interval = setInterval(internalIpRanges.fetch, internalIpRanges.interval_timeout); }, fetchUrl: (url) => { return new Promise((resolve, reject) => { - logger.info('Fetching ' + url); - return https.get(url, (res) => { - res.setEncoding('utf8'); - let raw_data = ''; - res.on('data', (chunk) => { - raw_data += chunk; - }); + logger.info(`Fetching ${url}`); + return https + .get(url, (res) => { + res.setEncoding("utf8"); + let raw_data = ""; + res.on("data", (chunk) => { + raw_data += chunk; + }); - res.on('end', () => { - resolve(raw_data); + res.on("end", () => { + resolve(raw_data); + }); + }) + .on("error", (err) => { + reject(err); }); - }).on('error', (err) => { - reject(err); - }); }); }, @@ -49,27 +55,30 @@ const internalIpRanges = { fetch: () => { if (!internalIpRanges.interval_processing) { internalIpRanges.interval_processing = true; - logger.info('Fetching IP Ranges from online services...'); + logger.info("Fetching IP Ranges from online services..."); let ip_ranges = []; - return internalIpRanges.fetchUrl(CLOUDFRONT_URL) + return internalIpRanges + .fetchUrl(CLOUDFRONT_URL) .then((cloudfront_data) => { - let data = JSON.parse(cloudfront_data); + const data = JSON.parse(cloudfront_data); - if (data && typeof data.prefixes !== 'undefined') { + if (data && typeof data.prefixes !== "undefined") { data.prefixes.map((item) => { - if (item.service === 'CLOUDFRONT') { + if (item.service === "CLOUDFRONT") { ip_ranges.push(item.ip_prefix); } + return true; }); } - if (data && typeof data.ipv6_prefixes !== 'undefined') { + if (data && typeof data.ipv6_prefixes !== "undefined") { data.ipv6_prefixes.map((item) => { - if (item.service === 'CLOUDFRONT') { + if (item.service === "CLOUDFRONT") { ip_ranges.push(item.ipv6_prefix); } + return true; }); } }) @@ -77,38 +86,38 @@ const internalIpRanges = { return internalIpRanges.fetchUrl(CLOUDFARE_V4_URL); }) .then((cloudfare_data) => { - let items = cloudfare_data.split('\n').filter((line) => regIpV4.test(line)); - ip_ranges = [... ip_ranges, ... items]; + const items = cloudfare_data.split("\n").filter((line) => regIpV4.test(line)); + ip_ranges = [...ip_ranges, ...items]; }) .then(() => { return internalIpRanges.fetchUrl(CLOUDFARE_V6_URL); }) .then((cloudfare_data) => { - let items = cloudfare_data.split('\n').filter((line) => regIpV6.test(line)); - ip_ranges = [... ip_ranges, ... items]; + const items = cloudfare_data.split("\n").filter((line) => regIpV6.test(line)); + ip_ranges = [...ip_ranges, ...items]; }) .then(() => { - let clean_ip_ranges = []; + const clean_ip_ranges = []; ip_ranges.map((range) => { if (range) { clean_ip_ranges.push(range); } + return true; }); - return internalIpRanges.generateConfig(clean_ip_ranges) - .then(() => { - if (internalIpRanges.iteration_count) { - // Reload nginx - return internalNginx.reload(); - } - }); + return internalIpRanges.generateConfig(clean_ip_ranges).then(() => { + if (internalIpRanges.iteration_count) { + // Reload nginx + return internalNginx.reload(); + } + }); }) .then(() => { internalIpRanges.interval_processing = false; internalIpRanges.iteration_count++; }) .catch((err) => { - logger.error(err.message); + logger.fatal(err.message); internalIpRanges.interval_processing = false; }); } @@ -122,26 +131,26 @@ const internalIpRanges = { const renderEngine = utils.getRenderEngine(); return new Promise((resolve, reject) => { let template = null; - let filename = '/etc/nginx/conf.d/include/ip_ranges.conf'; + const filename = "/etc/nginx/conf.d/include/ip_ranges.conf"; try { - template = fs.readFileSync(__dirname + '/../templates/ip_ranges.conf', {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/ip_ranges.conf`, { encoding: "utf8" }); } catch (err) { - reject(new error.ConfigurationError(err.message)); + reject(new errs.ConfigurationError(err.message)); return; } renderEngine - .parseAndRender(template, {ip_ranges: ip_ranges}) + .parseAndRender(template, { ip_ranges: ip_ranges }) .then((config_text) => { - fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); + fs.writeFileSync(filename, config_text, { encoding: "utf8" }); resolve(true); }) .catch((err) => { - logger.warn('Could not write ' + filename + ':', err.message); - reject(new error.ConfigurationError(err.message)); + logger.warn(`Could not write ${filename}: ${err.message}`); + reject(new errs.ConfigurationError(err.message)); }); }); - } + }, }; -module.exports = internalIpRanges; +export default internalIpRanges; diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 59694d3c..c4216fb5 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -1,12 +1,15 @@ -const _ = require('lodash'); -const fs = require('node:fs'); -const logger = require('../logger').nginx; -const config = require('../lib/config'); -const utils = require('../lib/utils'); -const error = require('../lib/error'); +import fs from "node:fs"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import _ from "lodash"; +import errs from "../lib/error.js"; +import utils from "../lib/utils.js"; +import { nginx as logger } from "../logger.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); const internalNginx = { - /** * This will: * - test the nginx config first to make sure it's OK @@ -24,7 +27,8 @@ const internalNginx = { configure: (model, host_type, host) => { let combined_meta = {}; - return internalNginx.test() + return internalNginx + .test() .then(() => { // Nginx is OK // We're deleting this config regardless. @@ -37,20 +41,18 @@ const internalNginx = { }) .then(() => { // Test nginx again and update meta with result - return internalNginx.test() + return internalNginx + .test() .then(() => { // nginx is ok combined_meta = _.assign({}, host.meta, { nginx_online: true, - nginx_err: null + nginx_err: null, }); - return model - .query() - .where('id', host.id) - .patch({ - meta: combined_meta - }); + return model.query().where("id", host.id).patch({ + meta: combined_meta, + }); }) .catch((err) => { // Remove the error_log line because it's a docker-ism false positive that doesn't need to be reported. @@ -58,28 +60,27 @@ const internalNginx = { // nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address) const valid_lines = []; - const err_lines = err.message.split('\n'); + const err_lines = err.message.split("\n"); err_lines.map((line) => { - if (line.indexOf('/var/log/nginx/error.log') === -1) { + if (line.indexOf("/var/log/nginx/error.log") === -1) { valid_lines.push(line); } + return true; }); - if (config.debug()) { - logger.error('Nginx test failed:', valid_lines.join('\n')); - } + logger.debug("Nginx test failed:", valid_lines.join("\n")); // config is bad, update meta and delete config combined_meta = _.assign({}, host.meta, { nginx_online: false, - nginx_err: valid_lines.join('\n') + nginx_err: valid_lines.join("\n"), }); return model .query() - .where('id', host.id) + .where("id", host.id) .patch({ - meta: combined_meta + meta: combined_meta, }) .then(() => { internalNginx.renameConfigAsError(host_type, host); @@ -101,22 +102,18 @@ const internalNginx = { * @returns {Promise} */ test: () => { - if (config.debug()) { - logger.info('Testing Nginx configuration'); - } - - return utils.execFile('/usr/sbin/nginx', ['-t', '-g', 'error_log off;']); + logger.debug("Testing Nginx configuration"); + return utils.execFile("/usr/sbin/nginx", ["-t", "-g", "error_log off;"]); }, /** * @returns {Promise} */ reload: () => { - return internalNginx.test() - .then(() => { - logger.info('Reloading Nginx'); - return utils.execFile('/usr/sbin/nginx', ['-s', 'reload']); - }); + return internalNginx.test().then(() => { + logger.info("Reloading Nginx"); + return utils.execFile("/usr/sbin/nginx", ["-s", "reload"]); + }); }, /** @@ -125,8 +122,8 @@ const internalNginx = { * @returns {String} */ getConfigName: (host_type, host_id) => { - if (host_type === 'default') { - return '/data/nginx/default_host/site.conf'; + if (host_type === "default") { + return "/data/nginx/default_host/site.conf"; } return `/data/nginx/${internalNginx.getFileFriendlyHostType(host_type)}/${host_id}.conf`; }, @@ -141,38 +138,45 @@ const internalNginx = { let template; try { - template = fs.readFileSync(`${__dirname}/../templates/_location.conf`, {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/_location.conf`, { encoding: "utf8" }); } catch (err) { - reject(new error.ConfigurationError(err.message)); + reject(new errs.ConfigurationError(err.message)); return; } - const renderEngine = utils.getRenderEngine(); - let renderedLocations = ''; + const renderEngine = utils.getRenderEngine(); + let renderedLocations = ""; const locationRendering = async () => { for (let i = 0; i < host.locations.length; i++) { - const locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id}, - {ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits}, - {allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support}, - {hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list}, - {certificate: host.certificate}, host.locations[i]); + const locationCopy = Object.assign( + {}, + { access_list_id: host.access_list_id }, + { certificate_id: host.certificate_id }, + { ssl_forced: host.ssl_forced }, + { caching_enabled: host.caching_enabled }, + { block_exploits: host.block_exploits }, + { allow_websocket_upgrade: host.allow_websocket_upgrade }, + { http2_support: host.http2_support }, + { hsts_enabled: host.hsts_enabled }, + { hsts_subdomains: host.hsts_subdomains }, + { access_list: host.access_list }, + { certificate: host.certificate }, + host.locations[i], + ); - if (locationCopy.forward_host.indexOf('/') > -1) { - const splitted = locationCopy.forward_host.split('/'); + if (locationCopy.forward_host.indexOf("/") > -1) { + const splitted = locationCopy.forward_host.split("/"); locationCopy.forward_host = splitted.shift(); - locationCopy.forward_path = `/${splitted.join('/')}`; + locationCopy.forward_path = `/${splitted.join("/")}`; } - // eslint-disable-next-line renderedLocations += await renderEngine.parseAndRender(template, locationCopy); } - }; locationRendering().then(() => resolve(renderedLocations)); - }); }, @@ -183,23 +187,21 @@ const internalNginx = { */ generateConfig: (host_type, host_row) => { // Prevent modifying the original object: - const host = JSON.parse(JSON.stringify(host_row)); + const host = JSON.parse(JSON.stringify(host_row)); const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); - if (config.debug()) { - logger.info(`Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2)); - } + logger.debug(`Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2)); const renderEngine = utils.getRenderEngine(); return new Promise((resolve, reject) => { - let template = null; + let template = null; const filename = internalNginx.getConfigName(nice_host_type, host.id); try { - template = fs.readFileSync(`${__dirname}/../templates/${nice_host_type}.conf`, {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/${nice_host_type}.conf`, { encoding: "utf8" }); } catch (err) { - reject(new error.ConfigurationError(err.message)); + reject(new errs.ConfigurationError(err.message)); return; } @@ -207,27 +209,26 @@ const internalNginx = { let origLocations; // Manipulate the data a bit before sending it to the template - if (nice_host_type !== 'default') { + if (nice_host_type !== "default") { host.use_default_location = true; - if (typeof host.advanced_config !== 'undefined' && host.advanced_config) { + if (typeof host.advanced_config !== "undefined" && host.advanced_config) { host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config); } } if (host.locations) { //logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2)); - origLocations = [].concat(host.locations); + origLocations = [].concat(host.locations); locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => { host.locations = renderedLocations; }); // Allow someone who is using / custom location path to use it, and skip the default / location _.map(host.locations, (location) => { - if (location.path === '/') { + if (location.path === "/") { host.use_default_location = false; } }); - } else { locationsPromise = Promise.resolve(); } @@ -239,11 +240,8 @@ const internalNginx = { renderEngine .parseAndRender(template, host) .then((config_text) => { - fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); - - if (config.debug()) { - logger.success('Wrote config:', filename, config_text); - } + fs.writeFileSync(filename, config_text, { encoding: "utf8" }); + logger.debug("Wrote config:", filename, config_text); // Restore locations array host.locations = origLocations; @@ -251,11 +249,8 @@ const internalNginx = { resolve(true); }) .catch((err) => { - if (config.debug()) { - logger.warn(`Could not write ${filename}:`, err.message); - } - - reject(new error.ConfigurationError(err.message)); + logger.debug(`Could not write ${filename}:`, err.message); + reject(new errs.ConfigurationError(err.message)); }); }); }); @@ -270,20 +265,17 @@ const internalNginx = { * @returns {Promise} */ generateLetsEncryptRequestConfig: (certificate) => { - if (config.debug()) { - logger.info('Generating LetsEncrypt Request Config:', certificate); - } - + logger.debug("Generating LetsEncrypt Request Config:", certificate); const renderEngine = utils.getRenderEngine(); return new Promise((resolve, reject) => { - let template = null; + let template = null; const filename = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; try { - template = fs.readFileSync(`${__dirname}/../templates/letsencrypt-request.conf`, {encoding: 'utf8'}); + template = fs.readFileSync(`${__dirname}/../templates/letsencrypt-request.conf`, { encoding: "utf8" }); } catch (err) { - reject(new error.ConfigurationError(err.message)); + reject(new errs.ConfigurationError(err.message)); return; } @@ -292,20 +284,13 @@ const internalNginx = { renderEngine .parseAndRender(template, certificate) .then((config_text) => { - fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); - - if (config.debug()) { - logger.success('Wrote config:', filename, config_text); - } - + fs.writeFileSync(filename, config_text, { encoding: "utf8" }); + logger.debug("Wrote config:", filename, config_text); resolve(true); }) .catch((err) => { - if (config.debug()) { - logger.warn(`Could not write ${filename}:`, err.message); - } - - reject(new error.ConfigurationError(err.message)); + logger.debug(`Could not write ${filename}:`, err.message); + reject(new errs.ConfigurationError(err.message)); }); }); }, @@ -320,7 +305,7 @@ const internalNginx = { try { fs.unlinkSync(filename); } catch (err) { - logger.debug('Could not delete file:', JSON.stringify(err, null, 2)); + logger.debug("Could not delete file:", JSON.stringify(err, null, 2)); } }, @@ -330,7 +315,7 @@ const internalNginx = { * @returns String */ getFileFriendlyHostType: (host_type) => { - return host_type.replace(/-/g, '_'); + return host_type.replace(/-/g, "_"); }, /** @@ -341,7 +326,7 @@ const internalNginx = { */ deleteLetsEncryptRequestConfig: (certificate) => { const config_file = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; - return new Promise((resolve/*, reject*/) => { + return new Promise((resolve /*, reject*/) => { internalNginx.deleteFile(config_file); resolve(); }); @@ -354,10 +339,13 @@ const internalNginx = { * @returns {Promise} */ deleteConfig: (host_type, host, delete_err_file) => { - const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); + const config_file = internalNginx.getConfigName( + internalNginx.getFileFriendlyHostType(host_type), + typeof host === "undefined" ? 0 : host.id, + ); const config_file_err = `${config_file}.err`; - return new Promise((resolve/*, reject*/) => { + return new Promise((resolve /*, reject*/) => { internalNginx.deleteFile(config_file); if (delete_err_file) { internalNginx.deleteFile(config_file_err); @@ -372,10 +360,13 @@ const internalNginx = { * @returns {Promise} */ renameConfigAsError: (host_type, host) => { - const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); + const config_file = internalNginx.getConfigName( + internalNginx.getFileFriendlyHostType(host_type), + typeof host === "undefined" ? 0 : host.id, + ); const config_file_err = `${config_file}.err`; - return new Promise((resolve/*, reject*/) => { + return new Promise((resolve /*, reject*/) => { fs.unlink(config_file, () => { // ignore result, continue fs.rename(config_file, config_file_err, () => { @@ -395,6 +386,7 @@ const internalNginx = { const promises = []; hosts.map((host) => { promises.push(internalNginx.generateConfig(host_type, host)); + return true; }); return Promise.all(promises); @@ -409,6 +401,7 @@ const internalNginx = { const promises = []; hosts.map((host) => { promises.push(internalNginx.deleteConfig(host_type, host, true)); + return true; }); return Promise.all(promises); @@ -424,13 +417,13 @@ const internalNginx = { * @returns {boolean} */ ipv6Enabled: () => { - if (typeof process.env.DISABLE_IPV6 !== 'undefined') { + if (typeof process.env.DISABLE_IPV6 !== "undefined") { const disabled = process.env.DISABLE_IPV6.toLowerCase(); - return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes'); + return !(disabled === "on" || disabled === "true" || disabled === "1" || disabled === "yes"); } return true; - } + }, }; -module.exports = internalNginx; +export default internalNginx; diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 32f2bc0d..1c51d801 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -1,107 +1,106 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const proxyHostModel = require('../models/proxy_host'); -const internalHost = require('./host'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); -const {castJsonIfNeed} = require('../lib/helpers'); +import _ from "lodash"; +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import utils from "../lib/utils.js"; +import proxyHostModel from "../models/proxy_host.js"; +import internalAuditLog from "./audit-log.js"; +import internalCertificate from "./certificate.js"; +import internalHost from "./host.js"; +import internalNginx from "./nginx.js"; -function omissions () { - return ['is_deleted', 'owner.is_deleted']; -} +const omissions = () => { + return ["is_deleted", "owner.is_deleted"]; +}; const internalProxyHost = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ create: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + let thisData = data; + const createCertificate = thisData.certificate_id === "new"; - if (create_certificate) { - delete data.certificate_id; + if (createCertificate) { + delete thisData.certificate_id; } - return access.can('proxy_hosts:create', data) + return access + .can("proxy_hosts:create", thisData) .then(() => { // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + const domain_name_check_promises = []; - data.domain_names.map(function (domain_name) { + thisData.domain_names.map((domain_name) => { domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); + return true; }); - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + return Promise.all(domain_name_check_promises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; }); + }); }) .then(() => { // At this point the domains should have been checked - data.owner_user_id = access.token.getUserId(1); - data = internalHost.cleanSslHstsData(data); + thisData.owner_user_id = access.token.getUserId(1); + thisData = internalHost.cleanSslHstsData(thisData); // Fix for db field not having a default value // for this optional field. - if (typeof data.advanced_config === 'undefined') { - data.advanced_config = ''; + if (typeof thisData.advanced_config === "undefined") { + thisData.advanced_config = ""; } - return proxyHostModel - .query() - .insertAndFetch(data) - .then(utils.omitRow(omissions())); + return proxyHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); }) .then((row) => { - if (create_certificate) { - return internalCertificate.createQuickCertificate(access, data) + if (createCertificate) { + return internalCertificate + .createQuickCertificate(access, thisData) .then((cert) => { // update host with cert id return internalProxyHost.update(access, { - id: row.id, - certificate_id: cert.id + id: row.id, + certificate_id: cert.id, }); }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // re-fetch with cert return internalProxyHost.get(access, { - id: row.id, - expand: ['certificate', 'owner', 'access_list.[clients,items]'] + id: row.id, + expand: ["certificate", "owner", "access_list.[clients,items]"], }); }) .then((row) => { // Configure nginx - return internalNginx.configure(proxyHostModel, 'proxy_host', row) - .then(() => { - return row; - }); + return internalNginx.configure(proxyHostModel, "proxy_host", row).then(() => { + return row; + }); }) .then((row) => { // Audit log - data.meta = _.assign({}, data.meta || {}, row.meta); + thisData.meta = _.assign({}, thisData.meta || {}, row.meta); // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'proxy-host', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "created", + object_type: "proxy-host", + object_id: row.id, + meta: thisData, + }) .then(() => { return row; }); @@ -115,100 +114,110 @@ const internalProxyHost = { * @return {Promise} */ update: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + let thisData = data; + const create_certificate = thisData.certificate_id === "new"; if (create_certificate) { - delete data.certificate_id; + delete thisData.certificate_id; } - return access.can('proxy_hosts:update', data.id) + return access + .can("proxy_hosts:update", thisData.id) .then((/*access_data*/) => { // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + const domain_name_check_promises = []; - if (typeof data.domain_names !== 'undefined') { - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'proxy', data.id)); + if (typeof thisData.domain_names !== "undefined") { + thisData.domain_names.map((domain_name) => { + return domain_name_check_promises.push( + internalHost.isHostnameTaken(domain_name, "proxy", thisData.id), + ); }); - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + return Promise.all(domain_name_check_promises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; }); + }); } }) .then(() => { - return internalProxyHost.get(access, {id: data.id}); + return internalProxyHost.get(access, { id: thisData.id }); }) .then((row) => { - if (row.id !== data.id) { + if (row.id !== thisData.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Proxy Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `Proxy Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`, + ); } if (create_certificate) { - return internalCertificate.createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta) - }) + return internalCertificate + .createQuickCertificate(access, { + domain_names: thisData.domain_names || row.domain_names, + meta: _.assign({}, row.meta, thisData.meta), + }) .then((cert) => { // update host with cert id - data.certificate_id = cert.id; + thisData.certificate_id = cert.id; }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign({}, { - domain_names: row.domain_names - }, data); + thisData = _.assign( + {}, + { + domain_names: row.domain_names, + }, + data, + ); - data = internalHost.cleanSslHstsData(data, row); + thisData = internalHost.cleanSslHstsData(thisData, row); return proxyHostModel .query() - .where({id: data.id}) - .patch(data) + .where({ id: thisData.id }) + .patch(thisData) .then(utils.omitRow(omissions())) .then((saved_row) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'proxy-host', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "updated", + object_type: "proxy-host", + object_id: row.id, + meta: thisData, + }) .then(() => { return saved_row; }); }); }) .then(() => { - return internalProxyHost.get(access, { - id: data.id, - expand: ['owner', 'certificate', 'access_list.[clients,items]'] - }) + return internalProxyHost + .get(access, { + id: thisData.id, + expand: ["owner", "certificate", "access_list.[clients,items]"], + }) .then((row) => { if (!row.enabled) { // No need to add nginx config if host is disabled return row; } // Configure nginx - return internalNginx.configure(proxyHostModel, 'proxy_host', row) - .then((new_meta) => { - row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); - }); + return internalNginx.configure(proxyHostModel, "proxy_host", row).then((new_meta) => { + row.meta = new_meta; + return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); + }); }); }); }, @@ -222,39 +231,38 @@ const internalProxyHost = { * @return {Promise} */ get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } + const thisData = data || {}; - return access.can('proxy_hosts:get', data.id) + return access + .can("proxy_hosts:get", thisData.id) .then((access_data) => { - let query = proxyHostModel + const query = proxyHostModel .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[owner,access_list.[clients,items],certificate]') + .where("is_deleted", 0) + .andWhere("id", thisData.id) + .allowGraph("[owner,access_list.[clients,items],certificate]") .first(); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(thisData.id); } - row = internalHost.cleanRowCertificateMeta(row); + const thisRow = internalHost.cleanRowCertificateMeta(row); // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { + return _.omit(row, thisData.omit); } - return row; + return thisRow; }); }, @@ -266,35 +274,35 @@ const internalProxyHost = { * @returns {Promise} */ delete: (access, data) => { - return access.can('proxy_hosts:delete', data.id) + return access + .can("proxy_hosts:delete", data.id) .then(() => { - return internalProxyHost.get(access, {id: data.id}); + return internalProxyHost.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(data.id); } return proxyHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('proxy_host', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("proxy_host", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'proxy-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "deleted", + object_type: "proxy-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -311,39 +319,41 @@ const internalProxyHost = { * @returns {Promise} */ enable: (access, data) => { - return access.can('proxy_hosts:update', data.id) + return access + .can("proxy_hosts:update", data.id) .then(() => { return internalProxyHost.get(access, { - id: data.id, - expand: ['certificate', 'owner', 'access_list'] + id: data.id, + expand: ["certificate", "owner", "access_list"], }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Host is already enabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Host is already enabled"); } row.enabled = 1; return proxyHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 1 + enabled: 1, }) .then(() => { // Configure nginx - return internalNginx.configure(proxyHostModel, 'proxy_host', row); + return internalNginx.configure(proxyHostModel, "proxy_host", row); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'proxy-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "enabled", + object_type: "proxy-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -360,39 +370,40 @@ const internalProxyHost = { * @returns {Promise} */ disable: (access, data) => { - return access.can('proxy_hosts:update', data.id) + return access + .can("proxy_hosts:update", data.id) .then(() => { - return internalProxyHost.get(access, {id: data.id}); + return internalProxyHost.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Host is already disabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (!row.enabled) { + throw new errs.ValidationError("Host is already disabled"); } row.enabled = 0; return proxyHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 0 + enabled: 0, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('proxy_host', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("proxy_host", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'proxy-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "disabled", + object_type: "proxy-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -410,34 +421,35 @@ const internalProxyHost = { * @returns {Promise} */ getAll: (access, expand, search_query) => { - return access.can('proxy_hosts:list') + return access + .can("proxy_hosts:list") .then((access_data) => { - let query = proxyHostModel + const query = proxyHostModel .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[owner,access_list,certificate]') - .orderBy(castJsonIfNeed('domain_names'), 'ASC'); + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,access_list,certificate]") + .orderBy(castJsonIfNeed("domain_names"), "ASC"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { + if (typeof search_query === "string" && search_query.length > 0) { query.where(function () { - this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`); + this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`); }); } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); } return query.then(utils.omitRows(omissions())); }) .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { + if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { return internalHost.cleanAllRowsCertificateMeta(rows); } @@ -453,20 +465,16 @@ const internalProxyHost = { * @returns {Promise} */ getCount: (user_id, visibility) => { - let query = proxyHostModel - .query() - .count('id as count') - .where('is_deleted', 0); + const query = proxyHostModel.query().count("id as count").where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", user_id); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); - } + return query.first().then((row) => { + return Number.parseInt(row.count, 10); + }); + }, }; -module.exports = internalProxyHost; +export default internalProxyHost; diff --git a/backend/internal/redirection-host.js b/backend/internal/redirection-host.js index 6a81b866..159ffd8b 100644 --- a/backend/internal/redirection-host.js +++ b/backend/internal/redirection-host.js @@ -1,73 +1,73 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const redirectionHostModel = require('../models/redirection_host'); -const internalHost = require('./host'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); -const {castJsonIfNeed} = require('../lib/helpers'); +import _ from "lodash"; +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import utils from "../lib/utils.js"; +import redirectionHostModel from "../models/redirection_host.js"; +import internalAuditLog from "./audit-log.js"; +import internalCertificate from "./certificate.js"; +import internalHost from "./host.js"; +import internalNginx from "./nginx.js"; -function omissions () { - return ['is_deleted']; -} +const omissions = () => { + return ["is_deleted"]; +}; const internalRedirectionHost = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ create: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + let thisData = data || {}; + const createCertificate = thisData.certificate_id === "new"; - if (create_certificate) { - delete data.certificate_id; + if (createCertificate) { + delete thisData.certificate_id; } - return access.can('redirection_hosts:create', data) + return access + .can("redirection_hosts:create", thisData) .then((/*access_data*/) => { // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + const domain_name_check_promises = []; - data.domain_names.map(function (domain_name) { + thisData.domain_names.map((domain_name) => { domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); + return true; }); - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + return Promise.all(domain_name_check_promises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; }); + }); }) .then(() => { // At this point the domains should have been checked - data.owner_user_id = access.token.getUserId(1); - data = internalHost.cleanSslHstsData(data); + thisData.owner_user_id = access.token.getUserId(1); + thisData = internalHost.cleanSslHstsData(thisData); // Fix for db field not having a default value // for this optional field. - if (typeof data.advanced_config === 'undefined') { - data.advanced_config = ''; + if (typeof data.advanced_config === "undefined") { + data.advanced_config = ""; } - return redirectionHostModel - .query() - .insertAndFetch(data) - .then(utils.omitRow(omissions())); + return redirectionHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); }) .then((row) => { - if (create_certificate) { - return internalCertificate.createQuickCertificate(access, data) + if (createCertificate) { + return internalCertificate + .createQuickCertificate(access, thisData) .then((cert) => { // update host with cert id return internalRedirectionHost.update(access, { - id: row.id, - certificate_id: cert.id + id: row.id, + certificate_id: cert.id, }); }) .then(() => { @@ -79,27 +79,27 @@ const internalRedirectionHost = { .then((row) => { // re-fetch with cert return internalRedirectionHost.get(access, { - id: row.id, - expand: ['certificate', 'owner'] + id: row.id, + expand: ["certificate", "owner"], }); }) .then((row) => { // Configure nginx - return internalNginx.configure(redirectionHostModel, 'redirection_host', row) - .then(() => { - return row; - }); + return internalNginx.configure(redirectionHostModel, "redirection_host", row).then(() => { + return row; + }); }) .then((row) => { - data.meta = _.assign({}, data.meta || {}, row.meta); + thisData.meta = _.assign({}, thisData.meta || {}, row.meta); // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'redirection-host', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "created", + object_type: "redirection-host", + object_id: row.id, + meta: thisData, + }) .then(() => { return row; }); @@ -113,94 +113,107 @@ const internalRedirectionHost = { * @return {Promise} */ update: (access, data) => { - let create_certificate = data.certificate_id === 'new'; + let thisData = data || {}; + const createCertificate = thisData.certificate_id === "new"; - if (create_certificate) { - delete data.certificate_id; + if (createCertificate) { + delete thisData.certificate_id; } - return access.can('redirection_hosts:update', data.id) + return access + .can("redirection_hosts:update", thisData.id) .then((/*access_data*/) => { // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; + const domain_name_check_promises = []; - if (typeof data.domain_names !== 'undefined') { - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'redirection', data.id)); + if (typeof thisData.domain_names !== "undefined") { + thisData.domain_names.map((domain_name) => { + domain_name_check_promises.push( + internalHost.isHostnameTaken(domain_name, "redirection", thisData.id), + ); + return true; }); - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + return Promise.all(domain_name_check_promises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; }); + }); } }) .then(() => { - return internalRedirectionHost.get(access, {id: data.id}); + return internalRedirectionHost.get(access, { id: thisData.id }); }) .then((row) => { - if (row.id !== data.id) { + if (row.id !== thisData.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Redirection Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `Redirection Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`, + ); } - if (create_certificate) { - return internalCertificate.createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta) - }) + if (createCertificate) { + return internalCertificate + .createQuickCertificate(access, { + domain_names: thisData.domain_names || row.domain_names, + meta: _.assign({}, row.meta, thisData.meta), + }) .then((cert) => { // update host with cert id - data.certificate_id = cert.id; + thisData.certificate_id = cert.id; }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign({}, { - domain_names: row.domain_names - }, data); + thisData = _.assign( + {}, + { + domain_names: row.domain_names, + }, + thisData, + ); - data = internalHost.cleanSslHstsData(data, row); + thisData = internalHost.cleanSslHstsData(thisData, row); return redirectionHostModel .query() - .where({id: data.id}) - .patch(data) + .where({ id: thisData.id }) + .patch(thisData) .then((saved_row) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'redirection-host', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "updated", + object_type: "redirection-host", + object_id: row.id, + meta: thisData, + }) .then(() => { return _.omit(saved_row, omissions()); }); }); }) .then(() => { - return internalRedirectionHost.get(access, { - id: data.id, - expand: ['owner', 'certificate'] - }) + return internalRedirectionHost + .get(access, { + id: thisData.id, + expand: ["owner", "certificate"], + }) .then((row) => { // Configure nginx - return internalNginx.configure(redirectionHostModel, 'redirection_host', row) + return internalNginx + .configure(redirectionHostModel, "redirection_host", row) .then((new_meta) => { row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); + return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); }); }); }); @@ -215,39 +228,39 @@ const internalRedirectionHost = { * @return {Promise} */ get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } + const thisData = data || {}; - return access.can('redirection_hosts:get', data.id) + return access + .can("redirection_hosts:get", thisData.id) .then((access_data) => { - let query = redirectionHostModel + const query = redirectionHostModel .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[owner,certificate]') + .where("is_deleted", 0) + .andWhere("id", thisData.id) + .allowGraph("[owner,certificate]") .first(); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + let thisRow = row; + if (!thisRow || !thisRow.id) { + throw new errs.ItemNotFoundError(thisData.id); } - row = internalHost.cleanRowCertificateMeta(row); + thisRow = internalHost.cleanRowCertificateMeta(thisRow); // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { + return _.omit(thisRow, thisData.omit); } - return row; + return thisRow; }); }, @@ -259,35 +272,35 @@ const internalRedirectionHost = { * @returns {Promise} */ delete: (access, data) => { - return access.can('redirection_hosts:delete', data.id) + return access + .can("redirection_hosts:delete", data.id) .then(() => { - return internalRedirectionHost.get(access, {id: data.id}); + return internalRedirectionHost.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(data.id); } return redirectionHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('redirection_host', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("redirection_host", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'redirection-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "deleted", + object_type: "redirection-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -304,39 +317,41 @@ const internalRedirectionHost = { * @returns {Promise} */ enable: (access, data) => { - return access.can('redirection_hosts:update', data.id) + return access + .can("redirection_hosts:update", data.id) .then(() => { return internalRedirectionHost.get(access, { - id: data.id, - expand: ['certificate', 'owner'] + id: data.id, + expand: ["certificate", "owner"], }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Host is already enabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Host is already enabled"); } row.enabled = 1; return redirectionHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 1 + enabled: 1, }) .then(() => { // Configure nginx - return internalNginx.configure(redirectionHostModel, 'redirection_host', row); + return internalNginx.configure(redirectionHostModel, "redirection_host", row); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'redirection-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "enabled", + object_type: "redirection-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -353,39 +368,40 @@ const internalRedirectionHost = { * @returns {Promise} */ disable: (access, data) => { - return access.can('redirection_hosts:update', data.id) + return access + .can("redirection_hosts:update", data.id) .then(() => { - return internalRedirectionHost.get(access, {id: data.id}); + return internalRedirectionHost.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Host is already disabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (!row.enabled) { + throw new errs.ValidationError("Host is already disabled"); } row.enabled = 0; return redirectionHostModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 0 + enabled: 0, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('redirection_host', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("redirection_host", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'redirection-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "disabled", + object_type: "redirection-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -403,34 +419,35 @@ const internalRedirectionHost = { * @returns {Promise} */ getAll: (access, expand, search_query) => { - return access.can('redirection_hosts:list') + return access + .can("redirection_hosts:list") .then((access_data) => { - let query = redirectionHostModel + const query = redirectionHostModel .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[owner,certificate]') - .orderBy(castJsonIfNeed('domain_names'), 'ASC'); + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,certificate]") + .orderBy(castJsonIfNeed("domain_names"), "ASC"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { + if (typeof search_query === "string" && search_query.length > 0) { query.where(function () { - this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`); + this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`); }); } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); } return query.then(utils.omitRows(omissions())); }) .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { + if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { return internalHost.cleanAllRowsCertificateMeta(rows); } @@ -446,20 +463,16 @@ const internalRedirectionHost = { * @returns {Promise} */ getCount: (user_id, visibility) => { - let query = redirectionHostModel - .query() - .count('id as count') - .where('is_deleted', 0); + const query = redirectionHostModel.query().count("id as count").where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", user_id); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); - } + return query.first().then((row) => { + return Number.parseInt(row.count, 10); + }); + }, }; -module.exports = internalRedirectionHost; +export default internalRedirectionHost; diff --git a/backend/internal/report.js b/backend/internal/report.js index 4dde659b..8bad7ec2 100644 --- a/backend/internal/report.js +++ b/backend/internal/report.js @@ -1,38 +1,37 @@ -const internalProxyHost = require('./proxy-host'); -const internalRedirectionHost = require('./redirection-host'); -const internalDeadHost = require('./dead-host'); -const internalStream = require('./stream'); +import internalDeadHost from "./dead-host.js"; +import internalProxyHost from "./proxy-host.js"; +import internalRedirectionHost from "./redirection-host.js"; +import internalStream from "./stream.js"; const internalReport = { - /** * @param {Access} access * @return {Promise} */ getHostsReport: (access) => { - return access.can('reports:hosts', 1) + return access + .can("reports:hosts", 1) .then((access_data) => { - let user_id = access.token.getUserId(1); + const userId = access.token.getUserId(1); - let promises = [ - internalProxyHost.getCount(user_id, access_data.visibility), - internalRedirectionHost.getCount(user_id, access_data.visibility), - internalStream.getCount(user_id, access_data.visibility), - internalDeadHost.getCount(user_id, access_data.visibility) + const promises = [ + internalProxyHost.getCount(userId, access_data.visibility), + internalRedirectionHost.getCount(userId, access_data.visibility), + internalStream.getCount(userId, access_data.visibility), + internalDeadHost.getCount(userId, access_data.visibility), ]; return Promise.all(promises); }) .then((counts) => { return { - proxy: counts.shift(), + proxy: counts.shift(), redirection: counts.shift(), - stream: counts.shift(), - dead: counts.shift() + stream: counts.shift(), + dead: counts.shift(), }; }); - - } + }, }; -module.exports = internalReport; +export default internalReport; diff --git a/backend/internal/setting.js b/backend/internal/setting.js index d4ac67d8..f8fc7114 100644 --- a/backend/internal/setting.js +++ b/backend/internal/setting.js @@ -1,10 +1,9 @@ -const fs = require('fs'); -const error = require('../lib/error'); -const settingModel = require('../models/setting'); -const internalNginx = require('./nginx'); +import fs from "node:fs"; +import errs from "../lib/error.js"; +import settingModel from "../models/setting.js"; +import internalNginx from "./nginx.js"; const internalSetting = { - /** * @param {Access} access * @param {Object} data @@ -12,37 +11,38 @@ const internalSetting = { * @return {Promise} */ update: (access, data) => { - return access.can('settings:update', data.id) + return access + .can("settings:update", data.id) .then((/*access_data*/) => { - return internalSetting.get(access, {id: data.id}); + return internalSetting.get(access, { id: data.id }); }) .then((row) => { if (row.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Setting could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `Setting could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); } - return settingModel - .query() - .where({id: data.id}) - .patch(data); + return settingModel.query().where({ id: data.id }).patch(data); }) .then(() => { return internalSetting.get(access, { - id: data.id + id: data.id, }); }) .then((row) => { - if (row.id === 'default-site') { + if (row.id === "default-site") { // write the html if we need to - if (row.value === 'html') { - fs.writeFileSync('/data/nginx/default_www/index.html', row.meta.html, {encoding: 'utf8'}); + if (row.value === "html") { + fs.writeFileSync("/data/nginx/default_www/index.html", row.meta.html, { encoding: "utf8" }); } // Configure nginx - return internalNginx.deleteConfig('default') + return internalNginx + .deleteConfig("default") .then(() => { - return internalNginx.generateConfig('default', row); + return internalNginx.generateConfig("default", row); }) .then(() => { return internalNginx.test(); @@ -54,7 +54,8 @@ const internalSetting = { return row; }) .catch((/*err*/) => { - internalNginx.deleteConfig('default') + internalNginx + .deleteConfig("default") .then(() => { return internalNginx.test(); }) @@ -63,12 +64,11 @@ const internalSetting = { }) .then(() => { // I'm being slack here I know.. - throw new error.ValidationError('Could not reconfigure Nginx. Please check logs.'); + throw new errs.ValidationError("Could not reconfigure Nginx. Please check logs."); }); }); - } else { - return row; } + return row; }); }, @@ -79,19 +79,16 @@ const internalSetting = { * @return {Promise} */ get: (access, data) => { - return access.can('settings:get', data.id) + return access + .can("settings:get", data.id) .then(() => { - return settingModel - .query() - .where('id', data.id) - .first(); + return settingModel.query().where("id", data.id).first(); }) .then((row) => { if (row) { return row; - } else { - throw new error.ItemNotFoundError(data.id); } + throw new errs.ItemNotFoundError(data.id); }); }, @@ -102,15 +99,13 @@ const internalSetting = { * @returns {*} */ getCount: (access) => { - return access.can('settings:list') + return access + .can("settings:list") .then(() => { - return settingModel - .query() - .count('id as count') - .first(); + return settingModel.query().count("id as count").first(); }) .then((row) => { - return parseInt(row.count, 10); + return Number.parseInt(row.count, 10); }); }, @@ -121,13 +116,10 @@ const internalSetting = { * @returns {Promise} */ getAll: (access) => { - return access.can('settings:list') - .then(() => { - return settingModel - .query() - .orderBy('description', 'ASC'); - }); - } + return access.can("settings:list").then(() => { + return settingModel.query().orderBy("description", "ASC"); + }); + }, }; -module.exports = internalSetting; +export default internalSetting; diff --git a/backend/internal/stream.js b/backend/internal/stream.js index 50ce0832..bec95b6e 100644 --- a/backend/internal/stream.js +++ b/backend/internal/stream.js @@ -1,88 +1,85 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const streamModel = require('../models/stream'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); -const internalHost = require('./host'); -const {castJsonIfNeed} = require('../lib/helpers'); +import _ from "lodash"; +import errs from "../lib/error.js"; +import { castJsonIfNeed } from "../lib/helpers.js"; +import utils from "../lib/utils.js"; +import streamModel from "../models/stream.js"; +import internalAuditLog from "./audit-log.js"; +import internalCertificate from "./certificate.js"; +import internalHost from "./host.js"; +import internalNginx from "./nginx.js"; -function omissions () { - return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted']; -} +const omissions = () => { + return ["is_deleted", "owner.is_deleted", "certificate.is_deleted"]; +}; const internalStream = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ create: (access, data) => { - const create_certificate = data.certificate_id === 'new'; + const create_certificate = data.certificate_id === "new"; if (create_certificate) { delete data.certificate_id; } - return access.can('streams:create', data) + return access + .can("streams:create", data) .then((/*access_data*/) => { // TODO: At this point the existing ports should have been checked data.owner_user_id = access.token.getUserId(1); - if (typeof data.meta === 'undefined') { + if (typeof data.meta === "undefined") { data.meta = {}; } // streams aren't routed by domain name so don't store domain names in the DB - let data_no_domains = structuredClone(data); + const data_no_domains = structuredClone(data); delete data_no_domains.domain_names; - return streamModel - .query() - .insertAndFetch(data_no_domains) - .then(utils.omitRow(omissions())); + return streamModel.query().insertAndFetch(data_no_domains).then(utils.omitRow(omissions())); }) .then((row) => { if (create_certificate) { - return internalCertificate.createQuickCertificate(access, data) + return internalCertificate + .createQuickCertificate(access, data) .then((cert) => { // update host with cert id return internalStream.update(access, { - id: row.id, - certificate_id: cert.id + id: row.id, + certificate_id: cert.id, }); }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // re-fetch with cert return internalStream.get(access, { - id: row.id, - expand: ['certificate', 'owner'] + id: row.id, + expand: ["certificate", "owner"], }); }) .then((row) => { // Configure nginx - return internalNginx.configure(streamModel, 'stream', row) - .then(() => { - return row; - }); + return internalNginx.configure(streamModel, "stream", row).then(() => { + return row; + }); }) .then((row) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'stream', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "created", + object_type: "stream", + object_id: row.id, + meta: data, + }) .then(() => { return row; }); @@ -96,72 +93,78 @@ const internalStream = { * @return {Promise} */ update: (access, data) => { - const create_certificate = data.certificate_id === 'new'; + let thisData = data; + const create_certificate = thisData.certificate_id === "new"; if (create_certificate) { - delete data.certificate_id; + delete thisData.certificate_id; } - return access.can('streams:update', data.id) + return access + .can("streams:update", thisData.id) .then((/*access_data*/) => { // TODO: at this point the existing streams should have been checked - return internalStream.get(access, {id: data.id}); + return internalStream.get(access, { id: thisData.id }); }) .then((row) => { - if (row.id !== data.id) { + if (row.id !== thisData.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `Stream could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`, + ); } if (create_certificate) { - return internalCertificate.createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta) - }) + return internalCertificate + .createQuickCertificate(access, { + domain_names: thisData.domain_names || row.domain_names, + meta: _.assign({}, row.meta, thisData.meta), + }) .then((cert) => { // update host with cert id - data.certificate_id = cert.id; + thisData.certificate_id = cert.id; }) .then(() => { return row; }); - } else { - return row; } + return row; }) .then((row) => { // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign({}, { - domain_names: row.domain_names - }, data); + thisData = _.assign( + {}, + { + domain_names: row.domain_names, + }, + thisData, + ); return streamModel .query() - .patchAndFetchById(row.id, data) + .patchAndFetchById(row.id, thisData) .then(utils.omitRow(omissions())) .then((saved_row) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'stream', - object_id: row.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "updated", + object_type: "stream", + object_id: row.id, + meta: thisData, + }) .then(() => { return saved_row; }); }); }) .then(() => { - return internalStream.get(access, {id: data.id, expand: ['owner', 'certificate']}) - .then((row) => { - return internalNginx.configure(streamModel, 'stream', row) - .then((new_meta) => { - row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); - }); + return internalStream.get(access, { id: thisData.id, expand: ["owner", "certificate"] }).then((row) => { + return internalNginx.configure(streamModel, "stream", row).then((new_meta) => { + row.meta = new_meta; + return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); }); + }); }); }, @@ -174,39 +177,39 @@ const internalStream = { * @return {Promise} */ get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } + const thisData = data || {}; - return access.can('streams:get', data.id) + return access + .can("streams:get", thisData.id) .then((access_data) => { - let query = streamModel + const query = streamModel .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[owner,certificate]') + .where("is_deleted", 0) + .andWhere("id", thisData.id) + .allowGraph("[owner,certificate]") .first(); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + let thisRow = row; + if (!thisRow || !thisRow.id) { + throw new errs.ItemNotFoundError(thisData.id); } - row = internalHost.cleanRowCertificateMeta(row); + thisRow = internalHost.cleanRowCertificateMeta(thisRow); // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { + return _.omit(thisRow, thisData.omit); } - return row; + return thisRow; }); }, @@ -218,35 +221,35 @@ const internalStream = { * @returns {Promise} */ delete: (access, data) => { - return access.can('streams:delete', data.id) + return access + .can("streams:delete", data.id) .then(() => { - return internalStream.get(access, {id: data.id}); + return internalStream.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(data.id); } return streamModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('stream', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("stream", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'stream', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "deleted", + object_type: "stream", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -263,39 +266,41 @@ const internalStream = { * @returns {Promise} */ enable: (access, data) => { - return access.can('streams:update', data.id) + return access + .can("streams:update", data.id) .then(() => { return internalStream.get(access, { - id: data.id, - expand: ['certificate', 'owner'] + id: data.id, + expand: ["certificate", "owner"], }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Stream is already enabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Stream is already enabled"); } row.enabled = 1; return streamModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 1 + enabled: 1, }) .then(() => { // Configure nginx - return internalNginx.configure(streamModel, 'stream', row); + return internalNginx.configure(streamModel, "stream", row); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'stream', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "enabled", + object_type: "stream", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -312,39 +317,40 @@ const internalStream = { * @returns {Promise} */ disable: (access, data) => { - return access.can('streams:update', data.id) + return access + .can("streams:update", data.id) .then(() => { - return internalStream.get(access, {id: data.id}); + return internalStream.get(access, { id: data.id }); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Stream is already disabled'); + throw new errs.ItemNotFoundError(data.id); + } + if (!row.enabled) { + throw new errs.ValidationError("Stream is already disabled"); } row.enabled = 0; return streamModel .query() - .where('id', row.id) + .where("id", row.id) .patch({ - enabled: 0 + enabled: 0, }) .then(() => { // Delete Nginx Config - return internalNginx.deleteConfig('stream', row) - .then(() => { - return internalNginx.reload(); - }); + return internalNginx.deleteConfig("stream", row).then(() => { + return internalNginx.reload(); + }); }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'stream-host', - object_id: row.id, - meta: _.omit(row, omissions()) + action: "disabled", + object_type: "stream-host", + object_id: row.id, + meta: _.omit(row, omissions()), }); }); }) @@ -362,34 +368,35 @@ const internalStream = { * @returns {Promise} */ getAll: (access, expand, search_query) => { - return access.can('streams:list') + return access + .can("streams:list") .then((access_data) => { const query = streamModel .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[owner,certificate]') - .orderBy('incoming_port', 'ASC'); + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,certificate]") + .orderBy("incoming_port", "ASC"); - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); + if (access_data.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); } // Query is used for searching - if (typeof search_query === 'string' && search_query.length > 0) { + if (typeof search_query === "string" && search_query.length > 0) { query.where(function () { - this.where(castJsonIfNeed('incoming_port'), 'like', `%${search_query}%`); + this.where(castJsonIfNeed("incoming_port"), "like", `%${search_query}%`); }); } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); } return query.then(utils.omitRows(omissions())); }) .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { + if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { return internalHost.cleanAllRowsCertificateMeta(rows); } @@ -405,20 +412,16 @@ const internalStream = { * @returns {Promise} */ getCount: (user_id, visibility) => { - const query = streamModel - .query() - .count('id AS count') - .where('is_deleted', 0); + const query = streamModel.query().count("id AS count").where("is_deleted", 0); - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); + if (visibility !== "all") { + query.andWhere("owner_user_id", user_id); } - return query.first() - .then((row) => { - return parseInt(row.count, 10); - }); - } + return query.first().then((row) => { + return Number.parseInt(row.count, 10); + }); + }, }; -module.exports = internalStream; +export default internalStream; diff --git a/backend/internal/token.js b/backend/internal/token.js index 0e6dec5e..810ac6aa 100644 --- a/backend/internal/token.js +++ b/backend/internal/token.js @@ -1,14 +1,14 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const userModel = require('../models/user'); -const authModel = require('../models/auth'); -const helpers = require('../lib/helpers'); -const TokenModel = require('../models/token'); +import _ from "lodash"; +import errs from "../lib/error.js"; +import { parseDatePeriod } from "../lib/helpers.js"; +import authModel from "../models/auth.js"; +import TokenModel from "../models/token.js"; +import userModel from "../models/user.js"; -const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password'; - -module.exports = { +const ERROR_MESSAGE_INVALID_AUTH = "Invalid email or password"; +const ERROR_MESSAGE_INVALID_AUTH_I18N = "error.invalid-auth"; +export default { /** * @param {Object} data * @param {String} data.identity @@ -19,68 +19,65 @@ module.exports = { * @returns {Promise} */ getTokenFromEmail: (data, issuer) => { - let Token = new TokenModel(); + const Token = TokenModel(); - data.scope = data.scope || 'user'; - data.expiry = data.expiry || '1d'; + data.scope = data.scope || "user"; + data.expiry = data.expiry || "1d"; return userModel .query() - .where('email', data.identity.toLowerCase().trim()) - .andWhere('is_deleted', 0) - .andWhere('is_disabled', 0) + .where("email", data.identity.toLowerCase().trim()) + .andWhere("is_deleted", 0) + .andWhere("is_disabled", 0) .first() .then((user) => { if (user) { // Get auth return authModel .query() - .where('user_id', '=', user.id) - .where('type', '=', 'password') + .where("user_id", "=", user.id) + .where("type", "=", "password") .first() .then((auth) => { if (auth) { - return auth.verifyPassword(data.secret) - .then((valid) => { - if (valid) { - - if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) { - // The scope requested doesn't exist as a role against the user, - // you shall not pass. - throw new error.AuthError('Invalid scope: ' + data.scope); - } - - // Create a moment of the expiry expression - let expiry = helpers.parseDatePeriod(data.expiry); - if (expiry === null) { - throw new error.AuthError('Invalid expiry time: ' + data.expiry); - } - - return Token.create({ - iss: issuer || 'api', - attrs: { - id: user.id - }, - scope: [data.scope], - expiresIn: data.expiry - }) - .then((signed) => { - return { - token: signed.token, - expires: expiry.toISOString() - }; - }); - } else { - throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); + return auth.verifyPassword(data.secret).then((valid) => { + if (valid) { + if (data.scope !== "user" && _.indexOf(user.roles, data.scope) === -1) { + // The scope requested doesn't exist as a role against the user, + // you shall not pass. + throw new errs.AuthError(`Invalid scope: ${data.scope}`); } - }); - } else { - throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); + + // Create a moment of the expiry expression + const expiry = parseDatePeriod(data.expiry); + if (expiry === null) { + throw new errs.AuthError(`Invalid expiry time: ${data.expiry}`); + } + + return Token.create({ + iss: issuer || "api", + attrs: { + id: user.id, + }, + scope: [data.scope], + expiresIn: data.expiry, + }).then((signed) => { + return { + token: signed.token, + expires: expiry.toISOString(), + }; + }); + } + throw new errs.AuthError( + ERROR_MESSAGE_INVALID_AUTH, + ERROR_MESSAGE_INVALID_AUTH_I18N, + ); + }); } + throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH); }); - } else { - throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); } + throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH); }); }, @@ -92,48 +89,45 @@ module.exports = { * @returns {Promise} */ getFreshToken: (access, data) => { - let Token = new TokenModel(); + const Token = TokenModel(); + const thisData = data || {}; - data = data || {}; - data.expiry = data.expiry || '1d'; - - if (access && access.token.getUserId(0)) { + thisData.expiry = thisData.expiry || "1d"; + if (access?.token.getUserId(0)) { // Create a moment of the expiry expression - let expiry = helpers.parseDatePeriod(data.expiry); + const expiry = parseDatePeriod(thisData.expiry); if (expiry === null) { - throw new error.AuthError('Invalid expiry time: ' + data.expiry); + throw new errs.AuthError(`Invalid expiry time: ${thisData.expiry}`); } - let token_attrs = { - id: access.token.getUserId(0) + const token_attrs = { + id: access.token.getUserId(0), }; // Only admins can request otherwise scoped tokens - let scope = access.token.get('scope'); - if (data.scope && access.token.hasScope('admin')) { - scope = [data.scope]; + let scope = access.token.get("scope"); + if (thisData.scope && access.token.hasScope("admin")) { + scope = [thisData.scope]; - if (data.scope === 'job-board' || data.scope === 'worker') { + if (thisData.scope === "job-board" || thisData.scope === "worker") { token_attrs.id = 0; } } return Token.create({ - iss: 'api', - scope: scope, - attrs: token_attrs, - expiresIn: data.expiry - }) - .then((signed) => { - return { - token: signed.token, - expires: expiry.toISOString() - }; - }); - } else { - throw new error.AssertionFailedError('Existing token contained invalid user data'); + iss: "api", + scope: scope, + attrs: token_attrs, + expiresIn: thisData.expiry, + }).then((signed) => { + return { + token: signed.token, + expires: expiry.toISOString(), + }; + }); } + throw new error.AssertionFailedError("Existing token contained invalid user data"); }, /** @@ -141,24 +135,23 @@ module.exports = { * @returns {Promise} */ getTokenFromUser: (user) => { - const expire = '1d'; - const Token = new TokenModel(); - const expiry = helpers.parseDatePeriod(expire); + const expire = "1d"; + const Token = new TokenModel(); + const expiry = parseDatePeriod(expire); return Token.create({ - iss: 'api', + iss: "api", attrs: { - id: user.id + id: user.id, }, - scope: ['user'], - expiresIn: expire - }) - .then((signed) => { - return { - token: signed.token, - expires: expiry.toISOString(), - user: user - }; - }); - } + scope: ["user"], + expiresIn: expire, + }).then((signed) => { + return { + token: signed.token, + expires: expiry.toISOString(), + user: user, + }; + }); + }, }; diff --git a/backend/internal/user.js b/backend/internal/user.js index 742ab65d..012252cf 100644 --- a/backend/internal/user.js +++ b/backend/internal/user.js @@ -1,43 +1,40 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const userModel = require('../models/user'); -const userPermissionModel = require('../models/user_permission'); -const authModel = require('../models/auth'); -const gravatar = require('gravatar'); -const internalToken = require('./token'); -const internalAuditLog = require('./audit-log'); +import gravatar from "gravatar"; +import _ from "lodash"; +import errs from "../lib/error.js"; +import utils from "../lib/utils.js"; +import authModel from "../models/auth.js"; +import userModel from "../models/user.js"; +import userPermissionModel from "../models/user_permission.js"; +import internalAuditLog from "./audit-log.js"; +import internalToken from "./token.js"; -function omissions () { - return ['is_deleted']; +const omissions = () => { + return ["is_deleted"]; } const internalUser = { - /** * @param {Access} access * @param {Object} data * @returns {Promise} */ create: (access, data) => { - let auth = data.auth || null; + const auth = data.auth || null; delete data.auth; - data.avatar = data.avatar || ''; - data.roles = data.roles || []; + data.avatar = data.avatar || ""; + data.roles = data.roles || []; - if (typeof data.is_disabled !== 'undefined') { + if (typeof data.is_disabled !== "undefined") { data.is_disabled = data.is_disabled ? 1 : 0; } - return access.can('users:create', data) + return access + .can("users:create", data) .then(() => { - data.avatar = gravatar.url(data.email, {default: 'mm'}); + data.avatar = gravatar.url(data.email, { default: "mm" }); - return userModel - .query() - .insertAndFetch(data) - .then(utils.omitRow(omissions())); + return userModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); }) .then((user) => { if (auth) { @@ -45,45 +42,45 @@ const internalUser = { .query() .insert({ user_id: user.id, - type: auth.type, - secret: auth.secret, - meta: {} + type: auth.type, + secret: auth.secret, + meta: {}, }) .then(() => { return user; }); - } else { - return user; } + return user; }) .then((user) => { // Create permissions row as well - let is_admin = data.roles.indexOf('admin') !== -1; + const is_admin = data.roles.indexOf("admin") !== -1; return userPermissionModel .query() .insert({ - user_id: user.id, - visibility: is_admin ? 'all' : 'user', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - dead_hosts: 'manage', - streams: 'manage', - access_lists: 'manage', - certificates: 'manage' + user_id: user.id, + visibility: is_admin ? "all" : "user", + proxy_hosts: "manage", + redirection_hosts: "manage", + dead_hosts: "manage", + streams: "manage", + access_lists: "manage", + certificates: "manage", }) .then(() => { - return internalUser.get(access, {id: user.id, expand: ['permissions']}); + return internalUser.get(access, { id: user.id, expand: ["permissions"] }); }); }) .then((user) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'user', - object_id: user.id, - meta: user - }) + return internalAuditLog + .add(access, { + action: "created", + object_type: "user", + object_id: user.id, + meta: user, + }) .then(() => { return user; }); @@ -99,62 +96,58 @@ const internalUser = { * @return {Promise} */ update: (access, data) => { - if (typeof data.is_disabled !== 'undefined') { + if (typeof data.is_disabled !== "undefined") { data.is_disabled = data.is_disabled ? 1 : 0; } - return access.can('users:update', data.id) + return access + .can("users:update", data.id) .then(() => { - // Make sure that the user being updated doesn't change their email to another user that is already using it // 1. get user we want to update - return internalUser.get(access, {id: data.id}) - .then((user) => { + return internalUser.get(access, { id: data.id }).then((user) => { + // 2. if email is to be changed, find other users with that email + if (typeof data.email !== "undefined") { + data.email = data.email.toLowerCase().trim(); - // 2. if email is to be changed, find other users with that email - if (typeof data.email !== 'undefined') { - data.email = data.email.toLowerCase().trim(); - - if (user.email !== data.email) { - return internalUser.isEmailAvailable(data.email, data.id) - .then((available) => { - if (!available) { - throw new error.ValidationError('Email address already in use - ' + data.email); - } - - return user; - }); - } + if (user.email !== data.email) { + return internalUser.isEmailAvailable(data.email, data.id).then((available) => { + if (!available) { + throw new errs.ValidationError(`Email address already in use - ${data.email}`); + } + return user; + }); } + } - // No change to email: - return user; - }); + // No change to email: + return user; + }); }) .then((user) => { if (user.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `User could not be updated, IDs do not match: ${user.id} !== ${data.id}`, + ); } - data.avatar = gravatar.url(data.email || user.email, {default: 'mm'}); + data.avatar = gravatar.url(data.email || user.email, { default: "mm" }); - return userModel - .query() - .patchAndFetchById(user.id, data) - .then(utils.omitRow(omissions())); + return userModel.query().patchAndFetchById(user.id, data).then(utils.omitRow(omissions())); }) .then(() => { - return internalUser.get(access, {id: data.id}); + return internalUser.get(access, { id: data.id }); }) .then((user) => { // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'user', - object_id: user.id, - meta: data - }) + return internalAuditLog + .add(access, { + action: "updated", + object_type: "user", + object_id: user.id, + meta: data, + }) .then(() => { return user; }); @@ -170,36 +163,35 @@ const internalUser = { * @return {Promise} */ get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; + const thisData = data || {}; + + if (typeof thisData.id === "undefined" || !thisData.id) { + thisData.id = access.token.getUserId(0); } - if (typeof data.id === 'undefined' || !data.id) { - data.id = access.token.getUserId(0); - } - - return access.can('users:get', data.id) + return access + .can("users:get", thisData.id) .then(() => { - let query = userModel + const query = userModel .query() - .where('is_deleted', 0) - .andWhere('id', data.id) - .allowGraph('[permissions]') + .where("is_deleted", 0) + .andWhere("id", thisData.id) + .allowGraph("[permissions]") .first(); - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); } return query.then(utils.omitRow(omissions())); }) .then((row) => { if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(thisData.id); } // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); + if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { + return _.omit(row, thisData.omit); } return row; }); @@ -213,20 +205,15 @@ const internalUser = { * @param user_id */ isEmailAvailable: (email, user_id) => { - let query = userModel - .query() - .where('email', '=', email.toLowerCase().trim()) - .where('is_deleted', 0) - .first(); + const query = userModel.query().where("email", "=", email.toLowerCase().trim()).where("is_deleted", 0).first(); - if (typeof user_id !== 'undefined') { - query.where('id', '!=', user_id); + if (typeof user_id !== "undefined") { + query.where("id", "!=", user_id); } - return query - .then((user) => { - return !user; - }); + return query.then((user) => { + return !user; + }); }, /** @@ -237,33 +224,34 @@ const internalUser = { * @returns {Promise} */ delete: (access, data) => { - return access.can('users:delete', data.id) + return access + .can("users:delete", data.id) .then(() => { - return internalUser.get(access, {id: data.id}); + return internalUser.get(access, { id: data.id }); }) .then((user) => { if (!user) { - throw new error.ItemNotFoundError(data.id); + throw new errs.ItemNotFoundError(data.id); } // Make sure user can't delete themselves if (user.id === access.token.getUserId(0)) { - throw new error.PermissionError('You cannot delete yourself.'); + throw new errs.PermissionError("You cannot delete yourself."); } return userModel .query() - .where('id', user.id) + .where("id", user.id) .patch({ - is_deleted: 1 + is_deleted: 1, }) .then(() => { // Add to audit log return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'user', - object_id: user.id, - meta: _.omit(user, omissions()) + action: "deleted", + object_type: "user", + object_id: user.id, + meta: _.omit(user, omissions()), }); }); }) @@ -280,26 +268,26 @@ const internalUser = { * @returns {*} */ getCount: (access, search_query) => { - return access.can('users:list') + return access + .can("users:list") .then(() => { - let query = userModel - .query() - .count('id as count') - .where('is_deleted', 0) - .first(); + const query = userModel.query().count("id as count").where("is_deleted", 0).first(); // Query is used for searching - if (typeof search_query === 'string') { + if (typeof search_query === "string") { query.where(function () { - this.where('user.name', 'like', '%' + search_query + '%') - .orWhere('user.email', 'like', '%' + search_query + '%'); + this.where("user.name", "like", `%${search_query}%`).orWhere( + "user.email", + "like", + `%${search_query}%`, + ); }); } return query; }) .then((row) => { - return parseInt(row.count, 10); + return Number.parseInt(row.count, 10); }); }, @@ -312,29 +300,31 @@ const internalUser = { * @returns {Promise} */ getAll: (access, expand, search_query) => { - return access.can('users:list') - .then(() => { - let query = userModel - .query() - .where('is_deleted', 0) - .groupBy('id') - .allowGraph('[permissions]') - .orderBy('name', 'ASC'); + return access.can("users:list").then(() => { + const query = userModel + .query() + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[permissions]") + .orderBy("name", "ASC"); - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('name', 'like', '%' + search_query + '%') - .orWhere('email', 'like', '%' + search_query + '%'); - }); - } + // Query is used for searching + if (typeof search_query === "string") { + query.where(function () { + this.where("name", "like", `%${search_query}%`).orWhere( + "email", + "like", + `%${search_query}%`, + ); + }); + } - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } - return query.then(utils.omitRows(omissions())); - }); + return query.then(utils.omitRows(omissions())); + }); }, /** @@ -345,8 +335,8 @@ const internalUser = { getUserOmisionsByAccess: (access, id_requested) => { let response = []; // Admin response - if (!access.token.hasScope('admin') && access.token.getUserId(0) !== id_requested) { - response = ['roles', 'is_deleted']; // Restricted response + if (!access.token.hasScope("admin") && access.token.getUserId(0) !== id_requested) { + response = ["roles", "is_deleted"]; // Restricted response } return response; @@ -361,26 +351,30 @@ const internalUser = { * @return {Promise} */ setPassword: (access, data) => { - return access.can('users:password', data.id) + return access + .can("users:password", data.id) .then(() => { - return internalUser.get(access, {id: data.id}); + return internalUser.get(access, { id: data.id }); }) .then((user) => { if (user.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `User could not be updated, IDs do not match: ${user.id} !== ${data.id}`, + ); } if (user.id === access.token.getUserId(0)) { // they're setting their own password. Make sure their current password is correct - if (typeof data.current === 'undefined' || !data.current) { - throw new error.ValidationError('Current password was not supplied'); + if (typeof data.current === "undefined" || !data.current) { + throw new errs.ValidationError("Current password was not supplied"); } - return internalToken.getTokenFromEmail({ - identity: user.email, - secret: data.current - }) + return internalToken + .getTokenFromEmail({ + identity: user.email, + secret: data.current, + }) .then(() => { return user; }); @@ -392,43 +386,36 @@ const internalUser = { // Get auth, patch if it exists return authModel .query() - .where('user_id', user.id) - .andWhere('type', data.type) + .where("user_id", user.id) + .andWhere("type", data.type) .first() .then((existing_auth) => { if (existing_auth) { // patch - return authModel - .query() - .where('user_id', user.id) - .andWhere('type', data.type) - .patch({ - type: data.type, // This is required for the model to encrypt on save - secret: data.secret - }); - } else { - // insert - return authModel - .query() - .insert({ - user_id: user.id, - type: data.type, - secret: data.secret, - meta: {} - }); + return authModel.query().where("user_id", user.id).andWhere("type", data.type).patch({ + type: data.type, // This is required for the model to encrypt on save + secret: data.secret, + }); } + // insert + return authModel.query().insert({ + user_id: user.id, + type: data.type, + secret: data.secret, + meta: {}, + }); }) .then(() => { // Add to Audit Log return internalAuditLog.add(access, { - action: 'updated', - object_type: 'user', - object_id: user.id, - meta: { - name: user.name, + action: "updated", + object_type: "user", + object_id: user.id, + meta: { + name: user.name, password_changed: true, - auth_type: data.type - } + auth_type: data.type, + }, }); }); }) @@ -443,14 +430,17 @@ const internalUser = { * @return {Promise} */ setPermissions: (access, data) => { - return access.can('users:permissions', data.id) + return access + .can("users:permissions", data.id) .then(() => { - return internalUser.get(access, {id: data.id}); + return internalUser.get(access, { id: data.id }); }) .then((user) => { if (user.id !== data.id) { // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id); + throw new errs.InternalValidationError( + `User could not be updated, IDs do not match: ${user.id} !== ${data.id}`, + ); } return user; @@ -459,34 +449,30 @@ const internalUser = { // Get perms row, patch if it exists return userPermissionModel .query() - .where('user_id', user.id) + .where("user_id", user.id) .first() .then((existing_auth) => { if (existing_auth) { // patch return userPermissionModel .query() - .where('user_id', user.id) - .patchAndFetchById(existing_auth.id, _.assign({user_id: user.id}, data)); - } else { - // insert - return userPermissionModel - .query() - .insertAndFetch(_.assign({user_id: user.id}, data)); + .where("user_id", user.id) + .patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, data)); } + // insert + return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data)); }) .then((permissions) => { // Add to Audit Log return internalAuditLog.add(access, { - action: 'updated', - object_type: 'user', - object_id: user.id, - meta: { - name: user.name, - permissions: permissions - } + action: "updated", + object_type: "user", + object_id: user.id, + meta: { + name: user.name, + permissions: permissions, + }, }); - }); }) .then(() => { @@ -500,14 +486,15 @@ const internalUser = { * @param {Integer} data.id */ loginAs: (access, data) => { - return access.can('users:loginas', data.id) + return access + .can("users:loginas", data.id) .then(() => { return internalUser.get(access, data); }) .then((user) => { return internalToken.getTokenFromUser(user); }); - } + }, }; -module.exports = internalUser; +export default internalUser; diff --git a/backend/lib/access.js b/backend/lib/access.js index 0e658a65..20d9e04c 100644 --- a/backend/lib/access.js +++ b/backend/lib/access.js @@ -4,28 +4,32 @@ * "scope" in this file means "where did this token come from and what is using it", so 99% of the time * the "scope" is going to be "user" because it would be a user token. This is not to be confused with * the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else. - * - * */ -const _ = require('lodash'); -const logger = require('../logger').access; -const Ajv = require('ajv/dist/2020'); -const error = require('./error'); -const userModel = require('../models/user'); -const proxyHostModel = require('../models/proxy_host'); -const TokenModel = require('../models/token'); -const roleSchema = require('./access/roles.json'); -const permsSchema = require('./access/permissions.json'); +import fs from "node:fs"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import Ajv from "ajv/dist/2020.js"; +import _ from "lodash"; +import { access as logger } from "../logger.js"; +import proxyHostModel from "../models/proxy_host.js"; +import TokenModel from "../models/token.js"; +import userModel from "../models/user.js"; +import permsSchema from "./access/permissions.json" with { type: "json" }; +import roleSchema from "./access/roles.json" with { type: "json" }; +import errs from "./error.js"; -module.exports = function (token_string) { - let Token = new TokenModel(); - let token_data = null; - let initialised = false; - let object_cache = {}; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export default function (token_string) { + const Token = TokenModel(); + let token_data = null; + let initialised = false; + const object_cache = {}; let allow_internal_access = false; - let user_roles = []; - let permissions = {}; + let user_roles = []; + let permissions = {}; /** * Loads the Token object from the token string @@ -37,10 +41,10 @@ module.exports = function (token_string) { if (initialised) { resolve(); } else if (!token_string) { - reject(new error.PermissionError('Permission Denied')); + reject(new errs.PermissionError("Permission Denied")); } else { - resolve(Token.load(token_string) - .then((data) => { + resolve( + Token.load(token_string).then((data) => { token_data = data; // At this point we need to load the user from the DB and make sure they: @@ -48,21 +52,25 @@ module.exports = function (token_string) { // - still have the appropriate scopes for this token // This is only required when the User ID is supplied or if the token scope has `user` - if (token_data.attrs.id || (typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'user') !== -1)) { + if ( + token_data.attrs.id || + (typeof token_data.scope !== "undefined" && + _.indexOf(token_data.scope, "user") !== -1) + ) { // Has token user id or token user scope return userModel .query() - .where('id', token_data.attrs.id) - .andWhere('is_deleted', 0) - .andWhere('is_disabled', 0) - .allowGraph('[permissions]') - .withGraphFetched('[permissions]') + .where("id", token_data.attrs.id) + .andWhere("is_deleted", 0) + .andWhere("is_disabled", 0) + .allowGraph("[permissions]") + .withGraphFetched("[permissions]") .first() .then((user) => { if (user) { // make sure user has all scopes of the token // The `user` role is not added against the user row, so we have to just add it here to get past this check. - user.roles.push('user'); + user.roles.push("user"); let is_ok = true; _.forEach(token_data.scope, (scope_item) => { @@ -72,21 +80,19 @@ module.exports = function (token_string) { }); if (!is_ok) { - throw new error.AuthError('Invalid token scope for User'); - } else { - initialised = true; - user_roles = user.roles; - permissions = user.permissions; + throw new errs.AuthError("Invalid token scope for User"); } - + initialised = true; + user_roles = user.roles; + permissions = user.permissions; } else { - throw new error.AuthError('User cannot be loaded for Token'); + throw new errs.AuthError("User cannot be loaded for Token"); } }); - } else { - initialised = true; } - })); + initialised = true; + }), + ); } }); }; @@ -101,53 +107,55 @@ module.exports = function (token_string) { */ this.loadObjects = (object_type) => { return new Promise((resolve, reject) => { - if (Token.hasScope('user')) { - if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) { - reject(new error.AuthError('User Token supplied without a User ID')); + if (Token.hasScope("user")) { + if ( + typeof token_data.attrs.id === "undefined" || + !token_data.attrs.id + ) { + reject(new errs.AuthError("User Token supplied without a User ID")); } else { - let token_user_id = token_data.attrs.id ? token_data.attrs.id : 0; + const token_user_id = token_data.attrs.id ? token_data.attrs.id : 0; let query; - if (typeof object_cache[object_type] === 'undefined') { + if (typeof object_cache[object_type] === "undefined") { switch (object_type) { - - // USERS - should only return yourself - case 'users': - resolve(token_user_id ? [token_user_id] : []); - break; + // USERS - should only return yourself + case "users": + resolve(token_user_id ? [token_user_id] : []); + break; // Proxy Hosts - case 'proxy_hosts': - query = proxyHostModel - .query() - .select('id') - .andWhere('is_deleted', 0); + case "proxy_hosts": + query = proxyHostModel + .query() + .select("id") + .andWhere("is_deleted", 0); - if (permissions.visibility === 'user') { - query.andWhere('owner_user_id', token_user_id); - } + if (permissions.visibility === "user") { + query.andWhere("owner_user_id", token_user_id); + } - resolve(query - .then((rows) => { - let result = []; - _.forEach(rows, (rule_row) => { - result.push(rule_row.id); - }); + resolve( + query.then((rows) => { + const result = []; + _.forEach(rows, (rule_row) => { + result.push(rule_row.id); + }); - // enum should not have less than 1 item - if (!result.length) { - result.push(0); - } + // enum should not have less than 1 item + if (!result.length) { + result.push(0); + } - return result; - }) - ); - break; + return result; + }), + ); + break; // DEFAULT: null - default: - resolve(null); - break; + default: + resolve(null); + break; } } else { resolve(object_cache[object_type]); @@ -156,11 +164,10 @@ module.exports = function (token_string) { } else { resolve(null); } - }) - .then((objects) => { - object_cache[object_type] = objects; - return objects; - }); + }).then((objects) => { + object_cache[object_type] = objects; + return objects; + }); }; /** @@ -170,50 +177,48 @@ module.exports = function (token_string) { * @returns {Object} */ this.getObjectSchema = (permission_label) => { - let base_object_type = permission_label.split(':').shift(); + const base_object_type = permission_label.split(":").shift(); - let schema = { - $id: 'objects', - description: 'Actor Properties', - type: 'object', + const schema = { + $id: "objects", + description: "Actor Properties", + type: "object", additionalProperties: false, - properties: { + properties: { user_id: { anyOf: [ { - type: 'number', - enum: [Token.get('attrs').id] - } - ] + type: "number", + enum: [Token.get("attrs").id], + }, + ], }, scope: { - type: 'string', - pattern: '^' + Token.get('scope') + '$' - } - } + type: "string", + pattern: `^${Token.get("scope")}$`, + }, + }, }; - return this.loadObjects(base_object_type) - .then((object_result) => { - if (typeof object_result === 'object' && object_result !== null) { - schema.properties[base_object_type] = { - type: 'number', - enum: object_result, - minimum: 1 - }; - } else { - schema.properties[base_object_type] = { - type: 'number', - minimum: 1 - }; - } + return this.loadObjects(base_object_type).then((object_result) => { + if (typeof object_result === "object" && object_result !== null) { + schema.properties[base_object_type] = { + type: "number", + enum: object_result, + minimum: 1, + }; + } else { + schema.properties[base_object_type] = { + type: "number", + minimum: 1, + }; + } - return schema; - }); + return schema; + }); }; return { - token: Token, /** @@ -222,7 +227,7 @@ module.exports = function (token_string) { * @returns {Promise} */ load: (allow_internal) => { - return new Promise(function (resolve/*, reject*/) { + return new Promise((resolve /*, reject*/) => { if (token_string) { resolve(Token.load(token_string)); } else { @@ -240,68 +245,60 @@ module.exports = function (token_string) { * @param {*} [data] * @returns {Promise} */ - can: (permission, data) => { + can: async (permission, data) => { if (allow_internal_access === true) { - return Promise.resolve(true); - //return true; - } else { - return this.init() - .then(() => { - // Initialised, token decoded ok - return this.getObjectSchema(permission) - .then((objectSchema) => { - const data_schema = { - [permission]: { - data: data, - scope: Token.get('scope'), - roles: user_roles, - permission_visibility: permissions.visibility, - permission_proxy_hosts: permissions.proxy_hosts, - permission_redirection_hosts: permissions.redirection_hosts, - permission_dead_hosts: permissions.dead_hosts, - permission_streams: permissions.streams, - permission_access_lists: permissions.access_lists, - permission_certificates: permissions.certificates - } - }; - - let permissionSchema = { - $async: true, - $id: 'permissions', - type: 'object', - additionalProperties: false, - properties: {} - }; - - permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json'); - - const ajv = new Ajv({ - verbose: true, - allErrors: true, - breakOnError: true, - coerceTypes: true, - schemas: [ - roleSchema, - permsSchema, - objectSchema, - permissionSchema - ] - }); - - return ajv.validate('permissions', data_schema) - .then(() => { - return data_schema[permission]; - }); - }); - }) - .catch((err) => { - err.permission = permission; - err.permission_data = data; - logger.error(permission, data, err.message); - - throw new error.PermissionError('Permission Denied', err); - }); + return true; } - } + + try { + await this.init(); + const objectSchema = await this.getObjectSchema(permission); + + const dataSchema = { + [permission]: { + data: data, + scope: Token.get("scope"), + roles: user_roles, + permission_visibility: permissions.visibility, + permission_proxy_hosts: permissions.proxy_hosts, + permission_redirection_hosts: permissions.redirection_hosts, + permission_dead_hosts: permissions.dead_hosts, + permission_streams: permissions.streams, + permission_access_lists: permissions.access_lists, + permission_certificates: permissions.certificates, + }, + }; + + const permissionSchema = { + $async: true, + $id: "permissions", + type: "object", + additionalProperties: false, + properties: {}, + }; + + const rawData = fs.readFileSync( + `${__dirname}/access/${permission.replace(/:/gim, "-")}.json`, + { encoding: "utf8" }, + ); + permissionSchema.properties[permission] = JSON.parse(rawData); + + const ajv = new Ajv({ + verbose: true, + allErrors: true, + breakOnError: true, + coerceTypes: true, + schemas: [roleSchema, permsSchema, objectSchema, permissionSchema], + }); + + const valid = ajv.validate("permissions", dataSchema); + return valid && dataSchema[permission]; + } catch (err) { + err.permission = permission; + err.permission_data = data; + logger.error(permission, data, err.message); + throw errs.PermissionError("Permission Denied", err); + } + }, }; -}; +} diff --git a/backend/lib/certbot.js b/backend/lib/certbot.js index 96d94710..d850b9ad 100644 --- a/backend/lib/certbot.js +++ b/backend/lib/certbot.js @@ -1,85 +1,87 @@ -const dnsPlugins = require('../global/certbot-dns-plugins.json'); -const utils = require('./utils'); -const error = require('./error'); -const logger = require('../logger').certbot; -const batchflow = require('batchflow'); +import batchflow from "batchflow"; +import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; +import { certbot as logger } from "../logger.js"; +import errs from "./error.js"; +import utils from "./utils.js"; -const CERTBOT_VERSION_REPLACEMENT = '$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')'; +const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0-9]+)+')"; -const certbot = { +/** + * @param {array} pluginKeys + */ +const installPlugins = async (pluginKeys) => { + let hasErrors = false; - /** - * @param {array} pluginKeys - */ - installPlugins: async (pluginKeys) => { - let hasErrors = false; - - return new Promise((resolve, reject) => { - if (pluginKeys.length === 0) { - resolve(); - return; - } - - batchflow(pluginKeys).sequential() - .each((_i, pluginKey, next) => { - certbot.installPlugin(pluginKey) - .then(() => { - next(); - }) - .catch((err) => { - hasErrors = true; - next(err); - }); - }) - .error((err) => { - logger.error(err.message); - }) - .end(() => { - if (hasErrors) { - reject(new error.CommandError('Some plugins failed to install. Please check the logs above', 1)); - } else { - resolve(); - } - }); - }); - }, - - /** - * Installs a cerbot plugin given the key for the object from - * ../global/certbot-dns-plugins.json - * - * @param {string} pluginKey - * @returns {Object} - */ - installPlugin: async (pluginKey) => { - if (typeof dnsPlugins[pluginKey] === 'undefined') { - // throw Error(`Certbot plugin ${pluginKey} not found`); - throw new error.ItemNotFoundError(pluginKey); + return new Promise((resolve, reject) => { + if (pluginKeys.length === 0) { + resolve(); + return; } - const plugin = dnsPlugins[pluginKey]; - logger.start(`Installing ${pluginKey}...`); - - plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); - plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); - - // SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly - // in new versions of Python - let env = Object.assign({}, process.env, {SETUPTOOLS_USE_DISTUTILS: 'stdlib'}); - if (typeof plugin.env === 'object') { - env = Object.assign(env, plugin.env); - } - - const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version} && deactivate`; - return utils.exec(cmd, {env}) - .then((result) => { - logger.complete(`Installed ${pluginKey}`); - return result; + batchflow(pluginKeys) + .sequential() + .each((_i, pluginKey, next) => { + certbot + .installPlugin(pluginKey) + .then(() => { + next(); + }) + .catch((err) => { + hasErrors = true; + next(err); + }); }) - .catch((err) => { - throw err; + .error((err) => { + logger.error(err.message); + }) + .end(() => { + if (hasErrors) { + reject( + new errs.CommandError("Some plugins failed to install. Please check the logs above", 1), + ); + } else { + resolve(); + } }); - }, + }); }; -module.exports = certbot; +/** + * Installs a cerbot plugin given the key for the object from + * ../global/certbot-dns-plugins.json + * + * @param {string} pluginKey + * @returns {Object} + */ +const installPlugin = async (pluginKey) => { + if (typeof dnsPlugins[pluginKey] === "undefined") { + // throw Error(`Certbot plugin ${pluginKey} not found`); + throw new errs.ItemNotFoundError(pluginKey); + } + + const plugin = dnsPlugins[pluginKey]; + logger.start(`Installing ${pluginKey}...`); + + plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); + plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); + + // SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly + // in new versions of Python + let env = Object.assign({}, process.env, { SETUPTOOLS_USE_DISTUTILS: "stdlib" }); + if (typeof plugin.env === "object") { + env = Object.assign(env, plugin.env); + } + + const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version} && deactivate`; + return utils + .exec(cmd, { env }) + .then((result) => { + logger.complete(`Installed ${pluginKey}`); + return result; + }) + .catch((err) => { + throw err; + }); +}; + +export { installPlugins, installPlugin }; diff --git a/backend/lib/config.js b/backend/lib/config.js index 23184f3e..2176246a 100644 --- a/backend/lib/config.js +++ b/backend/lib/config.js @@ -1,6 +1,6 @@ -const fs = require('fs'); -const NodeRSA = require('node-rsa'); -const logger = require('../logger').global; +import fs from "node:fs"; +import NodeRSA from "node-rsa"; +import { global as logger } from "../logger.js"; const keysFile = '/data/keys.json'; const mysqlEngine = 'mysql2'; @@ -12,18 +12,20 @@ let instance = null; // 1. Load from config file first (not recommended anymore) // 2. Use config env variables next const configure = () => { - const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json'; + const filename = `${process.env.NODE_CONFIG_DIR || "./config"}/${process.env.NODE_ENV || "default"}.json`; if (fs.existsSync(filename)) { let configData; try { - configData = require(filename); + // Load this json synchronously + const rawData = fs.readFileSync(filename); + configData = JSON.parse(rawData); } catch (_) { // do nothing } - if (configData && configData.database) { + if (configData?.database) { logger.info(`Using configuration from file: ${filename}`); - instance = configData; + instance = configData; instance.keys = getKeys(); return; } @@ -34,15 +36,15 @@ const configure = () => { const envMysqlName = process.env.DB_MYSQL_NAME || null; if (envMysqlHost && envMysqlUser && envMysqlName) { // we have enough mysql creds to go with mysql - logger.info('Using MySQL configuration'); + logger.info("Using MySQL configuration"); instance = { database: { - engine: mysqlEngine, - host: envMysqlHost, - port: process.env.DB_MYSQL_PORT || 3306, - user: envMysqlUser, + engine: mysqlEngine, + host: envMysqlHost, + port: process.env.DB_MYSQL_PORT || 3306, + user: envMysqlUser, password: process.env.DB_MYSQL_PASSWORD, - name: envMysqlName, + name: envMysqlName, }, keys: getKeys(), }; @@ -54,33 +56,33 @@ const configure = () => { const envPostgresName = process.env.DB_POSTGRES_NAME || null; if (envPostgresHost && envPostgresUser && envPostgresName) { // we have enough postgres creds to go with postgres - logger.info('Using Postgres configuration'); + logger.info("Using Postgres configuration"); instance = { database: { - engine: postgresEngine, - host: envPostgresHost, - port: process.env.DB_POSTGRES_PORT || 5432, - user: envPostgresUser, + engine: postgresEngine, + host: envPostgresHost, + port: process.env.DB_POSTGRES_PORT || 5432, + user: envPostgresUser, password: process.env.DB_POSTGRES_PASSWORD, - name: envPostgresName, + name: envPostgresName, }, keys: getKeys(), }; return; } - const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite'; + const envSqliteFile = process.env.DB_SQLITE_FILE || "/data/database.sqlite"; logger.info(`Using Sqlite: ${envSqliteFile}`); instance = { database: { - engine: 'knex-native', - knex: { - client: sqliteClientName, + engine: "knex-native", + knex: { + client: sqliteClientName, connection: { - filename: envSqliteFile + filename: envSqliteFile, }, - useNullAsDefault: true - } + useNullAsDefault: true, + }, }, keys: getKeys(), }; @@ -88,150 +90,148 @@ const configure = () => { const getKeys = () => { // Get keys from file + logger.debug("Cheecking for keys file:", keysFile); if (!fs.existsSync(keysFile)) { generateKeys(); } else if (process.env.DEBUG) { - logger.info('Keys file exists OK'); + logger.info("Keys file exists OK"); } try { - return require(keysFile); + // Load this json keysFile synchronously and return the json object + const rawData = fs.readFileSync(keysFile); + return JSON.parse(rawData); } catch (err) { - logger.error('Could not read JWT key pair from config file: ' + keysFile, err); + logger.error(`Could not read JWT key pair from config file: ${keysFile}`, err); process.exit(1); } }; const generateKeys = () => { - logger.info('Creating a new JWT key pair...'); + logger.info("Creating a new JWT key pair..."); // Now create the keys and save them in the config. const key = new NodeRSA({ b: 2048 }); key.generateKeyPair(); const keys = { - key: key.exportKey('private').toString(), - pub: key.exportKey('public').toString(), + key: key.exportKey("private").toString(), + pub: key.exportKey("public").toString(), }; // Write keys config try { fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2)); } catch (err) { - logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' + err.message); + logger.error(`Could not write JWT key pair to config file: ${keysFile}: ${err.message}`); process.exit(1); } - logger.info('Wrote JWT key pair to config file: ' + keysFile); + logger.info(`Wrote JWT key pair to config file: ${keysFile}`); }; -module.exports = { - - /** - * - * @param {string} key ie: 'database' or 'database.engine' - * @returns {boolean} - */ - has: function(key) { - instance === null && configure(); - const keys = key.split('.'); - let level = instance; - let has = true; - keys.forEach((keyItem) =>{ - if (typeof level[keyItem] === 'undefined') { - has = false; - } else { - level = level[keyItem]; - } - }); - - return has; - }, - - /** - * Gets a specific key from the top level - * - * @param {string} key - * @returns {*} - */ - get: function (key) { - instance === null && configure(); - if (key && typeof instance[key] !== 'undefined') { - return instance[key]; +/** + * + * @param {string} key ie: 'database' or 'database.engine' + * @returns {boolean} + */ +const configHas = (key) => { + instance === null && configure(); + const keys = key.split("."); + let level = instance; + let has = true; + keys.forEach((keyItem) => { + if (typeof level[keyItem] === "undefined") { + has = false; + } else { + level = level[keyItem]; } - return instance; - }, + }); - /** - * Is this a sqlite configuration? - * - * @returns {boolean} - */ - isSqlite: function () { - instance === null && configure(); - return instance.database.knex && instance.database.knex.client === sqliteClientName; - }, + return has; +}; - /** - * Is this a mysql configuration? - * - * @returns {boolean} - */ - isMysql: function () { - instance === null && configure(); - return instance.database.engine === mysqlEngine; - }, - - /** - * Is this a postgres configuration? - * - * @returns {boolean} - */ - isPostgres: function () { - instance === null && configure(); - return instance.database.engine === postgresEngine; - }, - - /** - * Are we running in debug mdoe? - * - * @returns {boolean} - */ - debug: function () { - return !!process.env.DEBUG; - }, - - /** - * Returns a public key - * - * @returns {string} - */ - getPublicKey: function () { - instance === null && configure(); - return instance.keys.pub; - }, - - /** - * Returns a private key - * - * @returns {string} - */ - getPrivateKey: function () { - instance === null && configure(); - return instance.keys.key; - }, - - /** - * @returns {boolean} - */ - useLetsencryptStaging: function () { - return !!process.env.LE_STAGING; - }, - - /** - * @returns {string|null} - */ - useLetsencryptServer: function () { - if (process.env.LE_SERVER) { - return process.env.LE_SERVER; - } - return null; +/** + * Gets a specific key from the top level + * + * @param {string} key + * @returns {*} + */ +const configGet = (key) => { + instance === null && configure(); + if (key && typeof instance[key] !== "undefined") { + return instance[key]; } + return instance; }; + +/** + * Is this a sqlite configuration? + * + * @returns {boolean} + */ +const isSqlite = () => { + instance === null && configure(); + return instance.database.knex && instance.database.knex.client === sqliteClientName; +}; + +/** + * Is this a mysql configuration? + * + * @returns {boolean} + */ +const isMysql = () => { + instance === null && configure(); + return instance.database.engine === mysqlEngine; +}; + +/** + * Is this a postgres configuration? + * + * @returns {boolean} + */ +const isPostgres = () => { + instance === null && configure(); + return instance.database.engine === postgresEngine; +}; + +/** + * Are we running in debug mdoe? + * + * @returns {boolean} + */ +const isDebugMode = () => !!process.env.DEBUG; + +/** + * Returns a public key + * + * @returns {string} + */ +const getPublicKey = () => { + instance === null && configure(); + return instance.keys.pub; +}; + +/** + * Returns a private key + * + * @returns {string} + */ +const getPrivateKey = () => { + instance === null && configure(); + return instance.keys.key; +}; + +/** + * @returns {boolean} + */ +const useLetsencryptStaging = () => !!process.env.LE_STAGING; + +/** + * @returns {string|null} + */ +const useLetsencryptServer = () => { + if (process.env.LE_SERVER) { + return process.env.LE_SERVER; + } + return null; +}; + +export { configHas, configGet, isSqlite, isMysql, isPostgres, isDebugMode, getPrivateKey, getPublicKey, useLetsencryptStaging, useLetsencryptServer }; diff --git a/backend/lib/error.js b/backend/lib/error.js index 413d6a7d..00e1df23 100644 --- a/backend/lib/error.js +++ b/backend/lib/error.js @@ -1,99 +1,100 @@ -const _ = require('lodash'); -const util = require('util'); +import _ from "lodash"; -module.exports = { - - PermissionError: function (message, previous) { +const errs = { + PermissionError: function (_, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = 'Permission Denied'; - this.public = true; - this.status = 403; + this.message = "Permission Denied"; + this.public = true; + this.status = 403; }, ItemNotFoundError: function (id, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = 'Item Not Found - ' + id; - this.public = true; - this.status = 404; + this.message = `Item Not Found - ${id}`; + this.public = true; + this.status = 404; }, - AuthError: function (message, previous) { + AuthError: function (message, messageI18n, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.public = true; - this.status = 401; + this.message = message; + this.message_i18n = messageI18n; + this.public = true; + this.status = 400; }, InternalError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.status = 500; - this.public = false; + this.message = message; + this.status = 500; + this.public = false; }, InternalValidationError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.status = 400; - this.public = false; + this.message = message; + this.status = 400; + this.public = false; }, ConfigurationError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.status = 400; - this.public = true; + this.message = message; + this.status = 400; + this.public = true; }, CacheError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.message = message; + this.name = this.constructor.name; + this.message = message; this.previous = previous; - this.status = 500; - this.public = false; + this.status = 500; + this.public = false; }, ValidationError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.public = true; - this.status = 400; + this.message = message; + this.public = true; + this.status = 400; }, AssertionFailedError: function (message, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = message; - this.public = false; - this.status = 400; + this.message = message; + this.public = false; + this.status = 400; }, CommandError: function (stdErr, code, previous) { Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; + this.name = this.constructor.name; this.previous = previous; - this.message = stdErr; - this.code = code; - this.public = false; + this.message = stdErr; + this.code = code; + this.public = false; }, }; -_.forEach(module.exports, function (error) { - util.inherits(error, Error); +_.forEach(errs, (err) => { + err.prototype = Object.create(Error.prototype); }); + +export default errs; diff --git a/backend/lib/express/cors.js b/backend/lib/express/cors.js index 6d5b8b5f..6fbf3baf 100644 --- a/backend/lib/express/cors.js +++ b/backend/lib/express/cors.js @@ -1,12 +1,13 @@ -module.exports = function (req, res, next) { +export default (req, res, next) => { if (req.headers.origin) { res.set({ - 'Access-Control-Allow-Origin': req.headers.origin, - 'Access-Control-Allow-Credentials': true, - 'Access-Control-Allow-Methods': 'OPTIONS, GET, POST', - 'Access-Control-Allow-Headers': 'Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit', - 'Access-Control-Max-Age': 5 * 60, - 'Access-Control-Expose-Headers': 'X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit' + "Access-Control-Allow-Origin": req.headers.origin, + "Access-Control-Allow-Credentials": true, + "Access-Control-Allow-Methods": "OPTIONS, GET, POST", + "Access-Control-Allow-Headers": + "Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit", + "Access-Control-Max-Age": 5 * 60, + "Access-Control-Expose-Headers": "X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit", }); next(); } else { diff --git a/backend/lib/express/jwt-decode.js b/backend/lib/express/jwt-decode.js index 17edccec..55b7137b 100644 --- a/backend/lib/express/jwt-decode.js +++ b/backend/lib/express/jwt-decode.js @@ -1,10 +1,11 @@ -const Access = require('../access'); +import Access from "../access.js"; -module.exports = () => { - return function (req, res, next) { +export default () => { + return (_, res, next) => { res.locals.access = null; - let access = new Access(res.locals.token || null); - access.load() + const access = new Access(res.locals.token || null); + access + .load() .then(() => { res.locals.access = access; next(); @@ -12,4 +13,3 @@ module.exports = () => { .catch(next); }; }; - diff --git a/backend/lib/express/jwt.js b/backend/lib/express/jwt.js index 44aa3693..ce907b6d 100644 --- a/backend/lib/express/jwt.js +++ b/backend/lib/express/jwt.js @@ -1,13 +1,13 @@ -module.exports = function () { - return function (req, res, next) { +export default function () { + return (req, res, next) => { if (req.headers.authorization) { - let parts = req.headers.authorization.split(' '); + const parts = req.headers.authorization.split(" "); - if (parts && parts[0] === 'Bearer' && parts[1]) { + if (parts && parts[0] === "Bearer" && parts[1]) { res.locals.token = parts[1]; } } next(); }; -}; +} diff --git a/backend/lib/express/pagination.js b/backend/lib/express/pagination.js index 24ffa58d..188df277 100644 --- a/backend/lib/express/pagination.js +++ b/backend/lib/express/pagination.js @@ -1,7 +1,6 @@ -let _ = require('lodash'); - -module.exports = function (default_sort, default_offset, default_limit, max_limit) { +import _ from "lodash"; +export default (default_sort, default_offset, default_limit, max_limit) => { /** * This will setup the req query params with filtered data and defaults * @@ -11,34 +10,35 @@ module.exports = function (default_sort, default_offset, default_limit, max_limi * */ - return function (req, res, next) { - - req.query.offset = typeof req.query.limit === 'undefined' ? default_offset || 0 : parseInt(req.query.offset, 10); - req.query.limit = typeof req.query.limit === 'undefined' ? default_limit || 50 : parseInt(req.query.limit, 10); + return (req, _res, next) => { + req.query.offset = + typeof req.query.limit === "undefined" ? default_offset || 0 : Number.parseInt(req.query.offset, 10); + req.query.limit = + typeof req.query.limit === "undefined" ? default_limit || 50 : Number.parseInt(req.query.limit, 10); if (max_limit && req.query.limit > max_limit) { req.query.limit = max_limit; } // Sorting - let sort = typeof req.query.sort === 'undefined' ? default_sort : req.query.sort; - let myRegexp = /.*\.(asc|desc)$/ig; - let sort_array = []; + let sort = typeof req.query.sort === "undefined" ? default_sort : req.query.sort; + const myRegexp = /.*\.(asc|desc)$/gi; + const sort_array = []; - sort = sort.split(','); - _.map(sort, function (val) { - let matches = myRegexp.exec(val); + sort = sort.split(","); + _.map(sort, (val) => { + const matches = myRegexp.exec(val); if (matches !== null) { - let dir = matches[1]; + const dir = matches[1]; sort_array.push({ field: val.substr(0, val.length - (dir.length + 1)), - dir: dir.toLowerCase() + dir: dir.toLowerCase(), }); } else { sort_array.push({ field: val, - dir: 'asc' + dir: "asc", }); } }); diff --git a/backend/lib/express/user-id-from-me.js b/backend/lib/express/user-id-from-me.js index 4a37a406..9c29ba27 100644 --- a/backend/lib/express/user-id-from-me.js +++ b/backend/lib/express/user-id-from-me.js @@ -1,9 +1,8 @@ -module.exports = (req, res, next) => { +export default (req, res, next) => { if (req.params.user_id === 'me' && res.locals.access) { req.params.user_id = res.locals.access.token.get('attrs').id; } else { - req.params.user_id = parseInt(req.params.user_id, 10); + req.params.user_id = Number.parseInt(req.params.user_id, 10); } - next(); }; diff --git a/backend/lib/helpers.js b/backend/lib/helpers.js index ad3df3c2..853a7a55 100644 --- a/backend/lib/helpers.js +++ b/backend/lib/helpers.js @@ -1,62 +1,58 @@ -const moment = require('moment'); -const {isPostgres} = require('./config'); -const {ref} = require('objection'); +import moment from "moment"; +import { ref } from "objection"; +import { isPostgres } from "./config.js"; -module.exports = { - - /** - * Takes an expression such as 30d and returns a moment object of that date in future - * - * Key Shorthand - * ================== - * years y - * quarters Q - * months M - * weeks w - * days d - * hours h - * minutes m - * seconds s - * milliseconds ms - * - * @param {String} expression - * @returns {Object} - */ - parseDatePeriod: function (expression) { - let matches = expression.match(/^([0-9]+)(y|Q|M|w|d|h|m|s|ms)$/m); - if (matches) { - return moment().add(matches[1], matches[2]); - } - - return null; - }, - - convertIntFieldsToBool: function (obj, fields) { - fields.forEach(function (field) { - if (typeof obj[field] !== 'undefined') { - obj[field] = obj[field] === 1; - } - }); - return obj; - }, - - convertBoolFieldsToInt: function (obj, fields) { - fields.forEach(function (field) { - if (typeof obj[field] !== 'undefined') { - obj[field] = obj[field] ? 1 : 0; - } - }); - return obj; - }, - - /** - * Casts a column to json if using postgres - * - * @param {string} colName - * @returns {string|Objection.ReferenceBuilder} - */ - castJsonIfNeed: function (colName) { - return isPostgres() ? ref(colName).castText() : colName; +/** + * Takes an expression such as 30d and returns a moment object of that date in future + * + * Key Shorthand + * ================== + * years y + * quarters Q + * months M + * weeks w + * days d + * hours h + * minutes m + * seconds s + * milliseconds ms + * + * @param {String} expression + * @returns {Object} + */ +const parseDatePeriod = (expression) => { + const matches = expression.match(/^([0-9]+)(y|Q|M|w|d|h|m|s|ms)$/m); + if (matches) { + return moment().add(matches[1], matches[2]); } + return null; }; + +const convertIntFieldsToBool = (obj, fields) => { + fields.forEach((field) => { + if (typeof obj[field] !== "undefined") { + obj[field] = obj[field] === 1; + } + }); + return obj; +}; + +const convertBoolFieldsToInt = (obj, fields) => { + fields.forEach((field) => { + if (typeof obj[field] !== "undefined") { + obj[field] = obj[field] ? 1 : 0; + } + }); + return obj; +}; + +/** + * Casts a column to json if using postgres + * + * @param {string} colName + * @returns {string|Objection.ReferenceBuilder} + */ +const castJsonIfNeed = (colName) => (isPostgres() ? ref(colName).castText() : colName); + +export { parseDatePeriod, convertIntFieldsToBool, convertBoolFieldsToInt, castJsonIfNeed }; diff --git a/backend/lib/migrate_template.js b/backend/lib/migrate_template.js index f75f77ef..0b8e2840 100644 --- a/backend/lib/migrate_template.js +++ b/backend/lib/migrate_template.js @@ -1,33 +1,34 @@ -const migrate_name = 'identifier_for_migrate'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "identifier_for_migrate"; /** * Migrate * * @see http://knexjs.org/#Schema * - * @param {Object} knex - * @param {Promise} Promise + * @param {Object} knex * @returns {Promise} */ -exports.up = function (knex, Promise) { - - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (_knex) => { + logger.info(`[${migrateName}] Migrating Up...`); // Create Table example: - /*return knex.schema.createTable('notification', (table) => { + /* + return knex.schema.createTable('notification', (table) => { table.increments().primary(); table.string('name').notNull(); table.string('type').notNull(); table.integer('created_on').notNull(); table.integer('modified_on').notNull(); }) - .then(function () { - logger.info('[' + migrate_name + '] Notification Table created'); - });*/ + .then(function () { + logger.info('[' + migrateName + '] Notification Table created'); + }); + */ - logger.info('[' + migrate_name + '] Migrating Up Complete'); + logger.info(`[${migrateName}] Migrating Up Complete`); return Promise.resolve(true); }; @@ -35,21 +36,24 @@ exports.up = function (knex, Promise) { /** * Undo Migrate * - * @param {Object} knex - * @param {Promise} Promise + * @param {Object} knex * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (_knex) => { + logger.info(`[${migrateName}] Migrating Down...`); // Drop table example: - /*return knex.schema.dropTable('notification') - .then(() => { - logger.info('[' + migrate_name + '] Notification Table dropped'); - });*/ + /* + return knex.schema.dropTable('notification') + .then(() => { + logger.info(`[${migrateName}] Notification Table dropped`); + }); + */ - logger.info('[' + migrate_name + '] Migrating Down Complete'); + logger.info(`[${migrateName}] Migrating Down Complete`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/lib/utils.js b/backend/lib/utils.js index e2d60778..21904df2 100644 --- a/backend/lib/utils.js +++ b/backend/lib/utils.js @@ -1,110 +1,110 @@ -const _ = require('lodash'); -const exec = require('node:child_process').exec; -const execFile = require('node:child_process').execFile; -const { Liquid } = require('liquidjs'); -const logger = require('../logger').global; -const error = require('./error'); +import { exec as nodeExec, execFile as nodeExecFile } from "node:child_process"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { Liquid } from "liquidjs"; +import _ from "lodash"; +import { global as logger } from "../logger.js"; +import errs from "./error.js"; -module.exports = { +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); - exec: async (cmd, options = {}) => { - logger.debug('CMD:', cmd); - - const { stdout, stderr } = await new Promise((resolve, reject) => { - const child = exec(cmd, options, (isError, stdout, stderr) => { - if (isError) { - reject(new error.CommandError(stderr, isError)); - } else { - resolve({ stdout, stderr }); - } - }); - - child.on('error', (e) => { - reject(new error.CommandError(stderr, 1, e)); - }); - }); - return stdout; - }, - - /** - * @param {String} cmd - * @param {Array} args - * @param {Object|undefined} options - * @returns {Promise} - */ - execFile: (cmd, args, options) => { - logger.debug(`CMD: ${cmd} ${args ? args.join(' ') : ''}`); - if (typeof options === 'undefined') { - options = {}; - } - - return new Promise((resolve, reject) => { - execFile(cmd, args, options, (err, stdout, stderr) => { - if (err && typeof err === 'object') { - reject(new error.CommandError(stderr, 1, err)); - } else { - resolve(stdout.trim()); - } - }); - }); - }, - - /** - * Used in objection query builder - * - * @param {Array} omissions - * @returns {Function} - */ - omitRow: (omissions) => { - /** - * @param {Object} row - * @returns {Object} - */ - return (row) => { - return _.omit(row, omissions); - }; - }, - - /** - * Used in objection query builder - * - * @param {Array} omissions - * @returns {Function} - */ - omitRows: (omissions) => { - /** - * @param {Array} rows - * @returns {Object} - */ - return (rows) => { - rows.forEach((row, idx) => { - rows[idx] = _.omit(row, omissions); - }); - return rows; - }; - }, - - /** - * @returns {Object} Liquid render engine - */ - getRenderEngine: () => { - const renderEngine = new Liquid({ - root: `${__dirname}/../templates/` - }); - - /** - * nginxAccessRule expects the object given to have 2 properties: - * - * directive string - * address string - */ - renderEngine.registerFilter('nginxAccessRule', (v) => { - if (typeof v.directive !== 'undefined' && typeof v.address !== 'undefined' && v.directive && v.address) { - return `${v.directive} ${v.address};`; +const exec = async (cmd, options = {}) => { + logger.debug("CMD:", cmd); + const { stdout, stderr } = await new Promise((resolve, reject) => { + const child = nodeExec(cmd, options, (isError, stdout, stderr) => { + if (isError) { + reject(new errs.CommandError(stderr, isError)); + } else { + resolve({ stdout, stderr }); } - return ''; }); - return renderEngine; - } + child.on("error", (e) => { + reject(new errs.CommandError(stderr, 1, e)); + }); + }); + return stdout; }; + +/** + * @param {String} cmd + * @param {Array} args + * @param {Object|undefined} options + * @returns {Promise} + */ +const execFile = (cmd, args, options) => { + logger.debug(`CMD: ${cmd} ${args ? args.join(" ") : ""}`); + const opts = options || {}; + + return new Promise((resolve, reject) => { + nodeExecFile(cmd, args, opts, (err, stdout, stderr) => { + if (err && typeof err === "object") { + reject(new errs.CommandError(stderr, 1, err)); + } else { + resolve(stdout.trim()); + } + }); + }); +}; + +/** + * Used in objection query builder + * + * @param {Array} omissions + * @returns {Function} + */ +const omitRow = (omissions) => { + /** + * @param {Object} row + * @returns {Object} + */ + return (row) => { + return _.omit(row, omissions); + }; +}; + +/** + * Used in objection query builder + * + * @param {Array} omissions + * @returns {Function} + */ +const omitRows = (omissions) => { + /** + * @param {Array} rows + * @returns {Object} + */ + return (rows) => { + rows.forEach((row, idx) => { + rows[idx] = _.omit(row, omissions); + }); + return rows; + }; +}; + +/** + * @returns {Object} Liquid render engine + */ +const getRenderEngine = () => { + const renderEngine = new Liquid({ + root: `${__dirname}/../templates/`, + }); + + /** + * nginxAccessRule expects the object given to have 2 properties: + * + * directive string + * address string + */ + renderEngine.registerFilter("nginxAccessRule", (v) => { + if (typeof v.directive !== "undefined" && typeof v.address !== "undefined" && v.directive && v.address) { + return `${v.directive} ${v.address};`; + } + return ""; + }); + + return renderEngine; +}; + +export default { exec, execFile, omitRow, omitRows, getRenderEngine }; diff --git a/backend/lib/validator/api.js b/backend/lib/validator/api.js index fb31e64c..26bfca07 100644 --- a/backend/lib/validator/api.js +++ b/backend/lib/validator/api.js @@ -1,12 +1,12 @@ -const Ajv = require('ajv/dist/2020'); -const error = require('../error'); +import Ajv from "ajv/dist/2020.js"; +import errs from "../error.js"; const ajv = new Ajv({ - verbose: true, - allErrors: true, + verbose: true, + allErrors: true, allowUnionTypes: true, - strict: false, - coerceTypes: true, + strict: false, + coerceTypes: true, }); /** @@ -14,30 +14,30 @@ const ajv = new Ajv({ * @param {Object} payload * @returns {Promise} */ -function apiValidator (schema, payload/*, description*/) { - return new Promise(function Promise_apiValidator (resolve, reject) { +function apiValidator(schema, payload /*, description*/) { + return new Promise(function Promise_apiValidator(resolve, reject) { if (schema === null) { - reject(new error.ValidationError('Schema is undefined')); + reject(new errs.ValidationError("Schema is undefined")); return; } - if (typeof payload === 'undefined') { - reject(new error.ValidationError('Payload is undefined')); + if (typeof payload === "undefined") { + reject(new errs.ValidationError("Payload is undefined")); return; } const validate = ajv.compile(schema); - const valid = validate(payload); + const valid = validate(payload); if (valid && !validate.errors) { resolve(payload); } else { - let message = ajv.errorsText(validate.errors); - let err = new error.ValidationError(message); - err.debug = [validate.errors, payload]; + const message = ajv.errorsText(validate.errors); + const err = new errs.ValidationError(message); + err.debug = [validate.errors, payload]; reject(err); } }); } -module.exports = apiValidator; +export default apiValidator; diff --git a/backend/lib/validator/index.js b/backend/lib/validator/index.js index c6d24096..5f2586fd 100644 --- a/backend/lib/validator/index.js +++ b/backend/lib/validator/index.js @@ -1,17 +1,17 @@ -const _ = require('lodash'); -const Ajv = require('ajv/dist/2020'); -const error = require('../error'); -const commonDefinitions = require('../../schema/common.json'); +import Ajv from 'ajv/dist/2020.js'; +import _ from "lodash"; +import commonDefinitions from "../../schema/common.json" with { type: "json" }; +import errs from "../error.js"; RegExp.prototype.toJSON = RegExp.prototype.toString; const ajv = new Ajv({ - verbose: true, - allErrors: true, + verbose: true, + allErrors: true, allowUnionTypes: true, - coerceTypes: true, - strict: false, - schemas: [commonDefinitions] + coerceTypes: true, + strict: false, + schemas: [commonDefinitions], }); /** @@ -20,26 +20,26 @@ const ajv = new Ajv({ * @param {Object} payload * @returns {Promise} */ -function validator (schema, payload) { - return new Promise(function (resolve, reject) { +const validator = (schema, payload) => { + return new Promise((resolve, reject) => { if (!payload) { - reject(new error.InternalValidationError('Payload is falsy')); + reject(new errs.InternalValidationError("Payload is falsy")); } else { try { - let validate = ajv.compile(schema); - let valid = validate(payload); + const validate = ajv.compile(schema); + const valid = validate(payload); if (valid && !validate.errors) { resolve(_.cloneDeep(payload)); } else { - let message = ajv.errorsText(validate.errors); - reject(new error.InternalValidationError(message)); + const message = ajv.errorsText(validate.errors); + reject(new errs.InternalValidationError(message)); } } catch (err) { reject(err); } } }); -} +}; -module.exports = validator; +export default validator; diff --git a/backend/logger.js b/backend/logger.js index 0ebb07c5..6318a5fb 100644 --- a/backend/logger.js +++ b/backend/logger.js @@ -1,14 +1,18 @@ -const {Signale} = require('signale'); +import signale from "signale"; -module.exports = { - global: new Signale({scope: 'Global '}), - migrate: new Signale({scope: 'Migrate '}), - express: new Signale({scope: 'Express '}), - access: new Signale({scope: 'Access '}), - nginx: new Signale({scope: 'Nginx '}), - ssl: new Signale({scope: 'SSL '}), - certbot: new Signale({scope: 'Certbot '}), - import: new Signale({scope: 'Importer '}), - setup: new Signale({scope: 'Setup '}), - ip_ranges: new Signale({scope: 'IP Ranges'}) +const opts = { + logLevel: "info", }; + +const global = new signale.Signale({ scope: "Global ", ...opts }); +const migrate = new signale.Signale({ scope: "Migrate ", ...opts }); +const express = new signale.Signale({ scope: "Express ", ...opts }); +const access = new signale.Signale({ scope: "Access ", ...opts }); +const nginx = new signale.Signale({ scope: "Nginx ", ...opts }); +const ssl = new signale.Signale({ scope: "SSL ", ...opts }); +const certbot = new signale.Signale({ scope: "Certbot ", ...opts }); +const importer = new signale.Signale({ scope: "Importer ", ...opts }); +const setup = new signale.Signale({ scope: "Setup ", ...opts }); +const ipRanges = new signale.Signale({ scope: "IP Ranges", ...opts }); + +export { global, migrate, express, access, nginx, ssl, certbot, importer, setup, ipRanges }; diff --git a/backend/migrate.js b/backend/migrate.js index 263c8702..dd3f1b61 100644 --- a/backend/migrate.js +++ b/backend/migrate.js @@ -1,15 +1,13 @@ -const db = require('./db'); -const logger = require('./logger').migrate; +import db from "./db.js"; +import { migrate as logger } from "./logger.js"; -module.exports = { - latest: function () { - return db.migrate.currentVersion() - .then((version) => { - logger.info('Current database version:', version); - return db.migrate.latest({ - tableName: 'migrations', - directory: 'migrations' - }); - }); - } +const migrateUp = async () => { + const version = await db.migrate.currentVersion(); + logger.info("Current database version:", version); + return await db.migrate.latest({ + tableName: "migrations", + directory: "migrations", + }); }; + +export { migrateUp }; diff --git a/backend/migrations/20180618015850_initial.js b/backend/migrations/20180618015850_initial.js index a112e826..d3c55d9c 100644 --- a/backend/migrations/20180618015850_initial.js +++ b/backend/migrations/20180618015850_initial.js @@ -1,5 +1,6 @@ -const migrate_name = 'initial-schema'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "initial-schema"; /** * Migrate @@ -7,199 +8,199 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.createTable('auth', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('user_id').notNull().unsigned(); - table.string('type', 30).notNull(); - table.string('secret').notNull(); - table.json('meta').notNull(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - }) + return knex.schema + .createTable("auth", (table) => { + table.increments().primary(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("user_id").notNull().unsigned(); + table.string("type", 30).notNull(); + table.string("secret").notNull(); + table.json("meta").notNull(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + }) .then(() => { - logger.info('[' + migrate_name + '] auth Table created'); + logger.info(`[${migrateName}] auth Table created`); - return knex.schema.createTable('user', (table) => { + return knex.schema.createTable("user", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.integer('is_disabled').notNull().unsigned().defaultTo(0); - table.string('email').notNull(); - table.string('name').notNull(); - table.string('nickname').notNull(); - table.string('avatar').notNull(); - table.json('roles').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.integer("is_disabled").notNull().unsigned().defaultTo(0); + table.string("email").notNull(); + table.string("name").notNull(); + table.string("nickname").notNull(); + table.string("avatar").notNull(); + table.json("roles").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] user Table created'); + logger.info(`[${migrateName}] user Table created`); - return knex.schema.createTable('user_permission', (table) => { + return knex.schema.createTable("user_permission", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('user_id').notNull().unsigned(); - table.string('visibility').notNull(); - table.string('proxy_hosts').notNull(); - table.string('redirection_hosts').notNull(); - table.string('dead_hosts').notNull(); - table.string('streams').notNull(); - table.string('access_lists').notNull(); - table.string('certificates').notNull(); - table.unique('user_id'); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("user_id").notNull().unsigned(); + table.string("visibility").notNull(); + table.string("proxy_hosts").notNull(); + table.string("redirection_hosts").notNull(); + table.string("dead_hosts").notNull(); + table.string("streams").notNull(); + table.string("access_lists").notNull(); + table.string("certificates").notNull(); + table.unique("user_id"); }); }) .then(() => { - logger.info('[' + migrate_name + '] user_permission Table created'); + logger.info(`[${migrateName}] user_permission Table created`); - return knex.schema.createTable('proxy_host', (table) => { + return knex.schema.createTable("proxy_host", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.json('domain_names').notNull(); - table.string('forward_ip').notNull(); - table.integer('forward_port').notNull().unsigned(); - table.integer('access_list_id').notNull().unsigned().defaultTo(0); - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - table.integer('ssl_forced').notNull().unsigned().defaultTo(0); - table.integer('caching_enabled').notNull().unsigned().defaultTo(0); - table.integer('block_exploits').notNull().unsigned().defaultTo(0); - table.text('advanced_config').notNull().defaultTo(''); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.json("domain_names").notNull(); + table.string("forward_ip").notNull(); + table.integer("forward_port").notNull().unsigned(); + table.integer("access_list_id").notNull().unsigned().defaultTo(0); + table.integer("certificate_id").notNull().unsigned().defaultTo(0); + table.integer("ssl_forced").notNull().unsigned().defaultTo(0); + table.integer("caching_enabled").notNull().unsigned().defaultTo(0); + table.integer("block_exploits").notNull().unsigned().defaultTo(0); + table.text("advanced_config").notNull().defaultTo(""); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table created'); + logger.info(`[${migrateName}] proxy_host Table created`); - return knex.schema.createTable('redirection_host', (table) => { + return knex.schema.createTable("redirection_host", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.json('domain_names').notNull(); - table.string('forward_domain_name').notNull(); - table.integer('preserve_path').notNull().unsigned().defaultTo(0); - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - table.integer('ssl_forced').notNull().unsigned().defaultTo(0); - table.integer('block_exploits').notNull().unsigned().defaultTo(0); - table.text('advanced_config').notNull().defaultTo(''); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.json("domain_names").notNull(); + table.string("forward_domain_name").notNull(); + table.integer("preserve_path").notNull().unsigned().defaultTo(0); + table.integer("certificate_id").notNull().unsigned().defaultTo(0); + table.integer("ssl_forced").notNull().unsigned().defaultTo(0); + table.integer("block_exploits").notNull().unsigned().defaultTo(0); + table.text("advanced_config").notNull().defaultTo(""); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table created'); + logger.info(`[${migrateName}] redirection_host Table created`); - return knex.schema.createTable('dead_host', (table) => { + return knex.schema.createTable("dead_host", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.json('domain_names').notNull(); - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - table.integer('ssl_forced').notNull().unsigned().defaultTo(0); - table.text('advanced_config').notNull().defaultTo(''); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.json("domain_names").notNull(); + table.integer("certificate_id").notNull().unsigned().defaultTo(0); + table.integer("ssl_forced").notNull().unsigned().defaultTo(0); + table.text("advanced_config").notNull().defaultTo(""); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] dead_host Table created'); + logger.info(`[${migrateName}] dead_host Table created`); - return knex.schema.createTable('stream', (table) => { + return knex.schema.createTable("stream", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.integer('incoming_port').notNull().unsigned(); - table.string('forward_ip').notNull(); - table.integer('forwarding_port').notNull().unsigned(); - table.integer('tcp_forwarding').notNull().unsigned().defaultTo(0); - table.integer('udp_forwarding').notNull().unsigned().defaultTo(0); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.integer("incoming_port").notNull().unsigned(); + table.string("forward_ip").notNull(); + table.integer("forwarding_port").notNull().unsigned(); + table.integer("tcp_forwarding").notNull().unsigned().defaultTo(0); + table.integer("udp_forwarding").notNull().unsigned().defaultTo(0); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] stream Table created'); + logger.info(`[${migrateName}] stream Table created`); - return knex.schema.createTable('access_list', (table) => { + return knex.schema.createTable("access_list", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.string('name').notNull(); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.string("name").notNull(); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] access_list Table created'); + logger.info(`[${migrateName}] access_list Table created`); - return knex.schema.createTable('certificate', (table) => { + return knex.schema.createTable("certificate", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.string('provider').notNull(); - table.string('nice_name').notNull().defaultTo(''); - table.json('domain_names').notNull(); - table.dateTime('expires_on').notNull(); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("owner_user_id").notNull().unsigned(); + table.integer("is_deleted").notNull().unsigned().defaultTo(0); + table.string("provider").notNull(); + table.string("nice_name").notNull().defaultTo(""); + table.json("domain_names").notNull(); + table.dateTime("expires_on").notNull(); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] certificate Table created'); + logger.info(`[${migrateName}] certificate Table created`); - return knex.schema.createTable('access_list_auth', (table) => { + return knex.schema.createTable("access_list_auth", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('access_list_id').notNull().unsigned(); - table.string('username').notNull(); - table.string('password').notNull(); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("access_list_id").notNull().unsigned(); + table.string("username").notNull(); + table.string("password").notNull(); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] access_list_auth Table created'); + logger.info(`[${migrateName}] access_list_auth Table created`); - return knex.schema.createTable('audit_log', (table) => { + return knex.schema.createTable("audit_log", (table) => { table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('user_id').notNull().unsigned(); - table.string('object_type').notNull().defaultTo(''); - table.integer('object_id').notNull().unsigned().defaultTo(0); - table.string('action').notNull(); - table.json('meta').notNull(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("user_id").notNull().unsigned(); + table.string("object_type").notNull().defaultTo(""); + table.integer("object_id").notNull().unsigned().defaultTo(0); + table.string("action").notNull(); + table.json("meta").notNull(); }); }) .then(() => { - logger.info('[' + migrate_name + '] audit_log Table created'); + logger.info(`[${migrateName}] audit_log Table created`); }); - }; /** * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down the initial data.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down the initial data.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20180929054513_websockets.js b/backend/migrations/20180929054513_websockets.js index 06054850..cce80d37 100644 --- a/backend/migrations/20180929054513_websockets.js +++ b/backend/migrations/20180929054513_websockets.js @@ -1,5 +1,6 @@ -const migrate_name = 'websockets'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "websockets"; /** * Migrate @@ -7,29 +8,29 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.integer("allow_websocket_upgrade").notNull().unsigned().defaultTo(0); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); }); - }; /** * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); -}; \ No newline at end of file +}; + +export { up, down }; diff --git a/backend/migrations/20181019052346_forward_host.js b/backend/migrations/20181019052346_forward_host.js index 05c27739..fe11edc5 100644 --- a/backend/migrations/20181019052346_forward_host.js +++ b/backend/migrations/20181019052346_forward_host.js @@ -1,5 +1,6 @@ -const migrate_name = 'forward_host'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "forward_host"; /** * Migrate @@ -7,17 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.renameColumn('forward_ip', 'forward_host'); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.renameColumn("forward_ip", "forward_host"); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); }); }; @@ -25,10 +26,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); -}; \ No newline at end of file +}; + +export { up, down }; diff --git a/backend/migrations/20181113041458_http2_support.js b/backend/migrations/20181113041458_http2_support.js index 9f6b4336..cfa94a99 100644 --- a/backend/migrations/20181113041458_http2_support.js +++ b/backend/migrations/20181113041458_http2_support.js @@ -1,5 +1,6 @@ -const migrate_name = 'http2_support'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "http2_support"; /** * Migrate @@ -7,31 +8,31 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.integer('http2_support').notNull().unsigned().defaultTo(0); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.integer("http2_support").notNull().unsigned().defaultTo(0); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); - return knex.schema.table('redirection_host', function (redirection_host) { - redirection_host.integer('http2_support').notNull().unsigned().defaultTo(0); + return knex.schema.table("redirection_host", (redirection_host) => { + redirection_host.integer("http2_support").notNull().unsigned().defaultTo(0); }); }) .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + logger.info(`[${migrateName}] redirection_host Table altered`); - return knex.schema.table('dead_host', function (dead_host) { - dead_host.integer('http2_support').notNull().unsigned().defaultTo(0); + return knex.schema.table("dead_host", (dead_host) => { + dead_host.integer("http2_support").notNull().unsigned().defaultTo(0); }); }) .then(() => { - logger.info('[' + migrate_name + '] dead_host Table altered'); + logger.info(`[${migrateName}] dead_host Table altered`); }); }; @@ -39,11 +40,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; +export { up, down }; diff --git a/backend/migrations/20181213013211_forward_scheme.js b/backend/migrations/20181213013211_forward_scheme.js index 22ae619e..ba3bc562 100644 --- a/backend/migrations/20181213013211_forward_scheme.js +++ b/backend/migrations/20181213013211_forward_scheme.js @@ -1,5 +1,6 @@ -const migrate_name = 'forward_scheme'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "forward_scheme"; /** * Migrate @@ -7,17 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.string('forward_scheme').notNull().defaultTo('http'); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.string("forward_scheme").notNull().defaultTo("http"); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); }); }; @@ -25,10 +26,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20190104035154_disabled.js b/backend/migrations/20190104035154_disabled.js index 2780c4df..28fcc7b5 100644 --- a/backend/migrations/20190104035154_disabled.js +++ b/backend/migrations/20190104035154_disabled.js @@ -1,5 +1,6 @@ -const migrate_name = 'disabled'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "disabled"; /** * Migrate @@ -7,38 +8,38 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.integer('enabled').notNull().unsigned().defaultTo(1); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.integer("enabled").notNull().unsigned().defaultTo(1); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); - return knex.schema.table('redirection_host', function (redirection_host) { - redirection_host.integer('enabled').notNull().unsigned().defaultTo(1); + return knex.schema.table("redirection_host", (redirection_host) => { + redirection_host.integer("enabled").notNull().unsigned().defaultTo(1); }); }) .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + logger.info(`[${migrateName}] redirection_host Table altered`); - return knex.schema.table('dead_host', function (dead_host) { - dead_host.integer('enabled').notNull().unsigned().defaultTo(1); + return knex.schema.table("dead_host", (dead_host) => { + dead_host.integer("enabled").notNull().unsigned().defaultTo(1); }); }) .then(() => { - logger.info('[' + migrate_name + '] dead_host Table altered'); + logger.info(`[${migrateName}] dead_host Table altered`); - return knex.schema.table('stream', function (stream) { - stream.integer('enabled').notNull().unsigned().defaultTo(1); + return knex.schema.table("stream", (stream) => { + stream.integer("enabled").notNull().unsigned().defaultTo(1); }); }) .then(() => { - logger.info('[' + migrate_name + '] stream Table altered'); + logger.info(`[${migrateName}] stream Table altered`); }); }; @@ -46,10 +47,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20190215115310_customlocations.js b/backend/migrations/20190215115310_customlocations.js index 4bcfd51a..c4f77977 100644 --- a/backend/migrations/20190215115310_customlocations.js +++ b/backend/migrations/20190215115310_customlocations.js @@ -1,5 +1,6 @@ -const migrate_name = 'custom_locations'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "custom_locations"; /** * Migrate @@ -8,17 +9,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.json('locations'); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.json("locations"); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); }); }; @@ -26,10 +27,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20190218060101_hsts.js b/backend/migrations/20190218060101_hsts.js index 648b162a..1253130e 100644 --- a/backend/migrations/20190218060101_hsts.js +++ b/backend/migrations/20190218060101_hsts.js @@ -1,5 +1,6 @@ -const migrate_name = 'hsts'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "hsts"; /** * Migrate @@ -7,34 +8,34 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('proxy_host', function (proxy_host) { - proxy_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0); - proxy_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0); - }) + return knex.schema + .table("proxy_host", (proxy_host) => { + proxy_host.integer("hsts_enabled").notNull().unsigned().defaultTo(0); + proxy_host.integer("hsts_subdomains").notNull().unsigned().defaultTo(0); + }) .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); + logger.info(`[${migrateName}] proxy_host Table altered`); - return knex.schema.table('redirection_host', function (redirection_host) { - redirection_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0); - redirection_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0); + return knex.schema.table("redirection_host", (redirection_host) => { + redirection_host.integer("hsts_enabled").notNull().unsigned().defaultTo(0); + redirection_host.integer("hsts_subdomains").notNull().unsigned().defaultTo(0); }); }) .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + logger.info(`[${migrateName}] redirection_host Table altered`); - return knex.schema.table('dead_host', function (dead_host) { - dead_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0); - dead_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0); + return knex.schema.table("dead_host", (dead_host) => { + dead_host.integer("hsts_enabled").notNull().unsigned().defaultTo(0); + dead_host.integer("hsts_subdomains").notNull().unsigned().defaultTo(0); }); }) .then(() => { - logger.info('[' + migrate_name + '] dead_host Table altered'); + logger.info(`[${migrateName}] dead_host Table altered`); }); }; @@ -42,10 +43,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20190227065017_settings.js b/backend/migrations/20190227065017_settings.js index 7dc9c192..a6cbe2c9 100644 --- a/backend/migrations/20190227065017_settings.js +++ b/backend/migrations/20190227065017_settings.js @@ -1,5 +1,6 @@ -const migrate_name = 'settings'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "settings"; /** * Migrate @@ -7,11 +8,10 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); return knex.schema.createTable('setting', (table) => { table.string('id').notNull().primary(); @@ -21,7 +21,7 @@ exports.up = function (knex/*, Promise*/) { table.json('meta').notNull(); }) .then(() => { - logger.info('[' + migrate_name + '] setting Table created'); + logger.info(`[${migrateName}] setting Table created`); }); }; @@ -29,10 +29,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down the initial data.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down the initial data.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20200410143839_access_list_client.js b/backend/migrations/20200410143839_access_list_client.js index 3511e35b..e6825048 100644 --- a/backend/migrations/20200410143839_access_list_client.js +++ b/backend/migrations/20200410143839_access_list_client.js @@ -1,5 +1,6 @@ -const migrate_name = 'access_list_client'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "access_list_client"; /** * Migrate @@ -7,32 +8,30 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - logger.info('[' + migrate_name + '] Migrating Up...'); + return knex.schema + .createTable("access_list_client", (table) => { + table.increments().primary(); + table.dateTime("created_on").notNull(); + table.dateTime("modified_on").notNull(); + table.integer("access_list_id").notNull().unsigned(); + table.string("address").notNull(); + table.string("directive").notNull(); + table.json("meta").notNull(); + }) + .then(() => { + logger.info(`[${migrateName}] access_list_client Table created`); - return knex.schema.createTable('access_list_client', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('access_list_id').notNull().unsigned(); - table.string('address').notNull(); - table.string('directive').notNull(); - table.json('meta').notNull(); - - }) - .then(function () { - logger.info('[' + migrate_name + '] access_list_client Table created'); - - return knex.schema.table('access_list', function (access_list) { - access_list.integer('satify_any').notNull().defaultTo(0); + return knex.schema.table("access_list", (access_list) => { + access_list.integer("satify_any").notNull().defaultTo(0); }); }) .then(() => { - logger.info('[' + migrate_name + '] access_list Table altered'); + logger.info(`[${migrateName}] access_list Table altered`); }); }; @@ -40,14 +39,14 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.dropTable('access_list_client') - .then(() => { - logger.info('[' + migrate_name + '] access_list_client Table dropped'); - }); + return knex.schema.dropTable("access_list_client").then(() => { + logger.info(`[${migrateName}] access_list_client Table dropped`); + }); }; + +export { up, down }; diff --git a/backend/migrations/20200410143840_access_list_client_fix.js b/backend/migrations/20200410143840_access_list_client_fix.js index ee0f0906..6bdaedb6 100644 --- a/backend/migrations/20200410143840_access_list_client_fix.js +++ b/backend/migrations/20200410143840_access_list_client_fix.js @@ -1,5 +1,6 @@ -const migrate_name = 'access_list_client_fix'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "access_list_client_fix"; /** * Migrate @@ -7,17 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('access_list', function (access_list) { - access_list.renameColumn('satify_any', 'satisfy_any'); - }) + return knex.schema + .table("access_list", (access_list) => { + access_list.renameColumn("satify_any", "satisfy_any"); + }) .then(() => { - logger.info('[' + migrate_name + '] access_list Table altered'); + logger.info(`[${migrateName}] access_list Table altered`); }); }; @@ -25,10 +26,11 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); +const down = (_knex) => { + logger.warn(`[${migrateName}] You can't migrate down this one.`); return Promise.resolve(true); }; + +export { up, down }; diff --git a/backend/migrations/20201014143841_pass_auth.js b/backend/migrations/20201014143841_pass_auth.js index a7767eb1..dc57e2a7 100644 --- a/backend/migrations/20201014143841_pass_auth.js +++ b/backend/migrations/20201014143841_pass_auth.js @@ -1,5 +1,6 @@ -const migrate_name = 'pass_auth'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "pass_auth"; /** * Migrate @@ -7,18 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema.table('access_list', function (access_list) { - access_list.integer('pass_auth').notNull().defaultTo(1); - }) + return knex.schema + .table("access_list", (access_list) => { + access_list.integer("pass_auth").notNull().defaultTo(1); + }) .then(() => { - logger.info('[' + migrate_name + '] access_list Table altered'); + logger.info(`[${migrateName}] access_list Table altered`); }); }; @@ -26,16 +26,18 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.table('access_list', function (access_list) { - access_list.dropColumn('pass_auth'); - }) + return knex.schema + .table("access_list", (access_list) => { + access_list.dropColumn("pass_auth"); + }) .then(() => { - logger.info('[' + migrate_name + '] access_list pass_auth Column dropped'); + logger.info(`[${migrateName}] access_list pass_auth Column dropped`); }); }; + +export { up, down }; diff --git a/backend/migrations/20210210154702_redirection_scheme.js b/backend/migrations/20210210154702_redirection_scheme.js index 0dad4876..b3f18aeb 100644 --- a/backend/migrations/20210210154702_redirection_scheme.js +++ b/backend/migrations/20210210154702_redirection_scheme.js @@ -1,5 +1,6 @@ -const migrate_name = 'redirection_scheme'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "redirection_scheme"; /** * Migrate @@ -7,18 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema.table('redirection_host', (table) => { - table.string('forward_scheme').notNull().defaultTo('$scheme'); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + return knex.schema + .table("redirection_host", (table) => { + table.string("forward_scheme").notNull().defaultTo("$scheme"); + }) + .then(() => { + logger.info(`[${migrateName}] redirection_host Table altered`); }); }; @@ -26,16 +26,18 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.table('redirection_host', (table) => { - table.dropColumn('forward_scheme'); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + return knex.schema + .table("redirection_host", (table) => { + table.dropColumn("forward_scheme"); + }) + .then(() => { + logger.info(`[${migrateName}] redirection_host Table altered`); }); }; + +export { up, down }; diff --git a/backend/migrations/20210210154703_redirection_status_code.js b/backend/migrations/20210210154703_redirection_status_code.js index b9bea0b9..cf84298d 100644 --- a/backend/migrations/20210210154703_redirection_status_code.js +++ b/backend/migrations/20210210154703_redirection_status_code.js @@ -1,5 +1,6 @@ -const migrate_name = 'redirection_status_code'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "redirection_status_code"; /** * Migrate @@ -7,18 +8,17 @@ const logger = require('../logger').migrate; * @see http://knexjs.org/#Schema * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.up = function (knex/*, Promise*/) { +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema.table('redirection_host', (table) => { - table.integer('forward_http_code').notNull().unsigned().defaultTo(302); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + return knex.schema + .table("redirection_host", (table) => { + table.integer("forward_http_code").notNull().unsigned().defaultTo(302); + }) + .then(() => { + logger.info(`[${migrateName}] redirection_host Table altered`); }); }; @@ -26,16 +26,18 @@ exports.up = function (knex/*, Promise*/) { * Undo Migrate * * @param {Object} knex - * @param {Promise} Promise * @returns {Promise} */ -exports.down = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.table('redirection_host', (table) => { - table.dropColumn('forward_http_code'); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); + return knex.schema + .table("redirection_host", (table) => { + table.dropColumn("forward_http_code"); + }) + .then(() => { + logger.info(`[${migrateName}] redirection_host Table altered`); }); }; + +export { up, down }; diff --git a/backend/migrations/20210423103500_stream_domain.js b/backend/migrations/20210423103500_stream_domain.js index a894ca5e..b4afabd4 100644 --- a/backend/migrations/20210423103500_stream_domain.js +++ b/backend/migrations/20210423103500_stream_domain.js @@ -1,40 +1,43 @@ -const migrate_name = 'stream_domain'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "stream_domain"; /** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Up...'); + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @returns {Promise} + */ +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('stream', (table) => { - table.renameColumn('forward_ip', 'forwarding_host'); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); + return knex.schema + .table("stream", (table) => { + table.renameColumn("forward_ip", "forwarding_host"); + }) + .then(() => { + logger.info(`[${migrateName}] stream Table altered`); }); }; /** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex/*, Promise*/) { - logger.info('[' + migrate_name + '] Migrating Down...'); + * Undo Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.table('stream', (table) => { - table.renameColumn('forwarding_host', 'forward_ip'); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); + return knex.schema + .table("stream", (table) => { + table.renameColumn("forwarding_host", "forward_ip"); + }) + .then(() => { + logger.info(`[${migrateName}] stream Table altered`); }); }; + +export { up, down }; diff --git a/backend/migrations/20211108145214_regenerate_default_host.js b/backend/migrations/20211108145214_regenerate_default_host.js index 4c50941f..c2805152 100644 --- a/backend/migrations/20211108145214_regenerate_default_host.js +++ b/backend/migrations/20211108145214_regenerate_default_host.js @@ -1,17 +1,19 @@ -const migrate_name = 'stream_domain'; -const logger = require('../logger').migrate; -const internalNginx = require('../internal/nginx'); +import internalNginx from "../internal/nginx.js"; +import { migrate as logger } from "../logger.js"; + +const migrateName = "stream_domain"; async function regenerateDefaultHost(knex) { - const row = await knex('setting').select('*').where('id', 'default-site').first(); + const row = await knex("setting").select("*").where("id", "default-site").first(); if (!row) { return Promise.resolve(); } - return internalNginx.deleteConfig('default') + return internalNginx + .deleteConfig("default") .then(() => { - return internalNginx.generateConfig('default', row); + return internalNginx.generateConfig("default", row); }) .then(() => { return internalNginx.test(); @@ -22,29 +24,29 @@ async function regenerateDefaultHost(knex) { } /** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex) { - logger.info('[' + migrate_name + '] Migrating Up...'); + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @returns {Promise} + */ +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); return regenerateDefaultHost(knex); }; /** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex) { - logger.info('[' + migrate_name + '] Migrating Down...'); + * Undo Migrate + * + * @param {Object} knex + * @returns {Promise} + */ +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); return regenerateDefaultHost(knex); -}; \ No newline at end of file +}; + +export { up, down }; diff --git a/backend/migrations/20240427161436_stream_ssl.js b/backend/migrations/20240427161436_stream_ssl.js index 5f47b18e..0fbba110 100644 --- a/backend/migrations/20240427161436_stream_ssl.js +++ b/backend/migrations/20240427161436_stream_ssl.js @@ -1,5 +1,6 @@ -const migrate_name = 'stream_ssl'; -const logger = require('../logger').migrate; +import { migrate as logger } from "../logger.js"; + +const migrateName = "stream_ssl"; /** * Migrate @@ -9,14 +10,15 @@ const logger = require('../logger').migrate; * @param {Object} knex * @returns {Promise} */ -exports.up = function (knex) { - logger.info('[' + migrate_name + '] Migrating Up...'); +const up = (knex) => { + logger.info(`[${migrateName}] Migrating Up...`); - return knex.schema.table('stream', (table) => { - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); + return knex.schema + .table("stream", (table) => { + table.integer("certificate_id").notNull().unsigned().defaultTo(0); + }) + .then(() => { + logger.info(`[${migrateName}] stream Table altered`); }); }; @@ -26,13 +28,16 @@ exports.up = function (knex) { * @param {Object} knex * @returns {Promise} */ -exports.down = function (knex) { - logger.info('[' + migrate_name + '] Migrating Down...'); +const down = (knex) => { + logger.info(`[${migrateName}] Migrating Down...`); - return knex.schema.table('stream', (table) => { - table.dropColumn('certificate_id'); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); + return knex.schema + .table("stream", (table) => { + table.dropColumn("certificate_id"); + }) + .then(() => { + logger.info(`[${migrateName}] stream Table altered`); }); }; + +export { up, down }; diff --git a/backend/models/access_list.js b/backend/models/access_list.js index 959df05f..98016a17 100644 --- a/backend/models/access_list.js +++ b/backend/models/access_list.js @@ -1,103 +1,98 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const User = require('./user'); -const AccessListAuth = require('./access_list_auth'); -const AccessListClient = require('./access_list_client'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import AccessListAuth from "./access_list_auth.js"; +import AccessListClient from "./access_list_client.js"; +import now from "./now_helper.js"; +import ProxyHostModel from "./proxy_host.js"; +import User from "./user.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', - 'satisfy_any', - 'pass_auth', -]; +const boolFields = ["is_deleted", "satisfy_any", "pass_auth"]; class AccessList extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'AccessList'; + static get name() { + return "AccessList"; } - static get tableName () { - return 'access_list'; + static get tableName() { + return "access_list"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { - const ProxyHost = require('./proxy_host'); - + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'access_list.owner_user_id', - to: 'user.id' + join: { + from: "access_list.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, items: { - relation: Model.HasManyRelation, + relation: Model.HasManyRelation, modelClass: AccessListAuth, - join: { - from: 'access_list.id', - to: 'access_list_auth.access_list_id' - } + join: { + from: "access_list.id", + to: "access_list_auth.access_list_id", + }, }, clients: { - relation: Model.HasManyRelation, + relation: Model.HasManyRelation, modelClass: AccessListClient, - join: { - from: 'access_list.id', - to: 'access_list_client.access_list_id' - } + join: { + from: "access_list.id", + to: "access_list_client.access_list_id", + }, }, proxy_hosts: { - relation: Model.HasManyRelation, - modelClass: ProxyHost, - join: { - from: 'access_list.id', - to: 'proxy_host.access_list_id' + relation: Model.HasManyRelation, + modelClass: ProxyHostModel, + join: { + from: "access_list.id", + to: "proxy_host.access_list_id", }, - modify: function (qb) { - qb.where('proxy_host.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("proxy_host.is_deleted", 0); + }, + }, }; } } -module.exports = AccessList; +export default AccessList; diff --git a/backend/models/access_list_auth.js b/backend/models/access_list_auth.js index 3895539c..a4fd85a5 100644 --- a/backend/models/access_list_auth.js +++ b/backend/models/access_list_auth.js @@ -1,54 +1,55 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import accessListModel from "./access_list.js"; +import now from "./now_helper.js"; Model.knex(db); class AccessListAuth extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } - static get name () { - return 'AccessListAuth'; + static get name() { + return "AccessListAuth"; } - static get tableName () { - return 'access_list_auth'; + static get tableName() { + return "access_list_auth"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { + static get relationMappings() { return { access_list: { - relation: Model.HasOneRelation, - modelClass: require('./access_list'), - join: { - from: 'access_list_auth.access_list_id', - to: 'access_list.id' + relation: Model.HasOneRelation, + modelClass: accessListModel, + join: { + from: "access_list_auth.access_list_id", + to: "access_list.id", }, - modify: function (qb) { - qb.where('access_list.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("access_list.is_deleted", 0); + }, + }, }; } } -module.exports = AccessListAuth; +export default AccessListAuth; diff --git a/backend/models/access_list_client.js b/backend/models/access_list_client.js index bffc0023..4b63aec9 100644 --- a/backend/models/access_list_client.js +++ b/backend/models/access_list_client.js @@ -1,54 +1,55 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import accessListModel from "./access_list.js"; +import now from "./now_helper.js"; Model.knex(db); class AccessListClient extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } - static get name () { - return 'AccessListClient'; + static get name() { + return "AccessListClient"; } - static get tableName () { - return 'access_list_client'; + static get tableName() { + return "access_list_client"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { + static get relationMappings() { return { access_list: { - relation: Model.HasOneRelation, - modelClass: require('./access_list'), - join: { - from: 'access_list_client.access_list_id', - to: 'access_list.id' + relation: Model.HasOneRelation, + modelClass: accessListModel, + join: { + from: "access_list_client.access_list_id", + to: "access_list.id", }, - modify: function (qb) { - qb.where('access_list.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("access_list.is_deleted", 0); + }, + }, }; } } -module.exports = AccessListClient; +export default AccessListClient; diff --git a/backend/models/audit-log.js b/backend/models/audit-log.js index 45a4b460..a9b2d563 100644 --- a/backend/models/audit-log.js +++ b/backend/models/audit-log.js @@ -1,52 +1,52 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); class AuditLog extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } - static get name () { - return 'AuditLog'; + static get name() { + return "AuditLog"; } - static get tableName () { - return 'audit_log'; + static get tableName() { + return "audit_log"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { + static get relationMappings() { return { user: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'audit_log.user_id', - to: 'user.id' - } - } + join: { + from: "audit_log.user_id", + to: "user.id", + }, + }, }; } } -module.exports = AuditLog; +export default AuditLog; diff --git a/backend/models/auth.js b/backend/models/auth.js index 469e96bf..4ba50b41 100644 --- a/backend/models/auth.js +++ b/backend/models/auth.js @@ -1,59 +1,53 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const bcrypt = require('bcrypt'); -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const User = require('./user'); -const now = require('./now_helper'); +import bcrypt from "bcrypt"; +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', -]; +const boolFields = ["is_deleted"]; -function encryptPassword () { - /* jshint -W040 */ - let _this = this; - - if (_this.type === 'password' && _this.secret) { - return bcrypt.hash(_this.secret, 13) - .then(function (hash) { - _this.secret = hash; - }); +function encryptPassword() { + if (this.type === "password" && this.secret) { + return bcrypt.hash(this.secret, 13).then((hash) => { + this.secret = hash; + }); } return null; } class Auth extends Model { - $beforeInsert (queryContext) { - this.created_on = now(); + $beforeInsert(queryContext) { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } return encryptPassword.apply(this, queryContext); } - $beforeUpdate (queryContext) { + $beforeUpdate(queryContext) { this.modified_on = now(); return encryptPassword.apply(this, queryContext); } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } /** @@ -62,37 +56,37 @@ class Auth extends Model { * @param {String} password * @returns {Promise} */ - verifyPassword (password) { + verifyPassword(password) { return bcrypt.compare(password, this.secret); } - static get name () { - return 'Auth'; + static get name() { + return "Auth"; } - static get tableName () { - return 'auth'; + static get tableName() { + return "auth"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { + static get relationMappings() { return { user: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'auth.user_id', - to: 'user.id' + join: { + from: "auth.user_id", + to: "user.id", }, filter: { - is_deleted: 0 - } - } + is_deleted: 0, + }, + }, }; } } -module.exports = Auth; +export default Auth; diff --git a/backend/models/certificate.js b/backend/models/certificate.js index d4ea21ad..052d3187 100644 --- a/backend/models/certificate.js +++ b/backend/models/certificate.js @@ -1,124 +1,121 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import deadHostModel from "./dead_host.js"; +import now from "./now_helper.js"; +import proxyHostModel from "./proxy_host.js"; +import redirectionHostModel from "./redirection_host.js"; +import userModel from "./user.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', -]; +const boolFields = ["is_deleted"]; class Certificate extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for expires_on - if (typeof this.expires_on === 'undefined') { + if (typeof this.expires_on === "undefined") { this.expires_on = now(); } // Default for domain_names - if (typeof this.domain_names === 'undefined') { + if (typeof this.domain_names === "undefined") { this.domain_names = []; } // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } this.domain_names.sort(); } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); // Sort domain_names - if (typeof this.domain_names !== 'undefined') { + if (typeof this.domain_names !== "undefined") { this.domain_names.sort(); } } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'Certificate'; + static get name() { + return "Certificate"; } - static get tableName () { - return 'certificate'; + static get tableName() { + return "certificate"; } - static get jsonAttributes () { - return ['domain_names', 'meta']; + static get jsonAttributes() { + return ["domain_names", "meta"]; } - static get relationMappings () { - const ProxyHost = require('./proxy_host'); - const DeadHost = require('./dead_host'); - const User = require('./user'); - const RedirectionHost = require('./redirection_host'); - + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, - modelClass: User, - join: { - from: 'certificate.owner_user_id', - to: 'user.id' + relation: Model.HasOneRelation, + modelClass: userModel, + join: { + from: "certificate.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, proxy_hosts: { - relation: Model.HasManyRelation, - modelClass: ProxyHost, - join: { - from: 'certificate.id', - to: 'proxy_host.certificate_id' + relation: Model.HasManyRelation, + modelClass: proxyHostModel, + join: { + from: "certificate.id", + to: "proxy_host.certificate_id", + }, + modify: (qb) => { + qb.where("proxy_host.is_deleted", 0); }, - modify: function (qb) { - qb.where('proxy_host.is_deleted', 0); - } }, dead_hosts: { - relation: Model.HasManyRelation, - modelClass: DeadHost, - join: { - from: 'certificate.id', - to: 'dead_host.certificate_id' + relation: Model.HasManyRelation, + modelClass: deadHostModel, + join: { + from: "certificate.id", + to: "dead_host.certificate_id", + }, + modify: (qb) => { + qb.where("dead_host.is_deleted", 0); }, - modify: function (qb) { - qb.where('dead_host.is_deleted', 0); - } }, redirection_hosts: { - relation: Model.HasManyRelation, - modelClass: RedirectionHost, - join: { - from: 'certificate.id', - to: 'redirection_host.certificate_id' + relation: Model.HasManyRelation, + modelClass: redirectionHostModel, + join: { + from: "certificate.id", + to: "redirection_host.certificate_id", }, - modify: function (qb) { - qb.where('redirection_host.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("redirection_host.is_deleted", 0); + }, + }, }; } } -module.exports = Certificate; +export default Certificate; diff --git a/backend/models/dead_host.js b/backend/models/dead_host.js index 3386caab..56807012 100644 --- a/backend/models/dead_host.js +++ b/backend/models/dead_host.js @@ -1,99 +1,92 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const User = require('./user'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import Certificate from "./certificate.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', - 'ssl_forced', - 'http2_support', - 'enabled', - 'hsts_enabled', - 'hsts_subdomains', -]; +const boolFields = ["is_deleted", "ssl_forced", "http2_support", "enabled", "hsts_enabled", "hsts_subdomains"]; class DeadHost extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for domain_names - if (typeof this.domain_names === 'undefined') { + if (typeof this.domain_names === "undefined") { this.domain_names = []; } // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } this.domain_names.sort(); } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); // Sort domain_names - if (typeof this.domain_names !== 'undefined') { + if (typeof this.domain_names !== "undefined") { this.domain_names.sort(); } } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'DeadHost'; + static get name() { + return "DeadHost"; } - static get tableName () { - return 'dead_host'; + static get tableName() { + return "dead_host"; } - static get jsonAttributes () { - return ['domain_names', 'meta']; + static get jsonAttributes() { + return ["domain_names", "meta"]; } - static get relationMappings () { + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'dead_host.owner_user_id', - to: 'user.id' + join: { + from: "dead_host.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, certificate: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: Certificate, - join: { - from: 'dead_host.certificate_id', - to: 'certificate.id' + join: { + from: "dead_host.certificate_id", + to: "certificate.id", }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("certificate.is_deleted", 0); + }, + }, }; } } -module.exports = DeadHost; +export default DeadHost; diff --git a/backend/models/now_helper.js b/backend/models/now_helper.js index dec70c3d..4dc71cea 100644 --- a/backend/models/now_helper.js +++ b/backend/models/now_helper.js @@ -1,13 +1,12 @@ -const db = require('../db'); -const config = require('../lib/config'); -const Model = require('objection').Model; +import { Model } from "objection"; +import db from "../db.js"; +import { isSqlite } from "../lib/config.js"; Model.knex(db); -module.exports = function () { - if (config.isSqlite()) { - // eslint-disable-next-line +export default () => { + if (isSqlite()) { return Model.raw("datetime('now','localtime')"); } - return Model.raw('NOW()'); + return Model.raw("NOW()"); }; diff --git a/backend/models/proxy_host.js b/backend/models/proxy_host.js index 07aa5dd3..119fe2b7 100644 --- a/backend/models/proxy_host.js +++ b/backend/models/proxy_host.js @@ -1,114 +1,114 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const User = require('./user'); -const AccessList = require('./access_list'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import AccessList from "./access_list.js"; +import Certificate from "./certificate.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); const boolFields = [ - 'is_deleted', - 'ssl_forced', - 'caching_enabled', - 'block_exploits', - 'allow_websocket_upgrade', - 'http2_support', - 'enabled', - 'hsts_enabled', - 'hsts_subdomains', + "is_deleted", + "ssl_forced", + "caching_enabled", + "block_exploits", + "allow_websocket_upgrade", + "http2_support", + "enabled", + "hsts_enabled", + "hsts_subdomains", ]; class ProxyHost extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for domain_names - if (typeof this.domain_names === 'undefined') { + if (typeof this.domain_names === "undefined") { this.domain_names = []; } // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } this.domain_names.sort(); } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); // Sort domain_names - if (typeof this.domain_names !== 'undefined') { + if (typeof this.domain_names !== "undefined") { this.domain_names.sort(); } } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'ProxyHost'; + static get name() { + return "ProxyHost"; } - static get tableName () { - return 'proxy_host'; + static get tableName() { + return "proxy_host"; } - static get jsonAttributes () { - return ['domain_names', 'meta', 'locations']; + static get jsonAttributes() { + return ["domain_names", "meta", "locations"]; } - static get relationMappings () { + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'proxy_host.owner_user_id', - to: 'user.id' + join: { + from: "proxy_host.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, access_list: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: AccessList, - join: { - from: 'proxy_host.access_list_id', - to: 'access_list.id' + join: { + from: "proxy_host.access_list_id", + to: "access_list.id", + }, + modify: (qb) => { + qb.where("access_list.is_deleted", 0); }, - modify: function (qb) { - qb.where('access_list.is_deleted', 0); - } }, certificate: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: Certificate, - join: { - from: 'proxy_host.certificate_id', - to: 'certificate.id' + join: { + from: "proxy_host.certificate_id", + to: "certificate.id", }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("certificate.is_deleted", 0); + }, + }, }; } } -module.exports = ProxyHost; +export default ProxyHost; diff --git a/backend/models/redirection_host.js b/backend/models/redirection_host.js index 80162791..bb397baa 100644 --- a/backend/models/redirection_host.js +++ b/backend/models/redirection_host.js @@ -1,102 +1,101 @@ - // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const User = require('./user'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import Certificate from "./certificate.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); const boolFields = [ - 'is_deleted', - 'enabled', - 'preserve_path', - 'ssl_forced', - 'block_exploits', - 'hsts_enabled', - 'hsts_subdomains', - 'http2_support', + "is_deleted", + "enabled", + "preserve_path", + "ssl_forced", + "block_exploits", + "hsts_enabled", + "hsts_subdomains", + "http2_support", ]; class RedirectionHost extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for domain_names - if (typeof this.domain_names === 'undefined') { + if (typeof this.domain_names === "undefined") { this.domain_names = []; } // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } this.domain_names.sort(); } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); // Sort domain_names - if (typeof this.domain_names !== 'undefined') { + if (typeof this.domain_names !== "undefined") { this.domain_names.sort(); } } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'RedirectionHost'; + static get name() { + return "RedirectionHost"; } - static get tableName () { - return 'redirection_host'; + static get tableName() { + return "redirection_host"; } - static get jsonAttributes () { - return ['domain_names', 'meta']; + static get jsonAttributes() { + return ["domain_names", "meta"]; } - static get relationMappings () { + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'redirection_host.owner_user_id', - to: 'user.id' + join: { + from: "redirection_host.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, certificate: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: Certificate, - join: { - from: 'redirection_host.certificate_id', - to: 'certificate.id' + join: { + from: "redirection_host.certificate_id", + to: "certificate.id", }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("certificate.is_deleted", 0); + }, + }, }; } } -module.exports = RedirectionHost; +export default RedirectionHost; diff --git a/backend/models/setting.js b/backend/models/setting.js index 75aa9007..0e0d6f4f 100644 --- a/backend/models/setting.js +++ b/backend/models/setting.js @@ -1,8 +1,8 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; +import { Model } from "objection"; +import db from "../db.js"; Model.knex(db); @@ -27,4 +27,4 @@ class Setting extends Model { } } -module.exports = Setting; +export default Setting; diff --git a/backend/models/stream.js b/backend/models/stream.js index 5d1cb6c1..92d335ff 100644 --- a/backend/models/stream.js +++ b/backend/models/stream.js @@ -1,82 +1,77 @@ -const Model = require('objection').Model; -const db = require('../db'); -const helpers = require('../lib/helpers'); -const User = require('./user'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import Certificate from "./certificate.js"; +import now from "./now_helper.js"; +import User from "./user.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', - 'enabled', - 'tcp_forwarding', - 'udp_forwarding', -]; +const boolFields = ["is_deleted", "enabled", "tcp_forwarding", "udp_forwarding"]; class Stream extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for meta - if (typeof this.meta === 'undefined') { + if (typeof this.meta === "undefined") { this.meta = {}; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'Stream'; + static get name() { + return "Stream"; } - static get tableName () { - return 'stream'; + static get tableName() { + return "stream"; } - static get jsonAttributes () { - return ['meta']; + static get jsonAttributes() { + return ["meta"]; } - static get relationMappings () { + static get relationMappings() { return { owner: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: User, - join: { - from: 'stream.owner_user_id', - to: 'user.id' + join: { + from: "stream.owner_user_id", + to: "user.id", + }, + modify: (qb) => { + qb.where("user.is_deleted", 0); }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - } }, certificate: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: Certificate, - join: { - from: 'stream.certificate_id', - to: 'certificate.id' + join: { + from: "stream.certificate_id", + to: "certificate.id", }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - } - } + modify: (qb) => { + qb.where("certificate.is_deleted", 0); + }, + }, }; } } -module.exports = Stream; +export default Stream; diff --git a/backend/models/token.js b/backend/models/token.js index 7cf11e03..5edad90d 100644 --- a/backend/models/token.js +++ b/backend/models/token.js @@ -3,16 +3,16 @@ and then has abilities after that. */ -const _ = require('lodash'); -const jwt = require('jsonwebtoken'); -const crypto = require('crypto'); -const config = require('../lib/config'); -const error = require('../lib/error'); -const logger = require('../logger').global; -const ALGO = 'RS256'; +import crypto from "node:crypto"; +import jwt from "jsonwebtoken"; +import _ from "lodash"; +import { getPrivateKey, getPublicKey } from "../lib/config.js"; +import errs from "../lib/error.js"; +import { global as logger } from "../logger.js"; -module.exports = function () { +const ALGO = "RS256"; +export default () => { let token_data = {}; const self = { @@ -21,28 +21,26 @@ module.exports = function () { * @returns {Promise} */ create: (payload) => { - if (!config.getPrivateKey()) { - logger.error('Private key is empty!'); + if (!getPrivateKey()) { + logger.error("Private key is empty!"); } // sign with RSA SHA256 const options = { algorithm: ALGO, - expiresIn: payload.expiresIn || '1d' + expiresIn: payload.expiresIn || "1d", }; - payload.jti = crypto.randomBytes(12) - .toString('base64') - .substring(-8); + payload.jti = crypto.randomBytes(12).toString("base64").substring(-8); return new Promise((resolve, reject) => { - jwt.sign(payload, config.getPrivateKey(), options, (err, token) => { + jwt.sign(payload, getPrivateKey(), options, (err, token) => { if (err) { reject(err); } else { token_data = payload; resolve({ - token: token, - payload: payload + token: token, + payload: payload, }); } }); @@ -53,42 +51,47 @@ module.exports = function () { * @param {String} token * @returns {Promise} */ - load: function (token) { - if (!config.getPublicKey()) { - logger.error('Public key is empty!'); + load: (token) => { + if (!getPublicKey()) { + logger.error("Public key is empty!"); } return new Promise((resolve, reject) => { try { - if (!token || token === null || token === 'null') { - reject(new error.AuthError('Empty token')); + if (!token || token === null || token === "null") { + reject(new errs.AuthError("Empty token")); } else { - jwt.verify(token, config.getPublicKey(), {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => { - if (err) { - - if (err.name === 'TokenExpiredError') { - reject(new error.AuthError('Token has expired', err)); + jwt.verify( + token, + getPublicKey(), + { ignoreExpiration: false, algorithms: [ALGO] }, + (err, result) => { + if (err) { + if (err.name === "TokenExpiredError") { + reject(new errs.AuthError("Token has expired", err)); + } else { + reject(err); + } } else { - reject(err); + token_data = result; + + // Hack: some tokens out in the wild have a scope of 'all' instead of 'user'. + // For 30 days at least, we need to replace 'all' with user. + if ( + typeof token_data.scope !== "undefined" && + _.indexOf(token_data.scope, "all") !== -1 + ) { + token_data.scope = ["user"]; + } + + resolve(token_data); } - - } else { - token_data = result; - - // Hack: some tokens out in the wild have a scope of 'all' instead of 'user'. - // For 30 days at least, we need to replace 'all' with user. - if ((typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'all') !== -1)) { - token_data.scope = ['user']; - } - - resolve(token_data); - } - }); + }, + ); } } catch (err) { reject(err); } }); - }, /** @@ -97,16 +100,14 @@ module.exports = function () { * @param {String} scope * @returns {Boolean} */ - hasScope: function (scope) { - return typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, scope) !== -1; - }, + hasScope: (scope) => typeof token_data.scope !== "undefined" && _.indexOf(token_data.scope, scope) !== -1, /** * @param {String} key * @return {*} */ - get: function (key) { - if (typeof token_data[key] !== 'undefined') { + get: (key) => { + if (typeof token_data[key] !== "undefined") { return token_data[key]; } @@ -117,7 +118,7 @@ module.exports = function () { * @param {String} key * @param {*} value */ - set: function (key, value) { + set: (key, value) => { token_data[key] = value; }, @@ -126,13 +127,13 @@ module.exports = function () { * @returns {Integer} */ getUserId: (default_value) => { - const attrs = self.get('attrs'); - if (attrs && typeof attrs.id !== 'undefined' && attrs.id) { + const attrs = self.get("attrs"); + if (attrs && typeof attrs.id !== "undefined" && attrs.id) { return attrs.id; } return default_value || 0; - } + }, }; return self; diff --git a/backend/models/user.js b/backend/models/user.js index 78fd3dd6..64aed05d 100644 --- a/backend/models/user.js +++ b/backend/models/user.js @@ -1,69 +1,65 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const helpers = require('../lib/helpers'); -const Model = require('objection').Model; -const UserPermission = require('./user_permission'); -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; +import now from "./now_helper.js"; +import UserPermission from "./user_permission.js"; Model.knex(db); -const boolFields = [ - 'is_deleted', - 'is_disabled', -]; +const boolFields = ["is_deleted", "is_disabled"]; class User extends Model { - $beforeInsert () { - this.created_on = now(); + $beforeInsert() { + this.created_on = now(); this.modified_on = now(); // Default for roles - if (typeof this.roles === 'undefined') { + if (typeof this.roles === "undefined") { this.roles = []; } } - $beforeUpdate () { + $beforeUpdate() { this.modified_on = now(); } $parseDatabaseJson(json) { - json = super.$parseDatabaseJson(json); - return helpers.convertIntFieldsToBool(json, boolFields); + const thisJson = super.$parseDatabaseJson(json); + return convertIntFieldsToBool(thisJson, boolFields); } $formatDatabaseJson(json) { - json = helpers.convertBoolFieldsToInt(json, boolFields); - return super.$formatDatabaseJson(json); + const thisJson = convertBoolFieldsToInt(json, boolFields); + return super.$formatDatabaseJson(thisJson); } - static get name () { - return 'User'; + static get name() { + return "User"; } - static get tableName () { - return 'user'; + static get tableName() { + return "user"; } - static get jsonAttributes () { - return ['roles']; + static get jsonAttributes() { + return ["roles"]; } - static get relationMappings () { + static get relationMappings() { return { permissions: { - relation: Model.HasOneRelation, + relation: Model.HasOneRelation, modelClass: UserPermission, - join: { - from: 'user.id', - to: 'user_permission.user_id' - } - } + join: { + from: "user.id", + to: "user_permission.user_id", + }, + }, }; } - } -module.exports = User; +export default User; diff --git a/backend/models/user_permission.js b/backend/models/user_permission.js index bb87d5dc..49ea2d90 100644 --- a/backend/models/user_permission.js +++ b/backend/models/user_permission.js @@ -1,9 +1,9 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; -const now = require('./now_helper'); +import { Model } from "objection"; +import db from "../db.js"; +import now from "./now_helper.js"; Model.knex(db); @@ -26,4 +26,4 @@ class UserPermission extends Model { } } -module.exports = UserPermission; +export default UserPermission; diff --git a/backend/package.json b/backend/package.json index 30984a33..c5948504 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,8 +1,16 @@ { "name": "nginx-proxy-manager", - "version": "0.0.0", + "version": "2.0.0", "description": "A beautiful interface for creating Nginx endpoints", + "author": "Jamie Curnow ", + "license": "MIT", "main": "index.js", + "type": "module", + "scripts": { + "lint": "biome lint", + "prettier": "biome format --write .", + "validate-schema": "node validate-schema.js" + }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.7.0", "ajv": "^8.17.1", @@ -28,21 +36,14 @@ "sqlite3": "5.1.6", "temp-write": "^4.0.0" }, + "devDependencies": { + "@apidevtools/swagger-parser": "^10.1.0", + "@biomejs/biome": "2.2.0", + "chalk": "4.1.2", + "nodemon": "^2.0.2" + }, "signale": { "displayDate": true, "displayTimestamp": true - }, - "author": "Jamie Curnow ", - "license": "MIT", - "devDependencies": { - "@apidevtools/swagger-parser": "^10.1.0", - "chalk": "4.1.2", - "eslint": "^8.36.0", - "eslint-plugin-align-assignments": "^1.1.2", - "nodemon": "^2.0.2", - "prettier": "^2.0.4" - }, - "scripts": { - "validate-schema": "node validate-schema.js" } } diff --git a/backend/routes/audit-log.js b/backend/routes/audit-log.js index c68c7b35..80aabe22 100644 --- a/backend/routes/audit-log.js +++ b/backend/routes/audit-log.js @@ -1,19 +1,19 @@ -const express = require('express'); -const validator = require('../lib/validator'); -const jwtdecode = require('../lib/express/jwt-decode'); -const internalAuditLog = require('../internal/audit-log'); +import express from "express"; +import internalAuditLog from "../internal/audit-log.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; +import validator from "../lib/validator/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/audit-log */ router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -25,28 +25,30 @@ router * Retrieve all logs */ .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ) .then((data) => { return internalAuditLog.getAll(res.locals.access, data.expand, data.query); }) .then((rows) => { - res.status(200) - .send(rows); + res.status(200).send(rows); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/main.js b/backend/routes/main.js index b97096d0..b131fd99 100644 --- a/backend/routes/main.js +++ b/backend/routes/main.js @@ -1,51 +1,63 @@ -const express = require('express'); -const pjson = require('../package.json'); -const error = require('../lib/error'); +import express from "express"; +import errs from "../lib/error.js"; +import pjson from "../package.json" with { type: "json" }; +import auditLogRoutes from "./audit-log.js"; +import accessListsRoutes from "./nginx/access_lists.js"; +import certificatesHostsRoutes from "./nginx/certificates.js"; +import deadHostsRoutes from "./nginx/dead_hosts.js"; +import proxyHostsRoutes from "./nginx/proxy_hosts.js"; +import redirectionHostsRoutes from "./nginx/redirection_hosts.js"; +import streamsRoutes from "./nginx/streams.js"; +import reportsRoutes from "./reports.js"; +import schemaRoutes from "./schema.js"; +import settingsRoutes from "./settings.js"; +import tokensRoutes from "./tokens.js"; +import usersRoutes from "./users.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * Health Check * GET /api */ -router.get('/', (req, res/*, next*/) => { - let version = pjson.version.split('-').shift().split('.'); +router.get("/", (_, res /*, next*/) => { + const version = pjson.version.split("-").shift().split("."); res.status(200).send({ - status: 'OK', + status: "OK", version: { - major: parseInt(version.shift(), 10), - minor: parseInt(version.shift(), 10), - revision: parseInt(version.shift(), 10) - } + major: Number.parseInt(version.shift(), 10), + minor: Number.parseInt(version.shift(), 10), + revision: Number.parseInt(version.shift(), 10), + }, }); }); -router.use('/schema', require('./schema')); -router.use('/tokens', require('./tokens')); -router.use('/users', require('./users')); -router.use('/audit-log', require('./audit-log')); -router.use('/reports', require('./reports')); -router.use('/settings', require('./settings')); -router.use('/nginx/proxy-hosts', require('./nginx/proxy_hosts')); -router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts')); -router.use('/nginx/dead-hosts', require('./nginx/dead_hosts')); -router.use('/nginx/streams', require('./nginx/streams')); -router.use('/nginx/access-lists', require('./nginx/access_lists')); -router.use('/nginx/certificates', require('./nginx/certificates')); +router.use("/schema", schemaRoutes); +router.use("/tokens", tokensRoutes); +router.use("/users", usersRoutes); +router.use("/audit-log", auditLogRoutes); +router.use("/reports", reportsRoutes); +router.use("/settings", settingsRoutes); +router.use("/nginx/proxy-hosts", proxyHostsRoutes); +router.use("/nginx/redirection-hosts", redirectionHostsRoutes); +router.use("/nginx/dead-hosts", deadHostsRoutes); +router.use("/nginx/streams", streamsRoutes); +router.use("/nginx/access-lists", accessListsRoutes); +router.use("/nginx/certificates", certificatesHostsRoutes); /** * API 404 for all other routes * * ALL /api/* */ -router.all(/(.+)/, function (req, _, next) { - req.params.page = req.params['0']; - next(new error.ItemNotFoundError(req.params.page)); +router.all(/(.+)/, (req, _, next) => { + req.params.page = req.params["0"]; + next(new errs.ItemNotFoundError(req.params.page)); }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/access_lists.js b/backend/routes/nginx/access_lists.js index 38375127..6f066e36 100644 --- a/backend/routes/nginx/access_lists.js +++ b/backend/routes/nginx/access_lists.js @@ -1,22 +1,22 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalAccessList = require('../../internal/access-list'); -const schema = require('../../schema'); +import express from "express"; +import internalAccessList from "../../internal/access-list.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/access-lists */ router - .route('/') - .options((req, res) => { + .route("/") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -27,26 +27,28 @@ router * Retrieve all access-lists */ .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ) .then((data) => { return internalAccessList.getAll(res.locals.access, data.expand, data.query); }) .then((rows) => { - res.status(200) - .send(rows); + res.status(200).send(rows); }) .catch(next); }) @@ -57,13 +59,12 @@ router * Create a new access-list */ .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/access-lists', 'post'), req.body) + apiValidator(getValidationSchema("/nginx/access-lists", "post"), req.body) .then((payload) => { return internalAccessList.create(res.locals.access, payload); }) .then((result) => { - res.status(201) - .send(result); + res.status(201).send(result); }) .catch(next); }); @@ -74,7 +75,7 @@ router * /api/nginx/access-lists/123 */ router - .route('/:list_id') + .route("/:list_id") .options((_, res) => { res.sendStatus(204); }) @@ -86,30 +87,32 @@ router * Retrieve a specific access-list */ .get((req, res, next) => { - validator({ - required: ['list_id'], - additionalProperties: false, - properties: { - list_id: { - $ref: 'common#/properties/id' + validator( + { + required: ["list_id"], + additionalProperties: false, + properties: { + list_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - list_id: req.params.list_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) + }, + { + list_id: req.params.list_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ) .then((data) => { return internalAccessList.get(res.locals.access, { - id: parseInt(data.list_id, 10), - expand: data.expand + id: Number.parseInt(data.list_id, 10), + expand: data.expand, }); }) .then((row) => { - res.status(200) - .send(row); + res.status(200).send(row); }) .catch(next); }) @@ -120,14 +123,13 @@ router * Update and existing access-list */ .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/access-lists/{listID}', 'put'), req.body) + apiValidator(getValidationSchema("/nginx/access-lists/{listID}", "put"), req.body) .then((payload) => { - payload.id = parseInt(req.params.list_id, 10); + payload.id = Number.parseInt(req.params.list_id, 10); return internalAccessList.update(res.locals.access, payload); }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }) @@ -138,12 +140,12 @@ router * Delete and existing access-list */ .delete((req, res, next) => { - internalAccessList.delete(res.locals.access, {id: parseInt(req.params.list_id, 10)}) + internalAccessList + .delete(res.locals.access, { id: Number.parseInt(req.params.list_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/certificates.js b/backend/routes/nginx/certificates.js index 4b10d137..7f9d4227 100644 --- a/backend/routes/nginx/certificates.js +++ b/backend/routes/nginx/certificates.js @@ -1,22 +1,22 @@ -const express = require('express'); -const error = require('../../lib/error'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalCertificate = require('../../internal/certificate'); -const schema = require('../../schema'); +import express from "express"; +import internalCertificate from "../../internal/certificate.js"; +import errs from "../../lib/error.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { getValidationSchema } from "../../schema/index.js"; const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/certificates */ router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -28,26 +28,28 @@ router * Retrieve all certificates */ .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ) .then((data) => { return internalCertificate.getAll(res.locals.access, data.expand, data.query); }) .then((rows) => { - res.status(200) - .send(rows); + res.status(200).send(rows); }) .catch(next); }) @@ -58,14 +60,13 @@ router * Create a new certificate */ .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/certificates', 'post'), req.body) + apiValidator(getValidationSchema("/nginx/certificates", "post"), req.body) .then((payload) => { req.setTimeout(900000); // 15 minutes timeout return internalCertificate.create(res.locals.access, payload); }) .then((result) => { - res.status(201) - .send(result); + res.status(201).send(result); }) .catch(next); }); @@ -76,7 +77,7 @@ router * /api/nginx/certificates/test-http */ router - .route('/test-http') + .route("/test-http") .options((_, res) => { res.sendStatus(204); }) @@ -89,14 +90,14 @@ router */ .get((req, res, next) => { if (req.query.domains === undefined) { - next(new error.ValidationError('Domains are required as query parameters')); + next(new errs.ValidationError("Domains are required as query parameters")); return; } - internalCertificate.testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains)) + internalCertificate + .testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains)) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -107,7 +108,7 @@ router * /api/nginx/certificates/123 */ router - .route('/:certificate_id') + .route("/:certificate_id") .options((_, res) => { res.sendStatus(204); }) @@ -119,30 +120,32 @@ router * Retrieve a specific certificate */ .get((req, res, next) => { - validator({ - required: ['certificate_id'], - additionalProperties: false, - properties: { - certificate_id: { - $ref: 'common#/properties/id' + validator( + { + required: ["certificate_id"], + additionalProperties: false, + properties: { + certificate_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - certificate_id: req.params.certificate_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) + }, + { + certificate_id: req.params.certificate_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ) .then((data) => { return internalCertificate.get(res.locals.access, { - id: parseInt(data.certificate_id, 10), - expand: data.expand + id: Number.parseInt(data.certificate_id, 10), + expand: data.expand, }); }) .then((row) => { - res.status(200) - .send(row); + res.status(200).send(row); }) .catch(next); }) @@ -153,10 +156,10 @@ router * Update and existing certificate */ .delete((req, res, next) => { - internalCertificate.delete(res.locals.access, {id: parseInt(req.params.certificate_id, 10)}) + internalCertificate + .delete(res.locals.access, { id: Number.parseInt(req.params.certificate_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -167,7 +170,7 @@ router * /api/nginx/certificates/123/upload */ router - .route('/:certificate_id/upload') + .route("/:certificate_id/upload") .options((_, res) => { res.sendStatus(204); }) @@ -180,16 +183,15 @@ router */ .post((req, res, next) => { if (!req.files) { - res.status(400) - .send({error: 'No files were uploaded'}); + res.status(400).send({ error: "No files were uploaded" }); } else { - internalCertificate.upload(res.locals.access, { - id: parseInt(req.params.certificate_id, 10), - files: req.files - }) + internalCertificate + .upload(res.locals.access, { + id: Number.parseInt(req.params.certificate_id, 10), + files: req.files, + }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); } @@ -201,7 +203,7 @@ router * /api/nginx/certificates/123/renew */ router - .route('/:certificate_id/renew') + .route("/:certificate_id/renew") .options((_, res) => { res.sendStatus(204); }) @@ -214,12 +216,12 @@ router */ .post((req, res, next) => { req.setTimeout(900000); // 15 minutes timeout - internalCertificate.renew(res.locals.access, { - id: parseInt(req.params.certificate_id, 10) - }) + internalCertificate + .renew(res.locals.access, { + id: Number.parseInt(req.params.certificate_id, 10), + }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -230,7 +232,7 @@ router * /api/nginx/certificates/123/download */ router - .route('/:certificate_id/download') + .route("/:certificate_id/download") .options((_req, res) => { res.sendStatus(204); }) @@ -242,12 +244,12 @@ router * Renew certificate */ .get((req, res, next) => { - internalCertificate.download(res.locals.access, { - id: parseInt(req.params.certificate_id, 10) - }) + internalCertificate + .download(res.locals.access, { + id: Number.parseInt(req.params.certificate_id, 10), + }) .then((result) => { - res.status(200) - .download(result.fileName); + res.status(200).download(result.fileName); }) .catch(next); }); @@ -258,7 +260,7 @@ router * /api/nginx/certificates/validate */ router - .route('/validate') + .route("/validate") .options((_, res) => { res.sendStatus(204); }) @@ -271,18 +273,17 @@ router */ .post((req, res, next) => { if (!req.files) { - res.status(400) - .send({error: 'No files were uploaded'}); + res.status(400).send({ error: "No files were uploaded" }); } else { - internalCertificate.validate({ - files: req.files - }) + internalCertificate + .validate({ + files: req.files, + }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); } }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/dead_hosts.js b/backend/routes/nginx/dead_hosts.js index 83b37765..49a6cfed 100644 --- a/backend/routes/nginx/dead_hosts.js +++ b/backend/routes/nginx/dead_hosts.js @@ -1,21 +1,21 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalDeadHost = require('../../internal/dead-host'); -const schema = require('../../schema'); +import express from "express"; +import internalDeadHost from "../../internal/dead-host.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/dead-hosts */ router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -27,26 +27,28 @@ router * Retrieve all dead-hosts */ .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ) .then((data) => { return internalDeadHost.getAll(res.locals.access, data.expand, data.query); }) .then((rows) => { - res.status(200) - .send(rows); + res.status(200).send(rows); }) .catch(next); }) @@ -57,13 +59,12 @@ router * Create a new dead-host */ .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/dead-hosts', 'post'), req.body) + apiValidator(getValidationSchema("/nginx/dead-hosts", "post"), req.body) .then((payload) => { return internalDeadHost.create(res.locals.access, payload); }) .then((result) => { - res.status(201) - .send(result); + res.status(201).send(result); }) .catch(next); }); @@ -74,8 +75,8 @@ router * /api/nginx/dead-hosts/123 */ router - .route('/:host_id') - .options((req, res) => { + .route("/:host_id") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -86,30 +87,32 @@ router * Retrieve a specific dead-host */ .get((req, res, next) => { - validator({ - required: ['host_id'], - additionalProperties: false, - properties: { - host_id: { - $ref: 'common#/properties/id' + validator( + { + required: ["host_id"], + additionalProperties: false, + properties: { + host_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - host_id: req.params.host_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) + }, + { + host_id: req.params.host_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ) .then((data) => { return internalDeadHost.get(res.locals.access, { - id: parseInt(data.host_id, 10), - expand: data.expand + id: Number.parseInt(data.host_id, 10), + expand: data.expand, }); }) .then((row) => { - res.status(200) - .send(row); + res.status(200).send(row); }) .catch(next); }) @@ -120,14 +123,13 @@ router * Update and existing dead-host */ .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/dead-hosts/{hostID}', 'put'), req.body) + apiValidator(getValidationSchema("/nginx/dead-hosts/{hostID}", "put"), req.body) .then((payload) => { - payload.id = parseInt(req.params.host_id, 10); + payload.id = Number.parseInt(req.params.host_id, 10); return internalDeadHost.update(res.locals.access, payload); }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }) @@ -138,10 +140,10 @@ router * Update and existing dead-host */ .delete((req, res, next) => { - internalDeadHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalDeadHost + .delete(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -152,7 +154,7 @@ router * /api/nginx/dead-hosts/123/enable */ router - .route('/:host_id/enable') + .route("/:host_id/enable") .options((_, res) => { res.sendStatus(204); }) @@ -162,10 +164,10 @@ router * POST /api/nginx/dead-hosts/123/enable */ .post((req, res, next) => { - internalDeadHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalDeadHost + .enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -176,7 +178,7 @@ router * /api/nginx/dead-hosts/123/disable */ router - .route('/:host_id/disable') + .route("/:host_id/disable") .options((_, res) => { res.sendStatus(204); }) @@ -186,12 +188,12 @@ router * POST /api/nginx/dead-hosts/123/disable */ .post((req, res, next) => { - internalDeadHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalDeadHost + .disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/proxy_hosts.js b/backend/routes/nginx/proxy_hosts.js index 3be4582a..226938a4 100644 --- a/backend/routes/nginx/proxy_hosts.js +++ b/backend/routes/nginx/proxy_hosts.js @@ -1,22 +1,22 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalProxyHost = require('../../internal/proxy-host'); -const schema = require('../../schema'); +import express from "express"; +import internalProxyHost from "../../internal/proxy-host.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/proxy-hosts */ router - .route('/') - .options((req, res) => { + .route("/") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -27,26 +27,28 @@ router * Retrieve all proxy-hosts */ .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ) .then((data) => { return internalProxyHost.getAll(res.locals.access, data.expand, data.query); }) .then((rows) => { - res.status(200) - .send(rows); + res.status(200).send(rows); }) .catch(next); }) @@ -57,13 +59,12 @@ router * Create a new proxy-host */ .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/proxy-hosts', 'post'), req.body) + apiValidator(getValidationSchema("/nginx/proxy-hosts", "post"), req.body) .then((payload) => { return internalProxyHost.create(res.locals.access, payload); }) .then((result) => { - res.status(201) - .send(result); + res.status(201).send(result); }) .catch(next); }); @@ -74,8 +75,8 @@ router * /api/nginx/proxy-hosts/123 */ router - .route('/:host_id') - .options((req, res) => { + .route("/:host_id") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -86,30 +87,32 @@ router * Retrieve a specific proxy-host */ .get((req, res, next) => { - validator({ - required: ['host_id'], - additionalProperties: false, - properties: { - host_id: { - $ref: 'common#/properties/id' + validator( + { + required: ["host_id"], + additionalProperties: false, + properties: { + host_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - host_id: req.params.host_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) + }, + { + host_id: req.params.host_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ) .then((data) => { return internalProxyHost.get(res.locals.access, { - id: parseInt(data.host_id, 10), - expand: data.expand + id: Number.parseInt(data.host_id, 10), + expand: data.expand, }); }) .then((row) => { - res.status(200) - .send(row); + res.status(200).send(row); }) .catch(next); }) @@ -120,14 +123,13 @@ router * Update and existing proxy-host */ .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/proxy-hosts/{hostID}', 'put'), req.body) + apiValidator(getValidationSchema("/nginx/proxy-hosts/{hostID}", "put"), req.body) .then((payload) => { - payload.id = parseInt(req.params.host_id, 10); + payload.id = Number.parseInt(req.params.host_id, 10); return internalProxyHost.update(res.locals.access, payload); }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }) @@ -138,10 +140,10 @@ router * Update and existing proxy-host */ .delete((req, res, next) => { - internalProxyHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalProxyHost + .delete(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -152,7 +154,7 @@ router * /api/nginx/proxy-hosts/123/enable */ router - .route('/:host_id/enable') + .route("/:host_id/enable") .options((_, res) => { res.sendStatus(204); }) @@ -162,10 +164,10 @@ router * POST /api/nginx/proxy-hosts/123/enable */ .post((req, res, next) => { - internalProxyHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalProxyHost + .enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -176,7 +178,7 @@ router * /api/nginx/proxy-hosts/123/disable */ router - .route('/:host_id/disable') + .route("/:host_id/disable") .options((_, res) => { res.sendStatus(204); }) @@ -186,12 +188,12 @@ router * POST /api/nginx/proxy-hosts/123/disable */ .post((req, res, next) => { - internalProxyHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalProxyHost + .disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/redirection_hosts.js b/backend/routes/nginx/redirection_hosts.js index a46feb84..9be6deb2 100644 --- a/backend/routes/nginx/redirection_hosts.js +++ b/backend/routes/nginx/redirection_hosts.js @@ -1,22 +1,22 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalRedirectionHost = require('../../internal/redirection-host'); -const schema = require('../../schema'); +import express from "express"; +import internalRedirectionHost from "../../internal/redirection-host.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/redirection-hosts */ router - .route('/') - .options((req, res) => { + .route("/") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -27,26 +27,28 @@ router * Retrieve all redirection-hosts */ .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ) .then((data) => { return internalRedirectionHost.getAll(res.locals.access, data.expand, data.query); }) .then((rows) => { - res.status(200) - .send(rows); + res.status(200).send(rows); }) .catch(next); }) @@ -57,13 +59,12 @@ router * Create a new redirection-host */ .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/redirection-hosts', 'post'), req.body) + apiValidator(getValidationSchema("/nginx/redirection-hosts", "post"), req.body) .then((payload) => { return internalRedirectionHost.create(res.locals.access, payload); }) .then((result) => { - res.status(201) - .send(result); + res.status(201).send(result); }) .catch(next); }); @@ -74,8 +75,8 @@ router * /api/nginx/redirection-hosts/123 */ router - .route('/:host_id') - .options((req, res) => { + .route("/:host_id") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -86,30 +87,32 @@ router * Retrieve a specific redirection-host */ .get((req, res, next) => { - validator({ - required: ['host_id'], - additionalProperties: false, - properties: { - host_id: { - $ref: 'common#/properties/id' + validator( + { + required: ["host_id"], + additionalProperties: false, + properties: { + host_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - host_id: req.params.host_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) + }, + { + host_id: req.params.host_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ) .then((data) => { return internalRedirectionHost.get(res.locals.access, { - id: parseInt(data.host_id, 10), - expand: data.expand + id: Number.parseInt(data.host_id, 10), + expand: data.expand, }); }) .then((row) => { - res.status(200) - .send(row); + res.status(200).send(row); }) .catch(next); }) @@ -120,14 +123,13 @@ router * Update and existing redirection-host */ .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/redirection-hosts/{hostID}', 'put'), req.body) + apiValidator(getValidationSchema("/nginx/redirection-hosts/{hostID}", "put"), req.body) .then((payload) => { - payload.id = parseInt(req.params.host_id, 10); + payload.id = Number.parseInt(req.params.host_id, 10); return internalRedirectionHost.update(res.locals.access, payload); }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }) @@ -138,10 +140,10 @@ router * Update and existing redirection-host */ .delete((req, res, next) => { - internalRedirectionHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalRedirectionHost + .delete(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -152,8 +154,8 @@ router * /api/nginx/redirection-hosts/123/enable */ router - .route('/:host_id/enable') - .options((req, res) => { + .route("/:host_id/enable") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -162,10 +164,10 @@ router * POST /api/nginx/redirection-hosts/123/enable */ .post((req, res, next) => { - internalRedirectionHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalRedirectionHost + .enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -176,8 +178,8 @@ router * /api/nginx/redirection-hosts/123/disable */ router - .route('/:host_id/disable') - .options((req, res) => { + .route("/:host_id/disable") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -186,12 +188,12 @@ router * POST /api/nginx/redirection-hosts/123/disable */ .post((req, res, next) => { - internalRedirectionHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalRedirectionHost + .disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/nginx/streams.js b/backend/routes/nginx/streams.js index c033f2ef..45dd6d93 100644 --- a/backend/routes/nginx/streams.js +++ b/backend/routes/nginx/streams.js @@ -1,22 +1,22 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const apiValidator = require('../../lib/validator/api'); -const internalStream = require('../../internal/stream'); -const schema = require('../../schema'); +import express from "express"; +import internalStream from "../../internal/stream.js"; +import jwtdecode from "../../lib/express/jwt-decode.js"; +import apiValidator from "../../lib/validator/api.js"; +import validator from "../../lib/validator/index.js"; +import { getValidationSchema } from "../../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/nginx/streams */ router - .route('/') - .options((req, res) => { + .route("/") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes @@ -27,26 +27,28 @@ router * Retrieve all streams */ .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ) .then((data) => { return internalStream.getAll(res.locals.access, data.expand, data.query); }) .then((rows) => { - res.status(200) - .send(rows); + res.status(200).send(rows); }) .catch(next); }) @@ -57,13 +59,12 @@ router * Create a new stream */ .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/streams', 'post'), req.body) + apiValidator(getValidationSchema("/nginx/streams", "post"), req.body) .then((payload) => { return internalStream.create(res.locals.access, payload); }) .then((result) => { - res.status(201) - .send(result); + res.status(201).send(result); }) .catch(next); }); @@ -74,8 +75,8 @@ router * /api/nginx/streams/123 */ router - .route('/:stream_id') - .options((req, res) => { + .route("/:stream_id") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes @@ -86,30 +87,32 @@ router * Retrieve a specific stream */ .get((req, res, next) => { - validator({ - required: ['stream_id'], - additionalProperties: false, - properties: { - stream_id: { - $ref: 'common#/properties/id' + validator( + { + required: ["stream_id"], + additionalProperties: false, + properties: { + stream_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - stream_id: req.params.stream_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) + }, + { + stream_id: req.params.stream_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ) .then((data) => { return internalStream.get(res.locals.access, { - id: parseInt(data.stream_id, 10), - expand: data.expand + id: Number.parseInt(data.stream_id, 10), + expand: data.expand, }); }) .then((row) => { - res.status(200) - .send(row); + res.status(200).send(row); }) .catch(next); }) @@ -120,14 +123,13 @@ router * Update and existing stream */ .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/nginx/streams/{streamID}', 'put'), req.body) + apiValidator(getValidationSchema("/nginx/streams/{streamID}", "put"), req.body) .then((payload) => { - payload.id = parseInt(req.params.stream_id, 10); + payload.id = Number.parseInt(req.params.stream_id, 10); return internalStream.update(res.locals.access, payload); }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }) @@ -138,10 +140,10 @@ router * Update and existing stream */ .delete((req, res, next) => { - internalStream.delete(res.locals.access, {id: parseInt(req.params.stream_id, 10)}) + internalStream + .delete(res.locals.access, { id: Number.parseInt(req.params.stream_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -152,7 +154,7 @@ router * /api/nginx/streams/123/enable */ router - .route('/:host_id/enable') + .route("/:host_id/enable") .options((_, res) => { res.sendStatus(204); }) @@ -162,10 +164,10 @@ router * POST /api/nginx/streams/123/enable */ .post((req, res, next) => { - internalStream.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalStream + .enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -176,7 +178,7 @@ router * /api/nginx/streams/123/disable */ router - .route('/:host_id/disable') + .route("/:host_id/disable") .options((_, res) => { res.sendStatus(204); }) @@ -186,12 +188,12 @@ router * POST /api/nginx/streams/123/disable */ .post((req, res, next) => { - internalStream.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + internalStream + .disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/reports.js b/backend/routes/reports.js index 98c6cf86..3c83075f 100644 --- a/backend/routes/reports.js +++ b/backend/routes/reports.js @@ -1,15 +1,15 @@ -const express = require('express'); -const jwtdecode = require('../lib/express/jwt-decode'); -const internalReport = require('../internal/report'); +import express from "express"; +import internalReport from "../internal/report.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); router - .route('/hosts') + .route("/hosts") .options((_, res) => { res.sendStatus(204); }) @@ -18,12 +18,12 @@ router * GET /reports/hosts */ .get(jwtdecode(), (_, res, next) => { - internalReport.getHostsReport(res.locals.access) + internalReport + .getHostsReport(res.locals.access) .then((data) => { - res.status(200) - .send(data); + res.status(200).send(data); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/schema.js b/backend/routes/schema.js index fc3e48b6..d0561cfc 100644 --- a/backend/routes/schema.js +++ b/backend/routes/schema.js @@ -1,15 +1,15 @@ -const express = require('express'); -const schema = require('../schema'); -const PACKAGE = require('../package.json'); +import express from "express"; +import PACKAGE from "../package.json" with { type: "json" }; +import { getCompiledSchema } from "../schema/index.js"; const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -18,21 +18,21 @@ router * GET /schema */ .get(async (req, res) => { - let swaggerJSON = await schema.getCompiledSchema(); + const swaggerJSON = await getCompiledSchema(); let proto = req.protocol; - if (typeof req.headers['x-forwarded-proto'] !== 'undefined' && req.headers['x-forwarded-proto']) { - proto = req.headers['x-forwarded-proto']; + if (typeof req.headers["x-forwarded-proto"] !== "undefined" && req.headers["x-forwarded-proto"]) { + proto = req.headers["x-forwarded-proto"]; } - let origin = proto + '://' + req.hostname; - if (typeof req.headers.origin !== 'undefined' && req.headers.origin) { + let origin = `${proto}://${req.hostname}`; + if (typeof req.headers.origin !== "undefined" && req.headers.origin) { origin = req.headers.origin; } - swaggerJSON.info.version = PACKAGE.version; - swaggerJSON.servers[0].url = origin + '/api'; + swaggerJSON.info.version = PACKAGE.version; + swaggerJSON.servers[0].url = `${origin}/api`; res.status(200).send(swaggerJSON); }); -module.exports = router; +export default router; diff --git a/backend/routes/settings.js b/backend/routes/settings.js index dac4c3d1..1d8cc949 100644 --- a/backend/routes/settings.js +++ b/backend/routes/settings.js @@ -1,21 +1,21 @@ -const express = require('express'); -const validator = require('../lib/validator'); -const jwtdecode = require('../lib/express/jwt-decode'); -const apiValidator = require('../lib/validator/api'); -const internalSetting = require('../internal/setting'); -const schema = require('../schema'); +import express from "express"; +import internalSetting from "../internal/setting.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; +import apiValidator from "../lib/validator/api.js"; +import validator from "../lib/validator/index.js"; +import { getValidationSchema } from "../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/settings */ router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -27,10 +27,10 @@ router * Retrieve all settings */ .get((_, res, next) => { - internalSetting.getAll(res.locals.access) + internalSetting + .getAll(res.locals.access) .then((rows) => { - res.status(200) - .send(rows); + res.status(200).send(rows); }) .catch(next); }); @@ -41,7 +41,7 @@ router * /api/settings/something */ router - .route('/:setting_id') + .route("/:setting_id") .options((_, res) => { res.sendStatus(204); }) @@ -53,26 +53,28 @@ router * Retrieve a specific setting */ .get((req, res, next) => { - validator({ - required: ['setting_id'], - additionalProperties: false, - properties: { - setting_id: { - type: 'string', - minLength: 1 - } - } - }, { - setting_id: req.params.setting_id - }) + validator( + { + required: ["setting_id"], + additionalProperties: false, + properties: { + setting_id: { + type: "string", + minLength: 1, + }, + }, + }, + { + setting_id: req.params.setting_id, + }, + ) .then((data) => { return internalSetting.get(res.locals.access, { - id: data.setting_id + id: data.setting_id, }); }) .then((row) => { - res.status(200) - .send(row); + res.status(200).send(row); }) .catch(next); }) @@ -83,16 +85,15 @@ router * Update and existing setting */ .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/settings/{settingID}', 'put'), req.body) + apiValidator(getValidationSchema("/settings/{settingID}", "put"), req.body) .then((payload) => { payload.id = req.params.setting_id; return internalSetting.update(res.locals.access, payload); }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/tokens.js b/backend/routes/tokens.js index 72d01d41..fd563719 100644 --- a/backend/routes/tokens.js +++ b/backend/routes/tokens.js @@ -1,17 +1,17 @@ -const express = require('express'); -const jwtdecode = require('../lib/express/jwt-decode'); -const apiValidator = require('../lib/validator/api'); -const internalToken = require('../internal/token'); -const schema = require('../schema'); +import express from "express"; +import internalToken from "../internal/token.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; +import apiValidator from "../lib/validator/api.js"; +import { getValidationSchema } from "../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -24,13 +24,13 @@ router * for services like Job board and Worker. */ .get(jwtdecode(), (req, res, next) => { - internalToken.getFreshToken(res.locals.access, { - expiry: (typeof req.query.expiry !== 'undefined' ? req.query.expiry : null), - scope: (typeof req.query.scope !== 'undefined' ? req.query.scope : null) - }) + internalToken + .getFreshToken(res.locals.access, { + expiry: typeof req.query.expiry !== "undefined" ? req.query.expiry : null, + scope: typeof req.query.scope !== "undefined" ? req.query.scope : null, + }) .then((data) => { - res.status(200) - .send(data); + res.status(200).send(data); }) .catch(next); }) @@ -41,13 +41,12 @@ router * Create a new Token */ .post(async (req, res, next) => { - apiValidator(schema.getValidationSchema('/tokens', 'post'), req.body) + apiValidator(getValidationSchema("/tokens", "post"), req.body) .then(internalToken.getTokenFromEmail) .then((data) => { - res.status(200) - .send(data); + res.status(200).send(data); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/users.js b/backend/routes/users.js index e41bf6cf..9263bb66 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -1,22 +1,22 @@ -const express = require('express'); -const validator = require('../lib/validator'); -const jwtdecode = require('../lib/express/jwt-decode'); -const userIdFromMe = require('../lib/express/user-id-from-me'); -const internalUser = require('../internal/user'); -const apiValidator = require('../lib/validator/api'); -const schema = require('../schema'); +import express from "express"; +import internalUser from "../internal/user.js"; +import jwtdecode from "../lib/express/jwt-decode.js"; +import userIdFromMe from "../lib/express/user-id-from-me.js"; +import apiValidator from "../lib/validator/api.js"; +import validator from "../lib/validator/index.js"; +import { getValidationSchema } from "../schema/index.js"; -let router = express.Router({ +const router = express.Router({ caseSensitive: true, - strict: true, - mergeParams: true + strict: true, + mergeParams: true, }); /** * /api/users */ router - .route('/') + .route("/") .options((_, res) => { res.sendStatus(204); }) @@ -28,26 +28,28 @@ router * Retrieve all users */ .get((req, res, next) => { - validator({ - additionalProperties: false, - properties: { - expand: { - $ref: 'common#/properties/expand' + validator( + { + additionalProperties: false, + properties: { + expand: { + $ref: "common#/properties/expand", + }, + query: { + $ref: "common#/properties/query", + }, }, - query: { - $ref: 'common#/properties/query' - } - } - }, { - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), - query: (typeof req.query.query === 'string' ? req.query.query : null) - }) + }, + { + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + query: typeof req.query.query === "string" ? req.query.query : null, + }, + ) .then((data) => { return internalUser.getAll(res.locals.access, data.expand, data.query); }) .then((users) => { - res.status(200) - .send(users); + res.status(200).send(users); }) .catch((err) => { console.log(err); @@ -62,13 +64,12 @@ router * Create a new User */ .post((req, res, next) => { - apiValidator(schema.getValidationSchema('/users', 'post'), req.body) + apiValidator(getValidationSchema("/users", "post"), req.body) .then((payload) => { return internalUser.create(res.locals.access, payload); }) .then((result) => { - res.status(201) - .send(result); + res.status(201).send(result); }) .catch(next); }); @@ -79,7 +80,7 @@ router * /api/users/123 */ router - .route('/:user_id') + .route("/:user_id") .options((_, res) => { res.sendStatus(204); }) @@ -92,31 +93,33 @@ router * Retrieve a specific user */ .get((req, res, next) => { - validator({ - required: ['user_id'], - additionalProperties: false, - properties: { - user_id: { - $ref: 'common#/properties/id' + validator( + { + required: ["user_id"], + additionalProperties: false, + properties: { + user_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, }, - expand: { - $ref: 'common#/properties/expand' - } - } - }, { - user_id: req.params.user_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) - }) + }, + { + user_id: req.params.user_id, + expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, + }, + ) .then((data) => { return internalUser.get(res.locals.access, { - id: data.user_id, + id: data.user_id, expand: data.expand, - omit: internalUser.getUserOmisionsByAccess(res.locals.access, data.user_id) + omit: internalUser.getUserOmisionsByAccess(res.locals.access, data.user_id), }); }) .then((user) => { - res.status(200) - .send(user); + res.status(200).send(user); }) .catch((err) => { console.log(err); @@ -130,14 +133,13 @@ router * Update and existing user */ .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/users/{userID}', 'put'), req.body) + apiValidator(getValidationSchema("/users/{userID}", "put"), req.body) .then((payload) => { payload.id = req.params.user_id; return internalUser.update(res.locals.access, payload); }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }) @@ -148,10 +150,10 @@ router * Update and existing user */ .delete((req, res, next) => { - internalUser.delete(res.locals.access, {id: req.params.user_id}) + internalUser + .delete(res.locals.access, { id: req.params.user_id }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -162,8 +164,8 @@ router * /api/users/123/auth */ router - .route('/:user_id/auth') - .options((req, res) => { + .route("/:user_id/auth") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -175,14 +177,13 @@ router * Update password for a user */ .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/users/{userID}/auth', 'put'), req.body) + apiValidator(getValidationSchema("/users/{userID}/auth", "put"), req.body) .then((payload) => { payload.id = req.params.user_id; return internalUser.setPassword(res.locals.access, payload); }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -193,8 +194,8 @@ router * /api/users/123/permissions */ router - .route('/:user_id/permissions') - .options((req, res) => { + .route("/:user_id/permissions") + .options((_, res) => { res.sendStatus(204); }) .all(jwtdecode()) @@ -206,14 +207,13 @@ router * Set some or all permissions for a user */ .put((req, res, next) => { - apiValidator(schema.getValidationSchema('/users/{userID}/permissions', 'put'), req.body) + apiValidator(getValidationSchema("/users/{userID}/permissions", "put"), req.body) .then((payload) => { payload.id = req.params.user_id; return internalUser.setPermissions(res.locals.access, payload); }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); @@ -224,7 +224,7 @@ router * /api/users/123/login */ router - .route('/:user_id/login') + .route("/:user_id/login") .options((_, res) => { res.sendStatus(204); }) @@ -236,12 +236,12 @@ router * Log in as a user */ .post((req, res, next) => { - internalUser.loginAs(res.locals.access, {id: parseInt(req.params.user_id, 10)}) + internalUser + .loginAs(res.locals.access, { id: Number.parseInt(req.params.user_id, 10) }) .then((result) => { - res.status(200) - .send(result); + res.status(200).send(result); }) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/schema/index.js b/backend/schema/index.js index 87b75f25..0478486b 100644 --- a/backend/schema/index.js +++ b/backend/schema/index.js @@ -1,41 +1,46 @@ -const refParser = require('@apidevtools/json-schema-ref-parser'); +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import $RefParser from "@apidevtools/json-schema-ref-parser"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); let compiledSchema = null; -module.exports = { - - /** - * Compiles the schema, by dereferencing it, only once - * and returns the memory cached value - */ - getCompiledSchema: async () => { - if (compiledSchema === null) { - compiledSchema = await refParser.dereference(__dirname + '/swagger.json', { - mutateInputSchema: false, - }); - } - return compiledSchema; - }, - - /** - * Scans the schema for the validation schema for the given path and method - * and returns it. - * - * @param {string} path - * @param {string} method - * @returns string|null - */ - getValidationSchema: (path, method) => { - if (compiledSchema !== null && - typeof compiledSchema.paths[path] !== 'undefined' && - typeof compiledSchema.paths[path][method] !== 'undefined' && - typeof compiledSchema.paths[path][method].requestBody !== 'undefined' && - typeof compiledSchema.paths[path][method].requestBody.content !== 'undefined' && - typeof compiledSchema.paths[path][method].requestBody.content['application/json'] !== 'undefined' && - typeof compiledSchema.paths[path][method].requestBody.content['application/json'].schema !== 'undefined' - ) { - return compiledSchema.paths[path][method].requestBody.content['application/json'].schema; - } - return null; +/** + * Compiles the schema, by dereferencing it, only once + * and returns the memory cached value + */ +const getCompiledSchema = async () => { + if (compiledSchema === null) { + compiledSchema = await $RefParser.dereference(`${__dirname}/swagger.json`, { + mutateInputSchema: false, + }); } + return compiledSchema; }; + +/** + * Scans the schema for the validation schema for the given path and method + * and returns it. + * + * @param {string} path + * @param {string} method + * @returns string|null + */ +const getValidationSchema = (path, method) => { + if ( + compiledSchema !== null && + typeof compiledSchema.paths[path] !== "undefined" && + typeof compiledSchema.paths[path][method] !== "undefined" && + typeof compiledSchema.paths[path][method].requestBody !== "undefined" && + typeof compiledSchema.paths[path][method].requestBody.content !== "undefined" && + typeof compiledSchema.paths[path][method].requestBody.content["application/json"] !== "undefined" && + typeof compiledSchema.paths[path][method].requestBody.content["application/json"].schema !== "undefined" + ) { + return compiledSchema.paths[path][method].requestBody.content["application/json"].schema; + } + return null; +}; + +export { getCompiledSchema, getValidationSchema }; diff --git a/backend/scripts/install-certbot-plugins b/backend/scripts/install-certbot-plugins index bf995410..2c111b8a 100755 --- a/backend/scripts/install-certbot-plugins +++ b/backend/scripts/install-certbot-plugins @@ -10,10 +10,10 @@ // docker exec npm_core /command/s6-setuidgid 1000:1000 bash -c "/app/scripts/install-certbot-plugins" // -const dnsPlugins = require('../global/certbot-dns-plugins.json'); -const certbot = require('../lib/certbot'); -const logger = require('../logger').certbot; -const batchflow = require('batchflow'); +import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; +import { installPlugin } from "../lib/certbot.js"; +import { certbot as logger } from "../logger.js"; +import batchflow from "batchflow"; let hasErrors = false; let failingPlugins = []; @@ -25,7 +25,7 @@ if (process.argv.length > 2) { batchflow(pluginKeys).sequential() .each((i, pluginKey, next) => { - certbot.installPlugin(pluginKey) + installPlugin(pluginKey) .then(() => { next(); }) diff --git a/backend/setup.js b/backend/setup.js index 29208a0d..4307c6fc 100644 --- a/backend/setup.js +++ b/backend/setup.js @@ -1,12 +1,12 @@ -const config = require('./lib/config'); -const logger = require('./logger').setup; -const certificateModel = require('./models/certificate'); -const userModel = require('./models/user'); -const userPermissionModel = require('./models/user_permission'); -const utils = require('./lib/utils'); -const authModel = require('./models/auth'); -const settingModel = require('./models/setting'); -const certbot = require('./lib/certbot'); +import { installPlugins } from "./lib/certbot.js"; +import utils from "./lib/utils.js"; +import { setup as logger } from "./logger.js"; +import authModel from "./models/auth.js"; +import certificateModel from "./models/certificate.js"; +import settingModel from "./models/setting.js"; +import userModel from "./models/user.js"; +import userPermissionModel from "./models/user_permission.js"; + /** * Creates a default admin users if one doesn't already exist in the database * @@ -15,24 +15,24 @@ const certbot = require('./lib/certbot'); const setupDefaultUser = () => { return userModel .query() - .select('id', ) - .where('is_deleted', 0) + .select("id") + .where("is_deleted", 0) .first() .then((row) => { if (!row || !row.id) { // Create a new user and set password const email = (process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com').toLowerCase(); - const password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme'; + const password = process.env.INITIAL_ADMIN_PASSWORD || "changeme"; logger.info(`Creating a new user: ${email} with password: ${password}`); const data = { is_deleted: 0, - email: email, - name: 'Administrator', - nickname: 'Admin', - avatar: '', - roles: ['admin'], + email: email, + name: "Administrator", + nickname: "Admin", + avatar: "", + roles: ["admin"], }; return userModel @@ -43,29 +43,28 @@ const setupDefaultUser = () => { .query() .insert({ user_id: user.id, - type: 'password', - secret: password, - meta: {}, + type: "password", + secret: password, + meta: {}, }) .then(() => { return userPermissionModel.query().insert({ - user_id: user.id, - visibility: 'all', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - dead_hosts: 'manage', - streams: 'manage', - access_lists: 'manage', - certificates: 'manage', + user_id: user.id, + visibility: "all", + proxy_hosts: "manage", + redirection_hosts: "manage", + dead_hosts: "manage", + streams: "manage", + access_lists: "manage", + certificates: "manage", }); }); }) .then(() => { - logger.info('Initial admin setup completed'); + logger.info("Initial admin setup completed"); }); - } else if (config.debug()) { - logger.info('Admin user setup not required'); } + logger.debug("Admin user setup not required"); }); }; @@ -77,27 +76,25 @@ const setupDefaultUser = () => { const setupDefaultSettings = () => { return settingModel .query() - .select('id') - .where({id: 'default-site'}) + .select("id") + .where({ id: "default-site" }) .first() .then((row) => { if (!row || !row.id) { settingModel .query() .insert({ - id: 'default-site', - name: 'Default Site', - description: 'What to show when Nginx is hit with an unknown Host', - value: 'congratulations', - meta: {}, + id: "default-site", + name: "Default Site", + description: "What to show when Nginx is hit with an unknown Host", + value: "congratulations", + meta: {}, }) .then(() => { - logger.info('Default settings added'); + logger.info("Default settings added"); }); } - if (config.debug()) { - logger.info('Default setting setup not required'); - } + logger.debug("Default setting setup not required"); }); }; @@ -109,11 +106,11 @@ const setupDefaultSettings = () => { const setupCertbotPlugins = () => { return certificateModel .query() - .where('is_deleted', 0) - .andWhere('provider', 'letsencrypt') + .where("is_deleted", 0) + .andWhere("provider", "letsencrypt") .then((certificates) => { - if (certificates && certificates.length) { - const plugins = []; + if (certificates?.length) { + const plugins = []; const promises = []; certificates.map((certificate) => { @@ -125,26 +122,26 @@ const setupCertbotPlugins = () => { // Make sure credentials file exists const credentials_loc = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; // Escape single quotes and backslashes - const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\'); - const credentials_cmd = `[ -f '${credentials_loc}' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo '${escapedCredentials}' > '${credentials_loc}' && chmod 600 '${credentials_loc}'; }`; + const escapedCredentials = certificate.meta.dns_provider_credentials + .replaceAll("'", "\\'") + .replaceAll("\\", "\\\\"); + const credentials_cmd = `[ -f '${credentials_loc}' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo '${escapedCredentials}' > '${credentials_loc}' && chmod 600 '${credentials_loc}'; }`; promises.push(utils.exec(credentials_cmd)); } + return true; }); - return certbot.installPlugins(plugins) - .then(() => { - if (promises.length) { - return Promise.all(promises) - .then(() => { - logger.info(`Added Certbot plugins ${plugins.join(', ')}`); - }); - } - }); + return installPlugins(plugins).then(() => { + if (promises.length) { + return Promise.all(promises).then(() => { + logger.info(`Added Certbot plugins ${plugins.join(", ")}`); + }); + } + }); } }); }; - /** * Starts a timer to call run the logrotation binary every two days * @returns {Promise} @@ -154,18 +151,17 @@ const setupLogrotation = () => { const runLogrotate = async () => { try { - await utils.exec('logrotate /etc/logrotate.d/nginx-proxy-manager'); - logger.info('Logrotate completed.'); - } catch (e) { logger.warn(e); } + await utils.exec("logrotate /etc/logrotate.d/nginx-proxy-manager"); + logger.info("Logrotate completed."); + } catch (e) { + logger.warn(e); + } }; - logger.info('Logrotate Timer initialized'); + logger.info("Logrotate Timer initialized"); setInterval(runLogrotate, intervalTimeout); // And do this now as well return runLogrotate(); }; -module.exports = () => setupDefaultUser() - .then(setupDefaultSettings) - .then(setupCertbotPlugins) - .then(setupLogrotation); +export default () => setupDefaultUser().then(setupDefaultSettings).then(setupCertbotPlugins).then(setupLogrotation); diff --git a/backend/validate-schema.js b/backend/validate-schema.js old mode 100644 new mode 100755 index 71a05c81..b1870665 --- a/backend/validate-schema.js +++ b/backend/validate-schema.js @@ -1,16 +1,19 @@ -const SwaggerParser = require('@apidevtools/swagger-parser'); -const chalk = require('chalk'); -const schema = require('./schema'); -const log = console.log; +#!/usr/bin/node -schema.getCompiledSchema().then(async (swaggerJSON) => { +import SwaggerParser from "@apidevtools/swagger-parser"; +import chalk from "chalk"; +import { getCompiledSchema } from "./schema/index.js"; + +const log = console.log; + +getCompiledSchema().then(async (swaggerJSON) => { try { const api = await SwaggerParser.validate(swaggerJSON); - console.log('API name: %s, Version: %s', api.info.title, api.info.version); - log(chalk.green('❯ Schema is valid')); + console.log("API name: %s, Version: %s", api.info.title, api.info.version); + log(chalk.green("❯ Schema is valid")); } catch (e) { console.error(e); - log(chalk.red('❯', e.message), '\n'); + log(chalk.red("❯", e.message), "\n"); process.exit(1); } }); diff --git a/backend/yarn.lock b/backend/yarn.lock index bae734b4..bf8091ec 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -43,62 +43,65 @@ ajv-draft-04 "^1.0.0" call-me-maybe "^1.0.1" -"@eslint-community/eslint-utils@^4.2.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz#a556790523a351b4e47e9d385f47265eaaf9780a" - integrity sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA== - dependencies: - eslint-visitor-keys "^3.3.0" +"@biomejs/biome@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.2.0.tgz#823ba77363651f310c47909747c879791ebd15c9" + integrity sha512-3On3RSYLsX+n9KnoSgfoYlckYBoU6VRM22cw1gB4Y0OuUVSYd/O/2saOJMrA4HFfA1Ff0eacOvMN1yAAvHtzIw== + optionalDependencies: + "@biomejs/cli-darwin-arm64" "2.2.0" + "@biomejs/cli-darwin-x64" "2.2.0" + "@biomejs/cli-linux-arm64" "2.2.0" + "@biomejs/cli-linux-arm64-musl" "2.2.0" + "@biomejs/cli-linux-x64" "2.2.0" + "@biomejs/cli-linux-x64-musl" "2.2.0" + "@biomejs/cli-win32-arm64" "2.2.0" + "@biomejs/cli-win32-x64" "2.2.0" -"@eslint-community/regexpp@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403" - integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ== +"@biomejs/cli-darwin-arm64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.0.tgz#1abf9508e7d0776871710687ddad36e692dce3bc" + integrity sha512-zKbwUUh+9uFmWfS8IFxmVD6XwqFcENjZvEyfOxHs1epjdH3wyyMQG80FGDsmauPwS2r5kXdEM0v/+dTIA9FXAg== -"@eslint/eslintrc@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.1.tgz#7888fe7ec8f21bc26d646dbd2c11cd776e21192d" - integrity sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.5.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" +"@biomejs/cli-darwin-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.0.tgz#3a51aa569505fedd3a32bb914d608ec27d87f26d" + integrity sha512-+OmT4dsX2eTfhD5crUOPw3RPhaR+SKVspvGVmSdZ9y9O/AgL8pla6T4hOn1q+VAFBHuHhsdxDRJgFCSC7RaMOw== -"@eslint/js@8.36.0": - version "8.36.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.36.0.tgz#9837f768c03a1e4a30bd304a64fb8844f0e72efe" - integrity sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg== +"@biomejs/cli-linux-arm64-musl@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.0.tgz#4d720930732a825b7a8c7cfe1741aec9e7d5ae1d" + integrity sha512-egKpOa+4FL9YO+SMUMLUvf543cprjevNc3CAgDNFLcjknuNMcZ0GLJYa3EGTCR2xIkIUJDVneBV3O9OcIlCEZQ== + +"@biomejs/cli-linux-arm64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.0.tgz#d0a5c153ff9243b15600781947d70d6038226feb" + integrity sha512-6eoRdF2yW5FnW9Lpeivh7Mayhq0KDdaDMYOJnH9aT02KuSIX5V1HmWJCQQPwIQbhDh68Zrcpl8inRlTEan0SXw== + +"@biomejs/cli-linux-x64-musl@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.0.tgz#946095b0a444f395b2df9244153e1cd6b07404c0" + integrity sha512-I5J85yWwUWpgJyC1CcytNSGusu2p9HjDnOPAFG4Y515hwRD0jpR9sT9/T1cKHtuCvEQ/sBvx+6zhz9l9wEJGAg== + +"@biomejs/cli-linux-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.0.tgz#ae01e0a70c7cd9f842c77dfb4ebd425734667a34" + integrity sha512-5UmQx/OZAfJfi25zAnAGHUMuOd+LOsliIt119x2soA2gLggQYrVPA+2kMUxR6Mw5M1deUF/AWWP2qpxgH7Nyfw== + +"@biomejs/cli-win32-arm64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.0.tgz#09a3988b9d4bab8b8b3a41b4de9560bf70943964" + integrity sha512-n9a1/f2CwIDmNMNkFs+JI0ZjFnMO0jdOyGNtihgUNFnlmd84yIYY2KMTBmMV58ZlVHjgmY5Y6E1hVTnSRieggA== + +"@biomejs/cli-win32-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.0.tgz#5d2523b421d847b13fac146cf745436ea8a72b95" + integrity sha512-Nawu5nHjP/zPKTIryh2AavzTc/KEg4um/MxWdXW0A6P/RZOyIpa7+QSjeXwAwX/utJGaCoXRPWtF3m5U/bB3Ww== "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@humanwhocodes/config-array@^0.11.8": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" - integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== - dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.5" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - "@jsdevtools/ono@^7.1.3": version "7.1.3" resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" @@ -119,27 +122,6 @@ semver "^7.3.5" tar "^6.1.11" -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - "@npmcli/fs@^1.0.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" @@ -196,16 +178,6 @@ accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^8.8.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== - agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -235,16 +207,6 @@ ajv-draft-04@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - ajv@^8.17.1, ajv@^8.6.3: version "8.17.1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" @@ -594,17 +556,12 @@ call-me-maybe@^1.0.1: resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -chalk@4.1.2, chalk@^4.0.0: +chalk@4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -829,15 +786,6 @@ crc32-stream@^4.0.2: crc-32 "^1.2.0" readable-stream "^3.4.0" -cross-spawn@^7.0.2: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -855,7 +803,7 @@ debug@2.6.9, debug@^2.2.0: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3: +debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.3.3: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -886,11 +834,6 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - defer-to-connect@^1.0.1: version "1.1.3" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" @@ -942,13 +885,6 @@ dicer@0.3.0: dependencies: streamsearch "0.1.2" -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - dot-prop@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" @@ -1061,123 +997,16 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-plugin-align-assignments@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-align-assignments/-/eslint-plugin-align-assignments-1.1.2.tgz#83e1a8a826d4adf29e82b52d0bb39c88b301b576" - integrity sha512-I1ZJgk9EjHfGVU9M2Ex8UkVkkjLL5Y9BS6VNnQHq79eHj2H4/Cgxf36lQSUTLgm2ntB03A2NtF+zg9fyi5vChg== - -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== - -eslint@^8.36.0: - version "8.36.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.36.0.tgz#1bd72202200a5492f91803b113fb8a83b11285cf" - integrity sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.1" - "@eslint/js" "8.36.0" - "@humanwhocodes/config-array" "^0.11.8" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-visitor-keys "^3.3.0" - espree "^9.5.0" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - grapheme-splitter "^1.0.4" - ignore "^5.2.0" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-sdsl "^4.1.4" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.1" - strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" - text-table "^0.2.0" - esm@^3.2.25: version "3.2.25" resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^9.5.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.0.tgz#3646d4e3f58907464edba852fa047e6a27bdf113" - integrity sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw== - dependencies: - acorn "^8.8.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" - esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" @@ -1237,28 +1066,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - fast-uri@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.2.tgz#d78b298cf70fd3b752fd951175a3da6a7b48f024" integrity sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row== -fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== - dependencies: - reusify "^1.0.4" - figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -1266,13 +1078,6 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -1308,27 +1113,6 @@ find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== - forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -1468,13 +1252,6 @@ getopts@2.3.0: resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - glob-parent@~5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -1513,13 +1290,6 @@ global-dirs@^2.0.1: dependencies: ini "^1.3.5" -globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== - dependencies: - type-fest "^0.20.2" - gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -1559,11 +1329,6 @@ graceful-fs@^4.2.6: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - gravatar@^1.8.0: version "1.8.1" resolved "https://registry.yarnpkg.com/gravatar/-/gravatar-1.8.1.tgz#743bbdf3185c3433172e00e0e6ff5f6b30c58997" @@ -1696,27 +1461,6 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -ignore@^5.2.0: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -import-fresh@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -1823,20 +1567,13 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: +is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" -is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - is-installed-globally@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" @@ -1870,11 +1607,6 @@ is-path-inside@^3.0.1: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-property@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" @@ -1905,11 +1637,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -js-sdsl@^4.1.4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711" - integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== - js-yaml@^3.13.1: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" @@ -1935,21 +1662,11 @@ json-parse-better-errors@^1.0.1: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - jsonwebtoken@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" @@ -2018,14 +1735,6 @@ lazystream@^1.0.0: dependencies: readable-stream "^2.0.5" -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - liquidjs@10.6.1: version "10.6.1" resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.6.1.tgz#b401662cb8f0cca59b42f79fc08e411c86d92dab" @@ -2058,13 +1767,6 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" @@ -2085,11 +1787,6 @@ lodash.isplainobject@^4.0.6: resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" @@ -2217,13 +1914,6 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.5, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - minimist@^1.2.0, minimist@^1.2.5: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -2357,11 +2047,6 @@ named-placeholders@^1.1.3: dependencies: lru-cache "^7.14.1" -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - needle@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.0.tgz#e6fc4b3cc6c25caed7554bd613a5cf0bac8c31c0" @@ -2573,18 +2258,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.3" - os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -2622,13 +2295,6 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -2643,13 +2309,6 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" @@ -2677,13 +2336,6 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -2712,11 +2364,6 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -2836,21 +2483,11 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier@^2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" - integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== - printj@~1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" @@ -2931,11 +2568,6 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -3033,11 +2665,6 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" @@ -3064,11 +2691,6 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - rimraf@^2.6.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -3083,13 +2705,6 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -3209,18 +2824,6 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - side-channel@^1.0.4, side-channel@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -3413,11 +3016,6 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - 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" @@ -3504,11 +3102,6 @@ term-size@^2.1.0: resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - tildify@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" @@ -3543,18 +3136,6 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -3686,7 +3267,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@^2.0.1, which@^2.0.2: +which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -3714,11 +3295,6 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" -word-wrap@^1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" - integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== - wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -3793,11 +3369,6 @@ yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - zip-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" diff --git a/scripts/ci/frontend-build b/scripts/ci/frontend-build index ef1e75de..9bcf1a7e 100755 --- a/scripts/ci/frontend-build +++ b/scripts/ci/frontend-build @@ -17,7 +17,7 @@ if hash docker 2>/dev/null; then -v "$(pwd)/frontend:/app/frontend" \ -v "$(pwd)/global:/app/global" \ -w /app/frontend "${DOCKER_IMAGE}" \ - sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend" + sh -c "yarn install && yarn build && chown -R $(id -u):$(id -g) /app/frontend" echo -e "${BLUE}❯ ${GREEN}Building Frontend Complete${RESET}" else diff --git a/scripts/ci/test-and-build b/scripts/ci/test-and-build index a09ac9be..f9cba906 100755 --- a/scripts/ci/test-and-build +++ b/scripts/ci/test-and-build @@ -13,7 +13,7 @@ docker run --rm \ -v "$(pwd)/global:/app/global" \ -w /app \ "${TESTING_IMAGE}" \ - sh -c 'yarn install && yarn eslint . && rm -rf node_modules' + sh -c 'yarn install && yarn lint . && rm -rf node_modules' echo -e "${BLUE}❯ ${GREEN}Testing Complete${RESET}" # Build From fadec9751e6e93829a56e80d75fa8cf6017ef8bd Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Tue, 2 Sep 2025 23:56:00 +1000 Subject: [PATCH 10/82] React --- backend/internal/user.js | 55 +- backend/yarn.lock | 1982 ++-- docker/rootfs/etc/nginx/conf.d/dev.conf | 12 +- .../etc/s6-overlay/s6-rc.d/frontend/run | 2 +- frontend/.babelrc | 17 - frontend/.gitignore | 24 +- frontend/biome.json | 91 + frontend/check-locales.cjs | 172 + frontend/fonts/feather | 1 - ...urce-sans-pro-v14-latin-ext_latin-700.woff | Bin 31740 -> 0 bytes ...rce-sans-pro-v14-latin-ext_latin-700.woff2 | Bin 25348 -> 0 bytes ...ans-pro-v14-latin-ext_latin-700italic.woff | Bin 28540 -> 0 bytes ...ns-pro-v14-latin-ext_latin-700italic.woff2 | Bin 22240 -> 0 bytes ...e-sans-pro-v14-latin-ext_latin-italic.woff | Bin 28744 -> 0 bytes ...-sans-pro-v14-latin-ext_latin-italic.woff2 | Bin 22436 -> 0 bytes ...-sans-pro-v14-latin-ext_latin-regular.woff | Bin 32128 -> 0 bytes ...sans-pro-v14-latin-ext_latin-regular.woff2 | Bin 25656 -> 0 bytes frontend/html/index.ejs | 9 - frontend/html/login.ejs | 9 - frontend/html/partials/footer.ejs | 2 - frontend/html/partials/header.ejs | 34 - frontend/images | 1 - frontend/index.html | 44 + frontend/js/app/api.js | 757 -- frontend/js/app/audit-log/list/item.ejs | 80 - frontend/js/app/audit-log/list/item.js | 32 - frontend/js/app/audit-log/list/main.ejs | 9 - frontend/js/app/audit-log/list/main.js | 27 - frontend/js/app/audit-log/main.ejs | 25 - frontend/js/app/audit-log/main.js | 82 - frontend/js/app/audit-log/meta.ejs | 27 - frontend/js/app/audit-log/meta.js | 7 - frontend/js/app/cache.js | 10 - frontend/js/app/controller.js | 441 - frontend/js/app/dashboard/main.ejs | 67 - frontend/js/app/dashboard/main.js | 90 - frontend/js/app/empty/main.ejs | 11 - frontend/js/app/empty/main.js | 33 - frontend/js/app/error/main.ejs | 7 - frontend/js/app/error/main.js | 27 - frontend/js/app/help/main.ejs | 12 - frontend/js/app/help/main.js | 16 - frontend/js/app/i18n.js | 23 - frontend/js/app/main.js | 155 - frontend/js/app/nginx/access/delete.ejs | 23 - frontend/js/app/nginx/access/delete.js | 32 - frontend/js/app/nginx/access/form.ejs | 108 - frontend/js/app/nginx/access/form.js | 153 - frontend/js/app/nginx/access/form/client.ejs | 13 - frontend/js/app/nginx/access/form/client.js | 7 - frontend/js/app/nginx/access/form/item.ejs | 10 - frontend/js/app/nginx/access/form/item.js | 7 - frontend/js/app/nginx/access/list/item.ejs | 42 - frontend/js/app/nginx/access/list/item.js | 33 - frontend/js/app/nginx/access/list/main.ejs | 14 - frontend/js/app/nginx/access/list/main.js | 32 - frontend/js/app/nginx/access/main.ejs | 28 - frontend/js/app/nginx/access/main.js | 108 - .../js/app/nginx/certificates-list-item.ejs | 18 - frontend/js/app/nginx/certificates/delete.ejs | 19 - frontend/js/app/nginx/certificates/delete.js | 34 - frontend/js/app/nginx/certificates/form.ejs | 185 - frontend/js/app/nginx/certificates/form.js | 294 - .../js/app/nginx/certificates/list/item.ejs | 68 - .../js/app/nginx/certificates/list/item.js | 68 - .../js/app/nginx/certificates/list/main.ejs | 13 - .../js/app/nginx/certificates/list/main.js | 32 - frontend/js/app/nginx/certificates/main.ejs | 36 - frontend/js/app/nginx/certificates/main.js | 109 - frontend/js/app/nginx/certificates/renew.ejs | 14 - frontend/js/app/nginx/certificates/renew.js | 31 - frontend/js/app/nginx/certificates/test.ejs | 15 - frontend/js/app/nginx/certificates/test.js | 75 - frontend/js/app/nginx/dead/delete.ejs | 23 - frontend/js/app/nginx/dead/delete.js | 32 - frontend/js/app/nginx/dead/form.ejs | 206 - frontend/js/app/nginx/dead/form.js | 286 - frontend/js/app/nginx/dead/list/item.ejs | 54 - frontend/js/app/nginx/dead/list/item.js | 61 - frontend/js/app/nginx/dead/list/main.ejs | 12 - frontend/js/app/nginx/dead/list/main.js | 32 - frontend/js/app/nginx/dead/main.ejs | 28 - frontend/js/app/nginx/dead/main.js | 108 - .../js/app/nginx/proxy/access-list-item.ejs | 13 - frontend/js/app/nginx/proxy/delete.ejs | 23 - frontend/js/app/nginx/proxy/delete.js | 32 - frontend/js/app/nginx/proxy/form.ejs | 281 - frontend/js/app/nginx/proxy/form.js | 369 - frontend/js/app/nginx/proxy/list/item.ejs | 60 - frontend/js/app/nginx/proxy/list/item.js | 61 - frontend/js/app/nginx/proxy/list/main.ejs | 14 - frontend/js/app/nginx/proxy/list/main.js | 32 - frontend/js/app/nginx/proxy/location-item.ejs | 64 - frontend/js/app/nginx/proxy/location.js | 54 - frontend/js/app/nginx/proxy/main.ejs | 28 - frontend/js/app/nginx/proxy/main.js | 108 - frontend/js/app/nginx/redirection/delete.ejs | 23 - frontend/js/app/nginx/redirection/delete.js | 32 - frontend/js/app/nginx/redirection/form.ejs | 253 - frontend/js/app/nginx/redirection/form.js | 288 - .../js/app/nginx/redirection/list/item.ejs | 63 - .../js/app/nginx/redirection/list/item.js | 61 - .../js/app/nginx/redirection/list/main.ejs | 15 - .../js/app/nginx/redirection/list/main.js | 32 - frontend/js/app/nginx/redirection/main.ejs | 28 - frontend/js/app/nginx/redirection/main.js | 107 - frontend/js/app/nginx/stream/delete.ejs | 19 - frontend/js/app/nginx/stream/delete.js | 32 - frontend/js/app/nginx/stream/form.ejs | 194 - frontend/js/app/nginx/stream/form.js | 225 - frontend/js/app/nginx/stream/list/item.ejs | 59 - frontend/js/app/nginx/stream/list/item.js | 54 - frontend/js/app/nginx/stream/list/main.ejs | 14 - frontend/js/app/nginx/stream/list/main.js | 32 - frontend/js/app/nginx/stream/main.ejs | 28 - frontend/js/app/nginx/stream/main.js | 108 - frontend/js/app/router.js | 19 - .../js/app/settings/default-site/main.ejs | 57 - frontend/js/app/settings/default-site/main.js | 69 - frontend/js/app/settings/list/item.ejs | 21 - frontend/js/app/settings/list/item.js | 23 - frontend/js/app/settings/list/main.ejs | 8 - frontend/js/app/settings/list/main.js | 27 - frontend/js/app/settings/main.ejs | 14 - frontend/js/app/settings/main.js | 48 - frontend/js/app/tokens.js | 126 - frontend/js/app/ui/footer/main.ejs | 16 - frontend/js/app/ui/footer/main.js | 14 - frontend/js/app/ui/header/main.ejs | 34 - frontend/js/app/ui/header/main.js | 67 - frontend/js/app/ui/main.ejs | 21 - frontend/js/app/ui/main.js | 98 - frontend/js/app/ui/menu/main.ejs | 52 - frontend/js/app/ui/menu/main.js | 39 - frontend/js/app/user/delete.ejs | 19 - frontend/js/app/user/delete.js | 34 - frontend/js/app/user/form.ejs | 58 - frontend/js/app/user/form.js | 108 - frontend/js/app/user/password.ejs | 31 - frontend/js/app/user/password.js | 69 - frontend/js/app/user/permissions.ejs | 68 - frontend/js/app/user/permissions.js | 95 - frontend/js/app/users/list/item.ejs | 45 - frontend/js/app/users/list/item.js | 68 - frontend/js/app/users/list/main.ejs | 10 - frontend/js/app/users/list/main.js | 27 - frontend/js/app/users/main.ejs | 26 - frontend/js/app/users/main.js | 78 - frontend/js/i18n/messages.json | 301 - frontend/js/index.js | 119 - frontend/js/lib/helpers.js | 26 - frontend/js/lib/marionette.js | 15 - frontend/js/login.js | 5 - frontend/js/login/main.js | 14 - frontend/js/login/ui/login.ejs | 37 - frontend/js/login/ui/login.js | 42 - frontend/js/models/access-list.js | 25 - frontend/js/models/audit-log.js | 18 - frontend/js/models/certificate.js | 38 - frontend/js/models/dead-host.js | 32 - frontend/js/models/proxy-host-location.js | 35 - frontend/js/models/proxy-host.js | 40 - frontend/js/models/redirection-host.js | 37 - frontend/js/models/setting.js | 22 - frontend/js/models/stream.js | 32 - frontend/js/models/user.js | 54 - frontend/package.json | 107 +- .../images}/default-avatar.jpg | Bin .../favicon}/android-chrome-192x192.png | Bin .../favicon}/android-chrome-512x512.png | Bin .../images/favicon}/apple-touch-icon.png | Bin .../images/favicon}/browserconfig.xml | 0 .../images/favicon}/favicon-16x16.png | Bin .../images/favicon}/favicon-32x32.png | Bin .../images/favicon}/favicon.ico | Bin .../images/favicon}/mstile-150x150.png | Bin .../images/favicon}/safari-pinned-tab.svg | 0 .../images/favicon}/site.webmanifest | 0 .../images}/logo-256.png | Bin .../images/logo-bold-horizontal-grey.svg | 1 + frontend/public/images/logo-no-text.svg | 93 + .../images/logo-text-horizontal-grey.png | Bin 0 -> 22203 bytes .../images}/logo-text-vertical-grey.png | Bin frontend/public/images/unhealthy.svg | 63 + frontend/scss/custom.scss | 42 - frontend/scss/fonts.scss | 39 - frontend/scss/selectize.scss | 196 - frontend/scss/styles.scss | 17 - frontend/scss/tabler-extra.scss | 171 - frontend/src/App.css | 7 + frontend/src/App.tsx | 28 + frontend/src/Router.tsx | 78 + frontend/src/api/backend/base.ts | 152 + frontend/src/api/backend/createAccessList.ts | 13 + frontend/src/api/backend/createCertificate.ts | 13 + frontend/src/api/backend/createDeadHost.ts | 13 + frontend/src/api/backend/createProxyHost.ts | 13 + .../src/api/backend/createRedirectionHost.ts | 16 + frontend/src/api/backend/createStream.ts | 13 + frontend/src/api/backend/createUser.ts | 13 + frontend/src/api/backend/deleteAccessList.ts | 10 + frontend/src/api/backend/deleteCertificate.ts | 10 + frontend/src/api/backend/deleteDeadHost.ts | 10 + frontend/src/api/backend/deleteProxyHost.ts | 10 + .../src/api/backend/deleteRedirectionHost.ts | 10 + frontend/src/api/backend/deleteStream.ts | 10 + frontend/src/api/backend/deleteUser.ts | 10 + .../src/api/backend/downloadCertificate.ts | 11 + frontend/src/api/backend/getAccessList.ts | 11 + frontend/src/api/backend/getAccessLists.ts | 14 + frontend/src/api/backend/getAuditLog.ts | 12 + frontend/src/api/backend/getCertificate.ts | 11 + frontend/src/api/backend/getCertificates.ts | 12 + frontend/src/api/backend/getDeadHost.ts | 11 + frontend/src/api/backend/getDeadHosts.ts | 14 + frontend/src/api/backend/getHealth.ts | 11 + frontend/src/api/backend/getHostsReport.ts | 10 + frontend/src/api/backend/getProxyHost.ts | 11 + frontend/src/api/backend/getProxyHosts.ts | 14 + .../src/api/backend/getRedirectionHost.ts | 11 + .../src/api/backend/getRedirectionHosts.ts | 16 + frontend/src/api/backend/getSetting.ts | 11 + frontend/src/api/backend/getSettings.ts | 12 + frontend/src/api/backend/getStream.ts | 11 + frontend/src/api/backend/getStreams.ts | 14 + frontend/src/api/backend/getToken.ts | 19 + frontend/src/api/backend/getUser.ts | 10 + frontend/src/api/backend/getUsers.ts | 14 + frontend/src/api/backend/helpers.ts | 54 + frontend/src/api/backend/index.ts | 54 + frontend/src/api/backend/models.ts | 193 + frontend/src/api/backend/refreshToken.ts | 11 + frontend/src/api/backend/renewCertificate.ts | 11 + frontend/src/api/backend/responseTypes.ts | 18 + .../src/api/backend/testHttpCertificate.ts | 16 + frontend/src/api/backend/toggleDeadHost.ts | 14 + frontend/src/api/backend/toggleProxyHost.ts | 14 + .../src/api/backend/toggleRedirectionHost.ts | 14 + frontend/src/api/backend/toggleStream.ts | 10 + frontend/src/api/backend/updateAccessList.ts | 15 + frontend/src/api/backend/updateAuth.ts | 26 + frontend/src/api/backend/updateDeadHost.ts | 15 + frontend/src/api/backend/updateProxyHost.ts | 15 + .../src/api/backend/updateRedirectionHost.ts | 18 + frontend/src/api/backend/updateSetting.ts | 15 + frontend/src/api/backend/updateStream.ts | 15 + frontend/src/api/backend/updateUser.ts | 15 + frontend/src/api/backend/uploadCertificate.ts | 18 + .../src/api/backend/validateCertificate.ts | 17 + frontend/src/components/Button.tsx | 64 + frontend/src/components/ErrorNotFound.tsx | 23 + frontend/src/components/Flag.tsx | 24 + frontend/src/components/HasPermission.tsx | 50 + .../src/components/LoadingPage.module.css | 3 + frontend/src/components/LoadingPage.tsx | 27 + .../src/components/LocalePicker.module.css | 15 + frontend/src/components/LocalePicker.tsx | 71 + frontend/src/components/NavLink.tsx | 29 + frontend/src/components/Page.module.css | 5 + frontend/src/components/Page.tsx | 10 + frontend/src/components/SiteContainer.tsx | 6 + frontend/src/components/SiteFooter.tsx | 64 + frontend/src/components/SiteHeader.module.css | 8 + frontend/src/components/SiteHeader.tsx | 119 + frontend/src/components/SiteMenu.tsx | 195 + frontend/src/components/Table/EmptyRow.tsx | 16 + .../Table/Formatter/CertificateFormatter.tsx | 13 + .../Table/Formatter/DomainsFormatter.tsx | 25 + .../Table/Formatter/GravatarFormatter.tsx | 17 + .../Table/Formatter/StatusFormatter.tsx | 11 + .../Formatter/ValueWithDateFormatter.tsx | 21 + .../src/components/Table/Formatter/index.ts | 5 + frontend/src/components/Table/TableBody.tsx | 39 + frontend/src/components/Table/TableHeader.tsx | 26 + frontend/src/components/Table/TableHelpers.ts | 64 + frontend/src/components/Table/TableLayout.tsx | 22 + frontend/src/components/Table/index.ts | 4 + .../src/components/ThemeSwitcher.module.css | 15 + frontend/src/components/ThemeSwitcher.tsx | 41 + frontend/src/components/Unhealthy.tsx | 17 + frontend/src/components/index.ts | 15 + frontend/src/context/AuthContext.tsx | 72 + frontend/src/context/LocaleContext.tsx | 38 + frontend/src/context/ThemeContext.tsx | 68 + frontend/src/context/index.ts | 3 + frontend/src/declarations.d.ts | 1 + frontend/src/hooks/index.ts | 10 + frontend/src/hooks/useAccessLists.ts | 17 + frontend/src/hooks/useDeadHosts.ts | 17 + frontend/src/hooks/useHealth.ts | 18 + frontend/src/hooks/useHostReport.ts | 18 + frontend/src/hooks/useProxyHosts.ts | 17 + frontend/src/hooks/useRedirectionHosts.ts | 17 + frontend/src/hooks/useStreams.ts | 17 + frontend/src/hooks/useTheme.ts | 8 + frontend/src/hooks/useUser.ts | 37 + frontend/src/hooks/useUsers.ts | 17 + frontend/src/locale/IntlProvider.tsx | 64 + frontend/src/locale/README.md | 23 + frontend/src/locale/index.ts | 1 + frontend/src/locale/lang/de.json | 3 + frontend/src/locale/lang/en.json | 92 + frontend/src/locale/lang/fa.json | 3 + frontend/src/locale/lang/lang-list.json | 5 + frontend/src/locale/src/de.json | 5 + frontend/src/locale/src/en.json | 272 + frontend/src/locale/src/fa.json | 5 + frontend/src/locale/src/lang-list.json | 11 + frontend/src/main.tsx | 13 + frontend/src/modals/ChangePasswordModal.tsx | 153 + frontend/src/modals/UserModal.tsx | 213 + frontend/src/modals/index.ts | 2 + frontend/src/modules/AuthStore.ts | 89 + frontend/src/modules/Validations.tsx | 51 + frontend/src/pages/Access/Empty.tsx | 20 + frontend/src/pages/Access/Table.tsx | 123 + frontend/src/pages/Access/TableWrapper.tsx | 52 + frontend/src/pages/Access/index.tsx | 12 + frontend/src/pages/AuditLog/AuditTable.tsx | 128 + frontend/src/pages/AuditLog/index.tsx | 12 + .../pages/Certificates/CertificateTable.tsx | 132 + frontend/src/pages/Certificates/index.tsx | 12 + frontend/src/pages/Dashboard/index.tsx | 145 + frontend/src/pages/Login/index.module.css | 10 + frontend/src/pages/Login/index.tsx | 126 + frontend/src/pages/Nginx/DeadHosts/Empty.tsx | 20 + frontend/src/pages/Nginx/DeadHosts/Table.tsx | 109 + .../pages/Nginx/DeadHosts/TableWrapper.tsx | 52 + frontend/src/pages/Nginx/DeadHosts/index.tsx | 12 + frontend/src/pages/Nginx/ProxyHosts/Empty.tsx | 25 + frontend/src/pages/Nginx/ProxyHosts/Table.tsx | 125 + .../pages/Nginx/ProxyHosts/TableWrapper.tsx | 52 + frontend/src/pages/Nginx/ProxyHosts/index.tsx | 12 + .../pages/Nginx/RedirectionHosts/Empty.tsx | 20 + .../pages/Nginx/RedirectionHosts/Table.tsx | 130 + .../Nginx/RedirectionHosts/TableWrapper.tsx | 52 + .../pages/Nginx/RedirectionHosts/index.tsx | 12 + frontend/src/pages/Nginx/Streams/Empty.tsx | 20 + frontend/src/pages/Nginx/Streams/Table.tsx | 139 + .../src/pages/Nginx/Streams/TableWrapper.tsx | 52 + frontend/src/pages/Nginx/Streams/index.tsx | 12 + frontend/src/pages/Settings/SettingTable.tsx | 111 + frontend/src/pages/Settings/index.tsx | 12 + frontend/src/pages/Users/Empty.tsx | 20 + frontend/src/pages/Users/Table.tsx | 128 + frontend/src/pages/Users/TableWrapper.tsx | 62 + frontend/src/pages/Users/index.tsx | 12 + frontend/src/vite-env.d.ts | 1 + frontend/tsconfig.json | 43 + frontend/tsconfig.node.json | 10 + frontend/vite.config.ts | 44 + frontend/vitest-setup.js | 1 + frontend/webpack.config.js | 144 - frontend/yarn.lock | 8432 +++++------------ scripts/ci/frontend-build | 2 +- 355 files changed, 9308 insertions(+), 17813 deletions(-) delete mode 100644 frontend/.babelrc create mode 100644 frontend/biome.json create mode 100755 frontend/check-locales.cjs delete mode 120000 frontend/fonts/feather delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2 delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 delete mode 100644 frontend/html/index.ejs delete mode 100644 frontend/html/login.ejs delete mode 100644 frontend/html/partials/footer.ejs delete mode 100644 frontend/html/partials/header.ejs delete mode 120000 frontend/images create mode 100644 frontend/index.html delete mode 100644 frontend/js/app/api.js delete mode 100644 frontend/js/app/audit-log/list/item.ejs delete mode 100644 frontend/js/app/audit-log/list/item.js delete mode 100644 frontend/js/app/audit-log/list/main.ejs delete mode 100644 frontend/js/app/audit-log/list/main.js delete mode 100644 frontend/js/app/audit-log/main.ejs delete mode 100644 frontend/js/app/audit-log/main.js delete mode 100644 frontend/js/app/audit-log/meta.ejs delete mode 100644 frontend/js/app/audit-log/meta.js delete mode 100644 frontend/js/app/cache.js delete mode 100644 frontend/js/app/controller.js delete mode 100644 frontend/js/app/dashboard/main.ejs delete mode 100644 frontend/js/app/dashboard/main.js delete mode 100644 frontend/js/app/empty/main.ejs delete mode 100644 frontend/js/app/empty/main.js delete mode 100644 frontend/js/app/error/main.ejs delete mode 100644 frontend/js/app/error/main.js delete mode 100644 frontend/js/app/help/main.ejs delete mode 100644 frontend/js/app/help/main.js delete mode 100644 frontend/js/app/i18n.js delete mode 100644 frontend/js/app/main.js delete mode 100644 frontend/js/app/nginx/access/delete.ejs delete mode 100644 frontend/js/app/nginx/access/delete.js delete mode 100644 frontend/js/app/nginx/access/form.ejs delete mode 100644 frontend/js/app/nginx/access/form.js delete mode 100644 frontend/js/app/nginx/access/form/client.ejs delete mode 100644 frontend/js/app/nginx/access/form/client.js delete mode 100644 frontend/js/app/nginx/access/form/item.ejs delete mode 100644 frontend/js/app/nginx/access/form/item.js delete mode 100644 frontend/js/app/nginx/access/list/item.ejs delete mode 100644 frontend/js/app/nginx/access/list/item.js delete mode 100644 frontend/js/app/nginx/access/list/main.ejs delete mode 100644 frontend/js/app/nginx/access/list/main.js delete mode 100644 frontend/js/app/nginx/access/main.ejs delete mode 100644 frontend/js/app/nginx/access/main.js delete mode 100644 frontend/js/app/nginx/certificates-list-item.ejs delete mode 100644 frontend/js/app/nginx/certificates/delete.ejs delete mode 100644 frontend/js/app/nginx/certificates/delete.js delete mode 100644 frontend/js/app/nginx/certificates/form.ejs delete mode 100644 frontend/js/app/nginx/certificates/form.js delete mode 100644 frontend/js/app/nginx/certificates/list/item.ejs delete mode 100644 frontend/js/app/nginx/certificates/list/item.js delete mode 100644 frontend/js/app/nginx/certificates/list/main.ejs delete mode 100644 frontend/js/app/nginx/certificates/list/main.js delete mode 100644 frontend/js/app/nginx/certificates/main.ejs delete mode 100644 frontend/js/app/nginx/certificates/main.js delete mode 100644 frontend/js/app/nginx/certificates/renew.ejs delete mode 100644 frontend/js/app/nginx/certificates/renew.js delete mode 100644 frontend/js/app/nginx/certificates/test.ejs delete mode 100644 frontend/js/app/nginx/certificates/test.js delete mode 100644 frontend/js/app/nginx/dead/delete.ejs delete mode 100644 frontend/js/app/nginx/dead/delete.js delete mode 100644 frontend/js/app/nginx/dead/form.ejs delete mode 100644 frontend/js/app/nginx/dead/form.js delete mode 100644 frontend/js/app/nginx/dead/list/item.ejs delete mode 100644 frontend/js/app/nginx/dead/list/item.js delete mode 100644 frontend/js/app/nginx/dead/list/main.ejs delete mode 100644 frontend/js/app/nginx/dead/list/main.js delete mode 100644 frontend/js/app/nginx/dead/main.ejs delete mode 100644 frontend/js/app/nginx/dead/main.js delete mode 100644 frontend/js/app/nginx/proxy/access-list-item.ejs delete mode 100644 frontend/js/app/nginx/proxy/delete.ejs delete mode 100644 frontend/js/app/nginx/proxy/delete.js delete mode 100644 frontend/js/app/nginx/proxy/form.ejs delete mode 100644 frontend/js/app/nginx/proxy/form.js delete mode 100644 frontend/js/app/nginx/proxy/list/item.ejs delete mode 100644 frontend/js/app/nginx/proxy/list/item.js delete mode 100644 frontend/js/app/nginx/proxy/list/main.ejs delete mode 100644 frontend/js/app/nginx/proxy/list/main.js delete mode 100644 frontend/js/app/nginx/proxy/location-item.ejs delete mode 100644 frontend/js/app/nginx/proxy/location.js delete mode 100644 frontend/js/app/nginx/proxy/main.ejs delete mode 100644 frontend/js/app/nginx/proxy/main.js delete mode 100644 frontend/js/app/nginx/redirection/delete.ejs delete mode 100644 frontend/js/app/nginx/redirection/delete.js delete mode 100644 frontend/js/app/nginx/redirection/form.ejs delete mode 100644 frontend/js/app/nginx/redirection/form.js delete mode 100644 frontend/js/app/nginx/redirection/list/item.ejs delete mode 100644 frontend/js/app/nginx/redirection/list/item.js delete mode 100644 frontend/js/app/nginx/redirection/list/main.ejs delete mode 100644 frontend/js/app/nginx/redirection/list/main.js delete mode 100644 frontend/js/app/nginx/redirection/main.ejs delete mode 100644 frontend/js/app/nginx/redirection/main.js delete mode 100644 frontend/js/app/nginx/stream/delete.ejs delete mode 100644 frontend/js/app/nginx/stream/delete.js delete mode 100644 frontend/js/app/nginx/stream/form.ejs delete mode 100644 frontend/js/app/nginx/stream/form.js delete mode 100644 frontend/js/app/nginx/stream/list/item.ejs delete mode 100644 frontend/js/app/nginx/stream/list/item.js delete mode 100644 frontend/js/app/nginx/stream/list/main.ejs delete mode 100644 frontend/js/app/nginx/stream/list/main.js delete mode 100644 frontend/js/app/nginx/stream/main.ejs delete mode 100644 frontend/js/app/nginx/stream/main.js delete mode 100644 frontend/js/app/router.js delete mode 100644 frontend/js/app/settings/default-site/main.ejs delete mode 100644 frontend/js/app/settings/default-site/main.js delete mode 100644 frontend/js/app/settings/list/item.ejs delete mode 100644 frontend/js/app/settings/list/item.js delete mode 100644 frontend/js/app/settings/list/main.ejs delete mode 100644 frontend/js/app/settings/list/main.js delete mode 100644 frontend/js/app/settings/main.ejs delete mode 100644 frontend/js/app/settings/main.js delete mode 100644 frontend/js/app/tokens.js delete mode 100644 frontend/js/app/ui/footer/main.ejs delete mode 100644 frontend/js/app/ui/footer/main.js delete mode 100644 frontend/js/app/ui/header/main.ejs delete mode 100644 frontend/js/app/ui/header/main.js delete mode 100644 frontend/js/app/ui/main.ejs delete mode 100644 frontend/js/app/ui/main.js delete mode 100644 frontend/js/app/ui/menu/main.ejs delete mode 100644 frontend/js/app/ui/menu/main.js delete mode 100644 frontend/js/app/user/delete.ejs delete mode 100644 frontend/js/app/user/delete.js delete mode 100644 frontend/js/app/user/form.ejs delete mode 100644 frontend/js/app/user/form.js delete mode 100644 frontend/js/app/user/password.ejs delete mode 100644 frontend/js/app/user/password.js delete mode 100644 frontend/js/app/user/permissions.ejs delete mode 100644 frontend/js/app/user/permissions.js delete mode 100644 frontend/js/app/users/list/item.ejs delete mode 100644 frontend/js/app/users/list/item.js delete mode 100644 frontend/js/app/users/list/main.ejs delete mode 100644 frontend/js/app/users/list/main.js delete mode 100644 frontend/js/app/users/main.ejs delete mode 100644 frontend/js/app/users/main.js delete mode 100644 frontend/js/i18n/messages.json delete mode 100644 frontend/js/index.js delete mode 100644 frontend/js/lib/helpers.js delete mode 100644 frontend/js/lib/marionette.js delete mode 100644 frontend/js/login.js delete mode 100644 frontend/js/login/main.js delete mode 100644 frontend/js/login/ui/login.ejs delete mode 100644 frontend/js/login/ui/login.js delete mode 100644 frontend/js/models/access-list.js delete mode 100644 frontend/js/models/audit-log.js delete mode 100644 frontend/js/models/certificate.js delete mode 100644 frontend/js/models/dead-host.js delete mode 100644 frontend/js/models/proxy-host-location.js delete mode 100644 frontend/js/models/proxy-host.js delete mode 100644 frontend/js/models/redirection-host.js delete mode 100644 frontend/js/models/setting.js delete mode 100644 frontend/js/models/stream.js delete mode 100644 frontend/js/models/user.js rename frontend/{app-images => public/images}/default-avatar.jpg (100%) rename frontend/{app-images/favicons => public/images/favicon}/android-chrome-192x192.png (100%) rename frontend/{app-images/favicons => public/images/favicon}/android-chrome-512x512.png (100%) rename frontend/{app-images/favicons => public/images/favicon}/apple-touch-icon.png (100%) rename frontend/{app-images/favicons => public/images/favicon}/browserconfig.xml (100%) rename frontend/{app-images/favicons => public/images/favicon}/favicon-16x16.png (100%) rename frontend/{app-images/favicons => public/images/favicon}/favicon-32x32.png (100%) rename frontend/{app-images/favicons => public/images/favicon}/favicon.ico (100%) rename frontend/{app-images/favicons => public/images/favicon}/mstile-150x150.png (100%) rename frontend/{app-images/favicons => public/images/favicon}/safari-pinned-tab.svg (100%) rename frontend/{app-images/favicons => public/images/favicon}/site.webmanifest (100%) rename frontend/{app-images => public/images}/logo-256.png (100%) create mode 100644 frontend/public/images/logo-bold-horizontal-grey.svg create mode 100644 frontend/public/images/logo-no-text.svg create mode 100644 frontend/public/images/logo-text-horizontal-grey.png rename frontend/{app-images => public/images}/logo-text-vertical-grey.png (100%) create mode 100644 frontend/public/images/unhealthy.svg delete mode 100644 frontend/scss/custom.scss delete mode 100644 frontend/scss/fonts.scss delete mode 100644 frontend/scss/selectize.scss delete mode 100644 frontend/scss/styles.scss delete mode 100644 frontend/scss/tabler-extra.scss create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/Router.tsx create mode 100644 frontend/src/api/backend/base.ts create mode 100644 frontend/src/api/backend/createAccessList.ts create mode 100644 frontend/src/api/backend/createCertificate.ts create mode 100644 frontend/src/api/backend/createDeadHost.ts create mode 100644 frontend/src/api/backend/createProxyHost.ts create mode 100644 frontend/src/api/backend/createRedirectionHost.ts create mode 100644 frontend/src/api/backend/createStream.ts create mode 100644 frontend/src/api/backend/createUser.ts create mode 100644 frontend/src/api/backend/deleteAccessList.ts create mode 100644 frontend/src/api/backend/deleteCertificate.ts create mode 100644 frontend/src/api/backend/deleteDeadHost.ts create mode 100644 frontend/src/api/backend/deleteProxyHost.ts create mode 100644 frontend/src/api/backend/deleteRedirectionHost.ts create mode 100644 frontend/src/api/backend/deleteStream.ts create mode 100644 frontend/src/api/backend/deleteUser.ts create mode 100644 frontend/src/api/backend/downloadCertificate.ts create mode 100644 frontend/src/api/backend/getAccessList.ts create mode 100644 frontend/src/api/backend/getAccessLists.ts create mode 100644 frontend/src/api/backend/getAuditLog.ts create mode 100644 frontend/src/api/backend/getCertificate.ts create mode 100644 frontend/src/api/backend/getCertificates.ts create mode 100644 frontend/src/api/backend/getDeadHost.ts create mode 100644 frontend/src/api/backend/getDeadHosts.ts create mode 100644 frontend/src/api/backend/getHealth.ts create mode 100644 frontend/src/api/backend/getHostsReport.ts create mode 100644 frontend/src/api/backend/getProxyHost.ts create mode 100644 frontend/src/api/backend/getProxyHosts.ts create mode 100644 frontend/src/api/backend/getRedirectionHost.ts create mode 100644 frontend/src/api/backend/getRedirectionHosts.ts create mode 100644 frontend/src/api/backend/getSetting.ts create mode 100644 frontend/src/api/backend/getSettings.ts create mode 100644 frontend/src/api/backend/getStream.ts create mode 100644 frontend/src/api/backend/getStreams.ts create mode 100644 frontend/src/api/backend/getToken.ts create mode 100644 frontend/src/api/backend/getUser.ts create mode 100644 frontend/src/api/backend/getUsers.ts create mode 100644 frontend/src/api/backend/helpers.ts create mode 100644 frontend/src/api/backend/index.ts create mode 100644 frontend/src/api/backend/models.ts create mode 100644 frontend/src/api/backend/refreshToken.ts create mode 100644 frontend/src/api/backend/renewCertificate.ts create mode 100644 frontend/src/api/backend/responseTypes.ts create mode 100644 frontend/src/api/backend/testHttpCertificate.ts create mode 100644 frontend/src/api/backend/toggleDeadHost.ts create mode 100644 frontend/src/api/backend/toggleProxyHost.ts create mode 100644 frontend/src/api/backend/toggleRedirectionHost.ts create mode 100644 frontend/src/api/backend/toggleStream.ts create mode 100644 frontend/src/api/backend/updateAccessList.ts create mode 100644 frontend/src/api/backend/updateAuth.ts create mode 100644 frontend/src/api/backend/updateDeadHost.ts create mode 100644 frontend/src/api/backend/updateProxyHost.ts create mode 100644 frontend/src/api/backend/updateRedirectionHost.ts create mode 100644 frontend/src/api/backend/updateSetting.ts create mode 100644 frontend/src/api/backend/updateStream.ts create mode 100644 frontend/src/api/backend/updateUser.ts create mode 100644 frontend/src/api/backend/uploadCertificate.ts create mode 100644 frontend/src/api/backend/validateCertificate.ts create mode 100644 frontend/src/components/Button.tsx create mode 100644 frontend/src/components/ErrorNotFound.tsx create mode 100644 frontend/src/components/Flag.tsx create mode 100644 frontend/src/components/HasPermission.tsx create mode 100644 frontend/src/components/LoadingPage.module.css create mode 100644 frontend/src/components/LoadingPage.tsx create mode 100644 frontend/src/components/LocalePicker.module.css create mode 100644 frontend/src/components/LocalePicker.tsx create mode 100644 frontend/src/components/NavLink.tsx create mode 100644 frontend/src/components/Page.module.css create mode 100644 frontend/src/components/Page.tsx create mode 100644 frontend/src/components/SiteContainer.tsx create mode 100644 frontend/src/components/SiteFooter.tsx create mode 100644 frontend/src/components/SiteHeader.module.css create mode 100644 frontend/src/components/SiteHeader.tsx create mode 100644 frontend/src/components/SiteMenu.tsx create mode 100644 frontend/src/components/Table/EmptyRow.tsx create mode 100644 frontend/src/components/Table/Formatter/CertificateFormatter.tsx create mode 100644 frontend/src/components/Table/Formatter/DomainsFormatter.tsx create mode 100644 frontend/src/components/Table/Formatter/GravatarFormatter.tsx create mode 100644 frontend/src/components/Table/Formatter/StatusFormatter.tsx create mode 100644 frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx create mode 100644 frontend/src/components/Table/Formatter/index.ts create mode 100644 frontend/src/components/Table/TableBody.tsx create mode 100644 frontend/src/components/Table/TableHeader.tsx create mode 100644 frontend/src/components/Table/TableHelpers.ts create mode 100644 frontend/src/components/Table/TableLayout.tsx create mode 100644 frontend/src/components/Table/index.ts create mode 100644 frontend/src/components/ThemeSwitcher.module.css create mode 100644 frontend/src/components/ThemeSwitcher.tsx create mode 100644 frontend/src/components/Unhealthy.tsx create mode 100644 frontend/src/components/index.ts create mode 100644 frontend/src/context/AuthContext.tsx create mode 100644 frontend/src/context/LocaleContext.tsx create mode 100644 frontend/src/context/ThemeContext.tsx create mode 100644 frontend/src/context/index.ts create mode 100644 frontend/src/declarations.d.ts create mode 100644 frontend/src/hooks/index.ts create mode 100644 frontend/src/hooks/useAccessLists.ts create mode 100644 frontend/src/hooks/useDeadHosts.ts create mode 100644 frontend/src/hooks/useHealth.ts create mode 100644 frontend/src/hooks/useHostReport.ts create mode 100644 frontend/src/hooks/useProxyHosts.ts create mode 100644 frontend/src/hooks/useRedirectionHosts.ts create mode 100644 frontend/src/hooks/useStreams.ts create mode 100644 frontend/src/hooks/useTheme.ts create mode 100644 frontend/src/hooks/useUser.ts create mode 100644 frontend/src/hooks/useUsers.ts create mode 100644 frontend/src/locale/IntlProvider.tsx create mode 100644 frontend/src/locale/README.md create mode 100644 frontend/src/locale/index.ts create mode 100644 frontend/src/locale/lang/de.json create mode 100644 frontend/src/locale/lang/en.json create mode 100644 frontend/src/locale/lang/fa.json create mode 100644 frontend/src/locale/lang/lang-list.json create mode 100644 frontend/src/locale/src/de.json create mode 100644 frontend/src/locale/src/en.json create mode 100644 frontend/src/locale/src/fa.json create mode 100644 frontend/src/locale/src/lang-list.json create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/modals/ChangePasswordModal.tsx create mode 100644 frontend/src/modals/UserModal.tsx create mode 100644 frontend/src/modals/index.ts create mode 100644 frontend/src/modules/AuthStore.ts create mode 100644 frontend/src/modules/Validations.tsx create mode 100644 frontend/src/pages/Access/Empty.tsx create mode 100644 frontend/src/pages/Access/Table.tsx create mode 100644 frontend/src/pages/Access/TableWrapper.tsx create mode 100644 frontend/src/pages/Access/index.tsx create mode 100644 frontend/src/pages/AuditLog/AuditTable.tsx create mode 100644 frontend/src/pages/AuditLog/index.tsx create mode 100644 frontend/src/pages/Certificates/CertificateTable.tsx create mode 100644 frontend/src/pages/Certificates/index.tsx create mode 100644 frontend/src/pages/Dashboard/index.tsx create mode 100644 frontend/src/pages/Login/index.module.css create mode 100644 frontend/src/pages/Login/index.tsx create mode 100644 frontend/src/pages/Nginx/DeadHosts/Empty.tsx create mode 100644 frontend/src/pages/Nginx/DeadHosts/Table.tsx create mode 100644 frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx create mode 100644 frontend/src/pages/Nginx/DeadHosts/index.tsx create mode 100644 frontend/src/pages/Nginx/ProxyHosts/Empty.tsx create mode 100644 frontend/src/pages/Nginx/ProxyHosts/Table.tsx create mode 100644 frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx create mode 100644 frontend/src/pages/Nginx/ProxyHosts/index.tsx create mode 100644 frontend/src/pages/Nginx/RedirectionHosts/Empty.tsx create mode 100644 frontend/src/pages/Nginx/RedirectionHosts/Table.tsx create mode 100644 frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx create mode 100644 frontend/src/pages/Nginx/RedirectionHosts/index.tsx create mode 100644 frontend/src/pages/Nginx/Streams/Empty.tsx create mode 100644 frontend/src/pages/Nginx/Streams/Table.tsx create mode 100644 frontend/src/pages/Nginx/Streams/TableWrapper.tsx create mode 100644 frontend/src/pages/Nginx/Streams/index.tsx create mode 100644 frontend/src/pages/Settings/SettingTable.tsx create mode 100644 frontend/src/pages/Settings/index.tsx create mode 100644 frontend/src/pages/Users/Empty.tsx create mode 100644 frontend/src/pages/Users/Table.tsx create mode 100644 frontend/src/pages/Users/TableWrapper.tsx create mode 100644 frontend/src/pages/Users/index.tsx create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 frontend/vitest-setup.js delete mode 100644 frontend/webpack.config.js diff --git a/backend/internal/user.js b/backend/internal/user.js index 012252cf..e3d7ca4c 100644 --- a/backend/internal/user.js +++ b/backend/internal/user.js @@ -12,6 +12,8 @@ const omissions = () => { return ["is_deleted"]; } +const DEFAULT_AVATAR = 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=200&d=mp&r=g'; + const internalUser = { /** * @param {Access} access @@ -33,7 +35,6 @@ const internalUser = { .can("users:create", data) .then(() => { data.avatar = gravatar.url(data.email, { default: "mm" }); - return userModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); }) .then((user) => { @@ -133,7 +134,6 @@ const internalUser = { } data.avatar = gravatar.url(data.email || user.email, { default: "mm" }); - return userModel.query().patchAndFetchById(user.id, data).then(utils.omitRow(omissions())); }) .then(() => { @@ -193,6 +193,11 @@ const internalUser = { if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { return _.omit(row, thisData.omit); } + + if (row.avatar === "") { + row.avatar = DEFAULT_AVATAR; + } + return row; }); }, @@ -299,32 +304,32 @@ const internalUser = { * @param {String} [search_query] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access.can("users:list").then(() => { - const query = userModel - .query() - .where("is_deleted", 0) - .groupBy("id") - .allowGraph("[permissions]") - .orderBy("name", "ASC"); + getAll: async (access, expand, search_query) => { + await access.can("users:list"); + const query = userModel + .query() + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[permissions]") + .orderBy("name", "ASC"); - // Query is used for searching - if (typeof search_query === "string") { - query.where(function () { - this.where("name", "like", `%${search_query}%`).orWhere( - "email", - "like", - `%${search_query}%`, - ); - }); - } + // Query is used for searching + if (typeof search_query === "string") { + query.where(function () { + this.where("name", "like", `%${search_query}%`).orWhere( + "email", + "like", + `%${search_query}%`, + ); + }); + } - if (typeof expand !== "undefined" && expand !== null) { - query.withGraphFetched(`[${expand.join(", ")}]`); - } + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } - return query.then(utils.omitRows(omissions())); - }); + const res = await query; + return utils.omitRows(omissions())(res); }, /** diff --git a/backend/yarn.lock b/backend/yarn.lock index bf8091ec..fd936a9b 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2,19 +2,19 @@ # yarn lockfile v1 -"@apidevtools/json-schema-ref-parser@9.0.6": - version "9.0.6" - resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c" - integrity sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg== +"@apidevtools/json-schema-ref-parser@11.7.2": + version "11.7.2" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz#cdf3e0aded21492364a70e193b45b7cf4177f031" + integrity sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA== dependencies: "@jsdevtools/ono" "^7.1.3" - call-me-maybe "^1.0.1" - js-yaml "^3.13.1" + "@types/json-schema" "^7.0.15" + js-yaml "^4.1.0" "@apidevtools/json-schema-ref-parser@^11.7.0": - version "11.7.0" - resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.0.tgz#228d72018a0e7cbee744b677eaa01a8968f302d9" - integrity sha512-pRrmXMCwnmrkS3MLgAIW5dXRzeTv6GLjkjb4HmxNnvAKXN1Nfzp4KmGADBQvlVUcqi+a5D+hfGDLLnd5NnYxog== + version "11.9.3" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz#0e0c9061fc41cf03737d499a4e6a8299fdd2bfa7" + integrity sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ== dependencies: "@jsdevtools/ono" "^7.1.3" "@types/json-schema" "^7.0.15" @@ -31,17 +31,17 @@ integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== "@apidevtools/swagger-parser@^10.1.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.1.0.tgz#a987d71e5be61feb623203be0c96e5985b192ab6" - integrity sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw== + version "10.1.1" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.1.1.tgz#e29bf17cf94b487a340e06784e9fbe20cb671c45" + integrity sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA== dependencies: - "@apidevtools/json-schema-ref-parser" "9.0.6" + "@apidevtools/json-schema-ref-parser" "11.7.2" "@apidevtools/openapi-schemas" "^2.1.0" "@apidevtools/swagger-methods" "^3.0.2" "@jsdevtools/ono" "^7.1.3" - ajv "^8.6.3" + ajv "^8.17.1" ajv-draft-04 "^1.0.0" - call-me-maybe "^1.0.1" + call-me-maybe "^1.0.2" "@biomejs/biome@2.2.0": version "2.2.0" @@ -107,10 +107,10 @@ resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== -"@mapbox/node-pre-gyp@^1.0.0": - version "1.0.10" - resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" - integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== +"@mapbox/node-pre-gyp@^1.0.0", "@mapbox/node-pre-gyp@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== dependencies: detect-libc "^2.0.0" https-proxy-agent "^5.0.0" @@ -138,28 +138,11 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== - "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -170,7 +153,7 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.5, accepts@~1.3.8: +accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -186,12 +169,10 @@ agent-base@6, agent-base@^6.0.2: debug "4" agentkeepalive@^4.1.3: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" - integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== dependencies: - debug "^4.1.0" - depd "^2.0.0" humanize-ms "^1.2.1" aggregate-error@^3.0.0: @@ -207,7 +188,7 @@ ajv-draft-04@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== -ajv@^8.17.1, ajv@^8.6.3: +ajv@^8.17.1, ajv@^8.6.2: version "8.17.1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== @@ -217,43 +198,6 @@ ajv@^8.17.1, ajv@^8.6.3: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -ajv@^8.6.2: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-align@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" - integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== - dependencies: - string-width "^3.0.0" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -267,30 +211,24 @@ ansi-styles@^3.2.1: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: - "@types/color-name" "^1.1.1" color-convert "^2.0.1" -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - "aproba@^1.0.3 || ^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" - integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.1.0.tgz#75500a190313d95c64e871e7e4284c6ac219f0b1" + integrity sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew== archiver-utils@^2.1.0: version "2.1.0" @@ -308,16 +246,32 @@ archiver-utils@^2.1.0: normalize-path "^3.0.0" readable-stream "^2.0.0" +archiver-utils@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-3.0.4.tgz#a0d201f1cf8fce7af3b5a05aea0a337329e96ec7" + integrity sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw== + dependencies: + glob "^7.2.3" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + archiver@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba" - integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg== + version "5.3.2" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.2.tgz#99991d5957e53bd0303a392979276ac4ddccf3b0" + integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw== dependencies: archiver-utils "^2.1.0" - async "^3.2.0" + async "^3.2.4" buffer-crc32 "^0.2.1" readable-stream "^3.6.0" - readdir-glob "^1.0.0" + readdir-glob "^1.1.2" tar-stream "^2.2.0" zip-stream "^4.1.0" @@ -337,21 +291,6 @@ are-we-there-yet@^3.0.0: delegates "^1.0.0" readable-stream "^3.6.0" -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -360,19 +299,19 @@ argparse@^2.0.1: array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== asn1@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== dependencies: safer-buffer "~2.1.0" -async@^3.2.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== +async@^3.2.4: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== aws-ssl-profiles@^1.1.1: version "1.1.2" @@ -380,9 +319,9 @@ aws-ssl-profiles@^1.1.1: integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g== balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.3.1: version "1.5.1" @@ -392,20 +331,20 @@ base64-js@^1.3.1: batchflow@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/batchflow/-/batchflow-0.4.0.tgz#7d419df79b6b7587b06f9ea34f96ccef6f74e5b5" - integrity sha1-fUGd95trdYewb56jT5bM72905bU= + integrity sha512-XwQQoCGPUjdLWzmpAvRNZc91wnBYuKLmj52d9LLZ1Ww06ow5RBqBt8kUmU9/3ZvPq88j7Elh3V4cEhgNKXbIlQ== bcrypt@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.0.tgz#051407c7cd5ffbfb773d541ca3760ea0754e37e2" - integrity sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg== + version "5.1.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2" + integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww== dependencies: - node-addon-api "^3.0.0" - node-pre-gyp "0.15.0" + "@mapbox/node-pre-gyp" "^1.0.11" + node-addon-api "^5.0.0" binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== bl@^4.0.3: version "4.1.0" @@ -417,9 +356,9 @@ bl@^4.0.3: readable-stream "^3.4.0" blueimp-md5@^2.16.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.17.0.tgz#f4fcac088b115f7b4045f19f5da59e9d01b1bb96" - integrity sha512-x5PKJHY5rHQYaADj6NwPUR2QRCUVSggPzrUKkeENpj871o9l9IefJbO2jkT5UvYykeOK9dx0VmkIo6dZ+vThYw== + version "2.19.0" + resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0" + integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== body-parser@1.20.3, body-parser@^1.20.3: version "1.20.3" @@ -439,20 +378,6 @@ body-parser@1.20.3, body-parser@^1.20.3: type-is "~1.6.18" unpipe "1.0.0" -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - brace-expansion@^1.1.7: version "1.1.12" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" @@ -461,6 +386,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -471,12 +403,12 @@ braces@~3.0.2: buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== -buffer-equal-constant-time@1.0.1: +buffer-equal-constant-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== buffer@^5.5.0: version "5.7.1" @@ -486,17 +418,12 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -busboy@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" - integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw== +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== dependencies: - dicer "0.3.0" - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + streamsearch "^1.1.0" bytes@3.1.2: version "3.1.2" @@ -527,36 +454,28 @@ cacache@^15.2.0: tar "^6.0.2" unique-filename "^1.1.1" -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" es-errors "^1.3.0" function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" -camelcase@^5.0.0, camelcase@^5.3.1: +call-me-maybe@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" + integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== + +camelcase@^5.0.0: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -578,54 +497,31 @@ chalk@^2.3.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@^3.2.2: - version "3.4.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" - integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== - dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.4.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.1.2" - -chownr@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + fsevents "~2.3.2" chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-boxes@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" - integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== - cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -635,18 +531,6 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -664,7 +548,7 @@ color-convert@^2.0.1: color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" @@ -682,26 +566,26 @@ colorette@2.0.19: integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== commander@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.0.tgz#71797971162cd3cf65f0b9d24eb28f8d303acdf1" - integrity sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA== + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== commander@^9.1.0: version "9.5.0" resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== -compress-commons@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" - integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ== +compress-commons@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.2.tgz#6542e59cb63e1f46a8b21b0e06f9a32e4c8b06df" + integrity sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg== dependencies: buffer-crc32 "^0.2.13" crc32-stream "^4.0.2" normalize-path "^3.0.0" readable-stream "^3.6.0" -compressible@~2.0.16: +compressible@~2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== @@ -709,39 +593,27 @@ compressible@~2.0.16: mime-db ">= 1.43.0 < 2" compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + version "1.8.1" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79" + integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" + bytes "3.1.2" + compressible "~2.0.18" debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" + negotiator "~0.6.4" + on-headers "~1.1.0" + safe-buffer "5.2.1" vary "~1.1.2" concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== content-disposition@0.5.4: version "0.5.4" @@ -758,107 +630,80 @@ content-type@~1.0.4, content-type@~1.0.5: cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" - integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== crc-32@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" - integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA== - dependencies: - exit-on-epipe "~1.0.1" - printj "~1.1.0" + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== crc32-stream@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" - integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== + version "4.0.3" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.3.tgz#85dd677eb78fa7cad1ba17cc506a597d41fc6f33" + integrity sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw== dependencies: crc-32 "^1.2.0" readable-stream "^3.4.0" -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - db-errors@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/db-errors/-/db-errors-0.2.3.tgz#a6a38952e00b20e790f2695a6446b3c65497ffa2" integrity sha512-OOgqgDuCavHXjYSJoV2yGhv6SeG8nk42aoCSoyXLZUH7VwFG27rxbavU1z+VrZbZjphw5UkDQwUlD21MwZpUng== -debug@2.6.9, debug@^2.2.0: +debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.3.3: +debug@4, debug@^4.3.3: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +debug@4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== denque@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== -depd@2.0.0, depd@^2.0.0: +depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -868,34 +713,19 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-libc@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8" + integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== -dicer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" - integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== dependencies: - streamsearch "0.1.2" - -dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== - dependencies: - is-obj "^2.0.0" - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" ecdsa-sig-formatter@1.0.11: version "1.0.11" @@ -907,18 +737,13 @@ ecdsa-sig-formatter@1.0.11: ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== email-validator@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -927,7 +752,7 @@ emoji-regex@^8.0.0: encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== encodeurl@~2.0.0: version "2.0.0" @@ -941,10 +766,10 @@ encoding@^0.1.12: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== +end-of-stream@^1.4.1: + version "1.4.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== dependencies: once "^1.4.0" @@ -965,116 +790,106 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== esm@^3.2.25: version "3.2.25" resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - -exit-on-epipe@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" - integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== express-fileupload@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.1.9.tgz#e798e9318394ed5083e56217ad6cda576da465d2" - integrity sha512-f2w0aoe7lj3NeD8a4MXmYQsqir3Z66I08l9AKq04QbFUAjeZNmPwTlR5Lx2NGwSu/PslsAjGC38MWzo5tTjoBg== + version "1.5.2" + resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.5.2.tgz#4da70ba6f2ffd4c736eab0776445865a9dbd9bfa" + integrity sha512-wxUJn2vTHvj/kZCVmc5/bJO15C7aSMyHeuXYY3geKpeKibaAoQGcEv5+sM6nHS2T7VF+QHS4hTWPiY2mKofEdg== dependencies: - busboy "^0.3.1" + busboy "^1.6.0" express@^4.20.0: - version "4.20.0" - resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48" - integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw== + version "4.21.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" + integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== dependencies: accepts "~1.3.8" array-flatten "1.1.1" body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.6.0" + cookie "0.7.1" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.2.0" + finalhandler "1.3.1" fresh "0.5.2" http-errors "2.0.0" merge-descriptors "1.0.3" methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.10" + path-to-regexp "0.1.12" proxy-addr "~2.0.7" - qs "6.11.0" + qs "6.13.0" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.19.0" - serve-static "1.16.0" + serve-static "1.16.2" setprototypeof "1.2.0" statuses "2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-uri@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.2.tgz#d78b298cf70fd3b752fd951175a3da6a7b48f024" - integrity sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row== + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== dependencies: escape-string-regexp "^1.0.5" @@ -1085,13 +900,13 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== dependencies: debug "2.6.9" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" on-finished "2.4.1" parseurl "~1.3.3" @@ -1101,7 +916,7 @@ finalhandler@1.2.0: find-up@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== dependencies: locate-path "^2.0.0" @@ -1121,20 +936,13 @@ forwarded@0.2.0: fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-minipass@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -1145,17 +953,12 @@ fs-minipass@^2.0.0: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: version "1.1.2" @@ -1191,20 +994,6 @@ gauge@^4.0.3: strip-ansi "^6.0.1" wide-align "^1.1.5" -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - generate-function@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" @@ -1217,122 +1006,73 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" es-errors "^1.3.0" + es-object-atoms "^1.1.1" function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" getopts@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== -glob-parent@~5.1.0: +glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob@^7.1.3, glob@^7.1.4, glob@^7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.4: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -global-dirs@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" - integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== - dependencies: - ini "^1.3.5" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -graceful-fs@^4.1.15, graceful-fs@^4.1.2: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== - -graceful-fs@^4.2.0: - version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" - integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== - -graceful-fs@^4.2.6: +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== gravatar@^1.8.0: - version "1.8.1" - resolved "https://registry.yarnpkg.com/gravatar/-/gravatar-1.8.1.tgz#743bbdf3185c3433172e00e0e6ff5f6b30c58997" - integrity sha512-18frnfVp4kRYkM/eQW32Mfwlsh/KMbwd3S6nkescBZHioobflFEFHsvM71qZAkUSLNifyi2uoI+TuGxJAnQIOA== + version "1.8.2" + resolved "https://registry.yarnpkg.com/gravatar/-/gravatar-1.8.2.tgz#f298642b1562ed685af2ae938dbe31ec0c542cc1" + integrity sha512-GdRwLM3oYpFQKy47MKuluw9hZ2gaCtiKPbDGdcDEuYDKlc8eNnW27KYL9LVbIDzEsx88WtDWQm2ClBcsgBnj6w== dependencies: blueimp-md5 "^2.16.0" email-validator "^2.0.4" @@ -1342,58 +1082,34 @@ gravatar@^1.8.0: has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has-proto@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-unicode@^2.0.0, has-unicode@^2.0.1: +has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hasown@^2.0.0: +hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" -http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== +http-cache-semantics@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" + integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== http-errors@2.0.0: version "2.0.0" @@ -1430,20 +1146,27 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -iconv-lite@0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2, iconv-lite@^0.6.3: +iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.0.tgz#c50cd80e6746ca8115eb98743afa81aa0e147a3e" + integrity sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -1452,24 +1175,12 @@ ieee754@^1.1.13: ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" - integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= - -ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== - dependencies: - minimatch "^3.0.4" - -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== indent-string@^4.0.0: version "4.0.0" @@ -1484,7 +1195,7 @@ infer-owner@^1.0.4: inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" @@ -1497,22 +1208,17 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@^1.3.5, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== interpret@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -ip@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" - integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== +ip-address@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.0.1.tgz#a8180b783ce7788777d796286d61bce4276818ed" + integrity sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA== ipaddr.js@1.9.1: version "1.9.1" @@ -1522,7 +1228,7 @@ ipaddr.js@1.9.1: is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-binary-path@~2.1.0: version "2.1.0" @@ -1531,36 +1237,17 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: - ci-info "^2.0.0" - -is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== - dependencies: - has "^1.0.3" + hasown "^2.0.2" is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -1568,82 +1255,41 @@ is-fullwidth-code-point@^3.0.0: integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" -is-installed-globally@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== - dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" - is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" - integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== - is-property@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -js-yaml@^3.13.1: - version "3.14.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" - integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== js-yaml@^4.1.0: version "4.1.0" @@ -1652,11 +1298,6 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -1668,21 +1309,27 @@ json-schema-traverse@^1.0.0: integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== jsonwebtoken@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" - integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== dependencies: jws "^3.2.2" - lodash "^4.17.21" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" ms "^2.1.1" - semver "^7.3.8" + semver "^7.5.4" jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + version "1.4.2" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" + integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== dependencies: - buffer-equal-constant-time "1.0.1" + buffer-equal-constant-time "^1.0.1" ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" @@ -1694,13 +1341,6 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - knex@2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/knex/-/knex-2.4.2.tgz#a34a289d38406dc19a0447a78eeaf2d16ebedd61" @@ -1721,17 +1361,10 @@ knex@2.4.2: tarn "^3.0.2" tildify "2.0.0" -latest-version@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== dependencies: readable-stream "^2.0.5" @@ -1745,7 +1378,7 @@ liquidjs@10.6.1: load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== dependencies: graceful-fs "^4.1.2" parse-json "^4.0.0" @@ -1755,7 +1388,7 @@ load-json-file@^4.0.0: locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== dependencies: p-locate "^2.0.0" path-exists "^3.0.0" @@ -1770,27 +1403,57 @@ locate-path@^5.0.0: lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== lodash.difference@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" - integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= + integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= + integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== lodash@^4.17.21: version "4.17.21" @@ -1798,19 +1461,9 @@ lodash@^4.17.21: integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== long@^5.2.1: - version "5.2.3" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" - integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== - -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== lru-cache@^6.0.0: version "6.0.0" @@ -1824,10 +1477,10 @@ lru-cache@^7.14.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -lru-cache@^8.0.0: - version "8.0.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-8.0.5.tgz#983fe337f3e176667f8e567cfcce7cb064ea214e" - integrity sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA== +lru.min@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.2.tgz#01ce1d72cc50c7faf8bd1f809ebf05d4331021eb" + integrity sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg== make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" @@ -1858,10 +1511,15 @@ make-fetch-happen@^9.1.0: socks-proxy-agent "^6.0.0" ssri "^8.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== merge-descriptors@1.0.3: version "1.0.3" @@ -1871,26 +1529,19 @@ merge-descriptors@1.0.3: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -mime-db@1.44.0, "mime-db@>= 1.43.0 < 2": - version "1.44.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@~2.1.24: - version "2.1.27" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== - dependencies: - mime-db "1.44.0" +"mime-db@>= 1.43.0 < 2": + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@~2.1.34: +mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -1902,22 +1553,19 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minimatch@^5.1.0: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" minipass-collect@^1.0.2: version "1.0.2" @@ -1958,14 +1606,6 @@ minipass-sized@^1.0.3: dependencies: minipass "^3.0.0" -minipass@^2.6.0, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: version "3.3.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" @@ -1973,17 +1613,10 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: dependencies: yallist "^4.0.0" -minipass@^4.0.0: - version "4.2.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.5.tgz#9e0e5256f1e3513f8c34691dd68549e85b2c8ceb" - integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q== - -minizlib@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" @@ -1993,49 +1626,42 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -mkdirp@^0.5.3, mkdirp@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== moment@^2.29.4: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== mysql2@^3.11.1: - version "3.11.1" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.11.1.tgz#edfb856e2176fcf43d2cc066dd4959e9fc76ea85" - integrity sha512-Oc8Zffd0gpIJnJ/NOMp6IiiJJDdWc7nmWpS+UE3K9feTpYia8XkbgL6EaOJYz52f6+2pAoC0eAQqUzal4lnNGQ== + version "3.14.4" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.14.4.tgz#36e33a8d33820a299fb9e9221486310b1a4c8767" + integrity sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w== dependencies: aws-ssl-profiles "^1.1.1" denque "^2.1.0" generate-function "^2.3.1" - iconv-lite "^0.6.3" + iconv-lite "^0.7.0" long "^5.2.1" - lru-cache "^8.0.0" + lru.min "^1.0.0" named-placeholders "^1.1.3" seq-queue "^0.0.5" sqlstring "^2.3.2" @@ -2047,34 +1673,30 @@ named-placeholders@^1.1.3: dependencies: lru-cache "^7.14.1" -needle@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.0.tgz#e6fc4b3cc6c25caed7554bd613a5cf0bac8c31c0" - integrity sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - -negotiator@0.6.3, negotiator@^0.6.2: +negotiator@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== -node-addon-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.0.tgz#812446a1001a54f71663bed188314bba07e09247" - integrity sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg== +negotiator@^0.6.2, negotiator@~0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== node-addon-api@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + node-fetch@^2.6.7: - version "2.6.9" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" - integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg== + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" @@ -2094,22 +1716,6 @@ node-gyp@8.x: tar "^6.1.2" which "^2.0.2" -node-pre-gyp@0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz#c2fc383276b74c7ffa842925241553e8b40f1087" - integrity sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.3" - needle "^2.5.0" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-rsa@^1.0.8: version "1.1.1" resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.1.1.tgz#efd9ad382097782f506153398496f79e4464434d" @@ -2118,28 +1724,20 @@ node-rsa@^1.0.8: asn1 "^0.2.4" nodemon@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" - integrity sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ== + version "2.0.22" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.22.tgz#182c45c3a78da486f673d6c1702e00728daf5258" + integrity sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ== dependencies: - chokidar "^3.2.2" - debug "^3.2.6" + chokidar "^3.5.2" + debug "^3.2.7" ignore-by-default "^1.0.1" - minimatch "^3.0.4" - pstree.remy "^1.1.7" + minimatch "^3.1.2" + pstree.remy "^1.1.8" semver "^5.7.1" + simple-update-notifier "^1.0.7" supports-color "^5.5.0" touch "^3.1.0" - undefsafe "^2.0.2" - update-notifier "^4.0.0" - -nopt@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" + undefsafe "^2.0.5" nopt@^5.0.0: version "5.0.0" @@ -2148,54 +1746,11 @@ nopt@^5.0.0: dependencies: abbrev "1" -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= - dependencies: - abbrev "1" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^4.1.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" - integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== - -npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-packlist@^1.1.6: - version "1.4.8" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" - integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-normalize-package-bin "^1.0.1" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - npmlog@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" @@ -2216,20 +1771,15 @@ npmlog@^6.0.0: gauge "^4.0.3" set-blocking "^2.0.0" -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== objection@3.0.1: version "3.0.1" @@ -2246,41 +1796,18 @@ on-finished@2.4.1: dependencies: ee-first "1.1.1" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== +on-headers@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" + integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -2298,7 +1825,7 @@ p-limit@^2.2.0: p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== dependencies: p-limit "^1.1.0" @@ -2319,27 +1846,17 @@ p-map@^4.0.0: p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== dependencies: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" @@ -2352,7 +1869,7 @@ parseurl@~1.3.3: path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== path-exists@^4.0.0: version "4.0.0" @@ -2362,57 +1879,57 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.10: - version "0.1.10" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" - integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== +path-to-regexp@0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== path@^0.12.7: version "0.12.7" resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" - integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8= + integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q== dependencies: process "^0.11.1" util "^0.10.3" -pg-cloudflare@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" - integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== +pg-cloudflare@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz#a1f3d226bab2c45ae75ea54d65ec05ac6cfafbef" + integrity sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg== pg-connection-string@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== -pg-connection-string@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37" - integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA== +pg-connection-string@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.9.1.tgz#bb1fd0011e2eb76ac17360dc8fa183b2d3465238" + integrity sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w== pg-int8@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-pool@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec" - integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g== +pg-pool@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.10.1.tgz#481047c720be2d624792100cac1816f8850d31b2" + integrity sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg== -pg-protocol@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93" - integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ== +pg-protocol@^1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.10.3.tgz#ac9e4778ad3f84d0c5670583bab976ea0a34f69f" + integrity sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ== -pg-types@^2.1.0: +pg-types@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== @@ -2424,19 +1941,19 @@ pg-types@^2.1.0: postgres-interval "^1.1.0" pg@^8.13.1: - version "8.13.1" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.13.1.tgz#6498d8b0a87ff76c2df7a32160309d3168c0c080" - integrity sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ== + version "8.16.3" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.16.3.tgz#160741d0b44fdf64680e45374b06d632e86c99fd" + integrity sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw== dependencies: - pg-connection-string "^2.7.0" - pg-pool "^3.7.0" - pg-protocol "^1.7.0" - pg-types "^2.1.0" - pgpass "1.x" + pg-connection-string "^2.9.1" + pg-pool "^3.10.1" + pg-protocol "^1.10.3" + pg-types "2.2.0" + pgpass "1.0.5" optionalDependencies: - pg-cloudflare "^1.1.1" + pg-cloudflare "^1.2.7" -pgpass@1.x: +pgpass@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== @@ -2444,19 +1961,19 @@ pgpass@1.x: split2 "^4.1.0" picomatch@^2.0.4, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== pkg-conf@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" - integrity sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg= + integrity sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g== dependencies: find-up "^2.0.0" load-json-file "^4.0.0" @@ -2483,16 +2000,6 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - -printj@~1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" - integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -2501,7 +2008,7 @@ process-nextick-args@~2.0.0: process@^0.11.1: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== promise-inflight@^1.0.1: version "1.0.1" @@ -2524,38 +2031,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -pstree.remy@^1.1.7: +pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -pupa@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" - integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== - dependencies: - escape-goat "^2.0.0" - -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - qs@6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" @@ -2566,7 +2046,7 @@ qs@6.13.0: querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== range-parser@~1.2.1: version "1.2.1" @@ -2583,20 +2063,10 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.2.7, rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== +readable-stream@^2.0.0, readable-stream@^2.0.5: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -2607,25 +2077,25 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6: util-deprecate "~1.0.1" readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" util-deprecate "^1.0.1" -readdir-glob@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" - integrity sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA== +readdir-glob@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" + integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== dependencies: - minimatch "^3.0.4" + minimatch "^5.1.0" -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -2636,24 +2106,10 @@ rechoir@^0.8.0: dependencies: resolve "^1.20.0" -registry-auth-token@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.0.tgz#1d37dffda72bbecd0f581e4715540213a65eb7da" - integrity sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w== - dependencies: - rc "^1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-from-string@^2.0.2: version "2.0.2" @@ -2671,33 +2127,19 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve@^1.20.0: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.16.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== -rimraf@^2.6.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -2705,68 +2147,40 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver@^5.3.0, semver@^5.7.1: +semver@^5.7.1: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.8: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" +semver@^7.3.5, semver@^7.5.4: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" +semver@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== send@0.19.0: version "0.19.0" @@ -2792,54 +2206,67 @@ seq-queue@^0.0.5: resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== -serve-static@1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92" - integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA== +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== dependencies: - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.18.0" + send "0.19.0" -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: - call-bind "^1.0.7" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" + object-inspect "^1.13.3" -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" -signal-exit@^3.0.7: +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.0.6: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.0, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -2853,6 +2280,13 @@ signale@1.4.0: figures "^2.0.0" pkg-conf "^2.1.0" +simple-update-notifier@^1.0.7: + version "1.1.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" + integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== + dependencies: + semver "~7.0.0" + smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" @@ -2868,11 +2302,11 @@ socks-proxy-agent@^6.0.0: socks "^2.6.2" socks@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + version "2.8.7" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea" + integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== dependencies: - ip "^2.0.0" + ip-address "^10.0.1" smart-buffer "^4.2.0" split2@^4.1.0: @@ -2880,11 +2314,6 @@ split2@^4.1.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - sqlite3@5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.6.tgz#1d4fbc90fe4fbd51e952e0a90fd8f6c2b9098e97" @@ -2913,29 +2342,12 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -streamsearch@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" - integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2944,24 +2356,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -2976,35 +2370,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -3014,12 +2380,7 @@ strip-ansi@^6.0.1: strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" @@ -3029,9 +2390,9 @@ supports-color@^5.3.0, supports-color@^5.5.0: has-flag "^3.0.0" supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" @@ -3051,27 +2412,14 @@ tar-stream@^2.2.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.4.2: - version "4.4.19" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" - integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== - dependencies: - chownr "^1.1.4" - fs-minipass "^1.2.7" - minipass "^2.9.0" - minizlib "^1.3.3" - mkdirp "^0.5.5" - safe-buffer "^5.2.1" - yallist "^3.1.1" - tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: - version "6.1.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" - integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^4.0.0" + minipass "^5.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" @@ -3084,7 +2432,7 @@ tarn@^3.0.2: temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" - integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= + integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== temp-write@^4.0.0: version "4.0.0" @@ -3097,21 +2445,11 @@ temp-write@^4.0.0: temp-dir "^1.0.0" uuid "^3.3.2" -term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== - tildify@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -3125,22 +2463,15 @@ toidentifier@1.0.1: integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== touch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" - integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== - dependencies: - nopt "~1.0.10" + version "3.1.1" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -3149,19 +2480,10 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -undefsafe@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" - integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== - dependencies: - debug "^2.2.0" +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== unique-filename@^1.1.1: version "1.1.1" @@ -3177,55 +2499,15 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -update-notifier@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" - integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== - dependencies: - boxen "^4.2.0" - chalk "^3.0.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.0.0" - pupa "^2.0.1" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== util@^0.10.3: version "0.10.4" @@ -3237,7 +2519,7 @@ util@^0.10.3: utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== uuid@^3.3.2: version "3.4.0" @@ -3247,7 +2529,7 @@ uuid@^3.3.2: vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== webidl-conversions@^3.0.0: version "3.0.1" @@ -3263,9 +2545,9 @@ whatwg-url@^5.0.0: webidl-conversions "^3.0.0" which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== which@^2.0.2: version "2.0.2" @@ -3274,13 +2556,6 @@ which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" @@ -3288,13 +2563,6 @@ wide-align@^1.1.2, wide-align@^1.1.5: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -3307,22 +2575,7 @@ wrap-ansi@^6.2.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== xtend@^4.0.0: version "4.0.2" @@ -3330,14 +2583,9 @@ xtend@^4.0.0: integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" - integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== - -yallist@^3.0.0, yallist@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== yallist@^4.0.0: version "4.0.0" @@ -3370,10 +2618,10 @@ yargs@^15.4.1: yargs-parser "^18.1.2" zip-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" - integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A== + version "4.1.1" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.1.tgz#1337fe974dbaffd2fa9a1ba09662a66932bd7135" + integrity sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ== dependencies: - archiver-utils "^2.1.0" - compress-commons "^4.1.0" + archiver-utils "^3.0.4" + compress-commons "^4.1.2" readable-stream "^3.6.0" diff --git a/docker/rootfs/etc/nginx/conf.d/dev.conf b/docker/rootfs/etc/nginx/conf.d/dev.conf index edbdec8a..67efc0f8 100644 --- a/docker/rootfs/etc/nginx/conf.d/dev.conf +++ b/docker/rootfs/etc/nginx/conf.d/dev.conf @@ -12,6 +12,7 @@ server { location /api/ { add_header X-Served-By $host; + proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; @@ -23,7 +24,14 @@ server { } location / { - index index.html; - try_files $uri $uri.html $uri/ /index.html; + add_header X-Served-By $host; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header X-Forwarded-Scheme $scheme; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_pass http://127.0.0.1:5173; } } diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run index 4f203555..c8be37f9 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run @@ -15,7 +15,7 @@ if [ "$DEVELOPMENT" = 'true' ]; then log_info 'Starting frontend ...' s6-setuidgid "$PUID:$PGID" yarn install - exec s6-setuidgid "$PUID:$PGID" yarn watch + exec s6-setuidgid "$PUID:$PGID" yarn dev else exit 0 fi diff --git a/frontend/.babelrc b/frontend/.babelrc deleted file mode 100644 index 54071ecd..00000000 --- a/frontend/.babelrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "targets": { - "browsers": [ - "Chrome >= 65" - ] - }, - "debug": false, - "modules": false, - "useBuiltIns": "usage" - } - ] - ] -} \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index c8f4b4f9..8b7e5021 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,4 +1,22 @@ -dist +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + node_modules -webpack_stats.html -yarn-error.log +dist +dist-ssr +*.local + +# Editor directories and files +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/biome.json b/frontend/biome.json new file mode 100644 index 00000000..1c0d3cef --- /dev/null +++ b/frontend/biome.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false, + "includes": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "!**/dist/**/*" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "indentWidth": 4, + "lineWidth": 120, + "formatWithErrors": true + }, + "assist": { + "actions": { + "source": { + "organizeImports": { + "level": "on", + "options": { + "groups": [ + ":BUN:", + ":NODE:", + [ + "npm:*", + "npm:*/**" + ], + ":PACKAGE_WITH_PROTOCOL:", + ":URL:", + ":PACKAGE:", + [ + "/src/*", + "/src/**" + ], + [ + "/**" + ], + [ + "#*", + "#*/**" + ], + ":PATH:" + ] + } + } + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "useUniqueElementIds": "off" + }, + "suspicious": { + "noExplicitAny": "off" + }, + "performance": { + "noDelete": "off" + }, + "nursery": "off", + "a11y": { + "useSemanticElements": "off", + "useValidAnchor": "off" + }, + "style": { + "noParameterAssign": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSelfClosingElements": "error", + "useSingleVarDeclarator": "error", + "noUnusedTemplateLiteral": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error", + "noUselessElse": "error" + } + } + } +} diff --git a/frontend/check-locales.cjs b/frontend/check-locales.cjs new file mode 100755 index 00000000..6a91dcf3 --- /dev/null +++ b/frontend/check-locales.cjs @@ -0,0 +1,172 @@ +#!/usr/bin/env node + +// This file does a few things to ensure that the Locales are present and valid: +// - Ensures that the name of the locale exists in the language list +// - Ensures that each locale contains the translations used in the application +// - Ensures that there are no unused translations in the locale files +// - Also checks the error messages returned by the backend + +const allLocales = [ + ["en", "en-US"], + ["de", "de-DE"], + ["fa", "fa-IR"], +]; + +const ignoreUnused = [ + /^capability\..*$/, + /^status\..*$/, + /^type\..*$/, +]; + +const { spawnSync } = require("child_process"); +const fs = require("fs"); + +const tmp = require("tmp"); + +// Parse backend errors +const BACKEND_ERRORS_FILE = "../backend/internal/errors/errors.go"; +const BACKEND_ERRORS = []; +/* +try { + const backendErrorsContent = fs.readFileSync(BACKEND_ERRORS_FILE, "utf8"); + const backendErrorsContentRes = [ + ...backendErrorsContent.matchAll(/(?:errors|eris)\.New\("([^"]+)"\)/g), + ]; + backendErrorsContentRes.map((item) => { + BACKEND_ERRORS.push("error." + item[1]); + return null; + }); +} catch (err) { + console.log("\x1b[31m%s\x1b[0m", err); + process.exit(1); +} +*/ + +// get all translations used in frontend code +const tmpobj = tmp.fileSync({ postfix: ".json" }); +spawnSync("yarn", ["locale-extract", "--out-file", tmpobj.name]); + +const allLocalesInProject = require(tmpobj.name); + +// get list og language names and locales +const langList = require("./src/locale/src/lang-list.json"); + +// store a list of all validation errors +const allErrors = []; +const allWarnings = []; +const allKeys = []; + +const checkLangList = (fullCode) => { + const key = "locale-" + fullCode; + if (typeof langList[key] === "undefined") { + allErrors.push( + "ERROR: `" + key + "` language does not exist in lang-list.json", + ); + } +}; + +const compareLocale = (locale) => { + const projectLocaleKeys = Object.keys(allLocalesInProject); + // Check that locale contains the items used in the codebase + projectLocaleKeys.map((key) => { + if (typeof locale.data[key] === "undefined") { + allErrors.push( + "ERROR: `" + locale[0] + "` does not contain item: `" + key + "`", + ); + } + return null; + }); + // Check that locale contains all error.* items + BACKEND_ERRORS.forEach((key) => { + if (typeof locale.data[key] === "undefined") { + allErrors.push( + "ERROR: `" + locale[0] + "` does not contain item: `" + key + "`", + ); + } + return null; + }); + + // Check that locale does not contain items not used in the codebase + const localeKeys = Object.keys(locale.data); + localeKeys.map((key) => { + let ignored = false; + ignoreUnused.map((regex) => { + if (key.match(regex)) { + ignored = true; + } + return null; + }); + + if (!ignored && typeof allLocalesInProject[key] === "undefined") { + // ensure this key doesn't exist in the backend errors either + if (!BACKEND_ERRORS.includes(key)) { + allErrors.push( + "ERROR: `" + locale[0] + "` contains unused item: `" + key + "`", + ); + } + } + + // Add this key to allKeys + if (allKeys.indexOf(key) === -1) { + allKeys.push(key); + } + return null; + }); +}; + +// Checks for any keys missing from this locale, that +// have been defined in any other locales +const checkForMissing = (locale) => { + allKeys.forEach((key) => { + if (typeof locale.data[key] === "undefined") { + let ignored = false; + ignoreMissing.map((regex) => { + if (key.match(regex)) { + ignored = true; + } + return null; + }); + + if (!ignored) { + allWarnings.push( + "WARN: `" + locale[0] + "` does not contain item: `" + key + "`", + ); + } + } + return null; + }); +}; + +// Local all locale data +allLocales.map((locale, idx) => { + checkLangList(locale[1]); + allLocales[idx].data = require("./src/locale/src/" + locale[0] + ".json"); + return null; +}); + +// Verify all locale data +allLocales.map((locale) => { + compareLocale(locale); + checkForMissing(locale); + return null; +}); + +if (allErrors.length) { + allErrors.map((err) => { + console.log("\x1b[31m%s\x1b[0m", err); + return null; + }); +} +if (allWarnings.length) { + allWarnings.map((err) => { + console.log("\x1b[33m%s\x1b[0m", err); + return null; + }); +} + +if (allErrors.length) { + process.exit(1); +} + +console.log("\x1b[32m%s\x1b[0m", "Locale check passed"); +process.exit(0); diff --git a/frontend/fonts/feather b/frontend/fonts/feather deleted file mode 120000 index 440203ba..00000000 --- a/frontend/fonts/feather +++ /dev/null @@ -1 +0,0 @@ -../node_modules/tabler-ui/dist/assets/fonts/feather \ No newline at end of file diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff deleted file mode 100644 index 96d8768ea9a10f004e2215a1a674287c8c7abfe5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31740 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 diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff deleted file mode 100644 index 0829caef1eb96f768bedf713c983e2a3904ad835..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28540 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 diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 deleted file mode 100644 index 7c901cd8450cecf93767560eecf4a3dc6b0cefb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22240 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 diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff deleted file mode 100644 index 99652481a07f237145d733e06e6b2c0b926dfafc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28744 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 DnjJ_~ diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 deleted file mode 100644 index 343e5ba8d3f924aef2e589f25657cb97ff423ef5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22436 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 diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 deleted file mode 100644 index d552543be16ee93a7c66358c9cc80540a3b11860..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25656 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 jcp0p9&3Un?g -<%- include partials/header.ejs %> - -

- -
- - -<%- include partials/footer.ejs %> diff --git a/frontend/html/login.ejs b/frontend/html/login.ejs deleted file mode 100644 index bc4b9a27..00000000 --- a/frontend/html/login.ejs +++ /dev/null @@ -1,9 +0,0 @@ -<% var title = 'Login – Nginx Proxy Manager' %> -<%- include partials/header.ejs %> - -
- -
- - -<%- include partials/footer.ejs %> diff --git a/frontend/html/partials/footer.ejs b/frontend/html/partials/footer.ejs deleted file mode 100644 index 7fb2bd61..00000000 --- a/frontend/html/partials/footer.ejs +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/frontend/html/partials/header.ejs b/frontend/html/partials/header.ejs deleted file mode 100644 index cabb9df2..00000000 --- a/frontend/html/partials/header.ejs +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - <%- title %> - - - - - - - - - - - - - - diff --git a/frontend/images b/frontend/images deleted file mode 120000 index 37c31854..00000000 --- a/frontend/images +++ /dev/null @@ -1 +0,0 @@ -./node_modules/tabler-ui/dist/assets/images \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 00000000..80818044 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,44 @@ + + + + + + Nginx Proxy Manager + + + + + + + + + + + + + +
+ + + + diff --git a/frontend/js/app/api.js b/frontend/js/app/api.js deleted file mode 100644 index 6e33a6dc..00000000 --- a/frontend/js/app/api.js +++ /dev/null @@ -1,757 +0,0 @@ -const $ = require('jquery'); -const _ = require('underscore'); -const Tokens = require('./tokens'); - -/** - * @param {String} message - * @param {*} debug - * @param {Number} code - * @constructor - */ -const ApiError = function (message, debug, code) { - let temp = Error.call(this, message); - temp.name = this.name = 'ApiError'; - this.stack = temp.stack; - this.message = temp.message; - this.debug = debug; - this.code = code; -}; - -ApiError.prototype = Object.create(Error.prototype, { - constructor: { - value: ApiError, - writable: true, - configurable: true - } -}); - -/** - * - * @param {String} verb - * @param {String} path - * @param {Object} [data] - * @param {Object} [options] - * @returns {Promise} - */ -function fetch(verb, path, data, options) { - options = options || {}; - - return new Promise(function (resolve, reject) { - let api_url = '/api/'; - let url = api_url + path; - let token = Tokens.getTopToken(); - - if ((typeof options.contentType === 'undefined' || options.contentType.match(/json/im)) && typeof data === 'object') { - data = JSON.stringify(data); - } - - $.ajax({ - url: url, - data: typeof data === 'object' ? JSON.stringify(data) : data, - type: verb, - dataType: 'json', - contentType: options.contentType || 'application/json; charset=UTF-8', - processData: options.processData || true, - crossDomain: true, - timeout: options.timeout ? options.timeout : 180000, - xhrFields: { - withCredentials: true - }, - - beforeSend: function (xhr) { - xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); - }, - - success: function (data, textStatus, response) { - let total = response.getResponseHeader('X-Dataset-Total'); - if (total !== null) { - resolve({ - data: data, - pagination: { - total: parseInt(total, 10), - offset: parseInt(response.getResponseHeader('X-Dataset-Offset'), 10), - limit: parseInt(response.getResponseHeader('X-Dataset-Limit'), 10) - } - }); - } else { - resolve(response); - } - }, - - error: function (xhr, status, error_thrown) { - let code = 400; - - if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { - error_thrown = xhr.responseJSON.error.message; - code = xhr.responseJSON.error.code || 500; - } - - reject(new ApiError(error_thrown, xhr.responseText, code)); - } - }); - }); -} - -/** - * - * @param {Array} expand - * @returns {String} - */ -function makeExpansionString(expand) { - let items = []; - _.forEach(expand, function (exp) { - items.push(encodeURIComponent(exp)); - }); - - return items.join(','); -} - -/** - * @param {String} path - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ -function getAllObjects(path, expand, query) { - let params = []; - - if (typeof expand === 'object' && expand !== null && expand.length) { - params.push('expand=' + makeExpansionString(expand)); - } - - if (typeof query === 'string') { - params.push('query=' + query); - } - - return fetch('get', path + (params.length ? '?' + params.join('&') : '')); -} - -function FileUpload(path, fd) { - return new Promise((resolve, reject) => { - let xhr = new XMLHttpRequest(); - let token = Tokens.getTopToken(); - - xhr.open('POST', '/api/' + path); - xhr.overrideMimeType('text/plain'); - xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); - xhr.send(fd); - - xhr.onreadystatechange = function () { - if (this.readyState === XMLHttpRequest.DONE) { - if (xhr.status !== 200 && xhr.status !== 201) { - try { - reject(new Error('Upload failed: ' + JSON.parse(xhr.responseText).error.message)); - } catch (err) { - reject(new Error('Upload failed: ' + xhr.status)); - } - } else { - resolve(xhr.responseText); - } - } - }; - }); -} - -//ref : https://codepen.io/chrisdpratt/pen/RKxJNo -function DownloadFile(verb, path, filename) { - return new Promise(function (resolve, reject) { - let api_url = '/api/'; - let url = api_url + path; - let token = Tokens.getTopToken(); - - $.ajax({ - url: url, - type: verb, - crossDomain: true, - xhrFields: { - withCredentials: true, - responseType: 'blob' - }, - - beforeSend: function (xhr) { - xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); - }, - - success: function (data) { - var a = document.createElement('a'); - var url = window.URL.createObjectURL(data); - a.href = url; - a.download = filename; - document.body.append(a); - a.click(); - a.remove(); - window.URL.revokeObjectURL(url); - }, - - error: function (xhr, status, error_thrown) { - let code = 400; - - if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { - error_thrown = xhr.responseJSON.error.message; - code = xhr.responseJSON.error.code || 500; - } - - reject(new ApiError(error_thrown, xhr.responseText, code)); - } - }); - }); -} - -module.exports = { - status: function () { - return fetch('get', ''); - }, - - Tokens: { - - /** - * @param {String} identity - * @param {String} secret - * @param {Boolean} [wipe] Will wipe the stack before adding to it again if login was successful - * @returns {Promise} - */ - login: function (identity, secret, wipe) { - return fetch('post', 'tokens', {identity: identity, secret: secret}) - .then(response => { - if (response.token) { - if (wipe) { - Tokens.clearTokens(); - } - - // Set storage token - Tokens.addToken(response.token); - return response.token; - } else { - Tokens.clearTokens(); - throw(new Error('No token returned')); - } - }); - }, - - /** - * @returns {Promise} - */ - refresh: function () { - return fetch('get', 'tokens') - .then(response => { - if (response.token) { - Tokens.setCurrentToken(response.token); - return response.token; - } else { - Tokens.clearTokens(); - throw(new Error('No token returned')); - } - }); - } - }, - - Users: { - - /** - * @param {Number|String} user_id - * @param {Array} [expand] - * @returns {Promise} - */ - getById: function (user_id, expand) { - return fetch('get', 'users/' + user_id + (typeof expand === 'object' && expand.length ? '?expand=' + makeExpansionString(expand) : '')); - }, - - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('users', expand, query); - }, - - /** - * @param {Object} data - * @returns {Promise} - */ - create: function (data) { - return fetch('post', 'users', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'users/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'users/' + id); - }, - - /** - * - * @param {Number} id - * @param {Object} auth - * @returns {Promise} - */ - setPassword: function (id, auth) { - return fetch('put', 'users/' + id + '/auth', auth); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - loginAs: function (id) { - return fetch('post', 'users/' + id + '/login'); - }, - - /** - * - * @param {Number} id - * @param {Object} perms - * @returns {Promise} - */ - setPermissions: function (id, perms) { - return fetch('put', 'users/' + id + '/permissions', perms); - } - }, - - Nginx: { - - ProxyHosts: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/proxy-hosts', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/proxy-hosts', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/proxy-hosts/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/proxy-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/proxy-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/proxy-hosts/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/proxy-hosts/' + id + '/disable'); - } - }, - - RedirectionHosts: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/redirection-hosts', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/redirection-hosts', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/redirection-hosts/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/redirection-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/redirection-hosts/' + id); - }, - - /** - * @param {Number} id - * @param {FormData} form_data - * @params {Promise} - */ - setCerts: function (id, form_data) { - return FileUpload('nginx/redirection-hosts/' + id + '/certificates', form_data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/redirection-hosts/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/redirection-hosts/' + id + '/disable'); - } - }, - - Streams: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/streams', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/streams', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/streams/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/streams/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/streams/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/streams/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/streams/' + id + '/disable'); - } - }, - - DeadHosts: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/dead-hosts', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/dead-hosts', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/dead-hosts/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/dead-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/dead-hosts/' + id); - }, - - /** - * @param {Number} id - * @param {FormData} form_data - * @params {Promise} - */ - setCerts: function (id, form_data) { - return FileUpload('nginx/dead-hosts/' + id + '/certificates', form_data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/dead-hosts/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/dead-hosts/' + id + '/disable'); - } - }, - - AccessLists: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/access-lists', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/access-lists', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/access-lists/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/access-lists/' + id); - } - }, - - Certificates: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/certificates', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - - const timeout = 180000 + (data && data.meta && data.meta.propagation_seconds ? Number(data.meta.propagation_seconds) * 1000 : 0); - return fetch('post', 'nginx/certificates', data, {timeout}); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/certificates/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/certificates/' + id); - }, - - /** - * @param {Number} id - * @param {FormData} form_data - * @params {Promise} - */ - upload: function (id, form_data) { - return FileUpload('nginx/certificates/' + id + '/upload', form_data); - }, - - /** - * @param {FormData} form_data - * @params {Promise} - */ - validate: function (form_data) { - return FileUpload('nginx/certificates/validate', form_data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - renew: function (id, timeout = 180000) { - return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout}); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - testHttpChallenge: function (domains) { - return fetch('get', 'nginx/certificates/test-http?' + new URLSearchParams({ - domains: JSON.stringify(domains), - })); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - download: function (id) { - return DownloadFile('get', "nginx/certificates/" + id + "/download", "certificate.zip") - } - } - }, - - AuditLog: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('audit-log', expand, query); - } - }, - - Reports: { - - /** - * @returns {Promise} - */ - getHostStats: function () { - return fetch('get', 'reports/hosts'); - } - }, - - Settings: { - - /** - * @param {String} setting_id - * @returns {Promise} - */ - getById: function (setting_id) { - return fetch('get', 'settings/' + setting_id); - }, - - /** - * @returns {Promise} - */ - getAll: function () { - return getAllObjects('settings'); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'settings/' + id, data); - } - } -}; diff --git a/frontend/js/app/audit-log/list/item.ejs b/frontend/js/app/audit-log/list/item.ejs deleted file mode 100644 index 84743c8d..00000000 --- a/frontend/js/app/audit-log/list/item.ejs +++ /dev/null @@ -1,80 +0,0 @@ - -
- -
- - -
- <% if (user.is_deleted) { - %> - <%- user.name %> - <% - } else { - %> - <%- user.name %> - <% - } - %> -
- - -
- <% - var items = []; - switch (object_type) { - case 'proxy-host': - %> <% - items = meta.domain_names; - break; - case 'redirection-host': - %> <% - items = meta.domain_names; - break; - case 'stream': - %> <% - items.push(meta.incoming_port); - break; - case 'dead-host': - %> <% - items = meta.domain_names; - break; - case 'access-list': - %> <% - items.push(meta.name); - break; - case 'user': - %> <% - items.push(meta.name); - break; - case 'certificate': - %> <% - if (meta.provider === 'letsencrypt') { - items = meta.domain_names; - } else { - items.push(meta.nice_name); - } - break; - } - %> <%- i18n('audit-log', action, {name: i18n('audit-log', object_type)}) %> - — - <% - if (items && items.length) { - items.map(function(item) { - %> - <%- item %> - <% - }); - } else { - %> - #<%- object_id %> - <% - } - %> -
-
- <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %> -
- - - <%- i18n('audit-log', 'view-meta') %> - diff --git a/frontend/js/app/audit-log/list/item.js b/frontend/js/app/audit-log/list/item.js deleted file mode 100644 index 862ffc22..00000000 --- a/frontend/js/app/audit-log/list/item.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const Controller = require('../../controller'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - meta: 'a.meta' - }, - - events: { - 'click @ui.meta': function (e) { - e.preventDefault(); - Controller.showAuditMeta(this.model); - } - }, - - templateContext: { - more: function() { - switch (this.object_type) { - case 'redirection-host': - case 'stream': - case 'proxy-host': - return this.meta.domain_names.join(', '); - } - - return '#' + (this.object_id || '?'); - } - } -}); diff --git a/frontend/js/app/audit-log/list/main.ejs b/frontend/js/app/audit-log/list/main.ejs deleted file mode 100644 index ec3cf2a2..00000000 --- a/frontend/js/app/audit-log/list/main.ejs +++ /dev/null @@ -1,9 +0,0 @@ - -   - User - Event -   - - - - diff --git a/frontend/js/app/audit-log/list/main.js b/frontend/js/app/audit-log/list/main.js deleted file mode 100644 index 9d3e26fb..00000000 --- a/frontend/js/app/audit-log/list/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/audit-log/main.ejs b/frontend/js/app/audit-log/main.ejs deleted file mode 100644 index 8d182b59..00000000 --- a/frontend/js/app/audit-log/main.ejs +++ /dev/null @@ -1,25 +0,0 @@ -
-
-
-

<%- i18n('audit-log', 'title') %>

-
- -
-
-
-
-
-
- -
-
- -
-
diff --git a/frontend/js/app/audit-log/main.js b/frontend/js/app/audit-log/main.js deleted file mode 100644 index 0d03c5ca..00000000 --- a/frontend/js/app/audit-log/main.js +++ /dev/null @@ -1,82 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const AuditLogModel = require('../../models/audit-log'); -const ListView = require('./list/main'); -const template = require('./main.ejs'); -const ErrorView = require('../error/main'); -const EmptyView = require('../empty/main'); - -module.exports = Mn.View.extend({ - id: 'audit-log', - template: template, - - ui: { - list_region: '.list-region', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.AuditLog.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new AuditLogModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showAuditLog(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - this.showChildView('list_region', new EmptyView({ - title: App.i18n('audit-log', 'empty'), - subtitle: App.i18n('audit-log', 'empty-subtitle') - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['user'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - onRender: function () { - let view = this; - - view.fetch(['user']) - .then(response => { - if (!view.isDestroyed() && response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/audit-log/meta.ejs b/frontend/js/app/audit-log/meta.ejs deleted file mode 100644 index 98a2d973..00000000 --- a/frontend/js/app/audit-log/meta.ejs +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/frontend/js/app/audit-log/meta.js b/frontend/js/app/audit-log/meta.js deleted file mode 100644 index 815cdfac..00000000 --- a/frontend/js/app/audit-log/meta.js +++ /dev/null @@ -1,7 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./meta.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog wide' -}); diff --git a/frontend/js/app/cache.js b/frontend/js/app/cache.js deleted file mode 100644 index 6d1fbc4f..00000000 --- a/frontend/js/app/cache.js +++ /dev/null @@ -1,10 +0,0 @@ -const UserModel = require('../models/user'); - -let cache = { - User: new UserModel.Model(), - locale: 'en', - version: null -}; - -module.exports = cache; - diff --git a/frontend/js/app/controller.js b/frontend/js/app/controller.js deleted file mode 100644 index ebddd780..00000000 --- a/frontend/js/app/controller.js +++ /dev/null @@ -1,441 +0,0 @@ -const Backbone = require('backbone'); -const Cache = require('./cache'); -const Tokens = require('./tokens'); - -module.exports = { - - /** - * @param {String} route - * @param {Object} [options] - * @returns {Boolean} - */ - navigate: function (route, options) { - options = options || {}; - Backbone.history.navigate(route.toString(), options); - return true; - }, - - /** - * Login - */ - showLogin: function () { - window.location = '/login'; - }, - - /** - * Users - */ - showUsers: function () { - const controller = this; - if (Cache.User.isAdmin()) { - require(['./main', './users/main'], (App, View) => { - controller.navigate('/users'); - App.UI.showAppContent(new View()); - }); - } else { - this.showDashboard(); - } - }, - - /** - * User Form - * - * @param [model] - */ - showUserForm: function (model) { - if (Cache.User.isAdmin()) { - require(['./main', './user/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * User Permissions Form - * - * @param model - */ - showUserPermissions: function (model) { - if (Cache.User.isAdmin()) { - require(['./main', './user/permissions'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * User Password Form - * - * @param model - */ - showUserPasswordForm: function (model) { - if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) { - require(['./main', './user/password'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * User Delete Confirm - * - * @param model - */ - showUserDeleteConfirm: function (model) { - if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) { - require(['./main', './user/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Dashboard - */ - showDashboard: function () { - const controller = this; - require(['./main', './dashboard/main'], (App, View) => { - controller.navigate('/'); - App.UI.showAppContent(new View()); - }); - }, - - /** - * Nginx Proxy Hosts - */ - showNginxProxy: function () { - if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) { - const controller = this; - - require(['./main', './nginx/proxy/main'], (App, View) => { - controller.navigate('/nginx/proxy'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Proxy Host Form - * - * @param [model] - */ - showNginxProxyForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { - require(['./main', './nginx/proxy/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Proxy Host Delete Confirm - * - * @param model - */ - showNginxProxyDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { - require(['./main', './nginx/proxy/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Redirection Hosts - */ - showNginxRedirection: function () { - if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) { - const controller = this; - require(['./main', './nginx/redirection/main'], (App, View) => { - controller.navigate('/nginx/redirection'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Redirection Host Form - * - * @param [model] - */ - showNginxRedirectionForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { - require(['./main', './nginx/redirection/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Proxy Redirection Delete Confirm - * - * @param model - */ - showNginxRedirectionDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { - require(['./main', './nginx/redirection/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Stream Hosts - */ - showNginxStream: function () { - if (Cache.User.isAdmin() || Cache.User.canView('streams')) { - const controller = this; - require(['./main', './nginx/stream/main'], (App, View) => { - controller.navigate('/nginx/stream'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Stream Form - * - * @param [model] - */ - showNginxStreamForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { - require(['./main', './nginx/stream/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Stream Delete Confirm - * - * @param model - */ - showNginxStreamDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { - require(['./main', './nginx/stream/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Dead Hosts - */ - showNginxDead: function () { - if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) { - const controller = this; - require(['./main', './nginx/dead/main'], (App, View) => { - controller.navigate('/nginx/404'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Dead Host Form - * - * @param [model] - */ - showNginxDeadForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { - require(['./main', './nginx/dead/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Dead Host Delete Confirm - * - * @param model - */ - showNginxDeadDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { - require(['./main', './nginx/dead/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Help Dialog - * - * @param {String} title - * @param {String} content - */ - showHelp: function (title, content) { - require(['./main', './help/main'], function (App, View) { - App.UI.showModalDialog(new View({title: title, content: content})); - }); - }, - - /** - * Nginx Access - */ - showNginxAccess: function () { - if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) { - const controller = this; - require(['./main', './nginx/access/main'], (App, View) => { - controller.navigate('/nginx/access'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Access List Form - * - * @param [model] - */ - showNginxAccessListForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { - require(['./main', './nginx/access/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Access List Delete Confirm - * - * @param model - */ - showNginxAccessListDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { - require(['./main', './nginx/access/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Certificates - */ - showNginxCertificates: function () { - if (Cache.User.isAdmin() || Cache.User.canView('certificates')) { - const controller = this; - require(['./main', './nginx/certificates/main'], (App, View) => { - controller.navigate('/nginx/certificates'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Certificate Form - * - * @param [model] - */ - showNginxCertificateForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Certificate Renew - * - * @param model - */ - showNginxCertificateRenew: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/renew'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Certificate Delete Confirm - * - * @param model - */ - showNginxCertificateDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Certificate Test Reachability - * - * @param model - */ - showNginxCertificateTestReachability: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/test'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Audit Log - */ - showAuditLog: function () { - const controller = this; - if (Cache.User.isAdmin()) { - require(['./main', './audit-log/main'], (App, View) => { - controller.navigate('/audit-log'); - App.UI.showAppContent(new View()); - }); - } else { - this.showDashboard(); - } - }, - - /** - * Audit Log Metadata - * - * @param model - */ - showAuditMeta: function (model) { - if (Cache.User.isAdmin()) { - require(['./main', './audit-log/meta'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Settings - */ - showSettings: function () { - const controller = this; - if (Cache.User.isAdmin()) { - require(['./main', './settings/main'], (App, View) => { - controller.navigate('/settings'); - App.UI.showAppContent(new View()); - }); - } else { - this.showDashboard(); - } - }, - - /** - * Settings Item Form - * - * @param model - */ - showSettingForm: function (model) { - if (Cache.User.isAdmin()) { - if (model.get('id') === 'default-site') { - require(['./main', './settings/default-site/main'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - } - }, - - /** - * Logout - */ - logout: function () { - Tokens.dropTopToken(); - this.showLogin(); - } -}; diff --git a/frontend/js/app/dashboard/main.ejs b/frontend/js/app/dashboard/main.ejs deleted file mode 100644 index c00aa6d0..00000000 --- a/frontend/js/app/dashboard/main.ejs +++ /dev/null @@ -1,67 +0,0 @@ - - -<% if (columns) { %> -
- <% if (canShow('proxy_hosts')) { %> - - <% } %> - - <% if (canShow('redirection_hosts')) { %> - - <% } %> - - <% if (canShow('streams')) { %> - - <% } %> - - <% if (canShow('dead_hosts')) { %> - - <% } %> -
-<% } %> diff --git a/frontend/js/app/dashboard/main.js b/frontend/js/app/dashboard/main.js deleted file mode 100644 index ba4a99a6..00000000 --- a/frontend/js/app/dashboard/main.js +++ /dev/null @@ -1,90 +0,0 @@ -const Mn = require('backbone.marionette'); -const Cache = require('../cache'); -const Controller = require('../controller'); -const Api = require('../api'); -const Helpers = require('../../lib/helpers'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - template: template, - id: 'dashboard', - columns: 0, - - stats: {}, - - ui: { - links: 'a' - }, - - events: { - 'click @ui.links': function (e) { - e.preventDefault(); - Controller.navigate($(e.currentTarget).attr('href'), true); - } - }, - - templateContext: function () { - const view = this; - - return { - getUserName: function () { - return Cache.User.get('nickname') || Cache.User.get('name'); - }, - - getHostStat: function (type) { - if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') { - return Helpers.niceNumber(view.stats.hosts[type]); - } - - return '-'; - }, - - canShow: function (perm) { - return Cache.User.isAdmin() || Cache.User.canView(perm); - }, - - columns: view.columns - }; - }, - - onRender: function () { - const view = this; - if (typeof view.stats.hosts === 'undefined') { - Api.Reports.getHostStats() - .then(response => { - if (!view.isDestroyed()) { - view.stats.hosts = response; - view.render(); - } - }) - .catch(err => { - console.log(err); - }); - } - }, - - /** - * @param {Object} [model] - */ - preRender: function (model) { - this.columns = 0; - - // calculate the available columns based on permissions for the objects - // and store as a variable - const perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts']; - - perms.map(perm => { - this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0; - }); - - // Prevent double rendering on initial calls - if (typeof model !== 'undefined') { - this.render(); - } - }, - - initialize: function () { - this.preRender(); - this.listenTo(Cache.User, 'change', this.preRender); - } -}); diff --git a/frontend/js/app/empty/main.ejs b/frontend/js/app/empty/main.ejs deleted file mode 100644 index 11633dfc..00000000 --- a/frontend/js/app/empty/main.ejs +++ /dev/null @@ -1,11 +0,0 @@ -<% if (title) { %> -

<%- title %>

-<% } - -if (subtitle) { %> -

<%- subtitle %>

-<% } - -if (link) { %> - <%- link %> -<% } %> diff --git a/frontend/js/app/empty/main.js b/frontend/js/app/empty/main.js deleted file mode 100644 index 74998d65..00000000 --- a/frontend/js/app/empty/main.js +++ /dev/null @@ -1,33 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - className: 'text-center m-7', - template: template, - - options: { - btn_color: 'teal' - }, - - ui: { - action: 'a' - }, - - events: { - 'click @ui.action': function (e) { - e.preventDefault(); - this.getOption('action')(); - } - }, - - templateContext: function () { - return { - title: this.getOption('title'), - subtitle: this.getOption('subtitle'), - link: this.getOption('link'), - action: typeof this.getOption('action') === 'function', - btn_color: this.getOption('btn_color') - }; - } - -}); diff --git a/frontend/js/app/error/main.ejs b/frontend/js/app/error/main.ejs deleted file mode 100644 index f7fd709b..00000000 --- a/frontend/js/app/error/main.ejs +++ /dev/null @@ -1,7 +0,0 @@ - -<%= code ? '' + code + ' — ' : '' %> -<%- message %> - -<% if (retry) { %> -

<%- i18n('str', 'try-again') %> -<% } %> diff --git a/frontend/js/app/error/main.js b/frontend/js/app/error/main.js deleted file mode 100644 index 6fa85fc8..00000000 --- a/frontend/js/app/error/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'alert alert-icon alert-warning m-5', - - ui: { - retry: 'a.retry' - }, - - events: { - 'click @ui.retry': function (e) { - e.preventDefault(); - this.getOption('retry')(); - } - }, - - templateContext: function () { - return { - message: this.getOption('message'), - code: this.getOption('code'), - retry: typeof this.getOption('retry') === 'function' - }; - } - -}); diff --git a/frontend/js/app/help/main.ejs b/frontend/js/app/help/main.ejs deleted file mode 100644 index 6fb79e66..00000000 --- a/frontend/js/app/help/main.ejs +++ /dev/null @@ -1,12 +0,0 @@ - diff --git a/frontend/js/app/help/main.js b/frontend/js/app/help/main.js deleted file mode 100644 index b0f54374..00000000 --- a/frontend/js/app/help/main.js +++ /dev/null @@ -1,16 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog wide', - - templateContext: function () { - let content = this.getOption('content').split("\n"); - - return { - title: this.getOption('title'), - content: '

' + content.join('

') + '

' - }; - } -}); diff --git a/frontend/js/app/i18n.js b/frontend/js/app/i18n.js deleted file mode 100644 index c63cdc07..00000000 --- a/frontend/js/app/i18n.js +++ /dev/null @@ -1,23 +0,0 @@ -const Cache = ('./cache'); -const messages = require('../i18n/messages.json'); - -/** - * @param {String} namespace - * @param {String} key - * @param {Object} [data] - */ -module.exports = function (namespace, key, data) { - let locale = Cache.locale; - // check that the locale exists - if (typeof messages[locale] === 'undefined') { - locale = 'en'; - } - - if (typeof messages[locale][namespace] !== 'undefined' && typeof messages[locale][namespace][key] !== 'undefined') { - return messages[locale][namespace][key](data); - } else if (locale !== 'en' && typeof messages['en'][namespace] !== 'undefined' && typeof messages['en'][namespace][key] !== 'undefined') { - return messages['en'][namespace][key](data); - } - - return '(MISSING: ' + namespace + '/' + key + ')'; -}; diff --git a/frontend/js/app/main.js b/frontend/js/app/main.js deleted file mode 100644 index e85b4f62..00000000 --- a/frontend/js/app/main.js +++ /dev/null @@ -1,155 +0,0 @@ -const _ = require('underscore'); -const Backbone = require('backbone'); -const Mn = require('../lib/marionette'); -const Cache = require('./cache'); -const Controller = require('./controller'); -const Router = require('./router'); -const Api = require('./api'); -const Tokens = require('./tokens'); -const UI = require('./ui/main'); -const i18n = require('./i18n'); - -const App = Mn.Application.extend({ - - Cache: Cache, - Api: Api, - UI: null, - i18n: i18n, - Controller: Controller, - - region: { - el: '#app', - replaceElement: true - }, - - onStart: function (app, options) { - console.log(i18n('main', 'welcome')); - - // Check if token is coming through - if (this.getParam('token')) { - Tokens.addToken(this.getParam('token')); - } - - // Check if we are still logged in by refreshing the token - Api.status() - .then(result => { - Cache.version = [result.version.major, result.version.minor, result.version.revision].join('.'); - }) - .then(Api.Tokens.refresh) - .then(this.bootstrap) - .then(() => { - console.info(i18n('main', 'logged-in', Cache.User.attributes)); - this.bootstrapTimer(); - this.refreshTokenTimer(); - - this.UI = new UI(); - this.UI.on('render', () => { - new Router(options); - Backbone.history.start({pushState: true}); - - // Ask the admin use to change their details - if (Cache.User.get('email') === 'admin@example.com') { - Controller.showUserForm(Cache.User); - } - }); - - this.getRegion().show(this.UI); - }) - .catch(err => { - console.warn('Not logged in:', err.message); - Controller.showLogin(); - }); - }, - - History: { - replace: function (data) { - window.history.replaceState(_.extend(window.history.state || {}, data), document.title); - }, - - get: function (attr) { - return window.history.state ? window.history.state[attr] : undefined; - } - }, - - getParam: function (name) { - name = name.replace(/[\[\]]/g, '\\$&'); - let url = window.location.href; - let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); - let results = regex.exec(url); - - if (!results) { - return null; - } - - if (!results[2]) { - return ''; - } - - return decodeURIComponent(results[2].replace(/\+/g, ' ')); - }, - - /** - * Get user and other base info to start prime the cache and the application - * - * @returns {Promise} - */ - bootstrap: function () { - return Api.Users.getById('me', ['permissions']) - .then(response => { - Cache.User.set(response); - Tokens.setCurrentName(response.nickname || response.name); - }); - }, - - /** - * Bootstraps the user from time to time - */ - bootstrapTimer: function () { - setTimeout(() => { - Api.status() - .then(result => { - let version = [result.version.major, result.version.minor, result.version.revision].join('.'); - if (version !== Cache.version) { - document.location.reload(); - } - }) - .then(this.bootstrap) - .then(() => { - this.bootstrapTimer(); - }) - .catch(err => { - if (err.message !== 'timeout' && err.code && err.code !== 400) { - console.log(err); - console.error(err.message); - console.info('Not logged in?'); - Controller.showLogin(); - } else { - this.bootstrapTimer(); - } - }); - }, 30 * 1000); // 30 seconds - }, - - refreshTokenTimer: function () { - setTimeout(() => { - return Api.Tokens.refresh() - .then(this.bootstrap) - .then(() => { - this.refreshTokenTimer(); - }) - .catch(err => { - if (err.message !== 'timeout' && err.code && err.code !== 400) { - console.log(err); - console.error(err.message); - console.info('Not logged in?'); - Controller.showLogin(); - } else { - this.refreshTokenTimer(); - } - }); - }, 10 * 60 * 1000); - } -}); - -const app = new App(); -module.exports = app; diff --git a/frontend/js/app/nginx/access/delete.ejs b/frontend/js/app/nginx/access/delete.ejs deleted file mode 100644 index 3833549a..00000000 --- a/frontend/js/app/nginx/access/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/access/delete.js b/frontend/js/app/nginx/access/delete.js deleted file mode 100644 index 4af91ab1..00000000 --- a/frontend/js/app/nginx/access/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.AccessLists.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxAccess(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/access/form.ejs b/frontend/js/app/nginx/access/form.ejs deleted file mode 100644 index 79220b14..00000000 --- a/frontend/js/app/nginx/access/form.ejs +++ /dev/null @@ -1,108 +0,0 @@ - diff --git a/frontend/js/app/nginx/access/form.js b/frontend/js/app/nginx/access/form.js deleted file mode 100644 index bb075548..00000000 --- a/frontend/js/app/nginx/access/form.js +++ /dev/null @@ -1,153 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const AccessListModel = require('../../../models/access-list'); -const template = require('./form.ejs'); -const ItemView = require('./form/item'); -const ClientView = require('./form/client'); - -require('jquery-serializejson'); - -const ItemsView = Mn.CollectionView.extend({ - childView: ItemView -}); - -const ClientsView = Mn.CollectionView.extend({ - childView: ClientView -}); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - items_region: '.items', - clients_region: '.clients', - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - access_add: 'button.access_add', - auth_add: 'button.auth_add' - }, - - regions: { - items_region: '@ui.items_region', - clients_region: '@ui.clients_region' - }, - - events: { - 'click @ui.save': function (e) { - e.preventDefault(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let form_data = this.ui.form.serializeJSON(); - let items_data = []; - let clients_data = []; - - form_data.username.map(function (val, idx) { - if (val.trim().length) { - items_data.push({ - username: val.trim(), - password: form_data.password[idx] - }); - } - }); - - form_data.address.map(function (val, idx) { - if (val.trim().length) { - clients_data.push({ - address: val.trim(), - directive: form_data.directive[idx] - }) - } - }); - - if (!items_data.length && !clients_data.length) { - alert('You must specify at least 1 Authorization or Access rule'); - return; - } - - let data = { - name: form_data.name, - satisfy_any: !!form_data.satisfy_any, - pass_auth: !!form_data.pass_auth, - items: items_data, - clients: clients_data - }; - - console.log(data); - - let method = App.Api.Nginx.AccessLists.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.AccessLists.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxAccess(); - } - }); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - }, - 'click @ui.access_add': function (e) { - e.preventDefault(); - - let clients = this.model.get('clients'); - clients.push({}); - this.showChildView('clients_region', new ClientsView({ - collection: new Backbone.Collection(clients) - })); - }, - 'click @ui.auth_add': function (e) { - e.preventDefault(); - - let items = this.model.get('items'); - items.push({}); - this.showChildView('items_region', new ItemsView({ - collection: new Backbone.Collection(items) - })); - } - }, - - onRender: function () { - let items = this.model.get('items'); - let clients = this.model.get('clients'); - - // Ensure at least one field is shown initally - if (!items.length) items.push({}); - if (!clients.length) clients.push({}); - - this.showChildView('items_region', new ItemsView({ - collection: new Backbone.Collection(items) - })); - - this.showChildView('clients_region', new ClientsView({ - collection: new Backbone.Collection(clients) - })); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new AccessListModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/access/form/client.ejs b/frontend/js/app/nginx/access/form/client.ejs deleted file mode 100644 index 6b767b83..00000000 --- a/frontend/js/app/nginx/access/form/client.ejs +++ /dev/null @@ -1,13 +0,0 @@ -
-
- -
-
-
-
- -
-
diff --git a/frontend/js/app/nginx/access/form/client.js b/frontend/js/app/nginx/access/form/client.js deleted file mode 100644 index b4c00e2e..00000000 --- a/frontend/js/app/nginx/access/form/client.js +++ /dev/null @@ -1,7 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./client.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'row' -}); diff --git a/frontend/js/app/nginx/access/form/item.ejs b/frontend/js/app/nginx/access/form/item.ejs deleted file mode 100644 index c2435ecb..00000000 --- a/frontend/js/app/nginx/access/form/item.ejs +++ /dev/null @@ -1,10 +0,0 @@ -
-
- -
-
-
-
- -
-
diff --git a/frontend/js/app/nginx/access/form/item.js b/frontend/js/app/nginx/access/form/item.js deleted file mode 100644 index f15238dc..00000000 --- a/frontend/js/app/nginx/access/form/item.js +++ /dev/null @@ -1,7 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'row' -}); diff --git a/frontend/js/app/nginx/access/list/item.ejs b/frontend/js/app/nginx/access/list/item.ejs deleted file mode 100644 index fe043c98..00000000 --- a/frontend/js/app/nginx/access/list/item.ejs +++ /dev/null @@ -1,42 +0,0 @@ - -
- -
- - -
- <%- name %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - - <%- i18n('access-lists', 'item-count', {count: items.length || 0}) %> - - - <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> - - - <% if (satisfy_any) { %> - <%- i18n('str', 'any') %> - <%} else { %> - <%- i18n('str', 'all') %> - <% } %> - - - <%- i18n('access-lists', 'proxy-host-count', {count: proxy_host_count}) %> - -<% if (canManage) { %> - - - -<% } %> diff --git a/frontend/js/app/nginx/access/list/item.js b/frontend/js/app/nginx/access/list/item.js deleted file mode 100644 index 4f68aead..00000000 --- a/frontend/js/app/nginx/access/list/item.js +++ /dev/null @@ -1,33 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - edit: 'a.edit', - delete: 'a.delete' - }, - - events: { - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxAccessListForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxAccessListDeleteConfirm(this.model); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('access_lists') - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/access/list/main.ejs b/frontend/js/app/nginx/access/list/main.ejs deleted file mode 100644 index 7988e0c2..00000000 --- a/frontend/js/app/nginx/access/list/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ - -   - <%- i18n('str', 'name') %> - <%- i18n('access-lists', 'authorization') %> - <%- i18n('access-lists', 'access') %> - <%- i18n('access-lists', 'satisfy') %> - <%- i18n('proxy-hosts', 'title') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/access/list/main.js b/frontend/js/app/nginx/access/list/main.js deleted file mode 100644 index 577a77ef..00000000 --- a/frontend/js/app/nginx/access/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('access_lists') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/access/main.ejs b/frontend/js/app/nginx/access/main.ejs deleted file mode 100644 index 97585936..00000000 --- a/frontend/js/app/nginx/access/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('access-lists', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('access-lists', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/access/main.js b/frontend/js/app/nginx/access/main.js deleted file mode 100644 index 513f5865..00000000 --- a/frontend/js/app/nginx/access/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const AccessListModel = require('../../../models/access-list'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-access', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.AccessLists.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new AccessListModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxAccess(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('access_lists'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('access-lists', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('access-lists', 'add') : null, - btn_color: 'teal', - permission: 'access_lists', - action: function () { - App.Controller.showNginxAccessListForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxAccessListForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('access-lists', 'help-title'), App.i18n('access-lists', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'items', 'clients'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('access_lists') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'items', 'clients']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/certificates-list-item.ejs b/frontend/js/app/nginx/certificates-list-item.ejs deleted file mode 100644 index aa4b53ad..00000000 --- a/frontend/js/app/nginx/certificates-list-item.ejs +++ /dev/null @@ -1,18 +0,0 @@ -
- <% if (id === 'new') { %> -
- <%- i18n('all-hosts', 'new-cert') %> -
- <%- i18n('all-hosts', 'with-le') %> - <% } else if (id > 0) { %> -
- <%- provider === 'other' ? nice_name : domain_names.join(', ') %> -
- <%- i18n('ssl', provider) %> – Expires: <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> - <% } else { %> -
- <%- i18n('all-hosts', 'none') %> -
- <%- i18n('all-hosts', 'no-ssl') %> - <% } %> -
diff --git a/frontend/js/app/nginx/certificates/delete.ejs b/frontend/js/app/nginx/certificates/delete.ejs deleted file mode 100644 index b4e06866..00000000 --- a/frontend/js/app/nginx/certificates/delete.ejs +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/delete.js b/frontend/js/app/nginx/certificates/delete.js deleted file mode 100644 index 89a2e5e8..00000000 --- a/frontend/js/app/nginx/certificates/delete.js +++ /dev/null @@ -1,34 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.save.addClass('btn-loading'); - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - - App.Api.Nginx.Certificates.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxCertificates(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/certificates/form.ejs b/frontend/js/app/nginx/certificates/form.ejs deleted file mode 100644 index 6adb4b01..00000000 --- a/frontend/js/app/nginx/certificates/form.ejs +++ /dev/null @@ -1,185 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/form.js b/frontend/js/app/nginx/certificates/form.js deleted file mode 100644 index cab09155..00000000 --- a/frontend/js/app/nginx/certificates/form.js +++ /dev/null @@ -1,294 +0,0 @@ -const _ = require('underscore'); -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const CertificateModel = require('../../../models/certificate'); -const template = require('./form.ejs'); -const i18n = require('../../i18n'); -const dns_providers = sortProvidersAlphabetically(require('../../../../../global/certbot-dns-plugins')); - -require('jquery-serializejson'); -require('selectize'); - -function sortProvidersAlphabetically(obj) { - return Object.entries(obj) - .sort((a,b) => a[1].name.toLowerCase() > b[1].name.toLowerCase()) - .reduce((result, entry) => { - result[entry[0]] = entry[1]; - return result; - }, {}); -} - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - max_file_size: 102400, - - ui: { - form: 'form', - loader_content: '.loader-content', - non_loader_content: '.non-loader-content', - le_error_info: '#le-error-info', - domain_names: 'input[name="domain_names"]', - test_domains_container: '.test-domains-container', - test_domains_button: '.test-domains', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - other_certificate: '#other_certificate', - other_certificate_label: '#other_certificate_label', - other_certificate_key: '#other_certificate_key', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - other_certificate_key_label: '#other_certificate_key_label', - other_intermediate_certificate: '#other_intermediate_certificate', - other_intermediate_certificate_label: '#other_intermediate_certificate_label' - }, - - events: { - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - this.ui.test_domains_container.hide(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - this.ui.test_domains_container.show(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - $(this).removeClass('btn-loading'); - return; - } - - let data = this.ui.form.serializeJSON(); - data.provider = this.model.get('provider'); - let ssl_files = []; - - if (data.provider === 'letsencrypt') { - if (typeof data.meta === 'undefined') data.meta = {}; - - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.split(',').map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - - // Manipulate - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - } else if (data.provider === 'other' && !this.model.hasSslFiles()) { - // check files are attached - if (!this.ui.other_certificate[0].files.length || !this.ui.other_certificate[0].files[0].size) { - alert('Certificate file is not attached'); - return; - } else { - if (this.ui.other_certificate[0].files[0].size > this.max_file_size) { - alert('Certificate file is too large (> 100kb)'); - return; - } - ssl_files.push({name: 'certificate', file: this.ui.other_certificate[0].files[0]}); - } - - if (!this.ui.other_certificate_key[0].files.length || !this.ui.other_certificate_key[0].files[0].size) { - alert('Certificate key file is not attached'); - return; - } else { - if (this.ui.other_certificate_key[0].files[0].size > this.max_file_size) { - alert('Certificate key file is too large (> 100kb)'); - return; - } - ssl_files.push({name: 'certificate_key', file: this.ui.other_certificate_key[0].files[0]}); - } - - if (this.ui.other_intermediate_certificate[0].files.length && this.ui.other_intermediate_certificate[0].files[0].size) { - if (this.ui.other_intermediate_certificate[0].files[0].size > this.max_file_size) { - alert('Intermediate Certificate file is too large (> 100kb)'); - return; - } - ssl_files.push({name: 'intermediate_certificate', file: this.ui.other_intermediate_certificate[0].files[0]}); - } - } - - this.ui.loader_content.show(); - this.ui.non_loader_content.hide(); - - // compile file data - let form_data = new FormData(); - if (data.provider === 'other' && ssl_files.length) { - ssl_files.map(function (file) { - form_data.append(file.name, file.file); - }); - } - - new Promise(resolve => { - if (data.provider === 'other') { - resolve(App.Api.Nginx.Certificates.validate(form_data)); - } else { - resolve(); - } - }) - .then(() => { - return App.Api.Nginx.Certificates.create(data); - }) - .then(result => { - this.model.set(result); - - // Now upload the certs if we need to - if (data.provider === 'other') { - return App.Api.Nginx.Certificates.upload(this.model.get('id'), form_data) - .then(result => { - this.model.set('meta', _.assign({}, this.model.get('meta'), result)); - }); - } - }) - .then(() => { - App.UI.closeModal(function () { - App.Controller.showNginxCertificates(); - }); - }) - .catch(err => { - let more_info = ''; - if (err.code === 500 && err.debug) { - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.loader_content.hide(); - this.ui.non_loader_content.show(); - }); - }, - 'click @ui.test_domains_button': function (e) { - e.preventDefault(); - const domainNames = this.ui.domain_names[0].value.split(','); - if (domainNames && domainNames.length > 0) { - this.model.set('domain_names', domainNames); - this.model.set('back_to_add', true); - App.Controller.showNginxCertificateTestReachability(this.model); - } - }, - 'change @ui.domain_names': function(e){ - const domainNames = e.target.value.split(','); - if (domainNames && domainNames.length > 0) { - this.ui.test_domains_button.prop('disabled', false); - } else { - this.ui.test_domains_button.prop('disabled', true); - } - }, - 'change @ui.other_certificate_key': function(e){ - this.setFileName("other_certificate_key_label", e) - }, - 'change @ui.other_certificate': function(e){ - this.setFileName("other_certificate_label", e) - }, - 'change @ui.other_intermediate_certificate': function(e){ - this.setFileName("other_intermediate_certificate_label", e) - } - }, - setFileName(ui, e){ - this.getUI(ui).text(e.target.files[0].name) - }, - templateContext: { - getLetsencryptEmail: function () { - return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email'); - }, - getLetsencryptAgree: function () { - return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false; - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 100, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.loader_content.hide(); - this.ui.le_error_info.hide(); - if (this.ui.domain_names[0]) { - const domainNames = this.ui.domain_names[0].value.split(','); - if (!domainNames || domainNames.length === 0 || (domainNames.length === 1 && domainNames[0] === "")) { - this.ui.test_domains_button.prop('disabled', true); - } - } - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new CertificateModel.Model({provider: 'letsencrypt'}); - } - } -}); diff --git a/frontend/js/app/nginx/certificates/list/item.ejs b/frontend/js/app/nginx/certificates/list/item.ejs deleted file mode 100644 index 179a8195..00000000 --- a/frontend/js/app/nginx/certificates/list/item.ejs +++ /dev/null @@ -1,68 +0,0 @@ - -
- -
- - -
- <% - if (provider === 'letsencrypt') { - domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - } else { - %><%- nice_name %><% - } - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - - <%- i18n('ssl', provider) %><% if (meta.dns_provider) { %> - <%- dns_providers[meta.dns_provider].name %><% } %> - - - <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> - - - <% if (active_domain_names().length > 0) { %> - <%- i18n('certificates', 'in-use') %> - <% } else { %> - <%- i18n('certificates', 'inactive') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/certificates/list/item.js b/frontend/js/app/nginx/certificates/list/item.js deleted file mode 100644 index b9a927ad..00000000 --- a/frontend/js/app/nginx/certificates/list/item.js +++ /dev/null @@ -1,68 +0,0 @@ -const Mn = require('backbone.marionette'); -const moment = require('moment'); -const App = require('../../../main'); -const template = require('./item.ejs'); -const dns_providers = require('../../../../../../global/certbot-dns-plugins'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - host_link: '.host-link', - renew: 'a.renew', - delete: 'a.delete', - download: 'a.download', - test: 'a.test' - }, - - events: { - 'click @ui.renew': function (e) { - e.preventDefault(); - App.Controller.showNginxCertificateRenew(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxCertificateDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - }, - - 'click @ui.download': function (e) { - e.preventDefault(); - App.Api.Nginx.Certificates.download(this.model.get('id')); - }, - - 'click @ui.test': function (e) { - e.preventDefault(); - App.Controller.showNginxCertificateTestReachability(this.model); - }, - }, - - templateContext: function () { - return { - canManage: App.Cache.User.canManage('certificates'), - isExpired: function () { - return moment(this.expires_on).isBefore(moment()); - }, - dns_providers: dns_providers, - active_domain_names: function () { - const { proxy_hosts = [], redirect_hosts = [], dead_hosts = [] } = this; - return [...proxy_hosts, ...redirect_hosts, ...dead_hosts].reduce((acc, host) => { - acc.push(...(host.domain_names || [])); - return acc; - }, []); - } - }; - }, - - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/certificates/list/main.ejs b/frontend/js/app/nginx/certificates/list/main.ejs deleted file mode 100644 index 329b5843..00000000 --- a/frontend/js/app/nginx/certificates/list/main.ejs +++ /dev/null @@ -1,13 +0,0 @@ - -   - <%- i18n('str', 'name') %> - <%- i18n('all-hosts', 'cert-provider') %> - <%- i18n('str', 'expires') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/certificates/list/main.js b/frontend/js/app/nginx/certificates/list/main.js deleted file mode 100644 index d96b43e8..00000000 --- a/frontend/js/app/nginx/certificates/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('certificates') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/certificates/main.ejs b/frontend/js/app/nginx/certificates/main.ejs deleted file mode 100644 index dbd6fa85..00000000 --- a/frontend/js/app/nginx/certificates/main.ejs +++ /dev/null @@ -1,36 +0,0 @@ -
-
-
-

<%- i18n('certificates', 'title') %>

-
- - - <% if (showAddButton) { %> - - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/certificates/main.js b/frontend/js/app/nginx/certificates/main.js deleted file mode 100644 index 3f9f022e..00000000 --- a/frontend/js/app/nginx/certificates/main.js +++ /dev/null @@ -1,109 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const CertificateModel = require('../../../models/certificate'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-certificates', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.Certificates.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new CertificateModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxCertificates(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('certificates'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('certificates', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('certificates', 'add') : null, - btn_color: 'pink', - permission: 'certificates', - action: function () { - App.Controller.showNginxCertificateForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - let model = new CertificateModel.Model({provider: $(e.currentTarget).data('cert')}); - App.Controller.showNginxCertificateForm(model); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('certificates', 'help-title'), App.i18n('certificates', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('certificates') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/certificates/renew.ejs b/frontend/js/app/nginx/certificates/renew.ejs deleted file mode 100644 index 4af186d0..00000000 --- a/frontend/js/app/nginx/certificates/renew.ejs +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/renew.js b/frontend/js/app/nginx/certificates/renew.js deleted file mode 100644 index 73632881..00000000 --- a/frontend/js/app/nginx/certificates/renew.js +++ /dev/null @@ -1,31 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./renew.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - waiting: '.waiting', - error: '.error', - close: 'button.cancel' - }, - - onRender: function () { - this.ui.error.hide(); - - App.Api.Nginx.Certificates.renew(this.model.get('id')) - .then((result) => { - this.model.set(result); - setTimeout(() => { - App.UI.closeModal(); - }, 1000); - }) - .catch((err) => { - this.ui.waiting.hide(); - this.ui.error.text(err.message).show(); - this.ui.close.prop('disabled', false); - }); - } -}); diff --git a/frontend/js/app/nginx/certificates/test.ejs b/frontend/js/app/nginx/certificates/test.ejs deleted file mode 100644 index 6661f625..00000000 --- a/frontend/js/app/nginx/certificates/test.ejs +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/test.js b/frontend/js/app/nginx/certificates/test.js deleted file mode 100644 index 0886d26f..00000000 --- a/frontend/js/app/nginx/certificates/test.js +++ /dev/null @@ -1,75 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./test.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - waiting: '.waiting', - error: '.error', - success: '.success', - close: 'button.cancel' - }, - - events: { - 'click @ui.close': function (e) { - e.preventDefault(); - if (this.model.get('back_to_add')) { - App.Controller.showNginxCertificateForm(this.model); - } else { - App.UI.closeModal(); - } - }, - }, - - onRender: function () { - this.ui.error.hide(); - this.ui.success.hide(); - - App.Api.Nginx.Certificates.testHttpChallenge(this.model.get('domain_names')) - .then((result) => { - let allOk = true; - let text = ''; - - for (const domain in result) { - const status = result[domain]; - if (status === 'ok') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-ok')}

`; - } else { - allOk = false; - if (status === 'no-host') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-not-resolved')}

`; - } else if (status === 'failed') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-failed-to-check')}

`; - } else if (status === '404') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-404')}

`; - } else if (status === 'wrong-data') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-wrong-data')}

`; - } else if (status.startsWith('other:')) { - const code = status.substring(6); - text += `

${domain}: ${App.i18n('certificates', 'reachability-other', {code})}

`; - } else { - // This should never happen - text += `

${domain}: ?

`; - } - } - } - - this.ui.waiting.hide(); - if (allOk) { - this.ui.success.html(text).show(); - } else { - this.ui.error.html(text).show(); - } - this.ui.close.prop('disabled', false); - }) - .catch((e) => { - console.error(e); - this.ui.waiting.hide(); - this.ui.error.text(App.i18n('certificates', 'reachability-failed-to-reach-api')).show(); - this.ui.close.prop('disabled', false); - }); - } -}); diff --git a/frontend/js/app/nginx/dead/delete.ejs b/frontend/js/app/nginx/dead/delete.ejs deleted file mode 100644 index 4bebb436..00000000 --- a/frontend/js/app/nginx/dead/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/dead/delete.js b/frontend/js/app/nginx/dead/delete.js deleted file mode 100644 index d497d068..00000000 --- a/frontend/js/app/nginx/dead/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.DeadHosts.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxDead(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/dead/form.ejs b/frontend/js/app/nginx/dead/form.ejs deleted file mode 100644 index de3b87d8..00000000 --- a/frontend/js/app/nginx/dead/form.ejs +++ /dev/null @@ -1,206 +0,0 @@ - diff --git a/frontend/js/app/nginx/dead/form.js b/frontend/js/app/nginx/dead/form.js deleted file mode 100644 index a091717d..00000000 --- a/frontend/js/app/nginx/dead/form.js +++ /dev/null @@ -1,286 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const DeadHostModel = require('../../../models/dead-host'); -const template = require('./form.ejs'); -const certListItemTemplate = require('../certificates-list-item.ejs'); -const Helpers = require('../../../lib/helpers'); -const i18n = require('../../i18n'); -const dns_providers = require('../../../../../global/certbot-dns-plugins'); - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - letsencrypt: '.letsencrypt' - }, - - events: { - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.dns_challenge_content.hide(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - - - let enabled = id === 'new' || parseInt(id, 10) > 0; - - let inputs = this.ui.ssl_forced.add(this.ui.http2_support); - inputs - .prop('disabled', !enabled) - .parents('.form-group') - .css('opacity', enabled ? 1 : 0.5); - - if (!enabled) { - inputs.prop('checked', false); - } - - inputs.trigger('change'); - }, - - 'change @ui.ssl_forced': function () { - let checked = this.ui.ssl_forced.prop('checked'); - this.ui.hsts_enabled - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_enabled.prop('checked', false); - } - - this.ui.hsts_enabled.trigger('change'); - }, - - 'change @ui.hsts_enabled': function () { - let checked = this.ui.hsts_enabled.prop('checked'); - this.ui.hsts_subdomains - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_subdomains.prop('checked', false); - } - }, - - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Manipulate - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.http2_support = !!data.http2_support; - data.ssl_forced = !!data.ssl_forced; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.DeadHosts.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.DeadHosts.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - this.ui.save.addClass('btn-loading'); - - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxDead(); - } - }); - }) - .catch(err => { - let more_info = ''; - if(err.code === 500 && err.debug){ - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - // Domain names - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 100, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new DeadHostModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/dead/list/item.ejs b/frontend/js/app/nginx/dead/list/item.ejs deleted file mode 100644 index dede3b63..00000000 --- a/frontend/js/app/nginx/dead/list/item.ejs +++ /dev/null @@ -1,54 +0,0 @@ - -
- -
- - -
- <% domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/dead/list/item.js b/frontend/js/app/nginx/dead/list/item.js deleted file mode 100644 index a477dbfa..00000000 --- a/frontend/js/app/nginx/dead/list/item.js +++ /dev/null @@ -1,61 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete', - host_link: '.host-link' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.DeadHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.DeadHosts.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxDeadForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxDeadDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('dead_hosts'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/dead/list/main.ejs b/frontend/js/app/nginx/dead/list/main.ejs deleted file mode 100644 index e018a74b..00000000 --- a/frontend/js/app/nginx/dead/list/main.ejs +++ /dev/null @@ -1,12 +0,0 @@ - -   - <%- i18n('str', 'source') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/dead/list/main.js b/frontend/js/app/nginx/dead/list/main.js deleted file mode 100644 index 57931419..00000000 --- a/frontend/js/app/nginx/dead/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('dead_hosts') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/dead/main.ejs b/frontend/js/app/nginx/dead/main.ejs deleted file mode 100644 index 4c5d1ad1..00000000 --- a/frontend/js/app/nginx/dead/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('dead-hosts', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('dead-hosts', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/dead/main.js b/frontend/js/app/nginx/dead/main.js deleted file mode 100644 index e4d0c010..00000000 --- a/frontend/js/app/nginx/dead/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const DeadHostModel = require('../../../models/dead-host'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-dead', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.DeadHosts.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new DeadHostModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxDead(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('dead_hosts'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('dead-hosts', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('dead-hosts', 'add') : null, - btn_color: 'danger', - permission: 'dead_hosts', - action: function () { - App.Controller.showNginxDeadForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxDeadForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('dead-hosts', 'help-title'), App.i18n('dead-hosts', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'certificate'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('dead_hosts') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/proxy/access-list-item.ejs b/frontend/js/app/nginx/proxy/access-list-item.ejs deleted file mode 100644 index e5a7e116..00000000 --- a/frontend/js/app/nginx/proxy/access-list-item.ejs +++ /dev/null @@ -1,13 +0,0 @@ -
- <% if (id > 0) { %> -
- <%- name %> -
- <%- i18n('access-lists', 'item-count', {count: items.length || 0}) %>, <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %> - <% } else { %> -
- <%- i18n('access-lists', 'public') %> -
- <%- i18n('access-lists', 'public-sub') %> - <% } %> -
diff --git a/frontend/js/app/nginx/proxy/delete.ejs b/frontend/js/app/nginx/proxy/delete.ejs deleted file mode 100644 index 74da297c..00000000 --- a/frontend/js/app/nginx/proxy/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/proxy/delete.js b/frontend/js/app/nginx/proxy/delete.js deleted file mode 100644 index 63a8e020..00000000 --- a/frontend/js/app/nginx/proxy/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.ProxyHosts.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxProxy(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/proxy/form.ejs b/frontend/js/app/nginx/proxy/form.ejs deleted file mode 100644 index 8e7a2a2d..00000000 --- a/frontend/js/app/nginx/proxy/form.ejs +++ /dev/null @@ -1,281 +0,0 @@ - diff --git a/frontend/js/app/nginx/proxy/form.js b/frontend/js/app/nginx/proxy/form.js deleted file mode 100644 index 4437a6dd..00000000 --- a/frontend/js/app/nginx/proxy/form.js +++ /dev/null @@ -1,369 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const ProxyHostModel = require('../../../models/proxy-host'); -const ProxyLocationModel = require('../../../models/proxy-host-location'); -const template = require('./form.ejs'); -const certListItemTemplate = require('../certificates-list-item.ejs'); -const accessListItemTemplate = require('./access-list-item.ejs'); -const CustomLocation = require('./location'); -const Helpers = require('../../../lib/helpers'); -const i18n = require('../../i18n'); -const dns_providers = require('../../../../../global/certbot-dns-plugins'); - - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - locationsCollection: new ProxyLocationModel.Collection(), - - ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - forward_host: 'input[name="forward_host"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - add_location_btn: 'button.add_location', - locations_container: '.locations_container', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - access_list_select: 'select[name="access_list_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - forward_scheme: 'select[name="forward_scheme"]', - letsencrypt: '.letsencrypt' - }, - - regions: { - locations_regions: '@ui.locations_container' - }, - - events: { - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.dns_challenge_content.hide(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - - let enabled = id === 'new' || parseInt(id, 10) > 0; - - let inputs = this.ui.ssl_forced.add(this.ui.http2_support); - inputs - .prop('disabled', !enabled) - .parents('.form-group') - .css('opacity', enabled ? 1 : 0.5); - - if (!enabled) { - inputs.prop('checked', false); - } - - inputs.trigger('change'); - }, - - 'change @ui.ssl_forced': function () { - let checked = this.ui.ssl_forced.prop('checked'); - this.ui.hsts_enabled - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_enabled.prop('checked', false); - } - - this.ui.hsts_enabled.trigger('change'); - }, - - 'change @ui.hsts_enabled': function () { - let checked = this.ui.hsts_enabled.prop('checked'); - this.ui.hsts_subdomains - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_subdomains.prop('checked', false); - } - }, - - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.add_location_btn': function (e) { - e.preventDefault(); - - const model = new ProxyLocationModel.Model(); - this.locationsCollection.add(model); - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Add locations - data.locations = []; - this.locationsCollection.models.forEach((location) => { - data.locations.push(location.toJSON()); - }); - - // Serialize collects path from custom locations - // This field must be removed from root object - delete data.path; - - // Manipulate - data.forward_port = parseInt(data.forward_port, 10); - data.block_exploits = !!data.block_exploits; - data.caching_enabled = !!data.caching_enabled; - data.allow_websocket_upgrade = !!data.allow_websocket_upgrade; - data.http2_support = !!data.http2_support; - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.ssl_forced = !!data.ssl_forced; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.ProxyHosts.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.ProxyHosts.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - this.ui.save.addClass('btn-loading'); - - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxProxy(); - } - }); - }) - .catch(err => { - let more_info = ''; - if(err.code === 500 && err.debug){ - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - this.ui.ssl_forced.trigger('change'); - this.ui.hsts_enabled.trigger('change'); - - // Domain names - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 100, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - - // Access Lists - this.ui.access_list_select.selectize({ - valueField: 'id', - labelField: 'name', - searchField: ['name'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return accessListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.AccessLists.getAll(['items', 'clients']) - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.access_list_select[0].selectize.setValue(view.model.get('access_list_id')); - } - }); - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new ProxyHostModel.Model(); - } - - this.locationsCollection = new ProxyLocationModel.Collection(); - - // Custom locations - this.showChildView('locations_regions', new CustomLocation.LocationCollectionView({ - collection: this.locationsCollection - })); - - // Check wether there are any location defined - if (options.model && Array.isArray(options.model.attributes.locations)) { - options.model.attributes.locations.forEach((location) => { - let m = new ProxyLocationModel.Model(location); - this.locationsCollection.add(m); - }); - } - } -}); diff --git a/frontend/js/app/nginx/proxy/list/item.ejs b/frontend/js/app/nginx/proxy/list/item.ejs deleted file mode 100644 index 3eeaa6d2..00000000 --- a/frontend/js/app/nginx/proxy/list/item.ejs +++ /dev/null @@ -1,60 +0,0 @@ - -
- -
- - -
- <% domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- forward_scheme %>://<%- forward_host %>:<%- forward_port %>
- - -
<%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
- - -
<%- access_list_id ? access_list.name : i18n('str', 'public') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/proxy/list/item.js b/frontend/js/app/nginx/proxy/list/item.js deleted file mode 100644 index 37d199b4..00000000 --- a/frontend/js/app/nginx/proxy/list/item.js +++ /dev/null @@ -1,61 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete', - host_link: '.host-link' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.ProxyHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.ProxyHosts.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxProxyForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxProxyDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('proxy_hosts'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/proxy/list/main.ejs b/frontend/js/app/nginx/proxy/list/main.ejs deleted file mode 100644 index 6de5b9c6..00000000 --- a/frontend/js/app/nginx/proxy/list/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ - -   - <%- i18n('str', 'source') %> - <%- i18n('str', 'destination') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'access') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/proxy/list/main.js b/frontend/js/app/nginx/proxy/list/main.js deleted file mode 100644 index 09e984e6..00000000 --- a/frontend/js/app/nginx/proxy/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('proxy_hosts') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/proxy/location-item.ejs b/frontend/js/app/nginx/proxy/location-item.ejs deleted file mode 100644 index 466cb9ba..00000000 --- a/frontend/js/app/nginx/proxy/location-item.ejs +++ /dev/null @@ -1,64 +0,0 @@ -
-
-
-
-
- -
-
-
- - location - - -
-
-
-
- -
-
-
-
-
-
-
- - -
-
-
-
- - - <%- i18n('proxy-hosts', 'custom-forward-host-help') %> -
-
-
-
- - -
-
-
-
-
-
- -
-
-
- - - <%- i18n('locations', 'delete') %> - -
-
diff --git a/frontend/js/app/nginx/proxy/location.js b/frontend/js/app/nginx/proxy/location.js deleted file mode 100644 index e9513a48..00000000 --- a/frontend/js/app/nginx/proxy/location.js +++ /dev/null @@ -1,54 +0,0 @@ -const locationItemTemplate = require('./location-item.ejs'); -const Mn = require('backbone.marionette'); -const App = require('../../main'); - -const LocationView = Mn.View.extend({ - template: locationItemTemplate, - className: 'location_block', - - ui: { - toggle: 'input[type="checkbox"]', - config: '.config', - delete: '.location-delete' - }, - - events: { - 'change @ui.toggle': function(el) { - if (el.target.checked) { - this.ui.config.show(); - } else { - this.ui.config.hide(); - } - }, - - 'change .model': function (e) { - const map = {}; - map[e.target.name] = e.target.value; - this.model.set(map); - }, - - 'click @ui.delete': function () { - this.model.destroy(); - } - }, - - onRender: function() { - $(this.ui.config).hide(); - }, - - templateContext: function() { - return { - i18n: App.i18n - } - } -}); - -const LocationCollectionView = Mn.CollectionView.extend({ - className: 'locations_container', - childView: LocationView -}); - -module.exports = { - LocationCollectionView, - LocationView -} \ No newline at end of file diff --git a/frontend/js/app/nginx/proxy/main.ejs b/frontend/js/app/nginx/proxy/main.ejs deleted file mode 100644 index 4ecb9036..00000000 --- a/frontend/js/app/nginx/proxy/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('proxy-hosts', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('proxy-hosts', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/proxy/main.js b/frontend/js/app/nginx/proxy/main.js deleted file mode 100644 index baf67101..00000000 --- a/frontend/js/app/nginx/proxy/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const ProxyHostModel = require('../../../models/proxy-host'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-proxy', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.ProxyHosts.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new ProxyHostModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxProxy(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('proxy_hosts'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('proxy-hosts', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('proxy-hosts', 'add') : null, - btn_color: 'success', - permission: 'proxy_hosts', - action: function () { - App.Controller.showNginxProxyForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxProxyForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('proxy-hosts', 'help-title'), App.i18n('proxy-hosts', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'access_list', 'certificate'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('proxy_hosts') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'access_list', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/redirection/delete.ejs b/frontend/js/app/nginx/redirection/delete.ejs deleted file mode 100644 index 782d8435..00000000 --- a/frontend/js/app/nginx/redirection/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/redirection/delete.js b/frontend/js/app/nginx/redirection/delete.js deleted file mode 100644 index 6d2862f6..00000000 --- a/frontend/js/app/nginx/redirection/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.RedirectionHosts.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxRedirection(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/redirection/form.ejs b/frontend/js/app/nginx/redirection/form.ejs deleted file mode 100644 index f32178bc..00000000 --- a/frontend/js/app/nginx/redirection/form.ejs +++ /dev/null @@ -1,253 +0,0 @@ - diff --git a/frontend/js/app/nginx/redirection/form.js b/frontend/js/app/nginx/redirection/form.js deleted file mode 100644 index 5d061ff7..00000000 --- a/frontend/js/app/nginx/redirection/form.js +++ /dev/null @@ -1,288 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const RedirectionHostModel = require('../../../models/redirection-host'); -const template = require('./form.ejs'); -const certListItemTemplate = require('../certificates-list-item.ejs'); -const Helpers = require('../../../lib/helpers'); -const i18n = require('../../i18n'); -const dns_providers = require('../../../../../global/certbot-dns-plugins'); - - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - letsencrypt: '.letsencrypt' - }, - - events: { - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.dns_challenge_content.hide(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - - let enabled = id === 'new' || parseInt(id, 10) > 0; - - let inputs = this.ui.ssl_forced.add(this.ui.http2_support); - inputs - .prop('disabled', !enabled) - .parents('.form-group') - .css('opacity', enabled ? 1 : 0.5); - - if (!enabled) { - inputs.prop('checked', false); - } - - inputs.trigger('change'); - }, - - 'change @ui.ssl_forced': function () { - let checked = this.ui.ssl_forced.prop('checked'); - this.ui.hsts_enabled - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_enabled.prop('checked', false); - } - - this.ui.hsts_enabled.trigger('change'); - }, - - 'change @ui.hsts_enabled': function () { - let checked = this.ui.hsts_enabled.prop('checked'); - this.ui.hsts_subdomains - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_subdomains.prop('checked', false); - } - }, - - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Manipulate - data.block_exploits = !!data.block_exploits; - data.preserve_path = !!data.preserve_path; - data.http2_support = !!data.http2_support; - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.ssl_forced = !!data.ssl_forced; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.RedirectionHosts.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.RedirectionHosts.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - this.ui.save.addClass('btn-loading'); - - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxRedirection(); - } - }); - }) - .catch(err => { - let more_info = ''; - if(err.code === 500 && err.debug){ - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - // Domain names - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 100, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new RedirectionHostModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/redirection/list/item.ejs b/frontend/js/app/nginx/redirection/list/item.ejs deleted file mode 100644 index dff186fd..00000000 --- a/frontend/js/app/nginx/redirection/list/item.ejs +++ /dev/null @@ -1,63 +0,0 @@ - -
- -
- - -
- <% domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- forward_http_code %>
- - -
<%- forward_scheme == '$scheme' ? 'auto' : forward_scheme %>
- - -
<%- forward_domain_name %>
- - -
<%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> diff --git a/frontend/js/app/nginx/redirection/list/item.js b/frontend/js/app/nginx/redirection/list/item.js deleted file mode 100644 index 05adc251..00000000 --- a/frontend/js/app/nginx/redirection/list/item.js +++ /dev/null @@ -1,61 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete', - host_link: '.host-link' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.RedirectionHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.RedirectionHosts.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxRedirectionForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxRedirectionDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('redirection_hosts'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/redirection/list/main.ejs b/frontend/js/app/nginx/redirection/list/main.ejs deleted file mode 100644 index 8b6930d6..00000000 --- a/frontend/js/app/nginx/redirection/list/main.ejs +++ /dev/null @@ -1,15 +0,0 @@ - -   - <%- i18n('str', 'source') %> - <%- i18n('redirection-hosts', 'forward-http-status-code') %> - <%- i18n('redirection-hosts', 'forward-scheme') %> - <%- i18n('str', 'destination') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/redirection/list/main.js b/frontend/js/app/nginx/redirection/list/main.js deleted file mode 100644 index d368cf6a..00000000 --- a/frontend/js/app/nginx/redirection/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('redirection_hosts') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/redirection/main.ejs b/frontend/js/app/nginx/redirection/main.ejs deleted file mode 100644 index 87e28229..00000000 --- a/frontend/js/app/nginx/redirection/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('redirection-hosts', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('redirection-hosts', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/redirection/main.js b/frontend/js/app/nginx/redirection/main.js deleted file mode 100644 index 1f5351a7..00000000 --- a/frontend/js/app/nginx/redirection/main.js +++ /dev/null @@ -1,107 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const RedirectionHostModel = require('../../../models/redirection-host'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-redirection', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.RedirectionHosts.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new RedirectionHostModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxRedirection(); - } - })); - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('redirection_hosts'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('redirection-hosts', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('redirection-hosts', 'add') : null, - btn_color: 'yellow', - permission: 'redirection_hosts', - action: function () { - App.Controller.showNginxRedirectionForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxRedirectionForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('redirection-hosts', 'help-title'), App.i18n('redirection-hosts', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'certificate'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('proxy_hosts') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/stream/delete.ejs b/frontend/js/app/nginx/stream/delete.ejs deleted file mode 100644 index d7ba3a21..00000000 --- a/frontend/js/app/nginx/stream/delete.ejs +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/frontend/js/app/nginx/stream/delete.js b/frontend/js/app/nginx/stream/delete.js deleted file mode 100644 index 71eff18c..00000000 --- a/frontend/js/app/nginx/stream/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.Streams.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxStream(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/stream/form.ejs b/frontend/js/app/nginx/stream/form.ejs deleted file mode 100644 index 800945f3..00000000 --- a/frontend/js/app/nginx/stream/form.ejs +++ /dev/null @@ -1,194 +0,0 @@ - diff --git a/frontend/js/app/nginx/stream/form.js b/frontend/js/app/nginx/stream/form.js deleted file mode 100644 index cd012f9b..00000000 --- a/frontend/js/app/nginx/stream/form.js +++ /dev/null @@ -1,225 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const StreamModel = require('../../../models/stream'); -const template = require('./form.ejs'); -const dns_providers = require('../../../../../global/certbot-dns-plugins'); - -require('jquery-serializejson'); -require('jquery-mask-plugin'); -require('selectize'); -const Helpers = require("../../../lib/helpers"); -const certListItemTemplate = require("../certificates-list-item.ejs"); -const i18n = require("../../i18n"); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - forwarding_host: 'input[name="forwarding_host"]', - type_error: '.forward-type-error', - buttons: '.modal-footer button', - switches: '.custom-switch-input', - cancel: 'button.cancel', - save: 'button.save', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - domain_names: 'input[name="domain_names"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - letsencrypt: '.letsencrypt' - }, - - events: { - 'change @ui.switches': function () { - this.ui.type_error.hide(); - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - if (!data.tcp_forwarding && !data.udp_forwarding) { - this.ui.type_error.show(); - return; - } - - // Manipulate - data.incoming_port = parseInt(data.incoming_port, 10); - data.forwarding_port = parseInt(data.forwarding_port, 10); - data.tcp_forwarding = !!data.tcp_forwarding; - data.udp_forwarding = !!data.udp_forwarding; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = true; - - if (data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.Streams.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.Streams.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxStream(); - } - }); - }) - .catch(err => { - let more_info = ''; - if (err.code === 500 && err.debug) { - try { - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch (e) { - } - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
` : ''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - }, - - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.domain_names.prop('required', 'required'); - - this.ui.dns_challenge_switch - .prop('disabled', true) - .parents('.form-group') - .css('opacity', 0.5); - - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new StreamModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/stream/list/item.ejs b/frontend/js/app/nginx/stream/list/item.ejs deleted file mode 100644 index 936247ef..00000000 --- a/frontend/js/app/nginx/stream/list/item.ejs +++ /dev/null @@ -1,59 +0,0 @@ - -
- -
- - -
- <%- incoming_port %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- forwarding_host %>:<%- forwarding_port %>
- - -
- <% if (certificate) { %> - <%- i18n('streams', 'tcp+ssl') %> - <% } - else if (tcp_forwarding) { %> - <%- i18n('streams', 'tcp') %> - <% } - if (udp_forwarding) { %> - <%- i18n('streams', 'udp') %> - <% } %> -
- - -
<%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('all-hosts', 'none') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/stream/list/item.js b/frontend/js/app/nginx/stream/list/item.js deleted file mode 100644 index a6892ee2..00000000 --- a/frontend/js/app/nginx/stream/list/item.js +++ /dev/null @@ -1,54 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.Streams[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.Streams.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxStreamForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxStreamDeleteConfirm(this.model); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('streams'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/stream/list/main.ejs b/frontend/js/app/nginx/stream/list/main.ejs deleted file mode 100644 index 57ab6b2a..00000000 --- a/frontend/js/app/nginx/stream/list/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ - -   - <%- i18n('streams', 'incoming-port') %> - <%- i18n('str', 'destination') %> - <%- i18n('streams', 'protocol') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/stream/list/main.js b/frontend/js/app/nginx/stream/list/main.js deleted file mode 100644 index 36be621d..00000000 --- a/frontend/js/app/nginx/stream/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('streams') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/stream/main.ejs b/frontend/js/app/nginx/stream/main.ejs deleted file mode 100644 index 7dc0dbe8..00000000 --- a/frontend/js/app/nginx/stream/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('streams', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('streams', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/stream/main.js b/frontend/js/app/nginx/stream/main.js deleted file mode 100644 index 83bdc15e..00000000 --- a/frontend/js/app/nginx/stream/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const StreamModel = require('../../../models/stream'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-stream', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.Streams.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new StreamModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxStream(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('streams'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('streams', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('streams', 'add') : null, - btn_color: 'blue', - permission: 'streams', - action: function () { - App.Controller.showNginxStreamForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxStreamForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('streams', 'help-title'), App.i18n('streams', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('streams') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/router.js b/frontend/js/app/router.js deleted file mode 100644 index a036bfc5..00000000 --- a/frontend/js/app/router.js +++ /dev/null @@ -1,19 +0,0 @@ -const AppRouter = require('marionette.approuter'); -const Controller = require('./controller'); - -module.exports = AppRouter.default.extend({ - controller: Controller, - appRoutes: { - users: 'showUsers', - logout: 'logout', - 'nginx/proxy': 'showNginxProxy', - 'nginx/redirection': 'showNginxRedirection', - 'nginx/404': 'showNginxDead', - 'nginx/stream': 'showNginxStream', - 'nginx/access': 'showNginxAccess', - 'nginx/certificates': 'showNginxCertificates', - 'audit-log': 'showAuditLog', - 'settings': 'showSettings', - '*default': 'showDashboard' - } -}); diff --git a/frontend/js/app/settings/default-site/main.ejs b/frontend/js/app/settings/default-site/main.ejs deleted file mode 100644 index d74ac0bd..00000000 --- a/frontend/js/app/settings/default-site/main.ejs +++ /dev/null @@ -1,57 +0,0 @@ - diff --git a/frontend/js/app/settings/default-site/main.js b/frontend/js/app/settings/default-site/main.js deleted file mode 100644 index 06a45b8b..00000000 --- a/frontend/js/app/settings/default-site/main.js +++ /dev/null @@ -1,69 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./main.ejs'); - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - options: '.option-item', - value: 'input[name="value"]', - redirect: '.redirect-input', - html: '.html-content' - }, - - events: { - 'change @ui.value': function (e) { - let val = this.ui.value.filter(':checked').val(); - this.ui.options.hide(); - this.ui.options.filter('.option-' + val).show(); - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - - let val = this.ui.value.filter(':checked').val(); - - // Clear redirect field before validation - if (val !== 'redirect') { - this.ui.redirect.val('').attr('required', false); - } else { - this.ui.redirect.attr('required', true); - } - - this.ui.html.attr('required', val === 'html'); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - data.id = this.model.get('id'); - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - App.Api.Settings.update(data) - .then(result => { - view.model.set(result); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - onRender: function () { - this.ui.value.trigger('change'); - } -}); diff --git a/frontend/js/app/settings/list/item.ejs b/frontend/js/app/settings/list/item.ejs deleted file mode 100644 index 1623c4dc..00000000 --- a/frontend/js/app/settings/list/item.ejs +++ /dev/null @@ -1,21 +0,0 @@ - -
<%- i18n('settings', 'default-site') %>
-
- <%- i18n('settings', 'default-site-description') %> -
- - -
- <% if (id === 'default-site') { %> - <%- i18n('settings', 'default-site-' + value) %> - <% } %> -
- - - - \ No newline at end of file diff --git a/frontend/js/app/settings/list/item.js b/frontend/js/app/settings/list/item.js deleted file mode 100644 index 03f9ac05..00000000 --- a/frontend/js/app/settings/list/item.js +++ /dev/null @@ -1,23 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - edit: 'a.edit' - }, - - events: { - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showSettingForm(this.model); - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/settings/list/main.ejs b/frontend/js/app/settings/list/main.ejs deleted file mode 100644 index c96e923a..00000000 --- a/frontend/js/app/settings/list/main.ejs +++ /dev/null @@ -1,8 +0,0 @@ - - <%- i18n('str', 'name') %> - <%- i18n('str', 'value') %> -   - - - - diff --git a/frontend/js/app/settings/list/main.js b/frontend/js/app/settings/list/main.js deleted file mode 100644 index 9d3e26fb..00000000 --- a/frontend/js/app/settings/list/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/settings/main.ejs b/frontend/js/app/settings/main.ejs deleted file mode 100644 index 2b02769f..00000000 --- a/frontend/js/app/settings/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ -
-
-
-

<%- i18n('settings', 'title') %>

-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/settings/main.js b/frontend/js/app/settings/main.js deleted file mode 100644 index 96b2941f..00000000 --- a/frontend/js/app/settings/main.js +++ /dev/null @@ -1,48 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const SettingModel = require('../../models/setting'); -const ListView = require('./list/main'); -const ErrorView = require('../error/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'settings', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - dimmer: '.dimmer' - }, - - regions: { - list_region: '@ui.list_region' - }, - - onRender: function () { - let view = this; - - App.Api.Settings.getAll() - .then(response => { - if (!view.isDestroyed() && response && response.length) { - view.showChildView('list_region', new ListView({ - collection: new SettingModel.Collection(response) - })); - } - }) - .catch(err => { - view.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showSettings(); - } - })); - - console.error(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/tokens.js b/frontend/js/app/tokens.js deleted file mode 100644 index 4a56bcab..00000000 --- a/frontend/js/app/tokens.js +++ /dev/null @@ -1,126 +0,0 @@ -const STORAGE_NAME = 'nginx-proxy-manager-tokens'; - -/** - * @returns {Array} - */ -const getStorageTokens = function () { - let json = window.localStorage.getItem(STORAGE_NAME); - if (json) { - try { - return JSON.parse(json); - } catch (err) { - return []; - } - } - - return []; -}; - -/** - * @param {Array} tokens - */ -const setStorageTokens = function (tokens) { - window.localStorage.setItem(STORAGE_NAME, JSON.stringify(tokens)); -}; - -const Tokens = { - - /** - * @returns {Number} - */ - getTokenCount: () => { - return getStorageTokens().length; - }, - - /** - * @returns {Object} t,n - */ - getTopToken: () => { - let tokens = getStorageTokens(); - if (tokens && tokens.length) { - return tokens[0]; - } - - return null; - }, - - /** - * @returns {String} - */ - getNextTokenName: () => { - let tokens = getStorageTokens(); - if (tokens && tokens.length > 1 && typeof tokens[1] !== 'undefined' && typeof tokens[1].n !== 'undefined') { - return tokens[1].n; - } - - return null; - }, - - /** - * - * @param {String} token - * @param {String} [name] - * @returns {Number} - */ - addToken: (token, name) => { - // Get top token and if it's the same, ignore this call - let top = Tokens.getTopToken(); - if (!top || top.t !== token) { - let tokens = getStorageTokens(); - tokens.unshift({t: token, n: name || null}); - setStorageTokens(tokens); - } - - return Tokens.getTokenCount(); - }, - - /** - * @param {String} token - * @returns {Boolean} - */ - setCurrentToken: token => { - let tokens = getStorageTokens(); - if (tokens.length) { - tokens[0].t = token; - setStorageTokens(tokens); - return true; - } - - return false; - }, - - /** - * @param {String} name - * @returns {Boolean} - */ - setCurrentName: name => { - let tokens = getStorageTokens(); - if (tokens.length) { - tokens[0].n = name; - setStorageTokens(tokens); - return true; - } - - return false; - }, - - /** - * @returns {Number} - */ - dropTopToken: () => { - let tokens = getStorageTokens(); - tokens.shift(); - setStorageTokens(tokens); - return tokens.length; - }, - - /** - * - */ - clearTokens: () => { - window.localStorage.removeItem(STORAGE_NAME); - } - -}; - -module.exports = Tokens; diff --git a/frontend/js/app/ui/footer/main.ejs b/frontend/js/app/ui/footer/main.ejs deleted file mode 100644 index 99c2630a..00000000 --- a/frontend/js/app/ui/footer/main.ejs +++ /dev/null @@ -1,16 +0,0 @@ -
- -
- <%- i18n('main', 'version', {version: getVersion()}) %> - <%= i18n('footer', 'copy', {url: 'https://jc21.com?utm_source=nginx-proxy-manager'}) %> - <%= i18n('footer', 'theme', {url: 'https://tabler.github.io/?utm_source=nginx-proxy-manager'}) %> -
-
diff --git a/frontend/js/app/ui/footer/main.js b/frontend/js/app/ui/footer/main.js deleted file mode 100644 index 73f515e6..00000000 --- a/frontend/js/app/ui/footer/main.js +++ /dev/null @@ -1,14 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); -const Cache = require('../../cache'); - -module.exports = Mn.View.extend({ - className: 'container', - template: template, - - templateContext: { - getVersion: function () { - return Cache.version || '0.0.0'; - } - } -}); diff --git a/frontend/js/app/ui/header/main.ejs b/frontend/js/app/ui/header/main.ejs deleted file mode 100644 index 18ed2b6a..00000000 --- a/frontend/js/app/ui/header/main.ejs +++ /dev/null @@ -1,34 +0,0 @@ - diff --git a/frontend/js/app/ui/header/main.js b/frontend/js/app/ui/header/main.js deleted file mode 100644 index 9779b45c..00000000 --- a/frontend/js/app/ui/header/main.js +++ /dev/null @@ -1,67 +0,0 @@ -const $ = require('jquery'); -const Mn = require('backbone.marionette'); -const i18n = require('../../i18n'); -const Cache = require('../../cache'); -const Controller = require('../../controller'); -const Tokens = require('../../tokens'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'header', - className: 'header', - template: template, - - ui: { - link: 'a', - details: 'a.edit-details', - password: 'a.change-password' - }, - - events: { - 'click @ui.details': function (e) { - e.preventDefault(); - Controller.showUserForm(Cache.User); - }, - - 'click @ui.password': function (e) { - e.preventDefault(); - Controller.showUserPasswordForm(Cache.User); - }, - - 'click @ui.link': function (e) { - e.preventDefault(); - let href = $(e.currentTarget).attr('href'); - - switch (href) { - case '/': - Controller.showDashboard(); - break; - case '/logout': - Controller.logout(); - break; - } - } - }, - - templateContext: { - getUserField: function (field, default_val) { - return Cache.User.get(field) || default_val; - }, - - getRole: function () { - return i18n('roles', Cache.User.isAdmin() ? 'admin' : 'user'); - }, - - getLogoutText: function () { - if (Tokens.getTokenCount() > 1) { - return i18n('main', 'sign-in-as', {name: Tokens.getNextTokenName()}); - } - - return i18n('str', 'sign-out'); - } - }, - - initialize: function () { - this.listenTo(Cache.User, 'change', this.render); - } -}); diff --git a/frontend/js/app/ui/main.ejs b/frontend/js/app/ui/main.ejs deleted file mode 100644 index b62c3acd..00000000 --- a/frontend/js/app/ui/main.ejs +++ /dev/null @@ -1,21 +0,0 @@ -
- -
-
- -
-
-
- -
- -
- - diff --git a/frontend/js/app/ui/main.js b/frontend/js/app/ui/main.js deleted file mode 100644 index c90c61d5..00000000 --- a/frontend/js/app/ui/main.js +++ /dev/null @@ -1,98 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); -const HeaderView = require('./header/main'); -const MenuView = require('./menu/main'); -const FooterView = require('./footer/main'); -const Cache = require('../cache'); - -module.exports = Mn.View.extend({ - id: 'app', - className: 'page', - template: template, - modal_setup: false, - - modal: null, - - ui: { - modal: '#modal-dialog' - }, - - regions: { - header_region: { - el: '#header', - replaceElement: true - }, - menu_region: { - el: '#menu', - replaceElement: true - }, - footer_region: '.footer', - app_content_region: '#app-content', - modal_region: '#modal-dialog' - }, - - /** - * @param {Object} view - */ - showAppContent: function (view) { - this.showChildView('app_content_region', view); - }, - - /** - * @param {Object} view - * @param {Function} [show_callback] - * @param {Function} [shown_callback] - */ - showModalDialog: function (view, show_callback, shown_callback) { - this.showChildView('modal_region', view); - let modal = this.getRegion('modal_region').$el.modal('show'); - - modal.on('hidden.bs.modal', function (/*e*/) { - if (show_callback) { - modal.off('show.bs.modal', show_callback); - } - - if (shown_callback) { - modal.off('shown.bs.modal', shown_callback); - } - - modal.off('hidden.bs.modal'); - view.destroy(); - }); - - if (show_callback) { - modal.on('show.bs.modal', show_callback); - } - - if (shown_callback) { - modal.on('shown.bs.modal', shown_callback); - } - }, - - /** - * - * @param {Function} [hidden_callback] - */ - closeModal: function (hidden_callback) { - let modal = this.getRegion('modal_region').$el.modal('hide'); - - if (hidden_callback) { - modal.on('hidden.bs.modal', hidden_callback); - } - }, - - onRender: function () { - this.showChildView('header_region', new HeaderView({ - model: Cache.User - })); - - this.showChildView('menu_region', new MenuView()); - this.showChildView('footer_region', new FooterView()); - }, - - reset: function () { - this.getRegion('header_region').reset(); - this.getRegion('footer_region').reset(); - this.getRegion('modal_region').reset(); - } -}); diff --git a/frontend/js/app/ui/menu/main.ejs b/frontend/js/app/ui/menu/main.ejs deleted file mode 100644 index 671b4e3b..00000000 --- a/frontend/js/app/ui/menu/main.ejs +++ /dev/null @@ -1,52 +0,0 @@ -
-
-
- -
-
-
diff --git a/frontend/js/app/ui/menu/main.js b/frontend/js/app/ui/menu/main.js deleted file mode 100644 index dabe26d3..00000000 --- a/frontend/js/app/ui/menu/main.js +++ /dev/null @@ -1,39 +0,0 @@ -const $ = require('jquery'); -const Mn = require('backbone.marionette'); -const Controller = require('../../controller'); -const Cache = require('../../cache'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'menu', - className: 'header collapse d-lg-flex p-0', - template: template, - - ui: { - links: 'a' - }, - - events: { - 'click @ui.links': function (e) { - let href = $(e.currentTarget).attr('href'); - if (href !== '#') { - e.preventDefault(); - Controller.navigate(href, true); - } - } - }, - - templateContext: { - isAdmin: function () { - return Cache.User.isAdmin(); - }, - - canShow: function (perm) { - return Cache.User.isAdmin() || Cache.User.canView(perm); - } - }, - - initialize: function () { - this.listenTo(Cache.User, 'change', this.render); - } -}); diff --git a/frontend/js/app/user/delete.ejs b/frontend/js/app/user/delete.ejs deleted file mode 100644 index c10532ef..00000000 --- a/frontend/js/app/user/delete.ejs +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/frontend/js/app/user/delete.js b/frontend/js/app/user/delete.js deleted file mode 100644 index e8ed5c32..00000000 --- a/frontend/js/app/user/delete.js +++ /dev/null @@ -1,34 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./delete.ejs'); -const App = require('../main'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Users.delete(this.model.get('id')) - .then(() => { - App.Controller.showUsers(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/user/form.ejs b/frontend/js/app/user/form.ejs deleted file mode 100644 index 9ba84438..00000000 --- a/frontend/js/app/user/form.ejs +++ /dev/null @@ -1,58 +0,0 @@ - diff --git a/frontend/js/app/user/form.js b/frontend/js/app/user/form.js deleted file mode 100644 index 617a75fc..00000000 --- a/frontend/js/app/user/form.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const UserModel = require('../../models/user'); -const template = require('./form.ejs'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - error: '.secret-error' - }, - - events: { - - 'submit @ui.form': function (e) { - e.preventDefault(); - this.ui.error.hide(); - let view = this; - let data = this.ui.form.serializeJSON(); - - let show_password = this.model.get('email') === 'admin@example.com'; - - // admin@example.com is not allowed - if (data.email === 'admin@example.com') { - this.ui.error.text(App.i18n('users', 'default_error')).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - return; - } - - // Manipulate - data.roles = []; - if ((this.model.get('id') === App.Cache.User.get('id') && this.model.isAdmin()) || (typeof data.is_admin !== 'undefined' && data.is_admin)) { - data.roles.push('admin'); - delete data.is_admin; - } - - data.is_disabled = typeof data.is_disabled !== 'undefined' ? !!data.is_disabled : false; - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - let method = App.Api.Users.create; - - if (this.model.get('id')) { - // edit - method = App.Api.Users.update; - data.id = this.model.get('id'); - } - - method(data) - .then(result => { - if (result.id === App.Cache.User.get('id')) { - App.Cache.User.set(result); - } - - if (view.model.get('id') !== App.Cache.User.get('id')) { - App.Controller.showUsers(); - } - - view.model.set(result); - App.UI.closeModal(function () { - if (method === App.Api.Users.create) { - // Show permissions dialog immediately - App.Controller.showUserPermissions(view.model); - } else if (show_password) { - App.Controller.showUserPasswordForm(view.model); - } - }); - }) - .catch(err => { - this.ui.error.text(err.message).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - templateContext: function () { - let view = this; - - return { - isSelf: function () { - return view.model.get('id') === App.Cache.User.get('id'); - }, - - isAdmin: function () { - return App.Cache.User.isAdmin(); - }, - - isAdminUser: function () { - return view.model.isAdmin(); - }, - - isDisabled: function () { - return view.model.isDisabled(); - } - }; - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new UserModel.Model(); - } - } -}); diff --git a/frontend/js/app/user/password.ejs b/frontend/js/app/user/password.ejs deleted file mode 100644 index a45cc7ed..00000000 --- a/frontend/js/app/user/password.ejs +++ /dev/null @@ -1,31 +0,0 @@ - diff --git a/frontend/js/app/user/password.js b/frontend/js/app/user/password.js deleted file mode 100644 index 84030750..00000000 --- a/frontend/js/app/user/password.js +++ /dev/null @@ -1,69 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const template = require('./password.ejs'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - newSecretError: '.new-secret-error', - generalError: '#error-info', - }, - - events: { - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.newSecretError.hide(); - this.ui.generalError.hide(); - let form = this.ui.form.serializeJSON(); - - if (form.new_password1 !== form.new_password2) { - this.ui.newSecretError.text('Passwords do not match!').show(); - return; - } - - let data = { - type: 'password', - current: form.current_password, - secret: form.new_password1 - }; - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - App.Api.Users.setPassword(this.model.get('id'), data) - .then(() => { - App.UI.closeModal(); - App.Controller.showUsers(); - }) - .catch(err => { - // Change error message to make it a little clearer - if (err.message === 'Invalid password') { - err.message = 'Current password is invalid'; - } - this.ui.generalError.text(err.message).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - isSelf: function () { - return App.Cache.User.get('id') === this.model.get('id'); - }, - - templateContext: function () { - return { - isSelf: this.isSelf.bind(this) - }; - }, - - onRender: function () { - this.ui.newSecretError.hide(); - this.ui.generalError.hide(); - }, -}); diff --git a/frontend/js/app/user/permissions.ejs b/frontend/js/app/user/permissions.ejs deleted file mode 100644 index b6161796..00000000 --- a/frontend/js/app/user/permissions.ejs +++ /dev/null @@ -1,68 +0,0 @@ - diff --git a/frontend/js/app/user/permissions.js b/frontend/js/app/user/permissions.js deleted file mode 100644 index af8049ce..00000000 --- a/frontend/js/app/user/permissions.js +++ /dev/null @@ -1,95 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const UserModel = require('../../models/user'); -const template = require('./permissions.ejs'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - error: '.secret-error' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Manipulate - if (view.model.isAdmin()) { - // Force some attributes for admin - data = _.assign({}, data, { - access_lists: 'manage', - dead_hosts: 'manage', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - streams: 'manage', - certificates: 'manage' - }); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - - App.Api.Users.setPermissions(view.model.get('id'), data) - .then(() => { - if (view.model.get('id') === App.Cache.User.get('id')) { - App.Cache.User.set({permissions: data}); - } - - view.model.set({permissions: data}); - App.UI.closeModal(); - }) - .catch(err => { - this.ui.error.text(err.message).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - templateContext: function () { - let perms = this.model.get('permissions'); - let is_admin = this.model.isAdmin(); - - return { - getPerm: function (key) { - if (perms !== null && typeof perms[key] !== 'undefined') { - return perms[key]; - } - - return null; - }, - - getPermProps: function (key, item, forced_admin) { - if (forced_admin && is_admin) { - return 'checked disabled'; - } else if (is_admin) { - return 'disabled'; - } else if (perms !== null && typeof perms[key] !== 'undefined' && perms[key] === item) { - return 'checked'; - } - - return ''; - }, - - isAdmin: function () { - return is_admin; - } - }; - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new UserModel.Model(); - } - } -}); diff --git a/frontend/js/app/users/list/item.ejs b/frontend/js/app/users/list/item.ejs deleted file mode 100644 index fab5585b..00000000 --- a/frontend/js/app/users/list/item.ejs +++ /dev/null @@ -1,45 +0,0 @@ - -
- -
- - -
<%- name %>
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- email %>
- - -
- <% - var r = []; - roles.map(function(role) { - if (role) { - r.push(i18n('roles', role)); - } - }); - %> - <%- r.join(', ') %> -
- - - - diff --git a/frontend/js/app/users/list/item.js b/frontend/js/app/users/list/item.js deleted file mode 100644 index 4645a5c4..00000000 --- a/frontend/js/app/users/list/item.js +++ /dev/null @@ -1,68 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const Tokens = require('../../tokens'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - edit: 'a.edit-user', - permissions: 'a.edit-permissions', - password: 'a.set-password', - login: 'a.login', - delete: 'a.delete-user' - }, - - events: { - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showUserForm(this.model); - }, - - 'click @ui.permissions': function (e) { - e.preventDefault(); - App.Controller.showUserPermissions(this.model); - }, - - 'click @ui.password': function (e) { - e.preventDefault(); - App.Controller.showUserPasswordForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showUserDeleteConfirm(this.model); - }, - - 'click @ui.login': function (e) { - e.preventDefault(); - - if (App.Cache.User.get('id') !== this.model.get('id')) { - this.ui.login.prop('disabled', true).addClass('btn-disabled'); - - App.Api.Users.loginAs(this.model.get('id')) - .then(res => { - Tokens.addToken(res.token, res.user.nickname || res.user.name); - window.location = '/'; - window.location.reload(); - }) - .catch(err => { - alert(err.message); - this.ui.login.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } - }, - - templateContext: { - isSelf: function () { - return App.Cache.User.get('id') === this.id; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/users/list/main.ejs b/frontend/js/app/users/list/main.ejs deleted file mode 100644 index c85c9cb1..00000000 --- a/frontend/js/app/users/list/main.ejs +++ /dev/null @@ -1,10 +0,0 @@ - -   - <%- i18n('str', 'name') %> - <%- i18n('str', 'email') %> - <%- i18n('str', 'roles') %> -   - - - - diff --git a/frontend/js/app/users/list/main.js b/frontend/js/app/users/list/main.js deleted file mode 100644 index 9d3e26fb..00000000 --- a/frontend/js/app/users/list/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/users/main.ejs b/frontend/js/app/users/main.ejs deleted file mode 100644 index 892cb83f..00000000 --- a/frontend/js/app/users/main.ejs +++ /dev/null @@ -1,26 +0,0 @@ -
-
-
-

<%- i18n('users', 'title') %>

-
- - <%- i18n('users', 'add') %> -
-
-
-
-
-
- -
-
- -
-
diff --git a/frontend/js/app/users/main.js b/frontend/js/app/users/main.js deleted file mode 100644 index 42cb41ef..00000000 --- a/frontend/js/app/users/main.js +++ /dev/null @@ -1,78 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const UserModel = require('../../models/user'); -const ListView = require('./list/main'); -const ErrorView = require('../error/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'users', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Users.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new UserModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showUsers(); - } - })); - - console.error(err); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showUserForm(new UserModel.Model()); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['permissions'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - onRender: function () { - let view = this; - - view.fetch(['permissions']) - .then(response => { - if (!view.isDestroyed() && response && response.length) { - view.showData(response); - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/i18n/messages.json b/frontend/js/i18n/messages.json deleted file mode 100644 index d409942f..00000000 --- a/frontend/js/i18n/messages.json +++ /dev/null @@ -1,301 +0,0 @@ -{ - "en": { - "str": { - "email-address": "Email address", - "username": "Username", - "password": "Password", - "sign-in": "Sign in", - "sign-out": "Sign out", - "try-again": "Try again", - "name": "Name", - "email": "Email", - "roles": "Roles", - "created-on": "Created: {date}", - "save": "Save", - "cancel": "Cancel", - "close": "Close", - "enable": "Enable", - "disable": "Disable", - "sure": "Yes I'm Sure", - "disabled": "Disabled", - "choose-file": "Choose file", - "source": "Source", - "destination": "Destination", - "ssl": "SSL", - "access": "Access", - "public": "Public", - "edit": "Edit", - "delete": "Delete", - "logs": "Logs", - "status": "Status", - "online": "Online", - "offline": "Offline", - "unknown": "Unknown", - "expires": "Expires", - "value": "Value", - "please-wait": "Please wait...", - "all": "All", - "any": "Any" - }, - "login": { - "title": "Login to your account" - }, - "main": { - "app": "Nginx Proxy Manager", - "version": "v{version}", - "welcome": "Welcome to Nginx Proxy Manager", - "logged-in": "You are logged in as {name}", - "unknown-error": "Error loading stuff. Please reload the app.", - "unknown-user": "Unknown User", - "sign-in-as": "Sign back in as {name}" - }, - "roles": { - "title": "Roles", - "admin": "Administrator", - "user": "Apache Helicopter" - }, - "menu": { - "dashboard": "Dashboard", - "hosts": "Hosts" - }, - "footer": { - "fork-me": "Fork me on Github", - "copy": "© 2025 jc21.com.", - "theme": "Theme by Tabler" - }, - "dashboard": { - "title": "Hi {name}" - }, - "all-hosts": { - "empty-subtitle": "{manage, select, true{Why don't you create one?} other{And you don't have permission to create one.}}", - "details": "Details", - "enable-ssl": "Enable SSL", - "force-ssl": "Force SSL", - "http2-support": "HTTP/2 Support", - "domain-names": "Domain Names", - "cert-provider": "Certificate Provider", - "block-exploits": "Block Common Exploits", - "caching-enabled": "Cache Assets", - "ssl-certificate": "SSL Certificate", - "none": "None", - "new-cert": "Request a new SSL Certificate", - "with-le": "with Let's Encrypt", - "no-ssl": "This host will not use HTTPS", - "advanced": "Advanced", - "advanced-warning": "Enter your custom Nginx configuration here at your own risk!", - "advanced-config": "Custom Nginx Configuration", - "advanced-config-var-headline": "These proxy details are available as nginx variables:", - "advanced-config-header-info": "Please note, that any add_header or set_header directives added here will not be used by nginx. You will have to add a custom location '/' and add the header in the custom config there.", - "hsts-enabled": "HSTS Enabled", - "hsts-subdomains": "HSTS Subdomains", - "locations": "Custom locations" - }, - "locations": { - "new_location": "Add location", - "path": "/path", - "location_label": "Define location", - "delete": "Delete" - }, - "ssl": { - "letsencrypt": "Let's Encrypt", - "other": "Custom", - "none": "HTTP only", - "letsencrypt-email": "Email Address for Let's Encrypt", - "letsencrypt-agree": "I Agree to the Let's Encrypt Terms of Service", - "delete-ssl": "The SSL certificates attached will NOT be removed, they will need to be removed manually.", - "hosts-warning": "These domains must be already configured to point to this installation", - "no-wildcard-without-dns": "Cannot request Let's Encrypt Certificate for wildcard domains when not using DNS challenge", - "dns-challenge": "Use a DNS Challenge", - "certbot-warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.", - "dns-provider": "DNS Provider", - "please-choose": "Please Choose...", - "credentials-file-content": "Credentials File Content", - "credentials-file-content-info": "This plugin requires a configuration file containing an API token or other credentials to your provider", - "stored-as-plaintext-info": "This data will be stored as plaintext in the database and in a file!", - "propagation-seconds": "Propagation Seconds", - "propagation-seconds-info": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.", - "processing-info": "Processing... This might take a few minutes.", - "passphrase-protection-support-info": "Key files protected with a passphrase are not supported." - }, - "proxy-hosts": { - "title": "Proxy Hosts", - "empty": "There are no Proxy Hosts", - "add": "Add Proxy Host", - "form-title": "{id, select, undefined{New} other{Edit}} Proxy Host", - "forward-scheme": "Scheme", - "forward-host": "Forward Hostname / IP", - "forward-port": "Forward Port", - "delete": "Delete Proxy Host", - "delete-confirm": "Are you sure you want to delete the Proxy host for: {domains}?", - "help-title": "What is a Proxy Host?", - "help-content": "A Proxy Host is the incoming endpoint for a web service that you want to forward.\nIt provides optional SSL termination for your service that might not have SSL support built in.\nProxy Hosts are the most common use for the Nginx Proxy Manager.", - "access-list": "Access List", - "allow-websocket-upgrade": "Websockets Support", - "ignore-invalid-upstream-ssl": "Ignore Invalid SSL", - "custom-forward-host-help": "Add a path for sub-folder forwarding.\nExample: 203.0.113.25/path/", - "search": "Search Host…" - }, - "redirection-hosts": { - "title": "Redirection Hosts", - "empty": "There are no Redirection Hosts", - "add": "Add Redirection Host", - "form-title": "{id, select, undefined{New} other{Edit}} Redirection Host", - "forward-scheme": "Scheme", - "forward-http-status-code": "HTTP Code", - "forward-domain": "Forward Domain", - "preserve-path": "Preserve Path", - "delete": "Delete Redirection Host", - "delete-confirm": "Are you sure you want to delete the Redirection host for: {domains}?", - "help-title": "What is a Redirection Host?", - "help-content": "A Redirection Host will redirect requests from the incoming domain and push the viewer to another domain.\nThe most common reason to use this type of host is when your website changes domains but you still have search engine or referrer links pointing to the old domain.", - "search": "Search Host…" - }, - "dead-hosts": { - "title": "404 Hosts", - "empty": "There are no 404 Hosts", - "add": "Add 404 Host", - "form-title": "{id, select, undefined{New} other{Edit}} 404 Host", - "delete": "Delete 404 Host", - "delete-confirm": "Are you sure you want to delete this 404 Host?", - "help-title": "What is a 404 Host?", - "help-content": "A 404 Host is simply a host setup that shows a 404 page.\nThis can be useful when your domain is listed in search engines and you want to provide a nicer error page or specifically to tell the search indexers that the domain pages no longer exist.\nAnother benefit of having this host is to track the logs for hits to it and view the referrers.", - "search": "Search Host…" - }, - "streams": { - "title": "Streams", - "empty": "There are no Streams", - "add": "Add Stream", - "form-title": "{id, select, undefined{New} other{Edit}} Stream", - "incoming-port": "Incoming Port", - "forwarding-host": "Forward Host", - "forwarding-port": "Forward Port", - "tcp-forwarding": "TCP Forwarding", - "udp-forwarding": "UDP Forwarding", - "forward-type-error": "At least one type of protocol must be enabled", - "protocol": "Protocol", - "tcp": "TCP", - "udp": "UDP", - "delete": "Delete Stream", - "delete-confirm": "Are you sure you want to delete this Stream?", - "help-title": "What is a Stream?", - "help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.", - "search": "Search Incoming Port…", - "ssl-certificate": "SSL Certificate for TCP Forwarding", - "tcp+ssl": "TCP+SSL" - }, - "certificates": { - "title": "SSL Certificates", - "empty": "There are no SSL Certificates", - "add": "Add SSL Certificate", - "form-title": "Add {provider, select, letsencrypt{Let's Encrypt} other{Custom}} Certificate", - "delete": "Delete SSL Certificate", - "delete-confirm": "Are you sure you want to delete this SSL Certificate? Any hosts using it will need to be updated later.", - "help-title": "SSL Certificates", - "help-content": "SSL certificates (correctly known as TLS Certificates) are a form of encryption key which allows your site to be encrypted for the end user.\nNPM uses a service called Let's Encrypt to issue SSL certificates for free.\nIf you have any sort of personal information, passwords, or sensitive data behind NPM, it's probably a good idea to use a certificate.\nNPM also supports DNS authentication for if you're not running your site facing the internet, or if you just want a wildcard certificate.", - "other-certificate": "Certificate", - "other-certificate-key": "Certificate Key", - "other-intermediate-certificate": "Intermediate Certificate", - "force-renew": "Renew Now", - "test-reachability": "Test Server Reachability", - "reachability-title": "Test Server Reachability", - "reachability-info": "Test whether the domains are reachable from the public internet using Site24x7. This is not necessary when using the DNS Challenge.", - "reachability-failed-to-reach-api": "Communication with the API failed, is NPM running correctly?", - "reachability-failed-to-check": "Failed to check the reachability due to a communication error with site24x7.com.", - "reachability-ok": "Your server is reachable and creating certificates should be possible.", - "reachability-404": "There is a server found at this domain but it does not seem to be Nginx Proxy Manager. Please make sure your domain points to the IP where your NPM instance is running.", - "reachability-not-resolved": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router.", - "reachability-wrong-data": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", - "reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", - "download": "Download", - "renew-title": "Renew Let's Encrypt Certificate", - "search": "Search Certificate…", - "in-use" : "In use", - "inactive": "Inactive", - "active-domain_names": "Active domain names" - }, - "access-lists": { - "title": "Access Lists", - "empty": "There are no Access Lists", - "add": "Add Access List", - "form-title": "{id, select, undefined{New} other{Edit}} Access List", - "delete": "Delete Access List", - "delete-confirm": "Are you sure you want to delete this access list?", - "public": "Publicly Accessible", - "public-sub": "No Access Restrictions", - "help-title": "What is an Access List?", - "help-content": "Access Lists provide a blacklist or whitelist of specific client IP addresses along with authentication for the Proxy Hosts via Basic HTTP Authentication.\nYou can configure multiple client rules, usernames and passwords for a single Access List and then apply that to a Proxy Host.\nThis is most useful for forwarded web services that do not have authentication mechanisms built in or that you want to protect from access by unknown clients.", - "item-count": "{count} {count, select, 1{User} other{Users}}", - "client-count": "{count} {count, select, 1{Rule} other{Rules}}", - "proxy-host-count": "{count} {count, select, 1{Proxy Host} other{Proxy Hosts}}", - "delete-has-hosts": "This Access List is associated with {count} Proxy Hosts. They will become publicly available upon deletion.", - "details": "Details", - "authorization": "Authorization", - "access": "Access", - "satisfy": "Satisfy", - "satisfy-any": "Satisfy Any", - "pass-auth": "Pass Auth to Host", - "access-add": "Add", - "auth-add": "Add", - "search": "Search Access…" - }, - "users": { - "title": "Users", - "default_error": "Default email address must be changed", - "add": "Add User", - "nickname": "Nickname", - "full-name": "Full Name", - "edit-details": "Edit Details", - "change-password": "Change Password", - "edit-permissions": "Edit Permissions", - "sign-in-as": "Sign in as User", - "form-title": "{id, select, undefined{New} other{Edit}} User", - "delete": "Delete {name, select, undefined{User} other{{name}}}", - "delete-confirm": "Are you sure you want to delete {name}?", - "password-title": "Change Password{self, select, false{ for {name}} other{}}", - "current-password": "Current Password", - "new-password": "New Password", - "confirm-password": "Confirm Password", - "permissions-title": "Permissions for {name}", - "admin-perms": "This user is an Administrator and some items cannot be altered", - "perms-visibility": "Item Visibility", - "perms-visibility-user": "Created Items Only", - "perms-visibility-all": "All Items", - "perm-manage": "Manage", - "perm-view": "View Only", - "perm-hidden": "Hidden", - "search": "Search User…" - }, - "audit-log": { - "title": "Audit Log", - "empty": "There are no logs.", - "empty-subtitle": "As soon as you or another user changes something, history of those events will show up here.", - "proxy-host": "Proxy Host", - "redirection-host": "Redirection Host", - "dead-host": "404 Host", - "stream": "Stream", - "user": "User", - "certificate": "Certificate", - "access-list": "Access List", - "created": "Created {name}", - "updated": "Updated {name}", - "deleted": "Deleted {name}", - "enabled": "Enabled {name}", - "disabled": "Disabled {name}", - "renewed": "Renewed {name}", - "meta-title": "Details for Event", - "view-meta": "View Details", - "date": "Date", - "search": "Search Log…" - }, - "settings": { - "title": "Settings", - "default-site": "Default Site", - "default-site-description": "What to show when Nginx is hit with an unknown Host", - "default-site-congratulations": "Congratulations Page", - "default-site-404": "404 Page", - "default-site-444": "No Response (444)", - "default-site-html": "Custom Page", - "default-site-redirect": "Redirect" - } - } -} diff --git a/frontend/js/index.js b/frontend/js/index.js deleted file mode 100644 index 3d817d71..00000000 --- a/frontend/js/index.js +++ /dev/null @@ -1,119 +0,0 @@ -// This has to exist here so that Webpack picks it up -import '../scss/styles.scss'; - -window.tabler = { - colors: { - 'blue': '#467fcf', - 'blue-darkest': '#0e1929', - 'blue-darker': '#1c3353', - 'blue-dark': '#3866a6', - 'blue-light': '#7ea5dd', - 'blue-lighter': '#c8d9f1', - 'blue-lightest': '#edf2fa', - 'azure': '#45aaf2', - 'azure-darkest': '#0e2230', - 'azure-darker': '#1c4461', - 'azure-dark': '#3788c2', - 'azure-light': '#7dc4f6', - 'azure-lighter': '#c7e6fb', - 'azure-lightest': '#ecf7fe', - 'indigo': '#6574cd', - 'indigo-darkest': '#141729', - 'indigo-darker': '#282e52', - 'indigo-dark': '#515da4', - 'indigo-light': '#939edc', - 'indigo-lighter': '#d1d5f0', - 'indigo-lightest': '#f0f1fa', - 'purple': '#a55eea', - 'purple-darkest': '#21132f', - 'purple-darker': '#42265e', - 'purple-dark': '#844bbb', - 'purple-light': '#c08ef0', - 'purple-lighter': '#e4cff9', - 'purple-lightest': '#f6effd', - 'pink': '#f66d9b', - 'pink-darkest': '#31161f', - 'pink-darker': '#622c3e', - 'pink-dark': '#c5577c', - 'pink-light': '#f999b9', - 'pink-lighter': '#fcd3e1', - 'pink-lightest': '#fef0f5', - 'red': '#e74c3c', - 'red-darkest': '#2e0f0c', - 'red-darker': '#5c1e18', - 'red-dark': '#b93d30', - 'red-light': '#ee8277', - 'red-lighter': '#f8c9c5', - 'red-lightest': '#fdedec', - 'orange': '#fd9644', - 'orange-darkest': '#331e0e', - 'orange-darker': '#653c1b', - 'orange-dark': '#ca7836', - 'orange-light': '#feb67c', - 'orange-lighter': '#fee0c7', - 'orange-lightest': '#fff5ec', - 'yellow': '#f1c40f', - 'yellow-darkest': '#302703', - 'yellow-darker': '#604e06', - 'yellow-dark': '#c19d0c', - 'yellow-light': '#f5d657', - 'yellow-lighter': '#fbedb7', - 'yellow-lightest': '#fef9e7', - 'lime': '#7bd235', - 'lime-darkest': '#192a0b', - 'lime-darker': '#315415', - 'lime-dark': '#62a82a', - 'lime-light': '#a3e072', - 'lime-lighter': '#d7f2c2', - 'lime-lightest': '#f2fbeb', - 'green': '#5eba00', - 'green-darkest': '#132500', - 'green-darker': '#264a00', - 'green-dark': '#4b9500', - 'green-light': '#8ecf4d', - 'green-lighter': '#cfeab3', - 'green-lightest': '#eff8e6', - 'teal': '#2bcbba', - 'teal-darkest': '#092925', - 'teal-darker': '#11514a', - 'teal-dark': '#22a295', - 'teal-light': '#6bdbcf', - 'teal-lighter': '#bfefea', - 'teal-lightest': '#eafaf8', - 'cyan': '#17a2b8', - 'cyan-darkest': '#052025', - 'cyan-darker': '#09414a', - 'cyan-dark': '#128293', - 'cyan-light': '#5dbecd', - 'cyan-lighter': '#b9e3ea', - 'cyan-lightest': '#e8f6f8', - 'gray': '#868e96', - 'gray-darkest': '#1b1c1e', - 'gray-darker': '#36393c', - 'gray-light': '#aab0b6', - 'gray-lighter': '#dbdde0', - 'gray-lightest': '#f3f4f5', - 'gray-dark': '#343a40', - 'gray-dark-darkest': '#0a0c0d', - 'gray-dark-darker': '#15171a', - 'gray-dark-dark': '#2a2e33', - 'gray-dark-light': '#717579', - 'gray-dark-lighter': '#c2c4c6', - 'gray-dark-lightest': '#ebebec' - } -}; - -String.prototype.toHtmlEntities = function() { - return this.replace(/./gm, function(s) { - // return "&#" + s.charCodeAt(0) + ";"; - return (s.match(/[a-z0-9\s]+/i)) ? s : "&#" + s.charCodeAt(0) + ";"; - }); -}; - -require('tabler-core'); - -const App = require('./app/main'); - -$(document).ready(() => { - App.start(); -}); diff --git a/frontend/js/lib/helpers.js b/frontend/js/lib/helpers.js deleted file mode 100644 index 21ce7424..00000000 --- a/frontend/js/lib/helpers.js +++ /dev/null @@ -1,26 +0,0 @@ -const numeral = require('numeral'); -const moment = require('moment'); - -module.exports = { - - /** - * @param {Integer} number - * @returns {String} - */ - niceNumber: function (number) { - return numeral(number).format('0,0'); - }, - - /** - * @param {String|Number} date - * @param {String} format - * @returns {String} - */ - formatDbDate: function (date, format) { - if (typeof date === 'number') { - return moment.unix(date).format(format); - } - - return moment(date).format(format); - } -}; diff --git a/frontend/js/lib/marionette.js b/frontend/js/lib/marionette.js deleted file mode 100644 index c88368f8..00000000 --- a/frontend/js/lib/marionette.js +++ /dev/null @@ -1,15 +0,0 @@ -const _ = require('underscore'); -const Mn = require('backbone.marionette'); -const i18n = require('../app/i18n'); -const Helpers = require('./helpers'); -const TemplateCache = require('marionette.templatecache'); - -Mn.setRenderer(function (template, data, view) { - data = _.clone(data); - data.i18n = i18n; - data.formatDbDate = Helpers.formatDbDate; - - return TemplateCache.default.render.call(this, template, data, view); -}); - -module.exports = Mn; diff --git a/frontend/js/login.js b/frontend/js/login.js deleted file mode 100644 index 0094e2a2..00000000 --- a/frontend/js/login.js +++ /dev/null @@ -1,5 +0,0 @@ -const App = require('./login/main'); - -$(document).ready(() => { - App.start(); -}); diff --git a/frontend/js/login/main.js b/frontend/js/login/main.js deleted file mode 100644 index 03fdc7e5..00000000 --- a/frontend/js/login/main.js +++ /dev/null @@ -1,14 +0,0 @@ -const Mn = require('backbone.marionette'); -const LoginView = require('./ui/login'); - -const App = Mn.Application.extend({ - region: '#login', - UI: null, - - onStart: function (/*app, options*/) { - this.getRegion().show(new LoginView()); - } -}); - -const app = new App(); -module.exports = app; diff --git a/frontend/js/login/ui/login.ejs b/frontend/js/login/ui/login.ejs deleted file mode 100644 index 693bc050..00000000 --- a/frontend/js/login/ui/login.ejs +++ /dev/null @@ -1,37 +0,0 @@ -
-
- -
-
diff --git a/frontend/js/login/ui/login.js b/frontend/js/login/ui/login.js deleted file mode 100644 index 757eb4e3..00000000 --- a/frontend/js/login/ui/login.js +++ /dev/null @@ -1,42 +0,0 @@ -const $ = require('jquery'); -const Mn = require('backbone.marionette'); -const template = require('./login.ejs'); -const Api = require('../../app/api'); -const i18n = require('../../app/i18n'); - -module.exports = Mn.View.extend({ - template: template, - className: 'page-single', - - ui: { - form: 'form', - identity: 'input[name="identity"]', - secret: 'input[name="secret"]', - error: '.secret-error', - button: 'button' - }, - - events: { - 'submit @ui.form': function (e) { - e.preventDefault(); - this.ui.button.addClass('btn-loading').prop('disabled', true); - this.ui.error.hide(); - - Api.Tokens.login(this.ui.identity.val(), this.ui.secret.val(), true) - .then(() => { - window.location = '/'; - }) - .catch(err => { - this.ui.error.text(err.message).show(); - this.ui.button.removeClass('btn-loading').prop('disabled', false); - }); - } - }, - - templateContext: { - i18n: i18n, - getVersion: function () { - return $('#login').data('version'); - } - } -}); diff --git a/frontend/js/models/access-list.js b/frontend/js/models/access-list.js deleted file mode 100644 index 0c2c4abe..00000000 --- a/frontend/js/models/access-list.js +++ /dev/null @@ -1,25 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - name: '', - items: [], - clients: [], - // The following are expansions: - owner: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/audit-log.js b/frontend/js/models/audit-log.js deleted file mode 100644 index c929a0bd..00000000 --- a/frontend/js/models/audit-log.js +++ /dev/null @@ -1,18 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - name: '' - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/certificate.js b/frontend/js/models/certificate.js deleted file mode 100644 index c7d0b2d9..00000000 --- a/frontend/js/models/certificate.js +++ /dev/null @@ -1,38 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - provider: '', - nice_name: '', - domain_names: [], - expires_on: null, - meta: {}, - // The following are expansions: - owner: null, - proxy_hosts: [], - redirection_hosts: [], - dead_hosts: [] - }; - }, - - /** - * @returns {Boolean} - */ - hasSslFiles: function () { - let meta = this.get('meta'); - return typeof meta['certificate'] !== 'undefined' && meta['certificate'] && typeof meta['certificate_key'] !== 'undefined' && meta['certificate_key']; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/dead-host.js b/frontend/js/models/dead-host.js deleted file mode 100644 index 98ceef29..00000000 --- a/frontend/js/models/dead-host.js +++ /dev/null @@ -1,32 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - domain_names: [], - certificate_id: 0, - ssl_forced: false, - http2_support: false, - hsts_enabled: false, - hsts_subdomains: false, - enabled: true, - meta: {}, - advanced_config: '', - // The following are expansions: - owner: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/proxy-host-location.js b/frontend/js/models/proxy-host-location.js deleted file mode 100644 index 2a35059f..00000000 --- a/frontend/js/models/proxy-host-location.js +++ /dev/null @@ -1,35 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function() { - return { - opened: false, - path: '', - advanced_config: '', - forward_scheme: 'http', - forward_host: '', - forward_port: '80' - } - }, - - toJSON() { - const r = Object.assign({}, this.attributes); - delete r.opened; - return r; - }, - - toggleVisibility: function () { - this.save({ - opened: !this.get('opened') - }); - } -}) - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model - }) -} \ No newline at end of file diff --git a/frontend/js/models/proxy-host.js b/frontend/js/models/proxy-host.js deleted file mode 100644 index b82d09fe..00000000 --- a/frontend/js/models/proxy-host.js +++ /dev/null @@ -1,40 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - domain_names: [], - forward_scheme: 'http', - forward_host: '', - forward_port: null, - access_list_id: 0, - certificate_id: 0, - ssl_forced: false, - hsts_enabled: false, - hsts_subdomains: false, - caching_enabled: false, - allow_websocket_upgrade: false, - block_exploits: false, - http2_support: false, - advanced_config: '', - enabled: true, - meta: {}, - // The following are expansions: - owner: null, - access_list: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/redirection-host.js b/frontend/js/models/redirection-host.js deleted file mode 100644 index 1d0b0de2..00000000 --- a/frontend/js/models/redirection-host.js +++ /dev/null @@ -1,37 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - domain_names: [], - forward_http_code: 0, - forward_scheme: null, - forward_domain_name: '', - preserve_path: true, - certificate_id: 0, - ssl_forced: false, - hsts_enabled: false, - hsts_subdomains: false, - block_exploits: false, - http2_support: false, - advanced_config: '', - enabled: true, - meta: {}, - // The following are expansions: - owner: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/setting.js b/frontend/js/models/setting.js deleted file mode 100644 index c70a4e9c..00000000 --- a/frontend/js/models/setting.js +++ /dev/null @@ -1,22 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - name: '', - description: '', - value: null, - meta: [] - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/stream.js b/frontend/js/models/stream.js deleted file mode 100644 index 390c4fb0..00000000 --- a/frontend/js/models/stream.js +++ /dev/null @@ -1,32 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - incoming_port: null, - forwarding_host: null, - forwarding_port: null, - tcp_forwarding: true, - udp_forwarding: false, - enabled: true, - meta: {}, - certificate_id: 0, - domain_names: [], - // The following are expansions: - owner: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/user.js b/frontend/js/models/user.js deleted file mode 100644 index a8e4ed9e..00000000 --- a/frontend/js/models/user.js +++ /dev/null @@ -1,54 +0,0 @@ -const _ = require('underscore'); -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - name: '', - nickname: '', - email: '', - is_disabled: false, - roles: [], - permissions: null - }; - }, - - /** - * @returns {Boolean} - */ - isAdmin: function () { - return _.indexOf(this.get('roles'), 'admin') !== -1; - }, - - /** - * Checks if the perm has either `view` or `manage` value - * - * @param {String} item - * @returns {Boolean} - */ - canView: function (item) { - let permissions = this.get('permissions'); - return permissions !== null && typeof permissions[item] !== 'undefined' && ['view', 'manage'].indexOf(permissions[item]) !== -1; - }, - - /** - * Checks if the perm has `manage` value - * - * @param {String} item - * @returns {Boolean} - */ - canManage: function (item) { - let permissions = this.get('permissions'); - return permissions !== null && typeof permissions[item] !== 'undefined' && permissions[item] === 'manage'; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/package.json b/frontend/package.json index 7347faa0..8117f959 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,47 +1,64 @@ { - "name": "nginx-proxy-manager", - "version": "0.0.0", - "description": "A beautiful interface for creating Nginx endpoints", - "main": "js/index.js", - "devDependencies": { - "@babel/core": "^7.9.0", - "babel-core": "^6.26.3", - "babel-loader": "^8.1.0", - "babel-preset-env": "^1.7.0", - "backbone": "^1.4.0", - "backbone.marionette": "^4.1.2", - "copy-webpack-plugin": "^5.1.1", - "css-loader": "^3.5.0", - "ejs-lint": "^1.0.1", - "ejs-loader": "^0.3.6", - "ejs-webpack-loader": "^2.2.2", - "file-loader": "^6.0.0", - "html-webpack-plugin": "^4.0.4", - "imports-loader": "^0.8.0", - "jquery": "^3.5.0", - "jquery-mask-plugin": "^1.14.16", - "jquery-serializejson": "^2.9.0", - "marionette.approuter": "^1.0.2", - "marionette.templatecache": "^1.0.0", - "messageformat": "^2.3.0", - "messageformat-loader": "^0.8.1", - "mini-css-extract-plugin": "^0.9.0", - "moment": "^2.30.1", - "sass": "^1.92.1", - "nodemon": "^2.0.2", - "numeral": "^2.0.6", - "sass-loader": "^10.0.0", - "style-loader": "^1.1.3", - "tabler-ui": "git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813", - "underscore": "^1.12.1", - "webpack": "^4.42.1", - "webpack-cli": "^3.3.11", - "webpack-visualizer-plugin": "^0.1.11" - }, - "scripts": { - "build": "webpack --mode production", - "watch": "webpack --watch --mode development" - }, - "author": "Jamie Curnow ", - "license": "MIT" + "name": "nginx-proxy-manager", + "version": "2.0.0", + "type": "module", + "author": "Jamie Curnow ", + "license": "MIT", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "biome lint", + "preview": "vite preview", + "prettier": "biome format --write ./src", + "locale-extract": "formatjs extract 'src/**/*.tsx'", + "locale-compile": "formatjs compile-folder src/locale/src src/locale/lang", + "locale-sort": "./src/locale/scripts/locale-sort.sh", + "test": "vitest" + }, + "dependencies": { + "@tabler/core": "^1.4.0", + "@tabler/icons-react": "^3.35.0", + "@tanstack/react-query": "^5.89.0", + "@tanstack/react-table": "^8.21.3", + "@uiw/react-textarea-code-editor": "^3.1.1", + "classnames": "^2.5.1", + "country-flag-icons": "^1.5.20", + "date-fns": "^4.1.0", + "formik": "^2.4.6", + "generate-password-browser": "^1.1.0", + "humps": "^2.0.1", + "query-string": "^9.3.1", + "react": "^19.1.1", + "react-bootstrap": "^2.10.10", + "react-dom": "^19.1.1", + "react-intl": "^7.1.11", + "react-router-dom": "^7.9.1", + "react-select": "^5.10.2", + "react-toastify": "^11.0.5", + "rooks": "^9.3.0" + }, + "devDependencies": { + "@biomejs/biome": "^2.2.4", + "@formatjs/cli": "^6.7.2", + "@tanstack/react-query-devtools": "^5.89.0", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^16.3.0", + "@types/country-flag-icons": "^1.2.2", + "@types/humps": "^2.0.6", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@types/react-table": "^7.7.20", + "@vitejs/plugin-react": "^5.0.3", + "happy-dom": "^18.0.1", + "postcss": "^8.5.6", + "postcss-simple-vars": "^7.0.1", + "sass": "^1.93.0", + "tmp": "^0.2.5", + "typescript": "5.9.2", + "vite": "^7.1.6", + "vite-plugin-checker": "^0.10.3", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4" + } } diff --git a/frontend/app-images/default-avatar.jpg b/frontend/public/images/default-avatar.jpg similarity index 100% rename from frontend/app-images/default-avatar.jpg rename to frontend/public/images/default-avatar.jpg diff --git a/frontend/app-images/favicons/android-chrome-192x192.png b/frontend/public/images/favicon/android-chrome-192x192.png similarity index 100% rename from frontend/app-images/favicons/android-chrome-192x192.png rename to frontend/public/images/favicon/android-chrome-192x192.png diff --git a/frontend/app-images/favicons/android-chrome-512x512.png b/frontend/public/images/favicon/android-chrome-512x512.png similarity index 100% rename from frontend/app-images/favicons/android-chrome-512x512.png rename to frontend/public/images/favicon/android-chrome-512x512.png diff --git a/frontend/app-images/favicons/apple-touch-icon.png b/frontend/public/images/favicon/apple-touch-icon.png similarity index 100% rename from frontend/app-images/favicons/apple-touch-icon.png rename to frontend/public/images/favicon/apple-touch-icon.png diff --git a/frontend/app-images/favicons/browserconfig.xml b/frontend/public/images/favicon/browserconfig.xml similarity index 100% rename from frontend/app-images/favicons/browserconfig.xml rename to frontend/public/images/favicon/browserconfig.xml diff --git a/frontend/app-images/favicons/favicon-16x16.png b/frontend/public/images/favicon/favicon-16x16.png similarity index 100% rename from frontend/app-images/favicons/favicon-16x16.png rename to frontend/public/images/favicon/favicon-16x16.png diff --git a/frontend/app-images/favicons/favicon-32x32.png b/frontend/public/images/favicon/favicon-32x32.png similarity index 100% rename from frontend/app-images/favicons/favicon-32x32.png rename to frontend/public/images/favicon/favicon-32x32.png diff --git a/frontend/app-images/favicons/favicon.ico b/frontend/public/images/favicon/favicon.ico similarity index 100% rename from frontend/app-images/favicons/favicon.ico rename to frontend/public/images/favicon/favicon.ico diff --git a/frontend/app-images/favicons/mstile-150x150.png b/frontend/public/images/favicon/mstile-150x150.png similarity index 100% rename from frontend/app-images/favicons/mstile-150x150.png rename to frontend/public/images/favicon/mstile-150x150.png diff --git a/frontend/app-images/favicons/safari-pinned-tab.svg b/frontend/public/images/favicon/safari-pinned-tab.svg similarity index 100% rename from frontend/app-images/favicons/safari-pinned-tab.svg rename to frontend/public/images/favicon/safari-pinned-tab.svg diff --git a/frontend/app-images/favicons/site.webmanifest b/frontend/public/images/favicon/site.webmanifest similarity index 100% rename from frontend/app-images/favicons/site.webmanifest rename to frontend/public/images/favicon/site.webmanifest diff --git a/frontend/app-images/logo-256.png b/frontend/public/images/logo-256.png similarity index 100% rename from frontend/app-images/logo-256.png rename to frontend/public/images/logo-256.png diff --git a/frontend/public/images/logo-bold-horizontal-grey.svg b/frontend/public/images/logo-bold-horizontal-grey.svg new file mode 100644 index 00000000..c87396f6 --- /dev/null +++ b/frontend/public/images/logo-bold-horizontal-grey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/images/logo-no-text.svg b/frontend/public/images/logo-no-text.svg new file mode 100644 index 00000000..dc3c1163 --- /dev/null +++ b/frontend/public/images/logo-no-text.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/images/logo-text-horizontal-grey.png b/frontend/public/images/logo-text-horizontal-grey.png new file mode 100644 index 0000000000000000000000000000000000000000..057a83d1b7a433e79f9d7b022488f29aee1c1e0c GIT binary patch literal 22203 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%0Ai+xv4%rSdX>J#T}H=Vzde z7MCY4i9%)64EC%u>+_pL+ViE3n#mTjk`UKwz#|dxN99%`;NZb9%pJxG3?NfbvdoYv z-~M?l8n0~8mB!E0gZZz7*&J?EJW**#p(Kg4-xjW-o6q2hV4?)HFO)tLP2za-3Pz%` z%*#LwsI2v;N`gmn4-ll3$f_)29m$Im7f@-Iv1!d__(2vEWPaEL9-kgKW;7OI7>4O! zR#**W1_W#^B!o6!4KI&<=xV{dHrWb9;E_;s`~<#GszqA=u6`~$7nRkZ%80a3Nx--n zR$@d+57{7V(^~QA8^bRaOuRFe-sAqN`QjLcVU~{7GY{GYiS^d-yD`k$tY?{E`&&=?q~QhUt7dArtc>C(ihbMY^9296tuI3BU`BbWet1n2w`U zQu(&y{2Ks<0Q~8F+~>EpGu`P|O)ld27=~dwj!vruUvT!Z56^RR)HMJPIXm9jTi+Rm zVVI8HO!Tr-8QD9_N}}jqV--@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/scss/custom.scss b/frontend/scss/custom.scss deleted file mode 100644 index 4037dcf6..00000000 --- a/frontend/scss/custom.scss +++ /dev/null @@ -1,42 +0,0 @@ -$primary-color: #2bcbba; - -.loader { - color: $primary-color; -} - -a { - color: $primary-color; -} - -a:hover { - color: darken($primary-color, 10%); -} - -.dropdown-header { - padding-left: 1rem; -} - -.dropdown-item.active, .dropdown-item:active { - background-color: $primary-color; -} - -.custom-switch-input:checked ~ .custom-switch-indicator { - background: $primary-color; -} - -.min-100 { - min-height: 100px; -} - -.card-options .dropdown-menu a:not(.btn) { - margin-left: 0; -} - -.wrap { - display: flex; - flex-wrap: wrap; -} - -.col-login { - max-width: 48rem; -} \ No newline at end of file diff --git a/frontend/scss/fonts.scss b/frontend/scss/fonts.scss deleted file mode 100644 index f0ec1b73..00000000 --- a/frontend/scss/fonts.scss +++ /dev/null @@ -1,39 +0,0 @@ -/* source-sans-pro-regular - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 400; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} - -/* source-sans-pro-italic - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: italic; - font-weight: 400; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} - -/* source-sans-pro-700italic - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: italic; - font-weight: 700; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} - -/* source-sans-pro-700 - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 700; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} diff --git a/frontend/scss/selectize.scss b/frontend/scss/selectize.scss deleted file mode 100644 index e12d5b69..00000000 --- a/frontend/scss/selectize.scss +++ /dev/null @@ -1,196 +0,0 @@ -.selectize-dropdown-header { - position: relative; - padding: 5px 8px; - background: #f8f8f8; - border-bottom: 1px solid #d0d0d0; - -webkit-border-radius: 3px 3px 0 0; - -moz-border-radius: 3px 3px 0 0; - border-radius: 3px 3px 0 0; -} - -.selectize-dropdown-header-close { - position: absolute; - top: 50%; - right: 8px; - margin-top: -12px; - font-size: 20px !important; - line-height: 20px; - color: #303030; - opacity: 0.4; -} - -.selectize-dropdown-header-close:hover { - color: #000000; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup { - float: left; - border-top: 0 none; - border-right: 1px solid #f2f2f2; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child { - border-right: 0 none; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup:before { - display: none; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup-header { - border-top: 0 none; -} - -.selectize-control.plugin-remove_button [data-value] { - position: relative; - padding-right: 24px !important; -} - -.selectize-control.plugin-remove_button [data-value] .remove { - position: absolute; - top: 0; - right: 0; - bottom: 0; - display: inline-block; - width: 17px; - padding: 2px 0 0 0; - font-size: 12px; - font-weight: bold; - color: inherit; - text-align: center; - text-decoration: none; - vertical-align: middle; - border-left: 1px solid #0073bb; - -webkit-border-radius: 0 2px 2px 0; - -moz-border-radius: 0 2px 2px 0; - border-radius: 0 2px 2px 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.selectize-control.plugin-remove_button [data-value] .remove:hover { - background: rgba(0, 0, 0, 0.05); -} - -.selectize-control.plugin-remove_button [data-value].active .remove { - border-left-color: #00578d; -} - -.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover { - background: none; -} - -.selectize-control.plugin-remove_button .disabled [data-value] .remove { - border-left-color: #aaaaaa; -} - -.selectize-control { - position: relative; -} - -.selectize-dropdown { - font-family: inherit; - font-size: 13px; - -webkit-font-smoothing: inherit; - line-height: 18px; - color: #303030; -} - -.selectize-control.single { - display: inline-block; - cursor: text; - background: #ffffff; -} - -.selectize-dropdown { - position: absolute; - z-index: 10; - margin: -1px 0 0 0; - background: #ffffff; - border: 1px solid #d0d0d0; - border-top: 0 none; - -webkit-border-radius: 0 0 3px 3px; - -moz-border-radius: 0 0 3px 3px; - border-radius: 0 0 3px 3px; - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.selectize-dropdown [data-selectable] { - overflow: hidden; - cursor: pointer; -} - -.selectize-dropdown [data-selectable] .highlight { - background: rgba(125, 168, 208, 0.2); - -webkit-border-radius: 1px; - -moz-border-radius: 1px; - border-radius: 1px; -} - -.selectize-dropdown [data-selectable], -.selectize-dropdown .optgroup-header { - padding: 5px 8px; -} - -.selectize-dropdown .optgroup:first-child .optgroup-header { - border-top: 0 none; -} - -.selectize-dropdown .optgroup-header { - color: #303030; - cursor: default; - background: #ffffff; -} - -.selectize-dropdown .active { - color: #495c68; - background-color: #f5fafd; -} - -.selectize-dropdown .active.create { - color: #495c68; -} - -.selectize-dropdown .create { - color: rgba(48, 48, 48, 0.5); -} - -.selectize-dropdown-content { - max-height: 200px; - overflow-x: hidden; - overflow-y: auto; - - .title { - font-weight: bold; - } - - .description { - padding-left: 16px; - } -} - -.selectize-dropdown .optgroup-header { - padding-top: 7px; - font-size: 0.85em; - font-weight: bold; -} - -.selectize-dropdown .optgroup { - border-top: 1px solid #f0f0f0; -} - -.selectize-dropdown .optgroup:first-child { - border-top: 0 none; -} - -.custom-select { - height: auto; -} diff --git a/frontend/scss/styles.scss b/frontend/scss/styles.scss deleted file mode 100644 index 52733097..00000000 --- a/frontend/scss/styles.scss +++ /dev/null @@ -1,17 +0,0 @@ -@import "~tabler-ui/dist/assets/css/dashboard"; -@import "tabler-extra"; -@import "fonts"; -@import "selectize"; -@import "custom"; - -/* Before any JS content is loaded */ -#app > .loader, #login > .loader, .container > .loader { - position: absolute; - left: 49%; - top: 40%; - display: block; -} - -.no-js-warning { - margin-top: 100px; -} diff --git a/frontend/scss/tabler-extra.scss b/frontend/scss/tabler-extra.scss deleted file mode 100644 index c56aedbc..00000000 --- a/frontend/scss/tabler-extra.scss +++ /dev/null @@ -1,171 +0,0 @@ -$teal: #2bcbba; -$yellow: #f1c40f; -$blue: #467fcf; -$pink: #f66d9b; - -.tag { - margin-bottom: .5em; - margin-right: .5em; -} - -.tag.hover-green:hover, .tag.hover-green:active, .tag.hover-green:focus { - background-color: #5eba00; - cursor: pointer; - color: #fff; -} - -.tag.hover-red:hover, .tag.hover-red:active, .tag.hover-red:focus { - background-color: #cd201f; - cursor: pointer; - color: #fff; -} - -/* For Card bodies where I don't want padding */ -.card-body.no-padding { - padding: 0; -} - -/* For some reason this class doesn't have 'display: flex' when it should. https://preview.tabler.io/docs/buttons.html#list-of-buttons */ -.btn-list { - display: flex; -} - -/* Teal Outline Buttons */ -.btn-outline-teal { - color: $teal; - background-color: transparent; - background-image: none; - border-color: $teal; -} - -.btn-outline-teal:hover { - color: #fff; - background-color: $teal; - border-color: $teal; -} - -.btn-outline-teal:not(:disabled):not(.disabled):active, .btn-outline-teal:not(:disabled):not(.disabled).active, .show > .btn-outline-teal.dropdown-toggle { - color: #fff; - background-color: $teal; - border-color: $teal; -} - -.tag.hover-teal:hover, .tag.hover-teal:active, .tag.hover-teal:focus { - background-color: $teal; - color: #fff; - cursor: pointer; -} - -/* Yellow Outline Buttons */ -.btn-outline-yellow { - color: $yellow; - background-color: transparent; - background-image: none; - border-color: $yellow; -} - -.btn-outline-yellow:hover { - color: #fff; - background-color: $yellow; - border-color: $yellow; -} - -.btn-outline-yellow:not(:disabled):not(.disabled):active, .btn-outline-yellow:not(:disabled):not(.disabled).active, .show > .btn-outline-yellow.dropdown-toggle { - color: #fff; - background-color: $yellow; - border-color: $yellow; -} - -.tag.hover-yellow:hover, .tag.hover-yellow:active, .tag.hover-yellow:focus { - background-color: $yellow; - cursor: pointer; - color: #fff; -} - -/* Blue Outline Buttons */ -.btn-outline-blue { - color: $blue; - background-color: transparent; - background-image: none; - border-color: $blue; -} - -.btn-outline-blue:hover { - color: #fff; - background-color: $blue; - border-color: $blue; -} - -.btn-outline-blue:not(:disabled):not(.disabled):active, .btn-outline-blue:not(:disabled):not(.disabled).active, .show > .btn-outline-blue.dropdown-toggle { - color: #fff; - background-color: $blue; - border-color: $blue; -} - -.tag.hover-blue:hover, .tag.hover-blue:active, .tag.hover-blue:focus { - background-color: $blue; - cursor: pointer; - color: #fff; -} - -/* Pink Outline Buttons */ -.btn-outline-pink { - color: $pink; - background-color: transparent; - background-image: none; - border-color: $pink; -} - -.btn-outline-pink:hover { - color: #fff; - background-color: $pink; - border-color: $pink; -} - -.btn-outline-pink:not(:disabled):not(.disabled):active, .btn-outline-pink:not(:disabled):not(.disabled).active, .show > .btn-outline-pink.dropdown-toggle { - color: #fff; - background-color: $pink; - border-color: $pink; -} - -.tag.hover-pink:hover, .tag.hover-pink:active, .tag.hover-pink:focus { - background-color: $pink; - cursor: pointer; -} - -/* dimmer */ - -.dimmer .loader { - margin-top: 50px; -} - -/* modal tabs */ - -.modal-body.has-tabs { - padding: 0; - - .nav-tabs { - margin: 0; - } - - .tab-content { - padding: 1rem; - } -} - -/* modal wide */ - -@media (min-width: 576px) { - .modal-dialog.wide { - max-width: 700px; - margin: 1.75rem auto; - } -} - - -/* Form mod */ - -textarea.form-control.text-monospace { - font-size: 12px; - font-family: monospace; -} diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 00000000..1e642918 --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,7 @@ +.modal-backdrop { + --tblr-backdrop-opacity: 0.8 !important; +} + +.domain-name { + font-family: monospace; +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 00000000..aa2fb873 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,28 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { RawIntlProvider } from "react-intl"; +import { AuthProvider, LocaleProvider, ThemeProvider } from "src/context"; +import { intl } from "src/locale"; +import Router from "src/Router.tsx"; + +// Create a client +const queryClient = new QueryClient(); + +function App() { + return ( + + + + + + + + + + + + + ); +} + +export default App; diff --git a/frontend/src/Router.tsx b/frontend/src/Router.tsx new file mode 100644 index 00000000..054b4400 --- /dev/null +++ b/frontend/src/Router.tsx @@ -0,0 +1,78 @@ +import { lazy, Suspense } from "react"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import { + ErrorNotFound, + LoadingPage, + Page, + SiteContainer, + SiteFooter, + SiteHeader, + SiteMenu, + Unhealthy, +} from "src/components"; +import { useAuthState } from "src/context"; +import { useHealth } from "src/hooks"; + +const Dashboard = lazy(() => import("src/pages/Dashboard")); +const Login = lazy(() => import("src/pages/Login")); +const Settings = lazy(() => import("src/pages/Settings")); +const Certificates = lazy(() => import("src/pages/Certificates")); +const Access = lazy(() => import("src/pages/Access")); +const AuditLog = lazy(() => import("src/pages/AuditLog")); +const Users = lazy(() => import("src/pages/Users")); +const ProxyHosts = lazy(() => import("src/pages/Nginx/ProxyHosts")); +const RedirectionHosts = lazy(() => import("src/pages/Nginx/RedirectionHosts")); +const DeadHosts = lazy(() => import("src/pages/Nginx/DeadHosts")); +const Streams = lazy(() => import("src/pages/Nginx/Streams")); + +function Router() { + const health = useHealth(); + const { authenticated } = useAuthState(); + + if (health.isLoading) { + return ; + } + + if (health.isError || health.data?.status !== "OK") { + return ; + } + + if (!authenticated) { + return ( + }> + + + ); + } + + return ( + + +
+ + +
+ + }> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + +
+
+ ); +} + +export default Router; diff --git a/frontend/src/api/backend/base.ts b/frontend/src/api/backend/base.ts new file mode 100644 index 00000000..e15cd50e --- /dev/null +++ b/frontend/src/api/backend/base.ts @@ -0,0 +1,152 @@ +import { QueryClient } from "@tanstack/react-query"; +import { camelizeKeys, decamelize, decamelizeKeys } from "humps"; +import queryString, { type StringifiableRecord } from "query-string"; +import AuthStore from "src/modules/AuthStore"; + +const queryClient = new QueryClient(); +const contentTypeHeader = "Content-Type"; + +interface BuildUrlArgs { + url: string; + params?: StringifiableRecord; +} + +function decamelizeParams(params?: StringifiableRecord): StringifiableRecord | undefined { + if (!params) { + return undefined; + } + const result: StringifiableRecord = {}; + for (const [key, value] of Object.entries(params)) { + result[decamelize(key)] = value; + } + + return result; +} + +function buildUrl({ url, params }: BuildUrlArgs) { + const endpoint = url.replace(/^\/|\/$/g, ""); + const baseUrl = `/api/${endpoint}`; + const apiUrl = queryString.stringifyUrl({ + url: baseUrl, + query: decamelizeParams(params), + }); + return apiUrl; +} + +function buildAuthHeader(): Record | undefined { + if (AuthStore.token) { + return { Authorization: `Bearer ${AuthStore.token.token}` }; + } + return {}; +} + +function buildBody(data?: Record): string | undefined { + if (data) { + return JSON.stringify(decamelizeKeys(data)); + } +} + +async function processResponse(response: Response) { + const payload = await response.json(); + if (!response.ok) { + if (response.status === 401) { + // Force logout user and reload the page if Unauthorized + AuthStore.clear(); + queryClient.clear(); + window.location.reload(); + } + throw new Error( + typeof payload.error.messageI18n !== "undefined" ? payload.error.messageI18n : payload.error.message, + ); + } + return camelizeKeys(payload) as any; +} + +interface GetArgs { + url: string; + params?: queryString.StringifiableRecord; +} + +async function baseGet({ url, params }: GetArgs, abortController?: AbortController) { + const apiUrl = buildUrl({ url, params }); + const method = "GET"; + const headers = buildAuthHeader(); + const signal = abortController?.signal; + const response = await fetch(apiUrl, { method, headers, signal }); + return response; +} + +export async function get(args: GetArgs, abortController?: AbortController) { + return processResponse(await baseGet(args, abortController)); +} + +export async function download(args: GetArgs, abortController?: AbortController) { + return (await baseGet(args, abortController)).text(); +} + +interface PostArgs { + url: string; + params?: queryString.StringifiableRecord; + data?: any; +} + +export async function post({ url, params, data }: PostArgs, abortController?: AbortController) { + const apiUrl = buildUrl({ url, params }); + const method = "POST"; + + let headers = { + ...buildAuthHeader(), + }; + + let body: string | FormData | undefined; + // Check if the data is an instance of FormData + // If data is FormData, let the browser set the Content-Type header + if (data instanceof FormData) { + body = data; + } else { + // If data is JSON, set the Content-Type header to 'application/json' + headers = { + ...headers, + [contentTypeHeader]: "application/json", + }; + body = buildBody(data); + } + + const signal = abortController?.signal; + const response = await fetch(apiUrl, { method, headers, body, signal }); + return processResponse(response); +} + +interface PutArgs { + url: string; + params?: queryString.StringifiableRecord; + data?: Record; +} +export async function put({ url, params, data }: PutArgs, abortController?: AbortController) { + const apiUrl = buildUrl({ url, params }); + const method = "PUT"; + const headers = { + ...buildAuthHeader(), + [contentTypeHeader]: "application/json", + }; + const signal = abortController?.signal; + const body = buildBody(data); + const response = await fetch(apiUrl, { method, headers, body, signal }); + return processResponse(response); +} + +interface DeleteArgs { + url: string; + params?: queryString.StringifiableRecord; +} +export async function del({ url, params }: DeleteArgs, abortController?: AbortController) { + const apiUrl = buildUrl({ url, params }); + const method = "DELETE"; + const headers = { + ...buildAuthHeader(), + [contentTypeHeader]: "application/json", + }; + const signal = abortController?.signal; + const response = await fetch(apiUrl, { method, headers, signal }); + return processResponse(response); +} diff --git a/frontend/src/api/backend/createAccessList.ts b/frontend/src/api/backend/createAccessList.ts new file mode 100644 index 00000000..d90f7c97 --- /dev/null +++ b/frontend/src/api/backend/createAccessList.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { AccessList } from "./models"; + +export async function createAccessList(item: AccessList, abortController?: AbortController): Promise { + return await api.post( + { + url: "/nginx/access-lists", + // todo: only use whitelist of fields for this data + data: item, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/createCertificate.ts b/frontend/src/api/backend/createCertificate.ts new file mode 100644 index 00000000..ba9655a3 --- /dev/null +++ b/frontend/src/api/backend/createCertificate.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { Certificate } from "./models"; + +export async function createCertificate(item: Certificate, abortController?: AbortController): Promise { + return await api.post( + { + url: "/nginx/certificates", + // todo: only use whitelist of fields for this data + data: item, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/createDeadHost.ts b/frontend/src/api/backend/createDeadHost.ts new file mode 100644 index 00000000..ab090446 --- /dev/null +++ b/frontend/src/api/backend/createDeadHost.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { DeadHost } from "./models"; + +export async function createDeadHost(item: DeadHost, abortController?: AbortController): Promise { + return await api.post( + { + url: "/nginx/dead-hosts", + // todo: only use whitelist of fields for this data + data: item, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/createProxyHost.ts b/frontend/src/api/backend/createProxyHost.ts new file mode 100644 index 00000000..6b548b87 --- /dev/null +++ b/frontend/src/api/backend/createProxyHost.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { ProxyHost } from "./models"; + +export async function createProxyHost(item: ProxyHost, abortController?: AbortController): Promise { + return await api.post( + { + url: "/nginx/proxy-hosts", + // todo: only use whitelist of fields for this data + data: item, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/createRedirectionHost.ts b/frontend/src/api/backend/createRedirectionHost.ts new file mode 100644 index 00000000..ea73a226 --- /dev/null +++ b/frontend/src/api/backend/createRedirectionHost.ts @@ -0,0 +1,16 @@ +import * as api from "./base"; +import type { RedirectionHost } from "./models"; + +export async function createRedirectionHost( + item: RedirectionHost, + abortController?: AbortController, +): Promise { + return await api.post( + { + url: "/nginx/redirection-hosts", + // todo: only use whitelist of fields for this data + data: item, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/createStream.ts b/frontend/src/api/backend/createStream.ts new file mode 100644 index 00000000..c8a6cf1f --- /dev/null +++ b/frontend/src/api/backend/createStream.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { Stream } from "./models"; + +export async function createStream(item: Stream, abortController?: AbortController): Promise { + return await api.post( + { + url: "/nginx/streams", + // todo: only use whitelist of fields for this data + data: item, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/createUser.ts b/frontend/src/api/backend/createUser.ts new file mode 100644 index 00000000..e6789d7f --- /dev/null +++ b/frontend/src/api/backend/createUser.ts @@ -0,0 +1,13 @@ +import * as api from "./base"; +import type { User } from "./models"; + +export async function createUser(item: User, abortController?: AbortController): Promise { + return await api.post( + { + url: "/users", + // todo: only use whitelist of fields for this data + data: item, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/deleteAccessList.ts b/frontend/src/api/backend/deleteAccessList.ts new file mode 100644 index 00000000..96b51dbf --- /dev/null +++ b/frontend/src/api/backend/deleteAccessList.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function deleteAccessList(id: number, abortController?: AbortController): Promise { + return await api.del( + { + url: `/nginx/access-lists/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/deleteCertificate.ts b/frontend/src/api/backend/deleteCertificate.ts new file mode 100644 index 00000000..97debf87 --- /dev/null +++ b/frontend/src/api/backend/deleteCertificate.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function deleteCertificate(id: number, abortController?: AbortController): Promise { + return await api.del( + { + url: `/nginx/certificates/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/deleteDeadHost.ts b/frontend/src/api/backend/deleteDeadHost.ts new file mode 100644 index 00000000..50ba6fa0 --- /dev/null +++ b/frontend/src/api/backend/deleteDeadHost.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function deleteDeadHost(id: number, abortController?: AbortController): Promise { + return await api.del( + { + url: `/nginx/dead-hosts/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/deleteProxyHost.ts b/frontend/src/api/backend/deleteProxyHost.ts new file mode 100644 index 00000000..f5ddc4d4 --- /dev/null +++ b/frontend/src/api/backend/deleteProxyHost.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function deleteProxyHost(id: number, abortController?: AbortController): Promise { + return await api.del( + { + url: `/nginx/proxy-hosts/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/deleteRedirectionHost.ts b/frontend/src/api/backend/deleteRedirectionHost.ts new file mode 100644 index 00000000..0fe5cc2d --- /dev/null +++ b/frontend/src/api/backend/deleteRedirectionHost.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function deleteRedirectionHost(id: number, abortController?: AbortController): Promise { + return await api.del( + { + url: `/nginx/redirection-hosts/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/deleteStream.ts b/frontend/src/api/backend/deleteStream.ts new file mode 100644 index 00000000..17871d13 --- /dev/null +++ b/frontend/src/api/backend/deleteStream.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function deleteStream(id: number, abortController?: AbortController): Promise { + return await api.del( + { + url: `/nginx/streams/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/deleteUser.ts b/frontend/src/api/backend/deleteUser.ts new file mode 100644 index 00000000..30faa701 --- /dev/null +++ b/frontend/src/api/backend/deleteUser.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function deleteUser(id: number, abortController?: AbortController): Promise { + return await api.del( + { + url: `/users/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/downloadCertificate.ts b/frontend/src/api/backend/downloadCertificate.ts new file mode 100644 index 00000000..aa980071 --- /dev/null +++ b/frontend/src/api/backend/downloadCertificate.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { Binary } from "./responseTypes"; + +export async function downloadCertificate(id: number, abortController?: AbortController): Promise { + return await api.get( + { + url: `/nginx/certificates/${id}/download`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getAccessList.ts b/frontend/src/api/backend/getAccessList.ts new file mode 100644 index 00000000..75fea51e --- /dev/null +++ b/frontend/src/api/backend/getAccessList.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { AccessList } from "./models"; + +export async function getAccessList(id: number, abortController?: AbortController): Promise { + return await api.get( + { + url: `/nginx/access-lists/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getAccessLists.ts b/frontend/src/api/backend/getAccessLists.ts new file mode 100644 index 00000000..96eaa91a --- /dev/null +++ b/frontend/src/api/backend/getAccessLists.ts @@ -0,0 +1,14 @@ +import * as api from "./base"; +import type { AccessList } from "./models"; + +export type AccessListExpansion = "owner" | "items" | "clients"; + +export async function getAccessLists(expand?: AccessListExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/access-lists", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getAuditLog.ts b/frontend/src/api/backend/getAuditLog.ts new file mode 100644 index 00000000..e798b8fb --- /dev/null +++ b/frontend/src/api/backend/getAuditLog.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { AuditLog } from "./models"; + +export async function getAuditLog(expand?: string[], params = {}): Promise { + return await api.get({ + url: "/audit-log", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getCertificate.ts b/frontend/src/api/backend/getCertificate.ts new file mode 100644 index 00000000..fc6a4c6d --- /dev/null +++ b/frontend/src/api/backend/getCertificate.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { Certificate } from "./models"; + +export async function getCertificate(id: number, abortController?: AbortController): Promise { + return await api.get( + { + url: `/nginx/certificates/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getCertificates.ts b/frontend/src/api/backend/getCertificates.ts new file mode 100644 index 00000000..15660b97 --- /dev/null +++ b/frontend/src/api/backend/getCertificates.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { Certificate } from "./models"; + +export async function getCertificates(expand?: string[], params = {}): Promise { + return await api.get({ + url: "/nginx/certificates", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getDeadHost.ts b/frontend/src/api/backend/getDeadHost.ts new file mode 100644 index 00000000..7759b094 --- /dev/null +++ b/frontend/src/api/backend/getDeadHost.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { DeadHost } from "./models"; + +export async function getDeadHost(id: number, abortController?: AbortController): Promise { + return await api.get( + { + url: `/nginx/dead-hosts/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getDeadHosts.ts b/frontend/src/api/backend/getDeadHosts.ts new file mode 100644 index 00000000..398221b6 --- /dev/null +++ b/frontend/src/api/backend/getDeadHosts.ts @@ -0,0 +1,14 @@ +import * as api from "./base"; +import type { DeadHost } from "./models"; + +export type DeadHostExpansion = "owner" | "certificate"; + +export async function getDeadHosts(expand?: DeadHostExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/dead-hosts", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getHealth.ts b/frontend/src/api/backend/getHealth.ts new file mode 100644 index 00000000..b423f5de --- /dev/null +++ b/frontend/src/api/backend/getHealth.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { HealthResponse } from "./responseTypes"; + +export async function getHealth(abortController?: AbortController): Promise { + return await api.get( + { + url: "/", + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getHostsReport.ts b/frontend/src/api/backend/getHostsReport.ts new file mode 100644 index 00000000..b396e291 --- /dev/null +++ b/frontend/src/api/backend/getHostsReport.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function getHostsReport(abortController?: AbortController): Promise> { + return await api.get( + { + url: "/reports/hosts", + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getProxyHost.ts b/frontend/src/api/backend/getProxyHost.ts new file mode 100644 index 00000000..f51795ca --- /dev/null +++ b/frontend/src/api/backend/getProxyHost.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { ProxyHost } from "./models"; + +export async function getProxyHost(id: number, abortController?: AbortController): Promise { + return await api.get( + { + url: `/nginx/proxy-hosts/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getProxyHosts.ts b/frontend/src/api/backend/getProxyHosts.ts new file mode 100644 index 00000000..5db1779c --- /dev/null +++ b/frontend/src/api/backend/getProxyHosts.ts @@ -0,0 +1,14 @@ +import * as api from "./base"; +import type { ProxyHost } from "./models"; + +export type ProxyHostExpansion = "owner" | "access_list" | "certificate"; + +export async function getProxyHosts(expand?: ProxyHostExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/proxy-hosts", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getRedirectionHost.ts b/frontend/src/api/backend/getRedirectionHost.ts new file mode 100644 index 00000000..fa537130 --- /dev/null +++ b/frontend/src/api/backend/getRedirectionHost.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { ProxyHost } from "./models"; + +export async function getRedirectionHost(id: number, abortController?: AbortController): Promise { + return await api.get( + { + url: `/nginx/redirection-hosts/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getRedirectionHosts.ts b/frontend/src/api/backend/getRedirectionHosts.ts new file mode 100644 index 00000000..4e44c6ee --- /dev/null +++ b/frontend/src/api/backend/getRedirectionHosts.ts @@ -0,0 +1,16 @@ +import * as api from "./base"; +import type { RedirectionHost } from "./models"; + +export type RedirectionHostExpansion = "owner" | "certificate"; +export async function getRedirectionHosts( + expand?: RedirectionHostExpansion[], + params = {}, +): Promise { + return await api.get({ + url: "/nginx/redirection-hosts", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getSetting.ts b/frontend/src/api/backend/getSetting.ts new file mode 100644 index 00000000..f04f7fad --- /dev/null +++ b/frontend/src/api/backend/getSetting.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { Setting } from "./models"; + +export async function getSetting(id: string, abortController?: AbortController): Promise { + return await api.get( + { + url: `/settings/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getSettings.ts b/frontend/src/api/backend/getSettings.ts new file mode 100644 index 00000000..b8e992c6 --- /dev/null +++ b/frontend/src/api/backend/getSettings.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; +import type { Setting } from "./models"; + +export async function getSettings(expand?: string[], params = {}): Promise { + return await api.get({ + url: "/settings", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getStream.ts b/frontend/src/api/backend/getStream.ts new file mode 100644 index 00000000..61927b68 --- /dev/null +++ b/frontend/src/api/backend/getStream.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { Stream } from "./models"; + +export async function getStream(id: number, abortController?: AbortController): Promise { + return await api.get( + { + url: `/nginx/streams/${id}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getStreams.ts b/frontend/src/api/backend/getStreams.ts new file mode 100644 index 00000000..8468a55d --- /dev/null +++ b/frontend/src/api/backend/getStreams.ts @@ -0,0 +1,14 @@ +import * as api from "./base"; +import type { Stream } from "./models"; + +export type StreamExpansion = "owner" | "certificate"; + +export async function getStreams(expand?: StreamExpansion[], params = {}): Promise { + return await api.get({ + url: "/nginx/streams", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/getToken.ts b/frontend/src/api/backend/getToken.ts new file mode 100644 index 00000000..be20e112 --- /dev/null +++ b/frontend/src/api/backend/getToken.ts @@ -0,0 +1,19 @@ +import * as api from "./base"; +import type { TokenResponse } from "./responseTypes"; + +interface Options { + payload: { + identity: string; + secret: string; + }; +} + +export async function getToken({ payload }: Options, abortController?: AbortController): Promise { + return await api.post( + { + url: "/tokens", + data: payload, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/getUser.ts b/frontend/src/api/backend/getUser.ts new file mode 100644 index 00000000..ae57783f --- /dev/null +++ b/frontend/src/api/backend/getUser.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; +import type { User } from "./models"; + +export async function getUser(id: number | string = "me", params = {}): Promise { + const userId = id ? id : "me"; + return await api.get({ + url: `/users/${userId}`, + params, + }); +} diff --git a/frontend/src/api/backend/getUsers.ts b/frontend/src/api/backend/getUsers.ts new file mode 100644 index 00000000..a4a03f7c --- /dev/null +++ b/frontend/src/api/backend/getUsers.ts @@ -0,0 +1,14 @@ +import * as api from "./base"; +import type { User } from "./models"; + +export type UserExpansion = "permissions"; + +export async function getUsers(expand?: UserExpansion[], params = {}): Promise { + return await api.get({ + url: "/users", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/helpers.ts b/frontend/src/api/backend/helpers.ts new file mode 100644 index 00000000..8d977319 --- /dev/null +++ b/frontend/src/api/backend/helpers.ts @@ -0,0 +1,54 @@ +import { decamelize } from "humps"; + +/** + * This will convert a react-table sort object into + * a string that the backend api likes: + * name.asc,id.desc + */ +export function tableSortToAPI(sortBy: any): string | undefined { + if (sortBy?.length > 0) { + const strs: string[] = []; + sortBy.map((item: any) => { + strs.push(`${decamelize(item.id)}.${item.desc ? "desc" : "asc"}`); + return undefined; + }); + return strs.join(","); + } + return; +} + +/** + * This will convert a react-table filters object into + * a string that the backend api likes: + * name:contains=jam + */ +export function tableFiltersToAPI(filters: any[]): { [key: string]: string } { + const items: { [key: string]: string } = {}; + if (filters?.length > 0) { + filters.map((item: any) => { + items[`${decamelize(item.id)}:${item.value.modifier}`] = item.value.value; + return undefined; + }); + } + return items; +} + +/** + * Builds a filters object by removing entries with undefined, null, or empty string values. + * + */ +export function buildFilters(filters?: Record) { + if (!filters) { + return filters; + } + const result: Record = {}; + for (const key in filters) { + const value = filters[key]; + // If the value is undefined, null, or an empty string, skip it + if (value === undefined || value === null || value === "") { + continue; + } + result[key] = value.toString(); + } + return result; +} diff --git a/frontend/src/api/backend/index.ts b/frontend/src/api/backend/index.ts new file mode 100644 index 00000000..745ce3ce --- /dev/null +++ b/frontend/src/api/backend/index.ts @@ -0,0 +1,54 @@ +export * from "./createAccessList"; +export * from "./createCertificate"; +export * from "./createDeadHost"; +export * from "./createProxyHost"; +export * from "./createRedirectionHost"; +export * from "./createStream"; +export * from "./deleteAccessList"; +export * from "./deleteCertificate"; +export * from "./deleteDeadHost"; +export * from "./deleteProxyHost"; +export * from "./deleteRedirectionHost"; +export * from "./deleteStream"; +export * from "./deleteUser"; +export * from "./downloadCertificate"; +export * from "./getAccessList"; +export * from "./getAccessLists"; +export * from "./getAuditLog"; +export * from "./getCertificate"; +export * from "./getCertificates"; +export * from "./getDeadHost"; +export * from "./getDeadHosts"; +export * from "./getHealth"; +export * from "./getHostsReport"; +export * from "./getProxyHost"; +export * from "./getProxyHosts"; +export * from "./getRedirectionHost"; +export * from "./getRedirectionHosts"; +export * from "./getSetting"; +export * from "./getSettings"; +export * from "./getStream"; +export * from "./getStreams"; +export * from "./getToken"; +export * from "./getUser"; +export * from "./getUsers"; +export * from "./helpers"; +export * from "./models"; +export * from "./refreshToken"; +export * from "./renewCertificate"; +export * from "./responseTypes"; +export * from "./testHttpCertificate"; +export * from "./toggleDeadHost"; +export * from "./toggleProxyHost"; +export * from "./toggleRedirectionHost"; +export * from "./toggleStream"; +export * from "./updateAccessList"; +export * from "./updateAuth"; +export * from "./updateDeadHost"; +export * from "./updateProxyHost"; +export * from "./updateRedirectionHost"; +export * from "./updateSetting"; +export * from "./updateStream"; +export * from "./updateUser"; +export * from "./uploadCertificate"; +export * from "./validateCertificate"; diff --git a/frontend/src/api/backend/models.ts b/frontend/src/api/backend/models.ts new file mode 100644 index 00000000..4650849c --- /dev/null +++ b/frontend/src/api/backend/models.ts @@ -0,0 +1,193 @@ +export interface AppVersion { + major: number; + minor: number; + revision: number; +} + +export interface UserPermissions { + id: number; + createdOn: string; + modifiedOn: string; + userId: number; + visibility: string; + proxyHosts: string; + redirectionHosts: string; + deadHosts: string; + streams: string; + accessLists: string; + certificates: string; +} + +export interface User { + id: number; + createdOn: string; + modifiedOn: string; + isDisabled: boolean; + email: string; + name: string; + nickname: string; + avatar: string; + roles: string[]; + permissions?: UserPermissions; +} + +export interface AuditLog { + id: number; + createdOn: string; + modifiedOn: string; + userId: number; + objectType: string; + objectId: number; + action: string; + meta: Record; +} + +export interface AccessList { + id?: number; + createdOn?: string; + modifiedOn?: string; + ownerUserId: number; + name: string; + meta: Record; + satisfyAny: boolean; + passAuth: boolean; + proxyHostCount: number; + // Expansions: + owner?: User; + items?: AccessListItem[]; + clients?: AccessListClient[]; +} + +export interface AccessListItem { + id?: number; + createdOn?: string; + modifiedOn?: string; + accessListId?: number; + username: string; + password: string; + meta: Record; + hint: string; +} + +export type AccessListClient = { + id?: number; + createdOn?: string; + modifiedOn?: string; + accessListId?: number; + address: string; + directive: "allow" | "deny"; + meta: Record; +}; + +export interface Certificate { + id: number; + createdOn: string; + modifiedOn: string; + ownerUserId: number; + provider: string; + niceName: string; + domainNames: string[]; + expiresOn: string; + meta: Record; + owner?: User; + proxyHosts?: ProxyHost[]; + deadHosts?: DeadHost[]; + redirectionHosts?: RedirectionHost[]; +} + +export interface ProxyHost { + id: number; + createdOn: string; + modifiedOn: string; + ownerUserId: number; + domainNames: string[]; + forwardHost: string; + forwardPort: number; + accessListId: number; + certificateId: number; + sslForced: boolean; + cachingEnabled: boolean; + blockExploits: boolean; + advancedConfig: string; + meta: Record; + allowWebsocketUpgrade: boolean; + http2Support: boolean; + forwardScheme: string; + enabled: boolean; + locations: string[]; // todo: string or object? + hstsEnabled: boolean; + hstsSubdomains: boolean; + // Expansions: + owner?: User; + accessList?: AccessList; + certificate?: Certificate; +} + +export interface DeadHost { + id: number; + createdOn: string; + modifiedOn: string; + ownerUserId: number; + domainNames: string[]; + certificateId: number; + sslForced: boolean; + advancedConfig: string; + meta: Record; + http2Support: boolean; + enabled: boolean; + hstsEnabled: boolean; + hstsSubdomains: boolean; + // Expansions: + owner?: User; + certificate?: Certificate; +} + +export interface RedirectionHost { + id: number; + createdOn: string; + modifiedOn: string; + ownerUserId: number; + domainNames: string[]; + forwardDomainName: string; + preservePath: boolean; + certificateId: number; + sslForced: boolean; + blockExploits: boolean; + advancedConfig: string; + meta: Record; + http2Support: boolean; + forwardScheme: string; + forwardHttpCode: number; + enabled: boolean; + hstsEnabled: boolean; + hstsSubdomains: boolean; + // Expansions: + owner?: User; + certificate?: Certificate; +} + +export interface Stream { + id: number; + createdOn: string; + modifiedOn: string; + ownerUserId: number; + incomingPort: number; + forwardingHost: string; + forwardingPort: number; + tcpForwarding: boolean; + udpForwarding: boolean; + meta: Record; + enabled: boolean; + certificateId: number; + // Expansions: + owner?: User; + certificate?: Certificate; +} + +export interface Setting { + id: string; + name: string; + description: string; + value: string; + meta: Record; +} diff --git a/frontend/src/api/backend/refreshToken.ts b/frontend/src/api/backend/refreshToken.ts new file mode 100644 index 00000000..6502fdbe --- /dev/null +++ b/frontend/src/api/backend/refreshToken.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { TokenResponse } from "./responseTypes"; + +export async function refreshToken(abortController?: AbortController): Promise { + return await api.get( + { + url: "/tokens", + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/renewCertificate.ts b/frontend/src/api/backend/renewCertificate.ts new file mode 100644 index 00000000..9eefff5d --- /dev/null +++ b/frontend/src/api/backend/renewCertificate.ts @@ -0,0 +1,11 @@ +import * as api from "./base"; +import type { Certificate } from "./models"; + +export async function renewCertificate(id: number, abortController?: AbortController): Promise { + return await api.post( + { + url: `/nginx/certificates/${id}/renew`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/responseTypes.ts b/frontend/src/api/backend/responseTypes.ts new file mode 100644 index 00000000..6e5e9abb --- /dev/null +++ b/frontend/src/api/backend/responseTypes.ts @@ -0,0 +1,18 @@ +import type { AppVersion } from "./models"; + +export interface HealthResponse { + status: string; + version: AppVersion; +} + +export interface TokenResponse { + expires: number; + token: string; +} + +export interface ValidatedCertificateResponse { + certificate: Record; + certificateKey: boolean; +} + +export type Binary = number & { readonly __brand: unique symbol }; diff --git a/frontend/src/api/backend/testHttpCertificate.ts b/frontend/src/api/backend/testHttpCertificate.ts new file mode 100644 index 00000000..c6e4eaf0 --- /dev/null +++ b/frontend/src/api/backend/testHttpCertificate.ts @@ -0,0 +1,16 @@ +import * as api from "./base"; + +export async function testHttpCertificate( + domains: string[], + abortController?: AbortController, +): Promise> { + return await api.get( + { + url: "/nginx/certificates/test-http", + params: { + domains: domains.join(","), + }, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/toggleDeadHost.ts b/frontend/src/api/backend/toggleDeadHost.ts new file mode 100644 index 00000000..967d65db --- /dev/null +++ b/frontend/src/api/backend/toggleDeadHost.ts @@ -0,0 +1,14 @@ +import * as api from "./base"; + +export async function toggleDeadHost( + id: number, + enabled: boolean, + abortController?: AbortController, +): Promise { + return await api.post( + { + url: `/nginx/dead-hosts/${id}/${enabled ? "enable" : "disable"}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/toggleProxyHost.ts b/frontend/src/api/backend/toggleProxyHost.ts new file mode 100644 index 00000000..2de3f2a5 --- /dev/null +++ b/frontend/src/api/backend/toggleProxyHost.ts @@ -0,0 +1,14 @@ +import * as api from "./base"; + +export async function toggleProxyHost( + id: number, + enabled: boolean, + abortController?: AbortController, +): Promise { + return await api.post( + { + url: `/nginx/proxy-hosts/${id}/${enabled ? "enable" : "disable"}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/toggleRedirectionHost.ts b/frontend/src/api/backend/toggleRedirectionHost.ts new file mode 100644 index 00000000..4f2fe41d --- /dev/null +++ b/frontend/src/api/backend/toggleRedirectionHost.ts @@ -0,0 +1,14 @@ +import * as api from "./base"; + +export async function toggleRedirectionHost( + id: number, + enabled: boolean, + abortController?: AbortController, +): Promise { + return await api.post( + { + url: `/nginx/redirection-hosts/${id}/${enabled ? "enable" : "disable"}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/toggleStream.ts b/frontend/src/api/backend/toggleStream.ts new file mode 100644 index 00000000..3f8e2e9e --- /dev/null +++ b/frontend/src/api/backend/toggleStream.ts @@ -0,0 +1,10 @@ +import * as api from "./base"; + +export async function toggleStream(id: number, enabled: boolean, abortController?: AbortController): Promise { + return await api.post( + { + url: `/nginx/streams/${id}/${enabled ? "enable" : "disable"}`, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/updateAccessList.ts b/frontend/src/api/backend/updateAccessList.ts new file mode 100644 index 00000000..cfae9389 --- /dev/null +++ b/frontend/src/api/backend/updateAccessList.ts @@ -0,0 +1,15 @@ +import * as api from "./base"; +import type { AccessList } from "./models"; + +export async function updateAccessList(item: AccessList, abortController?: AbortController): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put( + { + url: `/nginx/access-lists/${id}`, + data: data, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/updateAuth.ts b/frontend/src/api/backend/updateAuth.ts new file mode 100644 index 00000000..c63dfeeb --- /dev/null +++ b/frontend/src/api/backend/updateAuth.ts @@ -0,0 +1,26 @@ +import * as api from "./base"; +import type { User } from "./models"; + +export async function updateAuth( + userId: number | "me", + newPassword: string, + current?: string, + abortController?: AbortController, +): Promise { + const data = { + type: "password", + current: current, + secret: newPassword, + }; + if (userId === "me") { + data.current = current; + } + + return await api.put( + { + url: `/users/${userId}/auth`, + data, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/updateDeadHost.ts b/frontend/src/api/backend/updateDeadHost.ts new file mode 100644 index 00000000..8afd44ab --- /dev/null +++ b/frontend/src/api/backend/updateDeadHost.ts @@ -0,0 +1,15 @@ +import * as api from "./base"; +import type { DeadHost } from "./models"; + +export async function updateDeadHost(item: DeadHost, abortController?: AbortController): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put( + { + url: `/nginx/dead-hosts/${id}`, + data: data, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/updateProxyHost.ts b/frontend/src/api/backend/updateProxyHost.ts new file mode 100644 index 00000000..55cdc24c --- /dev/null +++ b/frontend/src/api/backend/updateProxyHost.ts @@ -0,0 +1,15 @@ +import * as api from "./base"; +import type { ProxyHost } from "./models"; + +export async function updateProxyHost(item: ProxyHost, abortController?: AbortController): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put( + { + url: `/nginx/proxy-hosts/${id}`, + data: data, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/updateRedirectionHost.ts b/frontend/src/api/backend/updateRedirectionHost.ts new file mode 100644 index 00000000..e66aabb9 --- /dev/null +++ b/frontend/src/api/backend/updateRedirectionHost.ts @@ -0,0 +1,18 @@ +import * as api from "./base"; +import type { RedirectionHost } from "./models"; + +export async function updateRedirectionHost( + item: RedirectionHost, + abortController?: AbortController, +): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put( + { + url: `/nginx/redirection-hosts/${id}`, + data: data, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/updateSetting.ts b/frontend/src/api/backend/updateSetting.ts new file mode 100644 index 00000000..e257528a --- /dev/null +++ b/frontend/src/api/backend/updateSetting.ts @@ -0,0 +1,15 @@ +import * as api from "./base"; +import type { Setting } from "./models"; + +export async function updateSetting(item: Setting, abortController?: AbortController): Promise { + // Remove readonly fields + const { id, ...data } = item; + + return await api.put( + { + url: `/settings/${id}`, + data: data, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/updateStream.ts b/frontend/src/api/backend/updateStream.ts new file mode 100644 index 00000000..18da6ff4 --- /dev/null +++ b/frontend/src/api/backend/updateStream.ts @@ -0,0 +1,15 @@ +import * as api from "./base"; +import type { Stream } from "./models"; + +export async function updateStream(item: Stream, abortController?: AbortController): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put( + { + url: `/nginx/streams/${id}`, + data: data, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/updateUser.ts b/frontend/src/api/backend/updateUser.ts new file mode 100644 index 00000000..0df5c279 --- /dev/null +++ b/frontend/src/api/backend/updateUser.ts @@ -0,0 +1,15 @@ +import * as api from "./base"; +import type { User } from "./models"; + +export async function updateUser(item: User, abortController?: AbortController): Promise { + // Remove readonly fields + const { id, createdOn: _, modifiedOn: __, ...data } = item; + + return await api.put( + { + url: `/users/${id}`, + data: data, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/uploadCertificate.ts b/frontend/src/api/backend/uploadCertificate.ts new file mode 100644 index 00000000..6c920538 --- /dev/null +++ b/frontend/src/api/backend/uploadCertificate.ts @@ -0,0 +1,18 @@ +import * as api from "./base"; +import type { Certificate } from "./models"; + +export async function uploadCertificate( + id: number, + certificate: string, + certificateKey: string, + intermediateCertificate?: string, + abortController?: AbortController, +): Promise { + return await api.post( + { + url: `/nginx/certificates/${id}/upload`, + data: { certificate, certificateKey, intermediateCertificate }, + }, + abortController, + ); +} diff --git a/frontend/src/api/backend/validateCertificate.ts b/frontend/src/api/backend/validateCertificate.ts new file mode 100644 index 00000000..c4065076 --- /dev/null +++ b/frontend/src/api/backend/validateCertificate.ts @@ -0,0 +1,17 @@ +import * as api from "./base"; +import type { ValidatedCertificateResponse } from "./responseTypes"; + +export async function validateCertificate( + certificate: string, + certificateKey: string, + intermediateCertificate?: string, + abortController?: AbortController, +): Promise { + return await api.post( + { + url: "/nginx/certificates/validate", + data: { certificate, certificateKey, intermediateCertificate }, + }, + abortController, + ); +} diff --git a/frontend/src/components/Button.tsx b/frontend/src/components/Button.tsx new file mode 100644 index 00000000..6d02f931 --- /dev/null +++ b/frontend/src/components/Button.tsx @@ -0,0 +1,64 @@ +import cn from "classnames"; +import type { ReactNode } from "react"; + +interface Props { + children: ReactNode; + className?: string; + type?: "button" | "submit"; + actionType?: "primary" | "secondary" | "success" | "warning" | "danger" | "info" | "light" | "dark"; + variant?: "ghost" | "outline" | "pill" | "square" | "action"; + size?: "sm" | "md" | "lg" | "xl"; + fullWidth?: boolean; + isLoading?: boolean; + disabled?: boolean; + color?: + | "blue" + | "azure" + | "indigo" + | "purple" + | "pink" + | "red" + | "orange" + | "yellow" + | "lime" + | "green" + | "teal" + | "cyan"; + onClick?: () => void; +} +function Button({ + children, + className, + onClick, + type, + actionType, + variant, + size, + color, + fullWidth, + isLoading, + disabled, +}: Props) { + const myOnClick = () => { + !isLoading && onClick && onClick(); + }; + + const cns = cn( + "btn", + className, + actionType && `btn-${actionType}`, + variant && `btn-${variant}`, + size && `btn-${size}`, + color && `btn-${color}`, + fullWidth && "w-100", + isLoading && "btn-loading", + ); + + return ( + + ); +} + +export { Button }; diff --git a/frontend/src/components/ErrorNotFound.tsx b/frontend/src/components/ErrorNotFound.tsx new file mode 100644 index 00000000..ab864dad --- /dev/null +++ b/frontend/src/components/ErrorNotFound.tsx @@ -0,0 +1,23 @@ +import { intl } from "src/locale"; +import { useNavigate } from "react-router-dom"; +import { Button } from "src/components"; + +export function ErrorNotFound() { + const navigate = useNavigate(); + + return ( +
+
+

{intl.formatMessage({ id: "notfound.title" })}

+

+ {intl.formatMessage({ id: "notfound.text" })} +

+
+ +
+
+
+ ); +} diff --git a/frontend/src/components/Flag.tsx b/frontend/src/components/Flag.tsx new file mode 100644 index 00000000..420f6c8f --- /dev/null +++ b/frontend/src/components/Flag.tsx @@ -0,0 +1,24 @@ +import { IconWorld } from "@tabler/icons-react"; +import { hasFlag } from "country-flag-icons"; +// @ts-expect-error Creating a typing for a subfolder is not easily possible +import Flags from "country-flag-icons/react/3x2"; + +interface FlagProps { + className?: string; + countryCode: string; +} +function Flag({ className, countryCode }: FlagProps) { + countryCode = countryCode.toUpperCase(); + if (countryCode === "EN") { + return ; + } + + if (hasFlag(countryCode)) { + const FlagElement = Flags[countryCode] as any; + return ; + } + console.error(`No flag for country ${countryCode} found!`); + return null; +} + +export { Flag }; diff --git a/frontend/src/components/HasPermission.tsx b/frontend/src/components/HasPermission.tsx new file mode 100644 index 00000000..9b45e7f6 --- /dev/null +++ b/frontend/src/components/HasPermission.tsx @@ -0,0 +1,50 @@ +import type { ReactNode } from "react"; +import Alert from "react-bootstrap/Alert"; +import { useUser } from "src/hooks"; +import { intl } from "src/locale"; + +interface Props { + permission: string; + type: "manage" | "view"; + hideError?: boolean; + children?: ReactNode; +} +function HasPermission({ permission, type, children, hideError = false }: Props) { + const { data } = useUser("me"); + const perms = data?.permissions; + + let allowed = permission === ""; + const acceptable = ["manage", type]; + + switch (permission) { + case "admin": + allowed = data?.roles?.includes("admin") || false; + break; + case "proxyHosts": + allowed = acceptable.indexOf(perms?.proxyHosts || "") !== -1; + break; + case "redirectionHosts": + allowed = acceptable.indexOf(perms?.redirectionHosts || "") !== -1; + break; + case "deadHosts": + allowed = acceptable.indexOf(perms?.deadHosts || "") !== -1; + break; + case "streams": + allowed = acceptable.indexOf(perms?.streams || "") !== -1; + break; + case "accessLists": + allowed = acceptable.indexOf(perms?.accessLists || "") !== -1; + break; + case "certificates": + allowed = acceptable.indexOf(perms?.certificates || "") !== -1; + break; + } + + if (allowed) { + return <>{children}; + } + + return !hideError ? {intl.formatMessage({ id: "no-permission-error" })} : null; +} + +export { HasPermission }; diff --git a/frontend/src/components/LoadingPage.module.css b/frontend/src/components/LoadingPage.module.css new file mode 100644 index 00000000..7a13ded3 --- /dev/null +++ b/frontend/src/components/LoadingPage.module.css @@ -0,0 +1,3 @@ +.logo { + max-height: 100px; +} diff --git a/frontend/src/components/LoadingPage.tsx b/frontend/src/components/LoadingPage.tsx new file mode 100644 index 00000000..6be77ebb --- /dev/null +++ b/frontend/src/components/LoadingPage.tsx @@ -0,0 +1,27 @@ +import { Page } from "src/components"; +import { intl } from "src/locale"; +import styles from "./LoadingPage.module.css"; + +interface Props { + label?: string; + noLogo?: boolean; +} +export function LoadingPage({ label, noLogo }: Props) { + return ( + +
+
+ {noLogo ? null : ( +
+ +
+ )} +
{label || intl.formatMessage({ id: "loading" })}
+
+
+
+
+
+ + ); +} diff --git a/frontend/src/components/LocalePicker.module.css b/frontend/src/components/LocalePicker.module.css new file mode 100644 index 00000000..2233d2f8 --- /dev/null +++ b/frontend/src/components/LocalePicker.module.css @@ -0,0 +1,15 @@ +.darkBtn { + color: var(--tblr-light) !important; + &:hover { + border: var(--tblr-btn-border-width) solid transparent !important; + background: color-mix(in srgb, var(--tblr-btn-hover-bg) 10%, transparent) !important; + } +} + +.lightBtn { + color: var(--tblr-dark) !important; + &:hover { + border: var(--tblr-btn-border-width) solid transparent !important; + background: color-mix(in srgb, var(--tblr-btn-hover-bg) 10%, transparent) !important; + } +} diff --git a/frontend/src/components/LocalePicker.tsx b/frontend/src/components/LocalePicker.tsx new file mode 100644 index 00000000..ee4890cf --- /dev/null +++ b/frontend/src/components/LocalePicker.tsx @@ -0,0 +1,71 @@ +import cn from "classnames"; +import { Flag } from "src/components"; +import { useLocaleState } from "src/context"; +import { useTheme } from "src/hooks"; +import { changeLocale, getFlagCodeForLocale, intl, localeOptions } from "src/locale"; +import styles from "./LocalePicker.module.css"; + +function LocalePicker() { + const { locale, setLocale } = useLocaleState(); + const { getTheme } = useTheme(); + + const changeTo = (lang: string) => { + changeLocale(lang); + setLocale(lang); + location.reload(); + }; + + const classes = ["btn", "dropdown-toggle", "btn-sm"]; + let cns = cn(...classes, "btn-ghost-light", styles.lightBtn); + if (getTheme() === "dark") { + cns = cn(...classes, "btn-ghost-dark", styles.darkBtn); + } + + return ( + + ); + + //
+ // + // + // + // + // + // {localeOptions.map((item) => { + // return ( + // } + // onClick={() => changeTo(item[0])} + // key={`locale-${item[0]}`}> + // {intl.formatMessage({ id: `locale-${item[1]}` })} + // + // ); + // })} + // + // + // +} + +export { LocalePicker }; diff --git a/frontend/src/components/NavLink.tsx b/frontend/src/components/NavLink.tsx new file mode 100644 index 00000000..b6a414af --- /dev/null +++ b/frontend/src/components/NavLink.tsx @@ -0,0 +1,29 @@ +import { useNavigate } from "react-router-dom"; + +interface Props { + children: React.ReactNode; + to?: string; + isDropdownItem?: boolean; + onClick?: () => void; +} +export function NavLink({ children, to, isDropdownItem, onClick }: Props) { + const navigate = useNavigate(); + + return ( + { + e.preventDefault(); + if (onClick) { + onClick(); + } + if (to) { + navigate(to); + } + }} + > + {children} + + ); +} diff --git a/frontend/src/components/Page.module.css b/frontend/src/components/Page.module.css new file mode 100644 index 00000000..00086bba --- /dev/null +++ b/frontend/src/components/Page.module.css @@ -0,0 +1,5 @@ +.page { + display: grid; + grid-template-rows: auto 1fr auto; /* Header, Main Content, Footer */ + min-height: 100vh; +} diff --git a/frontend/src/components/Page.tsx b/frontend/src/components/Page.tsx new file mode 100644 index 00000000..a96a6b1f --- /dev/null +++ b/frontend/src/components/Page.tsx @@ -0,0 +1,10 @@ +import cn from "classnames"; +import styles from "./Page.module.css"; + +interface Props { + children: React.ReactNode; + className?: string; +} +export function Page({ children, className }: Props) { + return
{children}
; +} diff --git a/frontend/src/components/SiteContainer.tsx b/frontend/src/components/SiteContainer.tsx new file mode 100644 index 00000000..1722d88a --- /dev/null +++ b/frontend/src/components/SiteContainer.tsx @@ -0,0 +1,6 @@ +interface Props { + children: React.ReactNode; +} +export function SiteContainer({ children }: Props) { + return
{children}
; +} diff --git a/frontend/src/components/SiteFooter.tsx b/frontend/src/components/SiteFooter.tsx new file mode 100644 index 00000000..57bc8b74 --- /dev/null +++ b/frontend/src/components/SiteFooter.tsx @@ -0,0 +1,64 @@ +import { useHealth } from "src/hooks"; +import { intl } from "src/locale"; + +export function SiteFooter() { + const health = useHealth(); + + const getVersion = () => { + if (!health.data) { + return ""; + } + const v = health.data.version; + return `v${v.major}.${v.minor}.${v.revision}`; + }; + + return ( + + ); +} diff --git a/frontend/src/components/SiteHeader.module.css b/frontend/src/components/SiteHeader.module.css new file mode 100644 index 00000000..a88f81c9 --- /dev/null +++ b/frontend/src/components/SiteHeader.module.css @@ -0,0 +1,8 @@ +.logo { + font-size: 1.1rem; + font-weight: 500; + + img { + margin-right: 0.8rem; + } +} diff --git a/frontend/src/components/SiteHeader.tsx b/frontend/src/components/SiteHeader.tsx new file mode 100644 index 00000000..f3491fc3 --- /dev/null +++ b/frontend/src/components/SiteHeader.tsx @@ -0,0 +1,119 @@ +import { IconLock, IconLogout, IconUser } from "@tabler/icons-react"; +import { useState } from "react"; +import { LocalePicker, ThemeSwitcher } from "src/components"; +import { useAuthState } from "src/context"; +import { useUser } from "src/hooks"; +import { intl } from "src/locale"; +import { ChangePasswordModal, UserModal } from "src/modals"; +import styles from "./SiteHeader.module.css"; + +export function SiteHeader() { + const { data: currentUser } = useUser("me"); + const isAdmin = currentUser?.roles.includes("admin"); + const { logout } = useAuthState(); + const [showProfileEdit, setShowProfileEdit] = useState(false); + const [showChangePassword, setShowChangePassword] = useState(false); + + return ( +
+
+ ); +} diff --git a/frontend/src/components/SiteMenu.tsx b/frontend/src/components/SiteMenu.tsx new file mode 100644 index 00000000..1ceb8145 --- /dev/null +++ b/frontend/src/components/SiteMenu.tsx @@ -0,0 +1,195 @@ +import { + IconBook, + IconDeviceDesktop, + IconHome, + IconLock, + IconSettings, + IconShield, + IconUser, +} from "@tabler/icons-react"; +import cn from "classnames"; +import React from "react"; +import { HasPermission, NavLink } from "src/components"; +import { intl } from "src/locale"; + +interface MenuItem { + label: string; + icon?: React.ElementType; + to?: string; + items?: MenuItem[]; + permission?: string; + permissionType?: "view" | "manage"; +} + +const menuItems: MenuItem[] = [ + { + to: "/", + icon: IconHome, + label: "dashboard.title", + }, + { + icon: IconDeviceDesktop, + label: "hosts.title", + items: [ + { + to: "/nginx/proxy", + label: "proxy-hosts.title", + permission: "proxyHosts", + permissionType: "view", + }, + { + to: "/nginx/redirection", + label: "redirection-hosts.title", + permission: "redirectionHosts", + permissionType: "view", + }, + { + to: "/nginx/stream", + label: "streams.title", + permission: "streams", + permissionType: "view", + }, + { + to: "/nginx/404", + label: "dead-hosts.title", + permission: "deadHosts", + permissionType: "view", + }, + ], + }, + { + to: "/access", + icon: IconLock, + label: "access.title", + permission: "accessLists", + permissionType: "view", + }, + { + to: "/certificates", + icon: IconShield, + label: "certificates.title", + permission: "certificates", + permissionType: "view", + }, + { + to: "/users", + icon: IconUser, + label: "users.title", + permission: "admin", + }, + { + to: "/audit-log", + icon: IconBook, + label: "auditlog.title", + permission: "admin", + }, + { + to: "/settings", + icon: IconSettings, + label: "settings.title", + permission: "admin", + }, +]; + +const getMenuItem = (item: MenuItem, onClick?: () => void) => { + if (item.items && item.items.length > 0) { + return getMenuDropown(item, onClick); + } + + return ( + +
  • + + + {item.icon && React.createElement(item.icon, { height: 24, width: 24 })} + + {intl.formatMessage({ id: item.label })} + +
  • +
    + ); +}; + +const getMenuDropown = (item: MenuItem, onClick?: () => void) => { + const cns = cn("nav-item", "dropdown"); + return ( + +
  • + +
    + {item.items?.map((subitem, idx) => { + return ( + + + {intl.formatMessage({ id: subitem.label })} + + + ); + })} +
    +
  • +
    + ); +}; + +export function SiteMenu() { + // This is hacky AF. But that's the price of using a non-react UI kit. + const closeMenus = () => { + const navMenus = document.querySelectorAll(".nav-item.dropdown"); + navMenus.forEach((menu) => { + menu.classList.remove("show"); + const dropdown = menu.querySelector(".dropdown-menu"); + if (dropdown) { + dropdown.classList.remove("show"); + } + }); + }; + + return ( +
    +
    +
    +
    +
    +
    +
      + {menuItems.length > 0 && + menuItems.map((item) => { + return getMenuItem(item, closeMenus); + })} +
    +
    +
    +
    +
    +
    +
    + ); +} diff --git a/frontend/src/components/Table/EmptyRow.tsx b/frontend/src/components/Table/EmptyRow.tsx new file mode 100644 index 00000000..2daee952 --- /dev/null +++ b/frontend/src/components/Table/EmptyRow.tsx @@ -0,0 +1,16 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; + +interface Props { + tableInstance: ReactTable; +} +function EmptyRow({ tableInstance }: Props) { + return ( + + +

    There are no items

    + + + ); +} + +export { EmptyRow }; diff --git a/frontend/src/components/Table/Formatter/CertificateFormatter.tsx b/frontend/src/components/Table/Formatter/CertificateFormatter.tsx new file mode 100644 index 00000000..daa4b471 --- /dev/null +++ b/frontend/src/components/Table/Formatter/CertificateFormatter.tsx @@ -0,0 +1,13 @@ +import type { Certificate } from "src/api/backend"; +import { intl } from "src/locale"; + +interface Props { + certificate?: Certificate; +} +export function CertificateFormatter({ certificate }: Props) { + if (certificate) { + return intl.formatMessage({ id: "lets-encrypt" }); + } + + return intl.formatMessage({ id: "http-only" }); +} diff --git a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx new file mode 100644 index 00000000..5ab339f2 --- /dev/null +++ b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx @@ -0,0 +1,25 @@ +import { intlFormat, parseISO } from "date-fns"; +import { intl } from "src/locale"; + +interface Props { + domains: string[]; + createdOn?: string; +} +export function DomainsFormatter({ domains, createdOn }: Props) { + return ( +
    +
    + {domains.map((domain: string) => ( + + {domain} + + ))} +
    + {createdOn ? ( +
    + {intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })} +
    + ) : null} +
    + ); +} diff --git a/frontend/src/components/Table/Formatter/GravatarFormatter.tsx b/frontend/src/components/Table/Formatter/GravatarFormatter.tsx new file mode 100644 index 00000000..9b7bed95 --- /dev/null +++ b/frontend/src/components/Table/Formatter/GravatarFormatter.tsx @@ -0,0 +1,17 @@ +interface Props { + url: string; + name?: string; +} +export function GravatarFormatter({ url, name }: Props) { + return ( +
    + +
    + ); +} diff --git a/frontend/src/components/Table/Formatter/StatusFormatter.tsx b/frontend/src/components/Table/Formatter/StatusFormatter.tsx new file mode 100644 index 00000000..3ea1f5c7 --- /dev/null +++ b/frontend/src/components/Table/Formatter/StatusFormatter.tsx @@ -0,0 +1,11 @@ +import { intl } from "src/locale"; + +interface Props { + enabled: boolean; +} +export function StatusFormatter({ enabled }: Props) { + if (enabled) { + return {intl.formatMessage({ id: "online" })}; + } + return {intl.formatMessage({ id: "offline" })}; +} diff --git a/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx new file mode 100644 index 00000000..0754c633 --- /dev/null +++ b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx @@ -0,0 +1,21 @@ +import { intlFormat, parseISO } from "date-fns"; +import { intl } from "src/locale"; + +interface Props { + value: string; + createdOn?: string; +} +export function ValueWithDateFormatter({ value, createdOn }: Props) { + return ( +
    +
    +
    {value}
    +
    + {createdOn ? ( +
    + {intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })} +
    + ) : null} +
    + ); +} diff --git a/frontend/src/components/Table/Formatter/index.ts b/frontend/src/components/Table/Formatter/index.ts new file mode 100644 index 00000000..2d67aa2e --- /dev/null +++ b/frontend/src/components/Table/Formatter/index.ts @@ -0,0 +1,5 @@ +export * from "./CertificateFormatter"; +export * from "./DomainsFormatter"; +export * from "./GravatarFormatter"; +export * from "./StatusFormatter"; +export * from "./ValueWithDateFormatter"; diff --git a/frontend/src/components/Table/TableBody.tsx b/frontend/src/components/Table/TableBody.tsx new file mode 100644 index 00000000..a4b16242 --- /dev/null +++ b/frontend/src/components/Table/TableBody.tsx @@ -0,0 +1,39 @@ +import { flexRender } from "@tanstack/react-table"; +import type { TableLayoutProps } from "src/components"; +import { EmptyRow } from "./EmptyRow"; + +function TableBody(props: TableLayoutProps) { + const { tableInstance, extraStyles, emptyState } = props; + const rows = tableInstance.getRowModel().rows; + + if (rows.length === 0) { + return emptyState ? ( + emptyState + ) : ( + + + + ); + } + + return ( + + {rows.map((row: any) => { + return ( + + {row.getVisibleCells().map((cell: any) => { + const { className } = (cell.column.columnDef.meta as any) ?? {}; + return ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ); + })} + + ); + })} + + ); +} + +export { TableBody }; diff --git a/frontend/src/components/Table/TableHeader.tsx b/frontend/src/components/Table/TableHeader.tsx new file mode 100644 index 00000000..bf627ce9 --- /dev/null +++ b/frontend/src/components/Table/TableHeader.tsx @@ -0,0 +1,26 @@ +import type { TableLayoutProps } from "src/components"; + +function TableHeader(props: TableLayoutProps) { + const { tableInstance } = props; + const headerGroups = tableInstance.getHeaderGroups(); + + return ( + + {headerGroups.map((headerGroup: any) => ( + + {headerGroup.headers.map((header: any) => { + const { column } = header; + const { className } = (column.columnDef.meta as any) ?? {}; + return ( + + {typeof column.columnDef.header === "string" ? `${column.columnDef.header}` : null} + + ); + })} + + ))} + + ); +} + +export { TableHeader }; diff --git a/frontend/src/components/Table/TableHelpers.ts b/frontend/src/components/Table/TableHelpers.ts new file mode 100644 index 00000000..0df329c8 --- /dev/null +++ b/frontend/src/components/Table/TableHelpers.ts @@ -0,0 +1,64 @@ +export interface TablePagination { + limit: number; + offset: number; + total: number; +} + +export interface TableSortBy { + id: string; + desc: boolean; +} + +export interface TableFilter { + id: string; + value: any; +} + +const tableEvents = { + FILTERS_CHANGED: "FILTERS_CHANGED", + PAGE_CHANGED: "PAGE_CHANGED", + PAGE_SIZE_CHANGED: "PAGE_SIZE_CHANGED", + TOTAL_COUNT_CHANGED: "TOTAL_COUNT_CHANGED", + SORT_CHANGED: "SORT_CHANGED", +}; + +const tableEventReducer = (state: any, { type, payload }: any) => { + let offset = state.offset; + switch (type) { + case tableEvents.PAGE_CHANGED: + return { + ...state, + offset: payload * state.limit, + }; + case tableEvents.PAGE_SIZE_CHANGED: + return { + ...state, + limit: payload, + }; + case tableEvents.TOTAL_COUNT_CHANGED: + return { + ...state, + total: payload, + }; + case tableEvents.SORT_CHANGED: + return { + ...state, + sortBy: payload, + }; + case tableEvents.FILTERS_CHANGED: + if (state.filters !== payload) { + // this actually was a legit change + // sets to page 1 when filter is modified + offset = 0; + } + return { + ...state, + filters: payload, + offset, + }; + default: + throw new Error(`Unhandled action type: ${type}`); + } +}; + +export { tableEvents, tableEventReducer }; diff --git a/frontend/src/components/Table/TableLayout.tsx b/frontend/src/components/Table/TableLayout.tsx new file mode 100644 index 00000000..c23f50b3 --- /dev/null +++ b/frontend/src/components/Table/TableLayout.tsx @@ -0,0 +1,22 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; +import { TableBody } from "./TableBody"; +import { TableHeader } from "./TableHeader"; + +interface TableLayoutProps { + tableInstance: ReactTable; + emptyState?: React.ReactNode; + extraStyles?: { + row: (rowData: TFields) => any | undefined; + }; +} +function TableLayout(props: TableLayoutProps) { + const hasRows = props.tableInstance.getRowModel().rows.length > 0; + return ( + + {hasRows ? : null} + +
    + ); +} + +export { TableLayout, type TableLayoutProps }; diff --git a/frontend/src/components/Table/index.ts b/frontend/src/components/Table/index.ts new file mode 100644 index 00000000..d471d45d --- /dev/null +++ b/frontend/src/components/Table/index.ts @@ -0,0 +1,4 @@ +export * from "./Formatter"; +export * from "./TableHeader"; +export * from "./TableHelpers"; +export * from "./TableLayout"; diff --git a/frontend/src/components/ThemeSwitcher.module.css b/frontend/src/components/ThemeSwitcher.module.css new file mode 100644 index 00000000..2233d2f8 --- /dev/null +++ b/frontend/src/components/ThemeSwitcher.module.css @@ -0,0 +1,15 @@ +.darkBtn { + color: var(--tblr-light) !important; + &:hover { + border: var(--tblr-btn-border-width) solid transparent !important; + background: color-mix(in srgb, var(--tblr-btn-hover-bg) 10%, transparent) !important; + } +} + +.lightBtn { + color: var(--tblr-dark) !important; + &:hover { + border: var(--tblr-btn-border-width) solid transparent !important; + background: color-mix(in srgb, var(--tblr-btn-hover-bg) 10%, transparent) !important; + } +} diff --git a/frontend/src/components/ThemeSwitcher.tsx b/frontend/src/components/ThemeSwitcher.tsx new file mode 100644 index 00000000..9cb66978 --- /dev/null +++ b/frontend/src/components/ThemeSwitcher.tsx @@ -0,0 +1,41 @@ +import { IconMoon, IconSun } from "@tabler/icons-react"; +import cn from "classnames"; +import { Button } from "src/components"; +import { useTheme } from "src/hooks"; +import styles from "./ThemeSwitcher.module.css"; + +interface Props { + className?: string; +} +function ThemeSwitcher({ className }: Props) { + const { setTheme } = useTheme(); + + return ( +
    + + +
    + ); +} + +export { ThemeSwitcher }; diff --git a/frontend/src/components/Unhealthy.tsx b/frontend/src/components/Unhealthy.tsx new file mode 100644 index 00000000..d4b6e33c --- /dev/null +++ b/frontend/src/components/Unhealthy.tsx @@ -0,0 +1,17 @@ +import { Page } from "src/components"; + +export function Unhealthy() { + return ( + +
    +
    +
    + +
    +

    The API is not healthy.

    +

    We'll keep checking and hope to be back soon!

    +
    +
    +
    + ); +} diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts new file mode 100644 index 00000000..66e12a74 --- /dev/null +++ b/frontend/src/components/index.ts @@ -0,0 +1,15 @@ +export * from "./Button"; +export * from "./ErrorNotFound"; +export * from "./Flag"; +export * from "./HasPermission"; +export * from "./LoadingPage"; +export * from "./LocalePicker"; +export * from "./NavLink"; +export * from "./Page"; +export * from "./SiteContainer"; +export * from "./SiteFooter"; +export * from "./SiteHeader"; +export * from "./SiteMenu"; +export * from "./Table"; +export * from "./ThemeSwitcher"; +export * from "./Unhealthy"; diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx new file mode 100644 index 00000000..f940722e --- /dev/null +++ b/frontend/src/context/AuthContext.tsx @@ -0,0 +1,72 @@ +import { useQueryClient } from "@tanstack/react-query"; +import { createContext, type ReactNode, useContext, useState } from "react"; +import { useIntervalWhen } from "rooks"; +import { getToken, refreshToken, type TokenResponse } from "src/api/backend"; +import AuthStore from "src/modules/AuthStore"; + +// Context +export interface AuthContextType { + authenticated: boolean; + login: (username: string, password: string) => Promise; + logout: () => void; + token?: string; +} + +const initalValue = null; +const AuthContext = createContext(initalValue); + +// Provider +interface Props { + children?: ReactNode; + tokenRefreshInterval?: number; +} +function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props) { + const queryClient = useQueryClient(); + const [authenticated, setAuthenticated] = useState(AuthStore.hasActiveToken()); + + const handleTokenUpdate = (response: TokenResponse) => { + AuthStore.set(response); + setAuthenticated(true); + }; + + const login = async (identity: string, secret: string) => { + const response = await getToken({ payload: { identity, secret } }); + handleTokenUpdate(response); + }; + + const logout = () => { + AuthStore.clear(); + setAuthenticated(false); + queryClient.clear(); + }; + + const refresh = async () => { + const response = await refreshToken(); + handleTokenUpdate(response); + }; + + useIntervalWhen( + () => { + if (authenticated) { + refresh(); + } + }, + tokenRefreshInterval, + true, + ); + + const value = { authenticated, login, logout }; + + return {children}; +} + +function useAuthState() { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuthState must be used within a AuthProvider"); + } + return context; +} + +export { AuthProvider, useAuthState }; +export default AuthContext; diff --git a/frontend/src/context/LocaleContext.tsx b/frontend/src/context/LocaleContext.tsx new file mode 100644 index 00000000..ca30a5f3 --- /dev/null +++ b/frontend/src/context/LocaleContext.tsx @@ -0,0 +1,38 @@ +import { createContext, type ReactNode, useContext, useState } from "react"; +import { getLocale } from "src/locale"; + +// Context +export interface LocaleContextType { + setLocale: (locale: string) => void; + locale?: string; +} + +const initalValue = null; +const LocaleContext = createContext(initalValue); + +// Provider +interface Props { + children?: ReactNode; +} +function LocaleProvider({ children }: Props) { + const [locale, setLocaleValue] = useState(getLocale()); + + const setLocale = async (locale: string) => { + setLocaleValue(locale); + }; + + const value = { locale, setLocale }; + + return {children}; +} + +function useLocaleState() { + const context = useContext(LocaleContext); + if (!context) { + throw new Error("useLocaleState must be used within a LocaleProvider"); + } + return context; +} + +export { LocaleProvider, useLocaleState }; +export default LocaleContext; diff --git a/frontend/src/context/ThemeContext.tsx b/frontend/src/context/ThemeContext.tsx new file mode 100644 index 00000000..648d9856 --- /dev/null +++ b/frontend/src/context/ThemeContext.tsx @@ -0,0 +1,68 @@ +import type React from "react"; +import { createContext, type ReactNode, useContext, useEffect, useState } from "react"; + +const StorageKey = "tabler-theme"; +export const Light = "light"; +export const Dark = "dark"; + +// Define theme types +export type Theme = "light" | "dark"; + +interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; + setTheme: (theme: Theme) => void; + getTheme: () => Theme; +} + +const ThemeContext = createContext(undefined); + +interface ThemeProviderProps { + children: ReactNode; +} + +const getBrowserDefault = (): Theme => { + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return Dark; + } + return Light; +}; + +export const ThemeProvider: React.FC = ({ children }) => { + const [theme, setThemeState] = useState(() => { + // Try to read theme from localStorage or use 'light' as default + if (typeof window !== "undefined") { + const stored = localStorage.getItem(StorageKey) as Theme | null; + return stored || getBrowserDefault(); + } + return getBrowserDefault(); + }); + + useEffect(() => { + document.body.dataset.theme = theme; + localStorage.setItem(StorageKey, theme); + }, [theme]); + + const toggleTheme = () => { + setThemeState((prev) => (prev === Light ? Dark : Light)); + }; + + const setTheme = (newTheme: Theme) => { + setThemeState(newTheme); + }; + + const getTheme = () => { + return theme; + } + + document.documentElement.setAttribute("data-bs-theme", theme); + return {children}; +}; + +export function useTheme(): ThemeContextType { + const context = useContext(ThemeContext); + if (!context) { + throw new Error("useTheme must be used within a ThemeProvider"); + } + return context; +} diff --git a/frontend/src/context/index.ts b/frontend/src/context/index.ts new file mode 100644 index 00000000..a24e0657 --- /dev/null +++ b/frontend/src/context/index.ts @@ -0,0 +1,3 @@ +export * from "./AuthContext"; +export * from "./LocaleContext"; +export * from "./ThemeContext"; diff --git a/frontend/src/declarations.d.ts b/frontend/src/declarations.d.ts new file mode 100644 index 00000000..18f423bf --- /dev/null +++ b/frontend/src/declarations.d.ts @@ -0,0 +1 @@ +declare module "*.md"; diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts new file mode 100644 index 00000000..2c9b4921 --- /dev/null +++ b/frontend/src/hooks/index.ts @@ -0,0 +1,10 @@ +export * from "./useAccessLists"; +export * from "./useDeadHosts"; +export * from "./useHealth"; +export * from "./useHostReport"; +export * from "./useProxyHosts"; +export * from "./useRedirectionHosts"; +export * from "./useStreams"; +export * from "./useTheme"; +export * from "./useUser"; +export * from "./useUsers"; diff --git a/frontend/src/hooks/useAccessLists.ts b/frontend/src/hooks/useAccessLists.ts new file mode 100644 index 00000000..cb052f68 --- /dev/null +++ b/frontend/src/hooks/useAccessLists.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type AccessList, type AccessListExpansion, getAccessLists } from "src/api/backend"; + +const fetchAccessLists = (expand?: AccessListExpansion[]) => { + return getAccessLists(expand); +}; + +const useAccessLists = (expand?: AccessListExpansion[], options = {}) => { + return useQuery({ + queryKey: ["access-lists", { expand }], + queryFn: () => fetchAccessLists(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchAccessLists, useAccessLists }; diff --git a/frontend/src/hooks/useDeadHosts.ts b/frontend/src/hooks/useDeadHosts.ts new file mode 100644 index 00000000..f08ed0ee --- /dev/null +++ b/frontend/src/hooks/useDeadHosts.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type DeadHost, type DeadHostExpansion, getDeadHosts } from "src/api/backend"; + +const fetchDeadHosts = (expand?: DeadHostExpansion[]) => { + return getDeadHosts(expand); +}; + +const useDeadHosts = (expand?: DeadHostExpansion[], options = {}) => { + return useQuery({ + queryKey: ["dead-hosts", { expand }], + queryFn: () => fetchDeadHosts(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchDeadHosts, useDeadHosts }; diff --git a/frontend/src/hooks/useHealth.ts b/frontend/src/hooks/useHealth.ts new file mode 100644 index 00000000..6a261c95 --- /dev/null +++ b/frontend/src/hooks/useHealth.ts @@ -0,0 +1,18 @@ +import { useQuery } from "@tanstack/react-query"; +import { getHealth, type HealthResponse } from "src/api/backend"; + +const fetchHealth = () => getHealth(); + +const useHealth = (options = {}) => { + return useQuery({ + queryKey: ["health"], + queryFn: fetchHealth, + refetchOnWindowFocus: false, + retry: 5, + refetchInterval: 15 * 1000, // 15 seconds + staleTime: 14 * 1000, // 14 seconds + ...options, + }); +}; + +export { fetchHealth, useHealth }; diff --git a/frontend/src/hooks/useHostReport.ts b/frontend/src/hooks/useHostReport.ts new file mode 100644 index 00000000..c54096d2 --- /dev/null +++ b/frontend/src/hooks/useHostReport.ts @@ -0,0 +1,18 @@ +import { useQuery } from "@tanstack/react-query"; +import { getHostsReport } from "src/api/backend"; + +const fetchHostReport = () => getHostsReport(); + +const useHostReport = (options = {}) => { + return useQuery, Error>({ + queryKey: ["host-report"], + queryFn: fetchHostReport, + refetchOnWindowFocus: false, + retry: 5, + refetchInterval: 15 * 1000, // 15 seconds + staleTime: 14 * 1000, // 14 seconds + ...options, + }); +}; + +export { fetchHostReport, useHostReport }; diff --git a/frontend/src/hooks/useProxyHosts.ts b/frontend/src/hooks/useProxyHosts.ts new file mode 100644 index 00000000..86366fef --- /dev/null +++ b/frontend/src/hooks/useProxyHosts.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { getProxyHosts, type ProxyHost, type ProxyHostExpansion } from "src/api/backend"; + +const fetchProxyHosts = (expand?: ProxyHostExpansion[]) => { + return getProxyHosts(expand); +}; + +const useProxyHosts = (expand?: ProxyHostExpansion[], options = {}) => { + return useQuery({ + queryKey: ["proxy-hosts", { expand }], + queryFn: () => fetchProxyHosts(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchProxyHosts, useProxyHosts }; diff --git a/frontend/src/hooks/useRedirectionHosts.ts b/frontend/src/hooks/useRedirectionHosts.ts new file mode 100644 index 00000000..03a66238 --- /dev/null +++ b/frontend/src/hooks/useRedirectionHosts.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { getRedirectionHosts, type RedirectionHost, type RedirectionHostExpansion } from "src/api/backend"; + +const fetchRedirectionHosts = (expand?: RedirectionHostExpansion[]) => { + return getRedirectionHosts(expand); +}; + +const useRedirectionHosts = (expand?: RedirectionHostExpansion[], options = {}) => { + return useQuery({ + queryKey: ["redirection-hosts", { expand }], + queryFn: () => fetchRedirectionHosts(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchRedirectionHosts, useRedirectionHosts }; diff --git a/frontend/src/hooks/useStreams.ts b/frontend/src/hooks/useStreams.ts new file mode 100644 index 00000000..8612050d --- /dev/null +++ b/frontend/src/hooks/useStreams.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { getStreams, type Stream, type StreamExpansion } from "src/api/backend"; + +const fetchStreams = (expand?: StreamExpansion[]) => { + return getStreams(expand); +}; + +const useStreams = (expand?: StreamExpansion[], options = {}) => { + return useQuery({ + queryKey: ["streams", { expand }], + queryFn: () => fetchStreams(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchStreams, useStreams }; diff --git a/frontend/src/hooks/useTheme.ts b/frontend/src/hooks/useTheme.ts new file mode 100644 index 00000000..941c0626 --- /dev/null +++ b/frontend/src/hooks/useTheme.ts @@ -0,0 +1,8 @@ +import { Dark, Light, useTheme as useThemeContext } from "src/context"; + +// Simple hook wrapper for clarity and scalability +const useTheme = () => { + return useThemeContext(); +}; + +export { useTheme, Dark, Light }; diff --git a/frontend/src/hooks/useUser.ts b/frontend/src/hooks/useUser.ts new file mode 100644 index 00000000..180032c2 --- /dev/null +++ b/frontend/src/hooks/useUser.ts @@ -0,0 +1,37 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { getUser, type User, updateUser } from "src/api/backend"; + +const fetchUser = (id: number | string) => { + return getUser(id, { expand: "permissions" }); +}; + +const useUser = (id: string | number, options = {}) => { + return useQuery({ + queryKey: ["user", id], + queryFn: () => fetchUser(id), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetUser = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: User) => updateUser(values), + onMutate: (values: User) => { + const previousObject = queryClient.getQueryData(["user", values.id]); + queryClient.setQueryData(["user", values.id], (old: User) => ({ + ...old, + ...values, + })); + return () => queryClient.setQueryData(["user", values.id], previousObject); + }, + onError: (_, __, rollback: any) => rollback(), + onSuccess: async ({ id }: User) => { + queryClient.invalidateQueries({ queryKey: ["user", id] }); + queryClient.invalidateQueries({ queryKey: ["users"] }); + }, + }); +}; + +export { useUser, useSetUser }; diff --git a/frontend/src/hooks/useUsers.ts b/frontend/src/hooks/useUsers.ts new file mode 100644 index 00000000..8c4d802f --- /dev/null +++ b/frontend/src/hooks/useUsers.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { getUsers, type User, type UserExpansion } from "src/api/backend"; + +const fetchUsers = (expand?: UserExpansion[]) => { + return getUsers(expand); +}; + +const useUsers = (expand?: UserExpansion[], options = {}) => { + return useQuery({ + queryKey: ["users", { expand }], + queryFn: () => fetchUsers(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchUsers, useUsers }; diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx new file mode 100644 index 00000000..ee256859 --- /dev/null +++ b/frontend/src/locale/IntlProvider.tsx @@ -0,0 +1,64 @@ +import { createIntl, createIntlCache } from "react-intl"; +import langDe from "./lang/de.json"; +import langEn from "./lang/en.json"; +import langFa from "./lang/fa.json"; +import langList from "./lang/lang-list.json"; + +// first item of each array should be the language code, +// not the country code +// Remember when adding to this list, also update check-locales.js script +const localeOptions = [ + ["en", "en-US"], + ["de", "de-DE"], + ["fa", "fa-IR"], +]; + +const loadMessages = (locale?: string): typeof langList & typeof langEn => { + const thisLocale = locale || "en"; + switch (thisLocale.slice(0, 2)) { + case "de": + return Object.assign({}, langList, langEn, langDe); + case "fa": + return Object.assign({}, langList, langEn, langFa); + default: + return Object.assign({}, langList, langEn); + } +}; + +const getFlagCodeForLocale = (locale?: string) => { + switch (locale) { + case "de-DE": + case "de": + return "DE"; + case "fa-IR": + case "fa": + return "IR"; + default: + return "EN"; + } +}; + +const getLocale = (short = false) => { + let loc = window.localStorage.getItem("locale"); + if (!loc) { + loc = document.documentElement.lang; + } + if (short) { + return loc.slice(0, 2); + } + return loc; +}; + +const cache = createIntlCache(); + +const initialMessages = loadMessages(getLocale()); +let intl = createIntl({ locale: getLocale(), messages: initialMessages }, cache); + +const changeLocale = (locale: string): void => { + const messages = loadMessages(locale); + intl = createIntl({ locale, messages }, cache); + window.localStorage.setItem("locale", locale); + document.documentElement.lang = locale; +}; + +export { localeOptions, getFlagCodeForLocale, getLocale, createIntl, changeLocale, intl }; diff --git a/frontend/src/locale/README.md b/frontend/src/locale/README.md new file mode 100644 index 00000000..94275c8f --- /dev/null +++ b/frontend/src/locale/README.md @@ -0,0 +1,23 @@ +# Internationalisation support + +## Adding new translations + +Modify the files in the `src` folder. Follow the conventions already there. + + +## After making changes + +You will need to run `yarn locale-compile` in this frontend folder for +the new translations to be compiled into the `lang` folder. + +When running in dev mode, this should automatically happen within Vite. + + +## Checking for missing translations in other languages + +Run `node check-locales.cjs` in this frontend folder. + + +## Adding new languages + +todo diff --git a/frontend/src/locale/index.ts b/frontend/src/locale/index.ts new file mode 100644 index 00000000..c6dca46d --- /dev/null +++ b/frontend/src/locale/index.ts @@ -0,0 +1 @@ +export * from "./IntlProvider"; diff --git a/frontend/src/locale/lang/de.json b/frontend/src/locale/lang/de.json new file mode 100644 index 00000000..bb89fd18 --- /dev/null +++ b/frontend/src/locale/lang/de.json @@ -0,0 +1,3 @@ +{ + "dashboard.title": "Armaturenbrett" +} \ No newline at end of file diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json new file mode 100644 index 00000000..eae027b8 --- /dev/null +++ b/frontend/src/locale/lang/en.json @@ -0,0 +1,92 @@ +{ + "access.access-count": "{count} Rules", + "access.actions-title": "Access List #{id}", + "access.add": "Add Access List", + "access.auth-count": "{count} Users", + "access.empty": "There are no Access Lists", + "access.satisfy-all": "All", + "access.satisfy-any": "Any", + "access.title": "Access", + "action.delete": "Delete", + "action.disable": "Disable", + "action.edit": "Edit", + "action.enable": "Enable", + "action.permissions": "Permissions", + "administrator": "Administrator", + "auditlog.title": "Audit Log", + "cancel": "Cancel", + "certificates.title": "SSL Certificates", + "column.access": "Access", + "column.authorization": "Authorization", + "column.destination": "Destination", + "column.email": "Email", + "column.http-code": "Access", + "column.incoming-port": "Incoming Port", + "column.name": "Name", + "column.protocol": "Protocol", + "column.roles": "Roles", + "column.satisfy": "Satisfy", + "column.scheme": "Scheme", + "column.source": "Source", + "column.ssl": "SSL", + "column.status": "Status", + "created-on": "Created: {date}", + "dashboard.title": "Dashboard", + "dead-hosts.actions-title": "404 Host #{id}", + "dead-hosts.add": "Add 404 Host", + "dead-hosts.count": "{count} 404 Hosts", + "dead-hosts.empty": "There are no 404 Hosts", + "dead-hosts.title": "404 Hosts", + "email-address": "Email address", + "empty-subtitle": "Why don't you create one?", + "error.invalid-auth": "Invalid email or password", + "error.passwords-must-match": "Passwords must match", + "footer.github-fork": "Fork me on Github", + "hosts.title": "Hosts", + "http-only": "HTTP Only", + "lets-encrypt": "Let's Encrypt", + "loading": "Loading…", + "login.title": "Login to your account", + "no-permission-error": "You do not have access to view this.", + "notfound.action": "Take me home", + "notfound.text": "We are sorry but the page you are looking for was not found", + "notfound.title": "Oops… You just found an error page", + "offline": "Offline", + "online": "Online", + "password": "Password", + "proxy-hosts.actions-title": "Proxy Host #{id}", + "proxy-hosts.add": "Add Proxy Host", + "proxy-hosts.count": "{count} Proxy Hosts", + "proxy-hosts.empty": "There are no Proxy Hosts", + "proxy-hosts.title": "Proxy Hosts", + "redirection-hosts.actions-title": "Redirection Host #{id}", + "redirection-hosts.add": "Add Redirection Host", + "redirection-hosts.count": "{count} Redirection Hosts", + "redirection-hosts.empty": "There are no Redirection Hosts", + "redirection-hosts.title": "Redirection Hosts", + "save": "Save", + "settings.title": "Settings", + "sign-in": "Sign in", + "standard-user": "Apache Helicopter", + "streams.actions-title": "Stream #{id}", + "streams.add": "Add Stream", + "streams.count": "{count} Streams", + "streams.empty": "There are no Streams", + "streams.tcp": "TCP", + "streams.title": "Streams", + "streams.udp": "UDP", + "user.change-password": "Change Password", + "user.confirm-password": "Confirm Password", + "user.current-password": "Current Password", + "user.edit": "Edit User", + "user.edit-profile": "Edit Profile", + "user.full-name": "Full Name", + "user.logout": "Logout", + "user.new-password": "New Password", + "user.nickname": "Nickname", + "user.switch-dark": "Switch to Dark mode", + "user.switch-light": "Switch to Light mode", + "users.actions-title": "User #{id}", + "users.add": "Add User", + "users.title": "Users" +} \ No newline at end of file diff --git a/frontend/src/locale/lang/fa.json b/frontend/src/locale/lang/fa.json new file mode 100644 index 00000000..77cecfad --- /dev/null +++ b/frontend/src/locale/lang/fa.json @@ -0,0 +1,3 @@ +{ + "dashboard.title": "داشبورد" +} \ No newline at end of file diff --git a/frontend/src/locale/lang/lang-list.json b/frontend/src/locale/lang/lang-list.json new file mode 100644 index 00000000..1d6bed3a --- /dev/null +++ b/frontend/src/locale/lang/lang-list.json @@ -0,0 +1,5 @@ +{ + "locale-de-DE": "Deutsch", + "locale-en-US": "English", + "locale-fa-IR": "فارسی" +} \ No newline at end of file diff --git a/frontend/src/locale/src/de.json b/frontend/src/locale/src/de.json new file mode 100644 index 00000000..2b2ab546 --- /dev/null +++ b/frontend/src/locale/src/de.json @@ -0,0 +1,5 @@ +{ + "dashboard.title": { + "defaultMessage": "Armaturenbrett" + } +} diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json new file mode 100644 index 00000000..b3e881de --- /dev/null +++ b/frontend/src/locale/src/en.json @@ -0,0 +1,272 @@ +{ + "access.actions-title": { + "defaultMessage": "Access List #{id}" + }, + "access.access-count": { + "defaultMessage": "{count} Rules" + }, + "access.add": { + "defaultMessage": "Add Access List" + }, + "access.auth-count": { + "defaultMessage": "{count} Users" + }, + "access.empty": { + "defaultMessage": "There are no Access Lists" + }, + "access.satisfy-all": { + "defaultMessage": "All" + }, + "access.satisfy-any": { + "defaultMessage": "Any" + }, + "access.title": { + "defaultMessage": "Access" + }, + "action.delete": { + "defaultMessage": "Delete" + }, + "action.disable": { + "defaultMessage": "Disable" + }, + "action.enable": { + "defaultMessage": "Enable" + }, + "action.edit": { + "defaultMessage": "Edit" + }, + "action.permissions": { + "defaultMessage": "Permissions" + }, + "administrator": { + "defaultMessage": "Administrator" + }, + "auditlog.title": { + "defaultMessage": "Audit Log" + }, + "cancel": { + "defaultMessage": "Cancel" + }, + "certificates.title": { + "defaultMessage": "SSL Certificates" + }, + "created-on": { + "defaultMessage": "Created: {date}" + }, + "column.access": { + "defaultMessage": "Access" + }, + "column.authorization": { + "defaultMessage": "Authorization" + }, + "column.destination": { + "defaultMessage": "Destination" + }, + "column.email": { + "defaultMessage": "Email" + }, + "column.http-code": { + "defaultMessage": "Access" + }, + "column.incoming-port": { + "defaultMessage": "Incoming Port" + }, + "column.name": { + "defaultMessage": "Name" + }, + "column.protocol": { + "defaultMessage": "Protocol" + }, + "column.roles": { + "defaultMessage": "Roles" + }, + "column.satisfy": { + "defaultMessage": "Satisfy" + }, + "column.scheme": { + "defaultMessage": "Scheme" + }, + "column.status": { + "defaultMessage": "Status" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.source": { + "defaultMessage": "Source" + }, + "dashboard.title": { + "defaultMessage": "Dashboard" + }, + "dead-hosts.actions-title": { + "defaultMessage": "404 Host #{id}" + }, + "dead-hosts.add": { + "defaultMessage": "Add 404 Host" + }, + "dead-hosts.count": { + "defaultMessage": "{count} 404 Hosts" + }, + "dead-hosts.empty": { + "defaultMessage": "There are no 404 Hosts" + }, + "dead-hosts.title": { + "defaultMessage": "404 Hosts" + }, + "email-address": { + "defaultMessage": "Email address" + }, + "error.passwords-must-match": { + "defaultMessage": "Passwords must match" + }, + "error.invalid-auth": { + "defaultMessage": "Invalid email or password" + }, + "footer.github-fork": { + "defaultMessage": "Fork me on Github" + }, + "empty-subtitle": { + "defaultMessage": "Why don't you create one?" + }, + "hosts.title": { + "defaultMessage": "Hosts" + }, + "http-only": { + "defaultMessage": "HTTP Only" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "loading": { + "defaultMessage": "Loading…" + }, + "login.title": { + "defaultMessage": "Login to your account" + }, + "no-permission-error": { + "defaultMessage": "You do not have access to view this." + }, + "notfound.action": { + "defaultMessage": "Take me home" + }, + "notfound.text": { + "defaultMessage": "We are sorry but the page you are looking for was not found" + }, + "notfound.title": { + "defaultMessage": "Oops… You just found an error page" + }, + "offline": { + "defaultMessage": "Offline" + }, + "online": { + "defaultMessage": "Online" + }, + "password": { + "defaultMessage": "Password" + }, + "proxy-hosts.actions-title": { + "defaultMessage": "Proxy Host #{id}" + }, + "proxy-hosts.add": { + "defaultMessage": "Add Proxy Host" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} Proxy Hosts" + }, + "proxy-hosts.empty": { + "defaultMessage": "There are no Proxy Hosts" + }, + "proxy-hosts.title": { + "defaultMessage": "Proxy Hosts" + }, + "redirection-hosts.actions-title": { + "defaultMessage": "Redirection Host #{id}" + }, + "redirection-hosts.add": { + "defaultMessage": "Add Redirection Host" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} Redirection Hosts" + }, + "redirection-hosts.empty": { + "defaultMessage": "There are no Redirection Hosts" + }, + "redirection-hosts.title": { + "defaultMessage": "Redirection Hosts" + }, + "save": { + "defaultMessage": "Save" + }, + "settings.title": { + "defaultMessage": "Settings" + }, + "sign-in": { + "defaultMessage": "Sign in" + }, + "standard-user": { + "defaultMessage": "Apache Helicopter" + }, + "streams.actions-title": { + "defaultMessage": "Stream #{id}" + }, + "streams.add": { + "defaultMessage": "Add Stream" + }, + "streams.count": { + "defaultMessage": "{count} Streams" + }, + "streams.empty": { + "defaultMessage": "There are no Streams" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.title": { + "defaultMessage": "Streams" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "user.change-password": { + "defaultMessage": "Change Password" + }, + "user.confirm-password": { + "defaultMessage": "Confirm Password" + }, + "user.current-password": { + "defaultMessage": "Current Password" + }, + "user.edit": { + "defaultMessage": "Edit User" + }, + "user.edit-profile": { + "defaultMessage": "Edit Profile" + }, + "user.full-name": { + "defaultMessage": "Full Name" + }, + "user.logout": { + "defaultMessage": "Logout" + }, + "user.new-password": { + "defaultMessage": "New Password" + }, + "user.nickname": { + "defaultMessage": "Nickname" + }, + "user.switch-dark": { + "defaultMessage": "Switch to Dark mode" + }, + "user.switch-light": { + "defaultMessage": "Switch to Light mode" + }, + "users.actions-title": { + "defaultMessage": "User #{id}" + }, + "users.add": { + "defaultMessage": "Add User" + }, + "users.title": { + "defaultMessage": "Users" + } +} diff --git a/frontend/src/locale/src/fa.json b/frontend/src/locale/src/fa.json new file mode 100644 index 00000000..03679a36 --- /dev/null +++ b/frontend/src/locale/src/fa.json @@ -0,0 +1,5 @@ +{ + "dashboard.title": { + "defaultMessage": "داشبورد" + } +} diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json new file mode 100644 index 00000000..cdd95d05 --- /dev/null +++ b/frontend/src/locale/src/lang-list.json @@ -0,0 +1,11 @@ +{ + "locale-de-DE": { + "defaultMessage": "Deutsch" + }, + "locale-en-US": { + "defaultMessage": "English" + }, + "locale-fa-IR": { + "defaultMessage": "فارسی" + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 00000000..62c1c481 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "src/App.tsx"; + +import "@tabler/core/dist/css/tabler.min.css"; +import "@tabler/core/dist/js/tabler.min.js"; +import "./App.css"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + , +); diff --git a/frontend/src/modals/ChangePasswordModal.tsx b/frontend/src/modals/ChangePasswordModal.tsx new file mode 100644 index 00000000..811674df --- /dev/null +++ b/frontend/src/modals/ChangePasswordModal.tsx @@ -0,0 +1,153 @@ +import { Field, Form, Formik } from "formik"; +import { useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { updateAuth } from "src/api/backend"; +import { Button } from "src/components"; +import { intl } from "src/locale"; +import { validateString } from "src/modules/Validations"; + +interface Props { + userId: number | "me"; + onClose: () => void; +} +export function ChangePasswordModal({ userId, onClose }: Props) { + const [error, setError] = useState(null); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + if (values.new !== values.confirm) { + setError(intl.formatMessage({ id: "error.passwords-must-match" })); + setSubmitting(false); + return; + } + setError(null); + try { + await updateAuth(userId, values.new, values.current); + onClose(); + } catch (err: any) { + setError(intl.formatMessage({ id: err.message })); + } + setSubmitting(false); + }; + + return ( + + + {({ isSubmitting }) => ( +
    + + {intl.formatMessage({ id: "user.change-password" })} + + + setError(null)} dismissible> + {error} + +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.name ? ( +
    + {form.errors.current && form.touched.current + ? form.errors.current + : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.new ? ( +
    + {form.errors.new && form.touched.new ? form.errors.new : null} +
    + ) : null} +
    + )} +
    +
    +
    + + {({ field, form }: any) => ( +
    + + {form.errors.confirm ? ( +
    + {form.errors.confirm && form.touched.confirm + ? form.errors.confirm + : null} +
    + ) : null} + +
    + )} +
    +
    +
    + + + + +
    + )} +
    +
    + ); +} diff --git a/frontend/src/modals/UserModal.tsx b/frontend/src/modals/UserModal.tsx new file mode 100644 index 00000000..0f25817a --- /dev/null +++ b/frontend/src/modals/UserModal.tsx @@ -0,0 +1,213 @@ +import { Field, Form, Formik } from "formik"; +import { useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { Button } from "src/components"; +import { useSetUser, useUser } from "src/hooks"; +import { intl } from "src/locale"; +import { validateEmail, validateString } from "src/modules/Validations"; + +interface Props { + userId: number | "me"; + onClose: () => void; +} +export function UserModal({ userId, onClose }: Props) { + const { data } = useUser(userId); + const { data: currentUser } = useUser("me"); + const { mutate: setUser } = useSetUser(); + const [error, setError] = useState(null); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + setError(null); + const { ...payload } = { + id: userId, + roles: [], + ...values, + }; + + console.log("values", values); + + if (data?.id === currentUser?.id) { + // Prevent user from locking themselves out + delete payload.isDisabled; + delete payload.roles; + } else if (payload.isAdmin) { + payload.roles = ["admin"]; + } + + // this isn't a real field, just for the form + delete payload.isAdmin; + + setUser(payload, { + onError: (err: any) => setError(err.message), + onSuccess: () => onClose(), + onSettled: () => setSubmitting(false), + }); + }; + + return ( + + + {({ isSubmitting }) => ( +
    + + {intl.formatMessage({ id: "user.edit" })} + + + setError(null)} dismissible> + {error} + +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.name ? ( +
    + {form.errors.name && form.touched.name + ? form.errors.name + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.nickname ? ( +
    + {form.errors.nickname && form.touched.nickname + ? form.errors.nickname + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.email ? ( +
    + {form.errors.email && form.touched.email ? form.errors.email : null} +
    + ) : null} +
    + )} +
    +
    + {currentUser && data && currentUser?.id !== data?.id ? ( +
    +

    Properties

    + +
    +
    + +
    +
    + +
    +
    +
    + ) : null} +
    + + + + +
    + )} +
    +
    + ); +} diff --git a/frontend/src/modals/index.ts b/frontend/src/modals/index.ts new file mode 100644 index 00000000..c581bcdf --- /dev/null +++ b/frontend/src/modals/index.ts @@ -0,0 +1,2 @@ +export * from "./ChangePasswordModal"; +export * from "./UserModal"; diff --git a/frontend/src/modules/AuthStore.ts b/frontend/src/modules/AuthStore.ts new file mode 100644 index 00000000..90cee8e4 --- /dev/null +++ b/frontend/src/modules/AuthStore.ts @@ -0,0 +1,89 @@ +import { getUnixTime, parseISO } from "date-fns"; +import type { TokenResponse } from "src/api/backend"; + +export const TOKEN_KEY = "authentications"; + +export class AuthStore { + // Get all tokens from stack + get tokens() { + const t = localStorage.getItem(TOKEN_KEY); + let tokens = []; + if (t !== null) { + try { + tokens = JSON.parse(t); + } catch (e) { + console.error("Failed to parse tokens from localStorage", e); + } + } + return tokens; + } + + // Get last token from stack + get token() { + const t = this.tokens; + if (t.length) { + return t[t.length - 1]; + } + return null; + } + + // Get expires from last token + get expires() { + const t = this.token; + if (t && typeof t.expires !== "undefined") { + const expires = Number(t.expires); + if (expires && !Number.isNaN(expires)) { + return expires; + } + } + return null; + } + + // Filter out invalid tokens and return true if we find one that is valid + // hasActiveToken() { + // const t = this.tokens; + // return t.length > 0; + // } + hasActiveToken() { + const t = this.tokens; + if (!t.length) { + return false; + } + + const now = Math.round(Date.now() / 1000); + const oneMinuteBuffer = 60; + for (let i = t.length - 1; i >= 0; i--) { + const dte = getUnixTime(parseISO(t[i].expires)); + const valid = dte - oneMinuteBuffer > now; + if (valid) { + return true; + } + this.drop(); + } + return false; + } + + // Set a single token on the stack + set({ token, expires }: TokenResponse) { + localStorage.setItem(TOKEN_KEY, JSON.stringify([{ token, expires }])); + } + + // Add a token to the stack + add({ token, expires }: TokenResponse) { + const t = this.tokens; + t.push({ token, expires }); + localStorage.setItem(TOKEN_KEY, JSON.stringify(t)); + } + + // Drop a token from the stack + drop() { + const t = this.tokens; + localStorage.setItem(TOKEN_KEY, JSON.stringify(t.splice(-1, 1))); + } + + clear() { + localStorage.removeItem(TOKEN_KEY); + } +} + +export default new AuthStore(); diff --git a/frontend/src/modules/Validations.tsx b/frontend/src/modules/Validations.tsx new file mode 100644 index 00000000..c3574c01 --- /dev/null +++ b/frontend/src/modules/Validations.tsx @@ -0,0 +1,51 @@ +const validateString = (minLength = 0, maxLength = 0) => { + if (minLength <= 0 && maxLength <= 0) { + // this doesn't require translation + console.error("validateString() must be called with a min or max or both values in order to work!"); + } + + return (value: string): string | undefined => { + if (minLength && (typeof value === "undefined" || !value.length)) { + return "This is required"; + } + if (minLength && value.length < minLength) { + return `Minimum length is ${minLength} character${minLength === 1 ? "" : "s"}`; + } + if (maxLength && (typeof value === "undefined" || value.length > maxLength)) { + return `Maximum length is ${maxLength} character${maxLength === 1 ? "" : "s"}`; + } + }; +}; + +const validateNumber = (min = -1, max = -1) => { + if (min === -1 && max === -1) { + // this doesn't require translation + console.error("validateNumber() must be called with a min or max or both values in order to work!"); + } + + return (value: string): string | undefined => { + const int: number = +value; + if (min > -1 && !int) { + return "This is required"; + } + if (min > -1 && int < min) { + return `Minimum is ${min}`; + } + if (max > -1 && int > max) { + return `Maximum is ${max}`; + } + }; +}; + +const validateEmail = () => { + return (value: string): string | undefined => { + if (!value.length) { + return "This is required"; + } + if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) { + return "Invalid email address"; + } + }; +}; + +export { validateEmail, validateNumber, validateString }; diff --git a/frontend/src/pages/Access/Empty.tsx b/frontend/src/pages/Access/Empty.tsx new file mode 100644 index 00000000..06445fc6 --- /dev/null +++ b/frontend/src/pages/Access/Empty.tsx @@ -0,0 +1,20 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; +import { Button } from "src/components"; +import { intl } from "src/locale"; + +interface Props { + tableInstance: ReactTable; +} +export default function Empty({ tableInstance }: Props) { + return ( + + +
    +

    {intl.formatMessage({ id: "access.empty" })}

    +

    {intl.formatMessage({ id: "empty-subtitle" })}

    + +
    + + + ); +} diff --git a/frontend/src/pages/Access/Table.tsx b/frontend/src/pages/Access/Table.tsx new file mode 100644 index 00000000..8fbf1c2a --- /dev/null +++ b/frontend/src/pages/Access/Table.tsx @@ -0,0 +1,123 @@ +import { IconDotsVertical, IconEdit, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { AccessList } from "src/api/backend"; +import { GravatarFormatter, ValueWithDateFormatter } from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl } from "src/locale"; +import Empty from "./Empty"; + +interface Props { + data: AccessList[]; + isFetching?: boolean; +} +export default function Table({ data, isFetching }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "name", + header: intl.formatMessage({ id: "column.name" }), + cell: (info: any) => { + const value = info.getValue(); + // Bit of a hack to reuse the DomainsFormatter component + return ; + }, + }), + columnHelper.accessor((row: any) => row.items, { + id: "items", + header: intl.formatMessage({ id: "column.authorization" }), + cell: (info: any) => { + const value = info.getValue(); + return intl.formatMessage({ id: "access.auth-count" }, { count: value.length }); + }, + }), + columnHelper.accessor((row: any) => row.clients, { + id: "clients", + header: intl.formatMessage({ id: "column.access" }), + cell: (info: any) => { + const value = info.getValue(); + return intl.formatMessage({ id: "access.access-count" }, { count: value.length }); + }, + }), + columnHelper.accessor((row: any) => row.satisfyAny, { + id: "satisfyAny", + header: intl.formatMessage({ id: "column.satisfy" }), + cell: (info: any) => { + const t = info.getValue() ? "access.satisfy-any" : "access.satisfy-all"; + return intl.formatMessage({ id: t }); + }, + }), + columnHelper.accessor((row: any) => row.proxyHostCount, { + id: "proxyHostCount", + header: intl.formatMessage({ id: "proxy-hosts.title" }), + cell: (info: any) => { + return intl.formatMessage({ id: "proxy-hosts.count" }, { count: info.getValue() }); + }, + }), + columnHelper.display({ + id: "id", // todo: not needed for a display? + cell: (info: any) => { + return ( + + +
    + + {intl.formatMessage( + { + id: "access.actions-title", + }, + { id: info.row.original.id }, + )} + + + + {intl.formatMessage({ id: "action.edit" })} + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return } />; +} diff --git a/frontend/src/pages/Access/TableWrapper.tsx b/frontend/src/pages/Access/TableWrapper.tsx new file mode 100644 index 00000000..71af23d7 --- /dev/null +++ b/frontend/src/pages/Access/TableWrapper.tsx @@ -0,0 +1,52 @@ +import { IconSearch } from "@tabler/icons-react"; +import Alert from "react-bootstrap/Alert"; +import { Button, LoadingPage } from "src/components"; +import { useAccessLists } from "src/hooks"; +import { intl } from "src/locale"; +import Table from "./Table"; + +export default function TableWrapper() { + const { isFetching, isLoading, isError, error, data } = useAccessLists(["owner", "items", "clients"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + return ( +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "access.title" })}

    +
    +
    +
    +
    + + + + +
    + +
    +
    +
    +
    + + + + ); +} diff --git a/frontend/src/pages/Access/index.tsx b/frontend/src/pages/Access/index.tsx new file mode 100644 index 00000000..2708f85f --- /dev/null +++ b/frontend/src/pages/Access/index.tsx @@ -0,0 +1,12 @@ +import { HasPermission } from "src/components"; +import TableWrapper from "./TableWrapper"; + +const Access = () => { + return ( + + + + ); +}; + +export default Access; diff --git a/frontend/src/pages/AuditLog/AuditTable.tsx b/frontend/src/pages/AuditLog/AuditTable.tsx new file mode 100644 index 00000000..018078b7 --- /dev/null +++ b/frontend/src/pages/AuditLog/AuditTable.tsx @@ -0,0 +1,128 @@ +import { IconDotsVertical, IconEdit, IconPower, IconSearch, IconTrash } from "@tabler/icons-react"; +import { intl } from "src/locale"; + +export default function AuditTable() { + return ( +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "auditlog.title" })}

    +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + +
    +
    + +
    +
    +
    +
    + blog.jc21.com +
    +
    Created: 20th September 2024
    +
    +
    http://172.17.0.1:3001Let's EncryptPublic + Online + + + +
    + Proxy Host #2 + + + Edit + + + + Disable + + + +
    +
    +
    +
    +
    + ); +} diff --git a/frontend/src/pages/AuditLog/index.tsx b/frontend/src/pages/AuditLog/index.tsx new file mode 100644 index 00000000..3680ecfb --- /dev/null +++ b/frontend/src/pages/AuditLog/index.tsx @@ -0,0 +1,12 @@ +import { HasPermission } from "src/components"; +import AuditTable from "./AuditTable"; + +const AuditLog = () => { + return ( + + + + ); +}; + +export default AuditLog; diff --git a/frontend/src/pages/Certificates/CertificateTable.tsx b/frontend/src/pages/Certificates/CertificateTable.tsx new file mode 100644 index 00000000..df607d13 --- /dev/null +++ b/frontend/src/pages/Certificates/CertificateTable.tsx @@ -0,0 +1,132 @@ +import { IconDotsVertical, IconEdit, IconPower, IconSearch, IconTrash } from "@tabler/icons-react"; +import { Button } from "src/components"; +import { intl } from "src/locale"; + +export default function CertificateTable() { + return ( +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "certificates.title" })}

    +
    +
    +
    +
    + + + + +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + +
    +
    + +
    +
    +
    +
    + blog.jc21.com +
    +
    Created: 20th September 2024
    +
    +
    http://172.17.0.1:3001Let's EncryptPublic + Online + + + +
    + Proxy Host #2 + + + Edit + + + + Disable + + + +
    +
    +
    +
    +
    + ); +} diff --git a/frontend/src/pages/Certificates/index.tsx b/frontend/src/pages/Certificates/index.tsx new file mode 100644 index 00000000..b4dca48c --- /dev/null +++ b/frontend/src/pages/Certificates/index.tsx @@ -0,0 +1,12 @@ +import { HasPermission } from "src/components"; +import CertificateTable from "./CertificateTable"; + +const Certificates = () => { + return ( + + + + ); +}; + +export default Certificates; diff --git a/frontend/src/pages/Dashboard/index.tsx b/frontend/src/pages/Dashboard/index.tsx new file mode 100644 index 00000000..78e2072d --- /dev/null +++ b/frontend/src/pages/Dashboard/index.tsx @@ -0,0 +1,145 @@ +import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc } from "@tabler/icons-react"; +import { useNavigate } from "react-router-dom"; +import { useHostReport } from "src/hooks"; +import { intl } from "src/locale"; + +const Dashboard = () => { + const { data: hostReport } = useHostReport(); + const navigate = useNavigate(); + + return ( +
    +

    {intl.formatMessage({ id: "dashboard.title" })}

    + +
    +				{`Todo:
    +
    +- modal dialgs for everything
    +- Tables
    +- check mobile
    +- fix bad jwt not refreshing entire page
    +- add help docs for host types
    +- show user as disabled on user table
    +
    +More for api, then implement here:
    +- Properly implement refresh tokens
    +- don't create default user, instead use the is_setup from v3
    +  - also remove the initial user/pass env vars
    +  - update docs for this
    +- Add error message_18n for all backend errors
    +- minor: certificates expand with hosts needs to omit 'is_deleted'
    +`}
    +			
    +
    + ); +}; + +export default Dashboard; diff --git a/frontend/src/pages/Login/index.module.css b/frontend/src/pages/Login/index.module.css new file mode 100644 index 00000000..16f8477c --- /dev/null +++ b/frontend/src/pages/Login/index.module.css @@ -0,0 +1,10 @@ +.logo { + width: 200px; +} + +.helperBtns { + position: absolute; + top: 10px; + right: 10px; + z-index: 1000; +} diff --git a/frontend/src/pages/Login/index.tsx b/frontend/src/pages/Login/index.tsx new file mode 100644 index 00000000..2bc8490c --- /dev/null +++ b/frontend/src/pages/Login/index.tsx @@ -0,0 +1,126 @@ +import cn from "classnames"; +import { Field, Form, Formik } from "formik"; +import { useEffect, useRef, useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { Button, LocalePicker, Page, ThemeSwitcher } from "src/components"; +import { useAuthState } from "src/context"; +import { useHealth } from "src/hooks"; +import { intl } from "src/locale"; +import { validateEmail, validateString } from "src/modules/Validations"; +import styles from "./index.module.css"; + +export default function Login() { + const emailRef = useRef(null); + const [formErr, setFormErr] = useState(""); + const { login } = useAuthState(); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + setFormErr(""); + try { + await login(values.email, values.password); + } catch (err) { + if (err instanceof Error) { + setFormErr(err.message); + } + } + setSubmitting(false); + }; + + useEffect(() => { + // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. + emailRef.current.focus(); + }, []); + + const health = useHealth(); + + const getVersion = () => { + if (!health.data) { + return ""; + } + const v = health.data.version; + return `v${v.major}.${v.minor}.${v.revision}`; + }; + + return ( + +
    + + +
    +
    +
    + Nginx Proxy Manager +
    +
    +
    +

    {intl.formatMessage({ id: "login.title" })}

    + {formErr !== "" && {formErr}} + + {({ isSubmitting }) => ( +
    +
    + + {({ field, form }: any) => ( + + )} + +
    +
    + + {({ field, form }: any) => ( + <> + + + )} + +
    +
    + +
    +
    + )} +
    +
    +
    +
    {getVersion()}
    +
    +
    + ); +} diff --git a/frontend/src/pages/Nginx/DeadHosts/Empty.tsx b/frontend/src/pages/Nginx/DeadHosts/Empty.tsx new file mode 100644 index 00000000..48865c88 --- /dev/null +++ b/frontend/src/pages/Nginx/DeadHosts/Empty.tsx @@ -0,0 +1,20 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; +import { Button } from "src/components"; +import { intl } from "src/locale"; + +interface Props { + tableInstance: ReactTable; +} +export default function Empty({ tableInstance }: Props) { + return ( + + +
    +

    {intl.formatMessage({ id: "dead-hosts.empty" })}

    +

    {intl.formatMessage({ id: "empty-subtitle" })}

    + +
    + + + ); +} diff --git a/frontend/src/pages/Nginx/DeadHosts/Table.tsx b/frontend/src/pages/Nginx/DeadHosts/Table.tsx new file mode 100644 index 00000000..d9531956 --- /dev/null +++ b/frontend/src/pages/Nginx/DeadHosts/Table.tsx @@ -0,0 +1,109 @@ +import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { DeadHost } from "src/api/backend"; +import { CertificateFormatter, DomainsFormatter, GravatarFormatter, StatusFormatter } from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl } from "src/locale"; +import Empty from "./Empty"; + +interface Props { + data: DeadHost[]; + isFetching?: boolean; +} +export default function Table({ data, isFetching }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "domainNames", + header: intl.formatMessage({ id: "column.source" }), + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + }), + columnHelper.accessor((row: any) => row.certificate, { + id: "certificate", + header: intl.formatMessage({ id: "column.ssl" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row.enabled, { + id: "enabled", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", // todo: not needed for a display? + cell: (info: any) => { + return ( + + +
    + + {intl.formatMessage( + { + id: "dead-hosts.actions-title", + }, + { id: info.row.original.id }, + )} + + + + {intl.formatMessage({ id: "action.edit" })} + + + + {intl.formatMessage({ id: "action.disable" })} + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return } />; +} diff --git a/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx new file mode 100644 index 00000000..5badc3b1 --- /dev/null +++ b/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx @@ -0,0 +1,52 @@ +import { IconSearch } from "@tabler/icons-react"; +import Alert from "react-bootstrap/Alert"; +import { Button, LoadingPage } from "src/components"; +import { useDeadHosts } from "src/hooks"; +import { intl } from "src/locale"; +import Table from "./Table"; + +export default function TableWrapper() { + const { isFetching, isLoading, isError, error, data } = useDeadHosts(["owner", "certificate"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + return ( +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "dead-hosts.title" })}

    +
    +
    +
    +
    + + + + +
    + +
    +
    +
    +
    + + + + ); +} diff --git a/frontend/src/pages/Nginx/DeadHosts/index.tsx b/frontend/src/pages/Nginx/DeadHosts/index.tsx new file mode 100644 index 00000000..94e0ab68 --- /dev/null +++ b/frontend/src/pages/Nginx/DeadHosts/index.tsx @@ -0,0 +1,12 @@ +import { HasPermission } from "src/components"; +import TableWrapper from "./TableWrapper"; + +const DeadHosts = () => { + return ( + + + + ); +}; + +export default DeadHosts; diff --git a/frontend/src/pages/Nginx/ProxyHosts/Empty.tsx b/frontend/src/pages/Nginx/ProxyHosts/Empty.tsx new file mode 100644 index 00000000..edfb8f8e --- /dev/null +++ b/frontend/src/pages/Nginx/ProxyHosts/Empty.tsx @@ -0,0 +1,25 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; +import { Button } from "src/components"; +import { intl } from "src/locale"; + +/** + * This component should never render as there should always be 1 user minimum, + * but I'm keeping it for consistency. + */ + +interface Props { + tableInstance: ReactTable; +} +export default function Empty({ tableInstance }: Props) { + return ( + + + + ); +} diff --git a/frontend/src/pages/Nginx/ProxyHosts/Table.tsx b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx new file mode 100644 index 00000000..bf423fc1 --- /dev/null +++ b/frontend/src/pages/Nginx/ProxyHosts/Table.tsx @@ -0,0 +1,125 @@ +import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { ProxyHost } from "src/api/backend"; +import { CertificateFormatter, DomainsFormatter, GravatarFormatter, StatusFormatter } from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl } from "src/locale"; +import Empty from "./Empty"; + +interface Props { + data: ProxyHost[]; + isFetching?: boolean; +} +export default function Table({ data, isFetching }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "domainNames", + header: intl.formatMessage({ id: "column.source" }), + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + }), + columnHelper.accessor((row: any) => row, { + id: "forwardHost", + header: intl.formatMessage({ id: "column.destination" }), + cell: (info: any) => { + const value = info.getValue(); + return `${value.forwardHost}:${value.forwardPort}`; + }, + }), + columnHelper.accessor((row: any) => row.certificate, { + id: "certificate", + header: intl.formatMessage({ id: "column.ssl" }), + cell: (info: any) => { + return ; + }, + }), + // TODO: formatter for access list + columnHelper.accessor((row: any) => row.access, { + id: "accessList", + header: intl.formatMessage({ id: "column.access" }), + cell: (info: any) => { + return info.getValue(); + }, + }), + columnHelper.accessor((row: any) => row.enabled, { + id: "enabled", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", // todo: not needed for a display? + cell: (info: any) => { + return ( + + +
    + + {intl.formatMessage( + { + id: "proxy-hosts.actions-title", + }, + { id: info.row.original.id }, + )} + + + + {intl.formatMessage({ id: "action.edit" })} + + + + {intl.formatMessage({ id: "action.disable" })} + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return } />; +} diff --git a/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx new file mode 100644 index 00000000..a297200d --- /dev/null +++ b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx @@ -0,0 +1,52 @@ +import { IconSearch } from "@tabler/icons-react"; +import Alert from "react-bootstrap/Alert"; +import { Button, LoadingPage } from "src/components"; +import { useProxyHosts } from "src/hooks"; +import { intl } from "src/locale"; +import Table from "./Table"; + +export default function TableWrapper() { + const { isFetching, isLoading, isError, error, data } = useProxyHosts(["owner", "access_list", "certificate"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + return ( +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "proxy-hosts.title" })}

    +
    +
    +
    +
    + + + + +
    + +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "proxy-hosts.empty" })}

    +

    {intl.formatMessage({ id: "empty-subtitle" })}

    + +
    +
    + + + ); +} diff --git a/frontend/src/pages/Nginx/ProxyHosts/index.tsx b/frontend/src/pages/Nginx/ProxyHosts/index.tsx new file mode 100644 index 00000000..aa0e4774 --- /dev/null +++ b/frontend/src/pages/Nginx/ProxyHosts/index.tsx @@ -0,0 +1,12 @@ +import { HasPermission } from "src/components"; +import TableWrapper from "./TableWrapper"; + +const ProxyHosts = () => { + return ( + + + + ); +}; + +export default ProxyHosts; diff --git a/frontend/src/pages/Nginx/RedirectionHosts/Empty.tsx b/frontend/src/pages/Nginx/RedirectionHosts/Empty.tsx new file mode 100644 index 00000000..f0d0ee8a --- /dev/null +++ b/frontend/src/pages/Nginx/RedirectionHosts/Empty.tsx @@ -0,0 +1,20 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; +import { Button } from "src/components"; +import { intl } from "src/locale"; + +interface Props { + tableInstance: ReactTable; +} +export default function Empty({ tableInstance }: Props) { + return ( + + + + ); +} diff --git a/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx b/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx new file mode 100644 index 00000000..aec651fb --- /dev/null +++ b/frontend/src/pages/Nginx/RedirectionHosts/Table.tsx @@ -0,0 +1,130 @@ +import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { RedirectionHost } from "src/api/backend"; +import { CertificateFormatter, DomainsFormatter, GravatarFormatter, StatusFormatter } from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl } from "src/locale"; +import Empty from "./Empty"; + +interface Props { + data: RedirectionHost[]; + isFetching?: boolean; +} +export default function Table({ data, isFetching }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "domainNames", + header: intl.formatMessage({ id: "column.source" }), + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + }), + columnHelper.accessor((row: any) => row.forwardHttpCode, { + id: "forwardHttpCode", + header: intl.formatMessage({ id: "column.http-code" }), + cell: (info: any) => { + return info.getValue(); + }, + }), + columnHelper.accessor((row: any) => row.forwardScheme, { + id: "forwardScheme", + header: intl.formatMessage({ id: "column.scheme" }), + cell: (info: any) => { + return info.getValue().toUpperCase(); + }, + }), + columnHelper.accessor((row: any) => row.forwardDomainName, { + id: "forwardDomainName", + header: intl.formatMessage({ id: "column.destination" }), + cell: (info: any) => { + return info.getValue(); + }, + }), + columnHelper.accessor((row: any) => row.certificate, { + id: "certificate", + header: intl.formatMessage({ id: "column.ssl" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row.enabled, { + id: "enabled", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", // todo: not needed for a display? + cell: (info: any) => { + return ( + + +
    + + {intl.formatMessage( + { + id: "redirection-hosts.actions-title", + }, + { id: info.row.original.id }, + )} + + + + {intl.formatMessage({ id: "action.edit" })} + + + + {intl.formatMessage({ id: "action.disable" })} + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return } />; +} diff --git a/frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx new file mode 100644 index 00000000..5dccd82a --- /dev/null +++ b/frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx @@ -0,0 +1,52 @@ +import { IconSearch } from "@tabler/icons-react"; +import Alert from "react-bootstrap/Alert"; +import { Button, LoadingPage } from "src/components"; +import { useRedirectionHosts } from "src/hooks"; +import { intl } from "src/locale"; +import Table from "./Table"; + +export default function TableWrapper() { + const { isFetching, isLoading, isError, error, data } = useRedirectionHosts(["owner", "certificate"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + return ( +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "redirection-hosts.title" })}

    +
    +
    +
    +
    + + + + +
    + +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "redirection-hosts.empty" })}

    +

    {intl.formatMessage({ id: "empty-subtitle" })}

    + +
    +
    + + + ); +} diff --git a/frontend/src/pages/Nginx/RedirectionHosts/index.tsx b/frontend/src/pages/Nginx/RedirectionHosts/index.tsx new file mode 100644 index 00000000..bc636b35 --- /dev/null +++ b/frontend/src/pages/Nginx/RedirectionHosts/index.tsx @@ -0,0 +1,12 @@ +import { HasPermission } from "src/components"; +import TableWrapper from "./TableWrapper"; + +const RedirectionHosts = () => { + return ( + + + + ); +}; + +export default RedirectionHosts; diff --git a/frontend/src/pages/Nginx/Streams/Empty.tsx b/frontend/src/pages/Nginx/Streams/Empty.tsx new file mode 100644 index 00000000..5ee73015 --- /dev/null +++ b/frontend/src/pages/Nginx/Streams/Empty.tsx @@ -0,0 +1,20 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; +import { Button } from "src/components"; +import { intl } from "src/locale"; + +interface Props { + tableInstance: ReactTable; +} +export default function Empty({ tableInstance }: Props) { + return ( + + + + ); +} diff --git a/frontend/src/pages/Nginx/Streams/Table.tsx b/frontend/src/pages/Nginx/Streams/Table.tsx new file mode 100644 index 00000000..49d007d9 --- /dev/null +++ b/frontend/src/pages/Nginx/Streams/Table.tsx @@ -0,0 +1,139 @@ +import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { Stream } from "src/api/backend"; +import { CertificateFormatter, DomainsFormatter, GravatarFormatter, StatusFormatter } from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl } from "src/locale"; +import Empty from "./Empty"; + +interface Props { + data: Stream[]; + isFetching?: boolean; +} +export default function Table({ data, isFetching }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "incomingPort", + header: intl.formatMessage({ id: "column.incoming-port" }), + cell: (info: any) => { + const value = info.getValue(); + // Bit of a hack to reuse the DomainsFormatter component + return ; + }, + }), + columnHelper.accessor((row: any) => row, { + id: "forwardHttpCode", + header: intl.formatMessage({ id: "column.destination" }), + cell: (info: any) => { + const value = info.getValue(); + return `${value.forwardingHost}:${value.forwardingPort}`; + }, + }), + columnHelper.accessor((row: any) => row, { + id: "tcpForwarding", + header: intl.formatMessage({ id: "column.protocol" }), + cell: (info: any) => { + const value = info.getValue(); + return ( + <> + {value.tcpForwarding ? ( + + {intl.formatMessage({ id: "streams.tcp" })} + + ) : null} + {value.udpForwarding ? ( + + {intl.formatMessage({ id: "streams.udp" })} + + ) : null} + + ); + }, + }), + columnHelper.accessor((row: any) => row.certificate, { + id: "certificate", + header: intl.formatMessage({ id: "column.ssl" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.accessor((row: any) => row.enabled, { + id: "enabled", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", // todo: not needed for a display? + cell: (info: any) => { + return ( + + +
    + + {intl.formatMessage( + { + id: "streams.actions-title", + }, + { id: info.row.original.id }, + )} + + + + {intl.formatMessage({ id: "action.edit" })} + + + + {intl.formatMessage({ id: "action.disable" })} + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return } />; +} diff --git a/frontend/src/pages/Nginx/Streams/TableWrapper.tsx b/frontend/src/pages/Nginx/Streams/TableWrapper.tsx new file mode 100644 index 00000000..7b6f8d50 --- /dev/null +++ b/frontend/src/pages/Nginx/Streams/TableWrapper.tsx @@ -0,0 +1,52 @@ +import { IconSearch } from "@tabler/icons-react"; +import Alert from "react-bootstrap/Alert"; +import { Button, LoadingPage } from "src/components"; +import { useStreams } from "src/hooks"; +import { intl } from "src/locale"; +import Table from "./Table"; + +export default function TableWrapper() { + const { isFetching, isLoading, isError, error, data } = useStreams(["owner", "certificate"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + return ( +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "streams.title" })}

    +
    +
    +
    +
    + + + + +
    + +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "streams.empty" })}

    +

    {intl.formatMessage({ id: "empty-subtitle" })}

    + +
    +
    + + + ); +} diff --git a/frontend/src/pages/Nginx/Streams/index.tsx b/frontend/src/pages/Nginx/Streams/index.tsx new file mode 100644 index 00000000..ab3f8fc6 --- /dev/null +++ b/frontend/src/pages/Nginx/Streams/index.tsx @@ -0,0 +1,12 @@ +import { HasPermission } from "src/components"; +import TableWrapper from "./TableWrapper"; + +const Streams = () => { + return ( + + + + ); +}; + +export default Streams; diff --git a/frontend/src/pages/Settings/SettingTable.tsx b/frontend/src/pages/Settings/SettingTable.tsx new file mode 100644 index 00000000..0cba2d58 --- /dev/null +++ b/frontend/src/pages/Settings/SettingTable.tsx @@ -0,0 +1,111 @@ +import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { intl } from "src/locale"; + +export default function AuditTable() { + return ( +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "settings.title" })}

    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + +
    +
    + +
    +
    +
    +
    + blog.jc21.com +
    +
    Created: 20th September 2024
    +
    +
    http://172.17.0.1:3001Let's EncryptPublic + Online + + + +
    + Proxy Host #2 + + + Edit + + + + Disable + + + +
    +
    +
    +
    +
    + ); +} diff --git a/frontend/src/pages/Settings/index.tsx b/frontend/src/pages/Settings/index.tsx new file mode 100644 index 00000000..bb2e84e2 --- /dev/null +++ b/frontend/src/pages/Settings/index.tsx @@ -0,0 +1,12 @@ +import { HasPermission } from "src/components"; +import SettingTable from "./SettingTable"; + +const Settings = () => { + return ( + + + + ); +}; + +export default Settings; diff --git a/frontend/src/pages/Users/Empty.tsx b/frontend/src/pages/Users/Empty.tsx new file mode 100644 index 00000000..162e7094 --- /dev/null +++ b/frontend/src/pages/Users/Empty.tsx @@ -0,0 +1,20 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; +import { Button } from "src/components"; +import { intl } from "src/locale"; + +interface Props { + tableInstance: ReactTable; +} +export default function Empty({ tableInstance }: Props) { + return ( + + +
    +

    {intl.formatMessage({ id: "proxy-hosts.empty" })}

    +

    {intl.formatMessage({ id: "empty-subtitle" })}

    + +
    + + + ); +} diff --git a/frontend/src/pages/Users/Table.tsx b/frontend/src/pages/Users/Table.tsx new file mode 100644 index 00000000..6f09892d --- /dev/null +++ b/frontend/src/pages/Users/Table.tsx @@ -0,0 +1,128 @@ +import { IconDotsVertical, IconEdit, IconLock, IconShield, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { User } from "src/api/backend"; +import { GravatarFormatter, ValueWithDateFormatter } from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl } from "src/locale"; +import Empty from "./Empty"; + +interface Props { + data: User[]; + isFetching?: boolean; + currentUserId?: number; + onEditUser?: (id: number) => void; +} +export default function Table({ data, isFetching, currentUserId, onEditUser }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row, { + id: "avatar", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "name", + header: intl.formatMessage({ id: "column.name" }), + cell: (info: any) => { + const value = info.getValue(); + // Hack to reuse domains formatter + return ; + }, + }), + columnHelper.accessor((row: any) => row.email, { + id: "email", + header: intl.formatMessage({ id: "column.email" }), + cell: (info: any) => { + return info.getValue(); + }, + }), + // TODO: formatter for roles + columnHelper.accessor((row: any) => row.roles, { + id: "roles", + header: intl.formatMessage({ id: "column.roles" }), + cell: (info: any) => { + return JSON.stringify(info.getValue()); + }, + }), + columnHelper.display({ + id: "id", // todo: not needed for a display? + cell: (info: any) => { + return ( + + +
    + + {intl.formatMessage( + { + id: "users.actions-title", + }, + { id: info.row.original.id }, + )} + + { + e.preventDefault(); + onEditUser?.(info.row.original.id); + }} + > + + {intl.formatMessage({ id: "user.edit" })} + + + + {intl.formatMessage({ id: "action.permissions" })} + + + + {intl.formatMessage({ id: "user.change-password" })} + + {currentUserId !== info.row.original.id ? ( + <> + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper, currentUserId, onEditUser], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return } />; +} diff --git a/frontend/src/pages/Users/TableWrapper.tsx b/frontend/src/pages/Users/TableWrapper.tsx new file mode 100644 index 00000000..4ec62e1e --- /dev/null +++ b/frontend/src/pages/Users/TableWrapper.tsx @@ -0,0 +1,62 @@ +import { IconSearch } from "@tabler/icons-react"; +import { useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { Button, LoadingPage } from "src/components"; +import { useUser, useUsers } from "src/hooks"; +import { intl } from "src/locale"; +import { UserModal } from "src/modals"; +import Table from "./Table"; + +export default function TableWrapper() { + const [editUserId, setEditUserId] = useState(0); + const { isFetching, isLoading, isError, error, data } = useUsers(["permissions"]); + const { data: currentUser } = useUser("me"); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + return ( +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "users.title" })}

    +
    +
    +
    +
    + + + + +
    + +
    +
    +
    +
    + setEditUserId(id)} + /> + {editUserId ? setEditUserId(0)} /> : null} + + + ); +} diff --git a/frontend/src/pages/Users/index.tsx b/frontend/src/pages/Users/index.tsx new file mode 100644 index 00000000..64e8e6d7 --- /dev/null +++ b/frontend/src/pages/Users/index.tsx @@ -0,0 +1,12 @@ +import { HasPermission } from "src/components"; +import TableWrapper from "./TableWrapper"; + +const Users = () => { + return ( + + + + ); +}; + +export default Users; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..fb86d45b --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,43 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "skipLibCheck": true, + "baseUrl": ".", + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "src/*": [ + "./src/*" + ], + "test/*": [ + "./test/*" + ] + } + }, + "include": [ + "src", + "test" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 00000000..eca66688 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 00000000..d2bded43 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,44 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; +import checker from "vite-plugin-checker"; +import tsconfigPaths from "vite-tsconfig-paths"; +import "vitest/config"; +import { execFile } from "node:child_process"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + { + name: "trigger-script-on-reload", + configureServer(server) { + server.watcher.on("change", (file) => { + if (file.includes("locale/src")) { + console.log(`File changed: ${file}, running locale-compile script...`); + execFile("yarn", ["locale-compile"], (error, stdout, _stderr) => { + if (error) { + throw error; + } + console.log(stdout); + }); + } + }); + }, + }, + react(), + checker({ + // e.g. use TypeScript check + typescript: true, + }), + tsconfigPaths(), + ], + server: { + host: true, + port: 5173, + strictPort: true, + allowedHosts: true, + }, + test: { + environment: "happy-dom", + setupFiles: ["./vitest-setup.js"], + }, +}); diff --git a/frontend/vitest-setup.js b/frontend/vitest-setup.js new file mode 100644 index 00000000..f149f27a --- /dev/null +++ b/frontend/vitest-setup.js @@ -0,0 +1 @@ +import "@testing-library/jest-dom/vitest"; diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js deleted file mode 100644 index 05350a47..00000000 --- a/frontend/webpack.config.js +++ /dev/null @@ -1,144 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const Visualizer = require('webpack-visualizer-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const PACKAGE = require('./package.json'); - -module.exports = { - entry: { - main: './js/index.js', - login: './js/login.js' - }, - output: { - path: path.resolve(__dirname, 'dist'), - filename: `js/[name].bundle.js?v=${PACKAGE.version}`, - chunkFilename: `js/[name].bundle.[id].js?v=${PACKAGE.version}`, - publicPath: '/' - }, - resolve: { - alias: { - 'tabler-core': 'tabler-ui/dist/assets/js/core', - 'bootstrap': 'tabler-ui/dist/assets/js/vendors/bootstrap.bundle.min', - 'sparkline': 'tabler-ui/dist/assets/js/vendors/jquery.sparkline.min', - 'selectize': 'tabler-ui/dist/assets/js/vendors/selectize.min', - 'tablesorter': 'tabler-ui/dist/assets/js/vendors/jquery.tablesorter.min', - 'vector-map': 'tabler-ui/dist/assets/js/vendors/jquery-jvectormap-2.0.3.min', - 'vector-map-de': 'tabler-ui/dist/assets/js/vendors/jquery-jvectormap-de-merc', - 'vector-map-world': 'tabler-ui/dist/assets/js/vendors/jquery-jvectormap-world-mill', - 'circle-progress': 'tabler-ui/dist/assets/js/vendors/circle-progress.min', - 'c3': 'tabler-ui/dist/assets/js/vendors/chart.bundle.min' - } - }, - module: { - rules: [ - // Shims for tabler-ui - { - test: /assets\/js\/core/, - loader: 'imports-loader?bootstrap' - }, - { - test: /jquery-jvectormap-de-merc/, - loader: 'imports-loader?vector-map' - }, - { - test: /jquery-jvectormap-world-mill/, - loader: 'imports-loader?vector-map' - }, - - // other: - { - type: 'javascript/auto', // <= Set the module.type explicitly - test: /\bmessages\.json$/, - loader: 'messageformat-loader', - options: { - biDiSupport: false, - disablePluralKeyChecks: false, - formatters: null, - intlSupport: false, - locale: ['en'], - strictNumberSign: false - } - }, - { - test: /\.js$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader' - } - }, - { - test: /\.ejs$/, - loader: 'ejs-loader' - }, - { - test: /\.scss$/, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - 'sass-loader' - ] - }, - { - test: /.*tabler.*\.(jpe?g|gif|png|svg|eot|woff|ttf)$/, - use: [ - { - loader: 'file-loader', - options: { - outputPath: 'assets/tabler-ui/' - } - } - ] - }, - { - test: /source-sans-pro.*\.(woff(2)?)(\?v=\d+\.\d+\.\d+)?$/, - use: [ - { - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'assets/' - } - } - ] - } - ] - }, - plugins: [ - new webpack.ProvidePlugin({ - $: 'jquery', - jQuery: 'jquery', - _: 'underscore' - }), - new HtmlWebpackPlugin({ - template: '!!ejs-webpack-loader!html/index.ejs', - filename: 'index.html', - inject: false, - templateParameters: { - version: PACKAGE.version - } - }), - new HtmlWebpackPlugin({ - template: '!!ejs-webpack-loader!html/login.ejs', - filename: 'login.html', - inject: false, - templateParameters: { - version: PACKAGE.version - } - }), - new MiniCssExtractPlugin({ - filename: 'css/[name].css', - chunkFilename: 'css/[id].css' - }), - new Visualizer({ - filename: '../webpack_stats.html' - }), - new CopyWebpackPlugin([{ - from: 'app-images', - to: 'images', - toType: 'dir', - context: '/app/frontend' - }]) - ] -}; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index d1522dbe..0c9abba8 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2,305 +2,590 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" - integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== +"@adobe/css-tools@^4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.4.tgz#2856c55443d3d461693f32d2b96fb6ea92e1ffa9" + integrity sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg== + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== dependencies: - "@babel/highlight" "^7.10.4" - -"@babel/code-frame@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== - dependencies: - "@babel/highlight" "^7.22.13" - chalk "^2.4.2" - -"@babel/core@^7.9.0": - version "7.11.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.1.tgz#2c55b604e73a40dc21b0e52650b11c65cf276643" - integrity sha512-XqF7F6FWQdKGGWAzGELL+aCO1p+lRY5Tj5/tbT3St1G8NaH70jhhDIKknIZaDans0OQBG5wRAldROLHSt44BgQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.11.0" - "@babel/helper-module-transforms" "^7.11.0" - "@babel/helpers" "^7.10.4" - "@babel/parser" "^7.11.1" - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.11.0" - "@babel/types" "^7.11.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.19" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/generator@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.0.tgz#4b90c78d8c12825024568cbe83ee6c9af193585c" - integrity sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ== - dependencies: - "@babel/types" "^7.11.0" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" - integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== - dependencies: - "@babel/types" "^7.23.0" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-member-expression-to-functions@^7.10.4": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" - integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q== - dependencies: - "@babel/types" "^7.11.0" - -"@babel/helper-module-imports@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" - integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-module-transforms@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" - integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg== - dependencies: - "@babel/helper-module-imports" "^7.10.4" - "@babel/helper-replace-supers" "^7.10.4" - "@babel/helper-simple-access" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/template" "^7.10.4" - "@babel/types" "^7.11.0" - lodash "^4.17.19" - -"@babel/helper-optimise-call-expression@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" - integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-replace-supers@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" - integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.10.4" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/helper-simple-access@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" - integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== - dependencies: - "@babel/template" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/helper-split-export-declaration@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" - integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== - dependencies: - "@babel/types" "^7.11.0" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - -"@babel/helper-validator-identifier@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" - integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helpers@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" - integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== - dependencies: - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/highlight@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" - integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== - dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.27.1" js-tokens "^4.0.0" + picocolors "^1.1.1" -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== +"@babel/compat-data@^7.27.2": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" + integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== + +"@babel/core@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" + integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.10.4", "@babel/parser@^7.11.1": - version "7.11.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.2.tgz#0882ab8a455df3065ea2dcb4c753b2460a24bead" - integrity sha512-Vuj/+7vLo6l1Vi7uuO+1ngCDNeVmNbTngcJFKCR/oEtz8tKz0CJxZEGmPt9KcIloZhOZ3Zit6xbpXT2MDlS9Vw== - -"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== - -"@babel/template@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" - integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/parser" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/template@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/traverse@^7.10.4", "@babel/traverse@^7.11.0": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" - integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.0" - "@babel/types" "^7.23.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.4" + "@babel/types" "^7.28.4" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" debug "^4.1.0" - globals "^11.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" -"@babel/types@^7.10.4", "@babel/types@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" - integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== +"@babel/generator@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - lodash "^4.17.19" - to-fast-properties "^2.0.0" + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" -"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" - integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@babel/helper-plugin-utils@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" + integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== + dependencies: + "@babel/types" "^7.28.2" + +"@babel/parser@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + +"@babel/plugin-transform-react-jsx-self@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz#af678d8506acf52c577cac73ff7fe6615c85fc92" + integrity sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-source@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz#dcfe2c24094bb757bf73960374e7c55e434f19f0" + integrity sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/runtime@^7.12.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" + integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== + +"@babel/runtime@^7.12.5", "@babel/runtime@^7.24.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.3.tgz#75c5034b55ba868121668be5d5bb31cc64e6e61a" + integrity sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA== + +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.3.tgz#6911a10795d2cce43ec6a28cffc440cca2593434" + integrity sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.3" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.2" + debug "^4.3.1" + +"@babel/traverse@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.28.2": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" + integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@babel/types@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@biomejs/biome@^2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.2.4.tgz#184e4b83f89bd0d4151682a5aa3840df37748e17" + integrity sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg== + optionalDependencies: + "@biomejs/cli-darwin-arm64" "2.2.4" + "@biomejs/cli-darwin-x64" "2.2.4" + "@biomejs/cli-linux-arm64" "2.2.4" + "@biomejs/cli-linux-arm64-musl" "2.2.4" + "@biomejs/cli-linux-x64" "2.2.4" + "@biomejs/cli-linux-x64-musl" "2.2.4" + "@biomejs/cli-win32-arm64" "2.2.4" + "@biomejs/cli-win32-x64" "2.2.4" + +"@biomejs/cli-darwin-arm64@2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.4.tgz#9b50620c93501e370b7e6d5a8445f117f9946a0c" + integrity sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA== + +"@biomejs/cli-darwin-x64@2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.4.tgz#343620c884fc8141155d114430e80e4eacfddc9e" + integrity sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg== + +"@biomejs/cli-linux-arm64-musl@2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.4.tgz#cabcdadce2bc88b697f4063374224266c6f8b6e5" + integrity sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ== + +"@biomejs/cli-linux-arm64@2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.4.tgz#55620f8f088145e62e1158eb85c568554d0c8673" + integrity sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw== + +"@biomejs/cli-linux-x64-musl@2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.4.tgz#6bfaea72505afdbda66a66c998d2d169a8b55f90" + integrity sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg== + +"@biomejs/cli-linux-x64@2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.4.tgz#8c1ed61dcafb8a5939346c714ec122651f57e1db" + integrity sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ== + +"@biomejs/cli-win32-arm64@2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.4.tgz#b2528f6c436e753d6083d7779f0662e08786cedb" + integrity sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ== + +"@biomejs/cli-win32-x64@2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.4.tgz#c8e21413120fe073fa49b78fdd987022941ff66f" + integrity sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg== + +"@emotion/babel-plugin@^11.13.5": + version "11.13.5" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz#eab8d65dbded74e0ecfd28dc218e75607c4e7bc0" + integrity sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/serialize" "^1.3.3" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@^11.14.0", "@emotion/cache@^11.4.0": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.14.0.tgz#ee44b26986eeb93c8be82bb92f1f7a9b21b2ed76" + integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + stylis "4.2.0" + +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== + +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + +"@emotion/react@^11.8.1": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.14.0.tgz#cfaae35ebc67dd9ef4ea2e9acc6cd29e157dd05d" + integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.13.5" + "@emotion/cache" "^11.14.0" + "@emotion/serialize" "^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.3.tgz#d291531005f17d704d0463a032fe679f376509e8" + integrity sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA== + dependencies: + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/unitless" "^0.10.0" + "@emotion/utils" "^1.4.2" + csstype "^3.0.2" + +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== + +"@emotion/unitless@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745" + integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== + +"@emotion/use-insertion-effect-with-fallbacks@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz#8a8cb77b590e09affb960f4ff1e9a89e532738bf" + integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== + +"@emotion/utils@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.2.tgz#6df6c45881fcb1c412d6688a311a98b7f59c1b52" + integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== + +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== + +"@esbuild/aix-ppc64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz#bef96351f16520055c947aba28802eede3c9e9a9" + integrity sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA== + +"@esbuild/android-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz#d2e70be7d51a529425422091e0dcb90374c1546c" + integrity sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg== + +"@esbuild/android-arm@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.9.tgz#d2a753fe2a4c73b79437d0ba1480e2d760097419" + integrity sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ== + +"@esbuild/android-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.9.tgz#5278836e3c7ae75761626962f902a0d55352e683" + integrity sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw== + +"@esbuild/darwin-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz#f1513eaf9ec8fa15dcaf4c341b0f005d3e8b47ae" + integrity sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg== + +"@esbuild/darwin-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz#e27dbc3b507b3a1cea3b9280a04b8b6b725f82be" + integrity sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ== + +"@esbuild/freebsd-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz#364e3e5b7a1fd45d92be08c6cc5d890ca75908ca" + integrity sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q== + +"@esbuild/freebsd-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz#7c869b45faeb3df668e19ace07335a0711ec56ab" + integrity sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg== + +"@esbuild/linux-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz#48d42861758c940b61abea43ba9a29b186d6cb8b" + integrity sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw== + +"@esbuild/linux-arm@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz#6ce4b9cabf148274101701d112b89dc67cc52f37" + integrity sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw== + +"@esbuild/linux-ia32@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz#207e54899b79cac9c26c323fc1caa32e3143f1c4" + integrity sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A== + +"@esbuild/linux-loong64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz#0ba48a127159a8f6abb5827f21198b999ffd1fc0" + integrity sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ== + +"@esbuild/linux-mips64el@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz#a4d4cc693d185f66a6afde94f772b38ce5d64eb5" + integrity sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA== + +"@esbuild/linux-ppc64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz#0f5805c1c6d6435a1dafdc043cb07a19050357db" + integrity sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w== + +"@esbuild/linux-riscv64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz#6776edece0f8fca79f3386398b5183ff2a827547" + integrity sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg== + +"@esbuild/linux-s390x@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz#3f6f29ef036938447c2218d309dc875225861830" + integrity sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA== + +"@esbuild/linux-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz#831fe0b0e1a80a8b8391224ea2377d5520e1527f" + integrity sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg== + +"@esbuild/netbsd-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz#06f99d7eebe035fbbe43de01c9d7e98d2a0aa548" + integrity sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q== + +"@esbuild/netbsd-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz#db99858e6bed6e73911f92a88e4edd3a8c429a52" + integrity sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g== + +"@esbuild/openbsd-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz#afb886c867e36f9d86bb21e878e1185f5d5a0935" + integrity sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ== + +"@esbuild/openbsd-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz#30855c9f8381fac6a0ef5b5f31ac6e7108a66ecf" + integrity sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA== + +"@esbuild/openharmony-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz#2f2144af31e67adc2a8e3705c20c2bd97bd88314" + integrity sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg== + +"@esbuild/sunos-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz#69b99a9b5bd226c9eb9c6a73f990fddd497d732e" + integrity sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw== + +"@esbuild/win32-arm64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz#d789330a712af916c88325f4ffe465f885719c6b" + integrity sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ== + +"@esbuild/win32-ia32@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz#52fc735406bd49688253e74e4e837ac2ba0789e3" + integrity sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww== + +"@esbuild/win32-x64@0.25.9": + version "0.25.9" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz#585624dc829cfb6e7c0aa6c3ca7d7e6daa87e34f" + integrity sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ== + +"@floating-ui/core@^1.7.3": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.3.tgz#462d722f001e23e46d86fd2bd0d21b7693ccb8b7" + integrity sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w== + dependencies: + "@floating-ui/utils" "^0.2.10" + +"@floating-ui/dom@^1.0.1": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.4.tgz#ee667549998745c9c3e3e84683b909c31d6c9a77" + integrity sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA== + dependencies: + "@floating-ui/core" "^1.7.3" + "@floating-ui/utils" "^0.2.10" + +"@floating-ui/utils@^0.2.10": + version "0.2.10" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c" + integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== + +"@formatjs/cli@^6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.7.2.tgz#8d1a3225dee98cadbc5331b1971f1b79dead4253" + integrity sha512-714/ifbtq7CmOtcLVGjYZp5EX0vKclQt3XZ+4Oiz0lhrHWU1ObFTtX9sLS9dADGta1pgDtS4+A3YpjWMY26Spg== + +"@formatjs/ecma402-abstract@2.3.4": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz#e90c5a846ba2b33d92bc400fdd709da588280fbc" + integrity sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA== + dependencies: + "@formatjs/fast-memoize" "2.2.7" + "@formatjs/intl-localematcher" "0.6.1" + decimal.js "^10.4.3" + tslib "^2.8.0" + +"@formatjs/fast-memoize@2.2.7": + version "2.2.7" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz#707f9ddaeb522a32f6715bb7950b0831f4cc7b15" + integrity sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ== + dependencies: + tslib "^2.8.0" + +"@formatjs/icu-messageformat-parser@2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz#85aea211bea40aa81ee1d44ac7accc3cf5500a73" + integrity sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA== + dependencies: + "@formatjs/ecma402-abstract" "2.3.4" + "@formatjs/icu-skeleton-parser" "1.8.14" + tslib "^2.8.0" + +"@formatjs/icu-skeleton-parser@1.8.14": + version "1.8.14" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz#b9581d00363908efb29817fdffc32b79f41dabe5" + integrity sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ== + dependencies: + "@formatjs/ecma402-abstract" "2.3.4" + tslib "^2.8.0" + +"@formatjs/intl-localematcher@0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz#25dc30675320bf65a9d7f73876fc1e4064c0e299" + integrity sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg== + dependencies: + tslib "^2.8.0" + +"@formatjs/intl@3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-3.1.6.tgz#4c7fec6f082628cfa80871fbe7f9bc2644300e3b" + integrity sha512-tDkXnA4qpIFcDWac8CyVJq6oW8DR7W44QDUBsfXWIIJD/FYYen0QoH46W7XsVMFfPOVKkvbufjboZrrWbEfmww== + dependencies: + "@formatjs/ecma402-abstract" "2.3.4" + "@formatjs/fast-memoize" "2.2.7" + "@formatjs/icu-messageformat-parser" "2.11.2" + intl-messageformat "10.7.16" + tslib "^2.8.0" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.19" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" - integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.30" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99" + integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@nodelib/fs.scandir@2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" - integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== - dependencies: - "@nodelib/fs.stat" "2.0.3" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" - integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" - integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== - dependencies: - "@nodelib/fs.scandir" "2.1.3" - fastq "^1.6.0" - "@parcel/watcher-android-arm64@2.5.1": version "2.5.1" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" @@ -390,1184 +675,561 @@ "@parcel/watcher-win32-ia32" "2.5.1" "@parcel/watcher-win32-x64" "2.5.1" -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - -"@types/anymatch@*": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" - integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== - -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== - -"@types/html-minifier-terser@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880" - integrity sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA== - -"@types/json-schema@^7.0.4": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== - -"@types/json-schema@^7.0.8": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/node@*": - version "14.0.27" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" - integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g== - -"@types/source-list-map@*": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" - integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== - -"@types/tapable@*", "@types/tapable@^1.0.5": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" - integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA== - -"@types/uglify-js@*": - version "3.9.3" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.3.tgz#d94ed608e295bc5424c9600e6b8565407b6b4b6b" - integrity sha512-KswB5C7Kwduwjj04Ykz+AjvPcfgv/37Za24O2EDzYNbwyzOo8+ydtvzUfZ5UMguiVu29Gx44l1A6VsPPcmYu9w== - dependencies: - source-map "^0.6.1" - -"@types/webpack-sources@*": - version "1.4.2" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-1.4.2.tgz#5d3d4dea04008a779a90135ff96fb5c0c9e6292c" - integrity sha512-77T++JyKow4BQB/m9O96n9d/UUHWLQHlcqXb9Vsf4F1+wKNrrlWNFPDLKNT92RJnCSL6CieTc+NDXtCVZswdTw== - dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.7.3" - -"@types/webpack@^4.41.8": - version "4.41.21" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.21.tgz#cc685b332c33f153bb2f5fc1fa3ac8adeb592dee" - integrity sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA== - dependencies: - "@types/anymatch" "*" - "@types/node" "*" - "@types/tapable" "*" - "@types/uglify-js" "*" - "@types/webpack-sources" "*" - source-map "^0.6.0" - -"@webassemblyjs/ast@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" - integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== - dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - -"@webassemblyjs/floating-point-hex-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" - integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== - -"@webassemblyjs/helper-api-error@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" - integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== - -"@webassemblyjs/helper-buffer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" - integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== - -"@webassemblyjs/helper-code-frame@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" - integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== - dependencies: - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/helper-fsm@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" - integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== - -"@webassemblyjs/helper-module-context@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" - integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== - dependencies: - "@webassemblyjs/ast" "1.9.0" - -"@webassemblyjs/helper-wasm-bytecode@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" - integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== - -"@webassemblyjs/helper-wasm-section@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" - integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - -"@webassemblyjs/ieee754@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" - integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" - integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" - integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== - -"@webassemblyjs/wasm-edit@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" - integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/helper-wasm-section" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-opt" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/wasm-gen@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" - integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wasm-opt@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" - integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - -"@webassemblyjs/wasm-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" - integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wast-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" - integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/floating-point-hex-parser" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-code-frame" "1.9.0" - "@webassemblyjs/helper-fsm" "1.9.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" - integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -acorn-jsx@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" - integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== - -acorn-node@^1.2.0: - version "1.8.2" - resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" - integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== - dependencies: - acorn "^7.0.0" - acorn-walk "^7.0.0" - xtend "^4.0.2" - -acorn-walk@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn@^5.2.1: - version "5.7.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" - integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== - -acorn@^6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" - integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== - -acorn@^7.0.0, acorn@^7.1.1: - version "7.4.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" - integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== - -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2: - version "6.12.3" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" - integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - integrity sha1-DNkKVhCT810KmSVsIrcGlDP60Rc= - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= - -ansi-align@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" - integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== - dependencies: - string-width "^3.0.0" - -ansi-colors@^3.0.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" - integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== - -ansi-escapes@^4.2.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== - dependencies: - type-fest "^0.11.0" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== - dependencies: - "@types/color-name" "^1.1.1" - color-convert "^2.0.1" - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= - dependencies: - array-uniq "^1.0.1" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= - -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -asn1.js@^5.2.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" - integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - safer-buffer "^2.1.0" - -assert@^1.1.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -ast-types@0.9.6: - version "0.9.6" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" - integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk= - -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== - -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -async@~0.2.6: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E= - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -babel-code-frame@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-core@^6.26.0, babel-core@^6.26.3: - version "6.26.3" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" - integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.1" - debug "^2.6.9" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.8" - slash "^1.0.0" - source-map "^0.5.7" - -babel-generator@^6.26.0: - version "6.26.1" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" - integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== - dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.7" - trim-right "^1.0.1" - -babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" - integrity sha1-zORReto1b0IgvK6KAsKzRvmlZmQ= - dependencies: - babel-helper-explode-assignable-expression "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-call-delegate@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" - integrity sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340= - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-define-map@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" - integrity sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8= - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-explode-assignable-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" - integrity sha1-8luCz33BBDPFX3BZLVdGQArCLKo= - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" - integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk= - dependencies: - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-get-function-arity@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" - integrity sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-hoist-variables@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" - integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-optimise-call-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" - integrity sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-regex@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" - integrity sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI= - dependencies: - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-remap-async-to-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" - integrity sha1-XsWBgnrXI/7N04HxySg5BnbkVRs= - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-replace-supers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" - integrity sha1-v22/5Dk40XNpohPKiov3S2qQqxo= - dependencies: - babel-helper-optimise-call-expression "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helpers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" - integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-loader@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" - integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw== - dependencies: - find-cache-dir "^2.1.0" - loader-utils "^1.4.0" - mkdirp "^0.5.3" - pify "^4.0.1" - schema-utils "^2.6.5" - -babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-check-es2015-constants@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" - integrity sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-syntax-async-functions@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" - integrity sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU= - -babel-plugin-syntax-exponentiation-operator@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" - integrity sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4= - -babel-plugin-syntax-trailing-function-commas@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" - integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM= - -babel-plugin-transform-async-to-generator@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" - integrity sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E= - dependencies: - babel-helper-remap-async-to-generator "^6.24.1" - babel-plugin-syntax-async-functions "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-arrow-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" - integrity sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" - integrity sha1-u8UbSflk1wy42OC5ToICRs46YUE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoping@^6.23.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" - integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8= - dependencies: - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-plugin-transform-es2015-classes@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" - integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs= - dependencies: - babel-helper-define-map "^6.24.1" - babel-helper-function-name "^6.24.1" - babel-helper-optimise-call-expression "^6.24.1" - babel-helper-replace-supers "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-computed-properties@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" - integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM= - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-destructuring@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" - integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-duplicate-keys@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" - integrity sha1-c+s9MQypaePvnskcU3QabxV2Qj4= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-for-of@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" - integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-function-name@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" - integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos= - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" - integrity sha1-T1SgLWzWbPkVKAAZox0xklN3yi4= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" - integrity sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ= - dependencies: - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: - version "6.26.2" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" - integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-types "^6.26.0" - -babel-plugin-transform-es2015-modules-systemjs@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" - integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM= - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-umd@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" - integrity sha1-rJl+YoXNGO1hdq22B9YCNErThGg= - dependencies: - babel-plugin-transform-es2015-modules-amd "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-object-super@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" - integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40= - dependencies: - babel-helper-replace-supers "^6.24.1" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-parameters@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" - integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys= - dependencies: - babel-helper-call-delegate "^6.24.1" - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-shorthand-properties@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" - integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-spread@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" - integrity sha1-1taKmfia7cRTbIGlQujdnxdG+NE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-sticky-regex@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" - integrity sha1-AMHNsaynERLN8M9hJsLta0V8zbw= - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-template-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" - integrity sha1-qEs0UPfp+PH2g51taH2oS7EjbY0= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-typeof-symbol@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" - integrity sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-unicode-regex@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" - integrity sha1-04sS9C6nMj9yk4fxinxa4frrNek= - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - regexpu-core "^2.0.0" - -babel-plugin-transform-exponentiation-operator@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" - integrity sha1-KrDJx/MJj6SJB3cruBP+QejeOg4= - dependencies: - babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" - babel-plugin-syntax-exponentiation-operator "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-regenerator@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" - integrity sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8= - dependencies: - regenerator-transform "^0.10.0" - -babel-plugin-transform-strict-mode@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" - integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-preset-env@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" - integrity sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg== - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-to-generator "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.23.0" - babel-plugin-transform-es2015-classes "^6.23.0" - babel-plugin-transform-es2015-computed-properties "^6.22.0" - babel-plugin-transform-es2015-destructuring "^6.23.0" - babel-plugin-transform-es2015-duplicate-keys "^6.22.0" - babel-plugin-transform-es2015-for-of "^6.23.0" - babel-plugin-transform-es2015-function-name "^6.22.0" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.22.0" - babel-plugin-transform-es2015-modules-commonjs "^6.23.0" - babel-plugin-transform-es2015-modules-systemjs "^6.23.0" - babel-plugin-transform-es2015-modules-umd "^6.23.0" - babel-plugin-transform-es2015-object-super "^6.22.0" - babel-plugin-transform-es2015-parameters "^6.23.0" - babel-plugin-transform-es2015-shorthand-properties "^6.22.0" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.22.0" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.23.0" - babel-plugin-transform-es2015-unicode-regex "^6.22.0" - babel-plugin-transform-exponentiation-operator "^6.22.0" - babel-plugin-transform-regenerator "^6.22.0" - browserslist "^3.2.6" - invariant "^2.2.2" - semver "^5.3.0" - -babel-register@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" - integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= - dependencies: - babel-core "^6.26.0" - babel-runtime "^6.26.0" - core-js "^2.5.0" - home-or-tmp "^2.0.0" - lodash "^4.17.4" - mkdirp "^0.5.1" - source-map-support "^0.4.15" - -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -babel-template@^6.24.1, babel-template@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.24.1, babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" - -babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== - -"backbone.marionette@^4.0.0, 4.0.0-beta.1": - version "4.0.0-beta.1" - resolved "https://registry.yarnpkg.com/backbone.marionette/-/backbone.marionette-4.0.0-beta.1.tgz#793528fc5c60620200e2440e6dcd04ba16c3aab3" - integrity sha1-eTUo/FxgYgIA4kQObc0EuhbDqrM= - dependencies: - backbone.radio "^2.0.0" - -backbone.marionette@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/backbone.marionette/-/backbone.marionette-4.1.2.tgz#55de74363219f6d5c343dab5bff6aeb20fc44419" - integrity sha512-T8wWxZZnuYjylONTnWZsGsgXtdx2ZrE38pZWJI9LmPqzYK5j0T8uduapFO7OEpsW5rtdbBgwof30xhzAkbb5eQ== - dependencies: - backbone.radio "^2.0.0" - -backbone.radio@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/backbone.radio/-/backbone.radio-2.0.0.tgz#bbe8672b373e313f99f36d2fbcf583fe77d04f42" - integrity sha1-u+hnKzc+MT+Z820vvPWD/nfQT0I= - -backbone@^1.4.0: +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + +"@react-aria/ssr@^3.5.0": + version "3.9.10" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.10.tgz#7fdc09e811944ce0df1d7e713de1449abd7435e6" + integrity sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ== + dependencies: + "@swc/helpers" "^0.5.0" + +"@restart/hooks@^0.4.9": + version "0.4.16" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.16.tgz#95ae8ac1cc7e2bd4fed5e39800ff85604c6d59fb" + integrity sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w== + dependencies: + dequal "^2.0.3" + +"@restart/hooks@^0.5.0": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.5.1.tgz#6776b3859e33aea72b23b81fc47021edf17fd247" + integrity sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q== + dependencies: + dequal "^2.0.3" + +"@restart/ui@^1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@restart/ui/-/ui-1.9.4.tgz#9d61f56f2647f5ab8a33d87b278b9ce183511a26" + integrity sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA== + dependencies: + "@babel/runtime" "^7.26.0" + "@popperjs/core" "^2.11.8" + "@react-aria/ssr" "^3.5.0" + "@restart/hooks" "^0.5.0" + "@types/warning" "^3.0.3" + dequal "^2.0.3" + dom-helpers "^5.2.0" + uncontrollable "^8.0.4" + warning "^4.0.3" + +"@rolldown/pluginutils@1.0.0-beta.35": + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.35.tgz#1a477e7742b154b67519d40e4fc17485de338e7a" + integrity sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg== + +"@rollup/rollup-android-arm-eabi@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.0.tgz#939c1be9625d428d8513e4ab60d406fe8db23718" + integrity sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ== + +"@rollup/rollup-android-arm64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.0.tgz#b74005775903f7a8f4e363d2840c1dcef3776ff3" + integrity sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw== + +"@rollup/rollup-darwin-arm64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz#8c04603cdcf1ec0cd6b27152b3827e49295f2962" + integrity sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg== + +"@rollup/rollup-darwin-x64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz#19ec976f1cc663def2692cd7ffb32981f2b0b733" + integrity sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw== + +"@rollup/rollup-freebsd-arm64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.0.tgz#a96b4ad8346229f6fcbd9d57f1c53040b037c2da" + integrity sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ== + +"@rollup/rollup-freebsd-x64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.0.tgz#fa565a282bc57967ee6668607b181678bdd74e4a" + integrity sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA== + +"@rollup/rollup-linux-arm-gnueabihf@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.0.tgz#dfc88f7295e1f98d77f25296be787e8a5d6ced75" + integrity sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w== + +"@rollup/rollup-linux-arm-musleabihf@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.0.tgz#32cd70c87455ca031f0361090cf17da5a2ef66d5" + integrity sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg== + +"@rollup/rollup-linux-arm64-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.0.tgz#0e7e1fe7241e3384f6c6b4ccdbcfa8ad8c78b869" + integrity sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g== + +"@rollup/rollup-linux-arm64-musl@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.0.tgz#5d421f2f3e4a84786c4dfd9ce97e595c9b59e7f4" + integrity sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ== + +"@rollup/rollup-linux-loongarch64-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.0.tgz#a0fb5c7d0e88319e18acfd9436f19ee39354b027" + integrity sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ== + +"@rollup/rollup-linux-ppc64-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.0.tgz#a65b598af12f25210c3295da551a6e3616ea488d" + integrity sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg== + +"@rollup/rollup-linux-riscv64-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.0.tgz#10ba776214ae2857c5bf4389690dabb2fbaf7d98" + integrity sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA== + +"@rollup/rollup-linux-riscv64-musl@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.0.tgz#c2a46cbaa329d5f21e5808f5a66bb9c78cf68aac" + integrity sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ== + +"@rollup/rollup-linux-s390x-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.0.tgz#a07447be069d64462e30c66611be20c4513963ed" + integrity sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ== + +"@rollup/rollup-linux-x64-gnu@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz#8887c58bd51242754ae9c56947d6e883332dcc74" + integrity sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA== + +"@rollup/rollup-linux-x64-musl@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.0.tgz#6403fda72a2b3b9fbbeeff93d14f1c45ef9775f3" + integrity sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw== + +"@rollup/rollup-openharmony-arm64@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.0.tgz#52809afccaff47e731b965a0c16e5686be819d5f" + integrity sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q== + +"@rollup/rollup-win32-arm64-msvc@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.0.tgz#23fe00ddbb40b27a3889bc1e99e6310d97353ad5" + integrity sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg== + +"@rollup/rollup-win32-ia32-msvc@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.0.tgz#520b588076b593413d919912d69dfd5728a1f305" + integrity sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw== + +"@rollup/rollup-win32-x64-msvc@4.50.0": + version "4.50.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz#d81efe6a12060c7feddf9805e2a94c3ab0679f48" + integrity sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg== + +"@swc/helpers@^0.5.0": + version "0.5.17" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971" + integrity sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A== + dependencies: + tslib "^2.8.0" + +"@tabler/core@^1.4.0": version "1.4.0" - resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" - integrity sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ== + resolved "https://registry.yarnpkg.com/@tabler/core/-/core-1.4.0.tgz#9e011289d92cf6120655793cef6576fd67eb07f3" + integrity sha512-5BigzOlbOH9N0Is4u0rYNRCiwtnUXWO57K9zwuscygcicAa8UV9MGaS4zTgQsZEtZ9tsNANhN/YD8gCBGKYCiw== dependencies: - underscore ">=1.8.3" + "@popperjs/core" "^2.11.8" + bootstrap "5.3.7" -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base62@^1.1.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428" - integrity sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA== - -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== +"@tabler/icons-react@^3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.35.0.tgz#27f295f36b42f8dc2e7841dd651c8905d0097818" + integrity sha512-XG7t2DYf3DyHT5jxFNp5xyLVbL4hMJYJhiSdHADzAjLRYfL7AnjlRfiHDHeXxkb2N103rEIvTsBRazxXtAUz2g== dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" + "@tabler/icons" "3.35.0" -big.js@^3.1.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" - integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== +"@tabler/icons@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.35.0.tgz#6f35247e41baba2a1b0f4dff048bb1335d6c1075" + integrity sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ== -big.js@^5.2.2: +"@tanstack/query-core@5.89.0": + version "5.89.0" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.89.0.tgz#67862fa9aa036942b1906bc51385115a2bbec45a" + integrity sha512-joFV1MuPhSLsKfTzwjmPDrp8ENfZ9N23ymFu07nLfn3JCkSHy0CFgsyhHTJOmWaumC/WiNIKM0EJyduCF/Ih/Q== + +"@tanstack/query-devtools@5.87.3": + version "5.87.3" + resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.87.3.tgz#0cfff30823f837d6b9ead08f8d1b16459203fdb2" + integrity sha512-LkzxzSr2HS1ALHTgDmJH5eGAVsSQiuwz//VhFW5OqNk0OQ+Fsqba0Tsf+NzWRtXYvpgUqwQr4b2zdFZwxHcGvg== + +"@tanstack/react-query-devtools@^5.89.0": + version "5.89.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.89.0.tgz#ca9cc274cbec7c1479ab67335282a06b4566d164" + integrity sha512-Syc4UjZeIJCkXCRGyQcWwlnv89JNb98MMg/DAkFCV3rwOcknj98+nG3Nm6xLXM6ne9sK6RZeDJMPLKZUh6NUGA== + dependencies: + "@tanstack/query-devtools" "5.87.3" + +"@tanstack/react-query@^5.89.0": + version "5.89.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.89.0.tgz#ae295ced60d1aee555337a572ad4045e2af3d00a" + integrity sha512-SXbtWSTSRXyBOe80mszPxpEbaN4XPRUp/i0EfQK1uyj3KCk/c8FuPJNIRwzOVe/OU3rzxrYtiNabsAmk1l714A== + dependencies: + "@tanstack/query-core" "5.89.0" + +"@tanstack/react-table@^8.21.3": + version "8.21.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.21.3.tgz#2c38c747a5731c1a07174fda764b9c2b1fb5e91b" + integrity sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww== + dependencies: + "@tanstack/table-core" "8.21.3" + +"@tanstack/table-core@8.21.3": + version "8.21.3" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.21.3.tgz#2977727d8fc8dfa079112d9f4d4c019110f1732c" + integrity sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg== + +"@testing-library/dom@^10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.1.tgz#d444f8a889e9a46e9a3b4f3b88e0fcb3efb6cf95" + integrity sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.3.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + picocolors "1.1.1" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@^6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz#697db9424f0d21d8216f1958fa0b1b69b5f43923" + integrity sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + picocolors "^1.1.1" + redent "^3.0.0" + +"@testing-library/react@^16.3.0": + version "16.3.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.0.tgz#3a85bb9bdebf180cd76dba16454e242564d598a6" + integrity sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw== + dependencies: + "@babel/runtime" "^7.12.5" + +"@types/aria-query@^5.0.1": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== + +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/chai@^5.2.2": version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - -binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== - -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== + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.2.tgz#6f14cea18180ffc4416bc0fd12be05fdd73bdd6b" + integrity sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg== dependencies: - file-uri-to-path "1.0.0" + "@types/deep-eql" "*" -bluebird@^3.5.5: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +"@types/country-flag-icons@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/country-flag-icons/-/country-flag-icons-1.2.2.tgz#8f51089cab857f0f700feabd38b3960d006d64f2" + integrity sha512-CefEn/J336TBDp7NX8JqzlDtCBOsm8M3r1Li0gEOt0HOMHF1XemNyrx9lSHjsafcb1yYWybU0N8ZAXuyCaND0w== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== -bn.js@^5.0.0, bn.js@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" - integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== +"@types/estree@1.0.8", "@types/estree@^1.0.0": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== -boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= - -bootstrap@^4.0.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.1.tgz#f7322c7dd3e6376d430efc0c3f57e4d8005eb5b2" - integrity sha512-bxUooHBSbvefnIZfjD0LE8nfdPKrtiFy2sgrxQwUZ0UpFzpjVbVMUxaGIoo9XWT4B2LG1HX6UQg0UMOakT0prQ== - -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== +"@types/hast@^2.0.0": + version "2.3.10" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.10.tgz#5c9d9e0b304bbb8879b857225c5ebab2d81d7643" + integrity sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw== dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" + "@types/unist" "^2" -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" + "@types/unist" "*" -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.7" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz#306e3a3a73828522efa1341159da4846e7573a6c" + integrity sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g== dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" + hoist-non-react-statics "^3.3.0" -braces@^3.0.1, braces@~3.0.2: +"@types/humps@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/humps/-/humps-2.0.6.tgz#a358688fe092e40b5f50261e0a55e2fa6d68cabe" + integrity sha512-Fagm1/a/1J9gDKzGdtlPmmTN5eSw/aaTzHtj740oSfo+MODsSY2WglxMmhTdOglC8nxqUhGGQ+5HfVtBvxo3Kg== + +"@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + +"@types/node@^20.0.0": + version "20.19.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.11.tgz#728cab53092bd5f143beed7fbba7ba99de3c16c4" + integrity sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow== + dependencies: + undici-types "~6.21.0" + +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + +"@types/prismjs@^1.0.0": + version "1.26.5" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.5.tgz#72499abbb4c4ec9982446509d2f14fb8483869d6" + integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ== + +"@types/prop-types@^15.7.12": + version "15.7.15" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" + integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== + +"@types/react-dom@^19.1.9": + version "19.1.9" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.9.tgz#5ab695fce1e804184767932365ae6569c11b4b4b" + integrity sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ== + +"@types/react-table@^7.7.20": + version "7.7.20" + resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.20.tgz#2f68e70ca7a703ad8011a8da55c38482f0eb4314" + integrity sha512-ahMp4pmjVlnExxNwxyaDrFgmKxSbPwU23sGQw2gJK4EhCvnvmib2s/O/+y1dfV57dXOwpr2plfyBol+vEHbi2w== + dependencies: + "@types/react" "*" + +"@types/react-transition-group@^4.4.0", "@types/react-transition-group@^4.4.6": + version "4.4.12" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" + integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== + +"@types/react@*", "@types/react@16 || 17 || 18 || 19", "@types/react@>=16.9.11": + version "19.1.12" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.12.tgz#7bfaa76aabbb0b4fe0493c21a3a7a93d33e8937b" + integrity sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w== + dependencies: + csstype "^3.0.2" + +"@types/react@^19.1.13": + version "19.1.13" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.13.tgz#fc650ffa680d739a25a530f5d7ebe00cdd771883" + integrity sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ== + dependencies: + csstype "^3.0.2" + +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/unist@^2", "@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + +"@types/warning@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.3.tgz#d1884c8cc4a426d1ac117ca2611bf333834c6798" + integrity sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q== + +"@types/whatwg-mimetype@^3.0.2": version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + resolved "https://registry.yarnpkg.com/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz#e5e06dcd3e92d4e622ef0129637707d66c28d6a4" + integrity sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA== + +"@uiw/react-textarea-code-editor@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@uiw/react-textarea-code-editor/-/react-textarea-code-editor-3.1.1.tgz#8ca1b706a3081a51c68bc0df91c9c3cdadd9944e" + integrity sha512-AERRbp/d85vWR+UPgsB5hEgerNXuyszdmhWl2fV2H2jN63jgOobwEnjIpb76Vwy8SaGa/AdehaoJX2XZgNXtJA== dependencies: - fill-range "^7.0.1" + "@babel/runtime" "^7.18.6" + rehype "~13.0.0" + rehype-prism-plus "2.0.0" + +"@ungap/structured-clone@^1.0.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@vitejs/plugin-react@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.0.3.tgz#182ea45406d89e55b4e35c92a4a8c2c8388726c8" + integrity sha512-PFVHhosKkofGH0Yzrw1BipSedTH68BFF8ZWy1kfUpCtJcouXXY0+racG8sExw7hw0HoX36813ga5o3LTWZ4FUg== + dependencies: + "@babel/core" "^7.28.4" + "@babel/plugin-transform-react-jsx-self" "^7.27.1" + "@babel/plugin-transform-react-jsx-source" "^7.27.1" + "@rolldown/pluginutils" "1.0.0-beta.35" + "@types/babel__core" "^7.20.5" + react-refresh "^0.17.0" + +"@vitest/expect@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433" + integrity sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig== + dependencies: + "@types/chai" "^5.2.2" + "@vitest/spy" "3.2.4" + "@vitest/utils" "3.2.4" + chai "^5.2.0" + tinyrainbow "^2.0.0" + +"@vitest/mocker@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-3.2.4.tgz#4471c4efbd62db0d4fa203e65cc6b058a85cabd3" + integrity sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ== + dependencies: + "@vitest/spy" "3.2.4" + estree-walker "^3.0.3" + magic-string "^0.30.17" + +"@vitest/pretty-format@3.2.4", "@vitest/pretty-format@^3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz#3c102f79e82b204a26c7a5921bf47d534919d3b4" + integrity sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA== + dependencies: + tinyrainbow "^2.0.0" + +"@vitest/runner@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-3.2.4.tgz#5ce0274f24a971f6500f6fc166d53d8382430766" + integrity sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ== + dependencies: + "@vitest/utils" "3.2.4" + 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" + +"@vitest/spy@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-3.2.4.tgz#cc18f26f40f3f028da6620046881f4e4518c2599" + integrity sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw== + dependencies: + tinyspy "^4.0.3" + +"@vitest/utils@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-3.2.4.tgz#c0813bc42d99527fb8c5b138c7a88516bca46fea" + integrity sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA== + dependencies: + "@vitest/pretty-format" "3.2.4" + loupe "^3.1.4" + tinyrainbow "^2.0.0" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.0.tgz#2f302e7550431b1b7762705fffb52cf1ffa20447" + integrity sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg== + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +aria-query@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + +aria-query@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + 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: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bootstrap@5.3.7: + version "5.3.7" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.7.tgz#8640065036124d961d885d80b5945745e1154d90" + integrity sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw== braces@^3.0.3: version "3.0.3" @@ -1576,1424 +1238,307 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== +browserslist@^4.24.0: + version "4.25.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.4.tgz#ebdd0e1d1cf3911834bab3a6cd7b917d9babf5af" + integrity sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg== dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" + caniuse-lite "^1.0.30001737" + electron-to-chromium "^1.5.211" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" + base64-js "^1.3.1" + ieee754 "^1.2.1" -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-rsa@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" - integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== - dependencies: - bn.js "^5.0.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e" - integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg== - dependencies: - bn.js "^5.2.1" - browserify-rsa "^4.1.0" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.5.4" - inherits "^2.0.4" - parse-asn1 "^5.1.6" - readable-stream "^3.6.2" - safe-buffer "^5.2.1" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -browserslist@^3.2.6: - version "3.2.8" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" - integrity sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ== - dependencies: - caniuse-lite "^1.0.30000844" - electron-to-chromium "^1.3.47" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= - -buffer@^4.3.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= - -cacache@^12.0.2, cacache@^12.0.3: - version "12.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" - integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - -call-bound@^1.0.3, call-bound@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" +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: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@3.0.x: +caniuse-lite@^1.0.30001737: + version "1.0.30001739" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz#b34ce2d56bfc22f4352b2af0144102d623a124f4" + integrity sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA== + +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + +chai@^5.2.0: + version "5.3.3" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.3.3.tgz#dd3da955e270916a4bd3f625f4b919996ada7e06" + integrity sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw== + 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: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" - integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= - dependencies: - no-case "^2.2.0" - upper-case "^1.1.1" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== -camel-case@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" - integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== - dependencies: - pascal-case "^3.1.1" - tslib "^1.10.0" +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== -camelcase@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +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== -caniuse-lite@^1.0.30000844: - version "1.0.30001111" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001111.tgz#dd0ce822c70eb6c7c068e4a55c22e19ec1501298" - integrity sha512-xnDje2wchd/8mlJu8sXvWxOGvMgv+uT3iZ3bkIAynKOzToCssWCmkz/ZIkQBs/2pUB4uwnJKVORWQ31UkbVjOg== - -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - integrity sha1-qg0yYptu6XIgBBHL1EYckHvCt60= - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.0.0, chalk@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -chokidar@^3.2.2, chokidar@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" - integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.4.0" - optionalDependencies: - fsevents "~2.1.2" - -chokidar@^4.0.0: +chokidar@^4.0.0, chokidar@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== dependencies: readdirp "^4.0.1" -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== +classnames@^2.3.2, classnames@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== -chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== -ci-info@^2.0.0: +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.6.tgz#8fe672437d01cd6c4561af5334e0cc50ff1955f7" - integrity sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw== - dependencies: - inherits "^2.0.4" - safe-buffer "^5.2.1" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -clean-css@4.2.x, clean-css@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" - integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== - dependencies: - source-map "~0.6.0" - -cli-boxes@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" - integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -cliui@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" - integrity sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE= - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -clone-response@^1.0.2: +cookie@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" + integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== + +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== dependencies: - mimic-response "^1.0.0" + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= +country-flag-icons@^1.5.20: + version "1.5.20" + resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.5.20.tgz#a373b13f2b2899a2ef85a4b6fb6a80b8f38f9a01" + integrity sha512-ldchBFYlw2dXXGXO6q/xCYaFiiK/eg5qSctBRDzUTDvHqAdIMOVTj1sp0OWVo7g6h+RURTjlu+5BhtGwIbe9tg== + +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +date-fns@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" + integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" + ms "^2.1.3" -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" +decimal.js@^10.4.3: + version "10.6.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" + integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -commander@2.17.x: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== - -commander@^2.20.0, commander@^2.5.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - -commander@~2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== - -common-prefix@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/common-prefix/-/common-prefix-1.1.0.tgz#e3a5ea7fafaefc7eb84e760523e1afb985f90f00" - integrity sha1-46Xqf6+u/H64TnYFI+GvuYX5DwA= - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= - -commoner@^0.10.1: - version "0.10.8" - resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" - integrity sha1-NPw2cs0kOT6LtH5wyqApOBH08sU= - dependencies: - commander "^2.5.0" - detective "^4.3.1" - glob "^5.0.15" - graceful-fs "^4.1.2" - iconv-lite "^0.4.5" - mkdirp "^0.5.0" - private "^0.1.6" - q "^1.1.2" - recast "^0.11.17" - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -console-browserify@^1.1.0: +decode-named-character-reference@^1.0.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" - integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= - -convert-source-map@^1.5.1, convert-source-map@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz#25c32ae6dd5e21889549d40f676030e9514cc0ed" + integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q== dependencies: - safe-buffer "~5.1.1" + character-entities "^2.0.0" -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" +decode-uri-component@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5" + integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ== -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +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== -copy-webpack-plugin@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz#5481a03dea1123d88a988c6ff8b78247214f0b88" - integrity sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg== - dependencies: - cacache "^12.0.3" - find-cache-dir "^2.1.0" - glob-parent "^3.1.0" - globby "^7.1.1" - is-glob "^4.0.1" - loader-utils "^1.2.3" - minimatch "^3.0.4" - normalize-path "^3.0.0" - p-limit "^2.2.1" - schema-utils "^1.0.0" - serialize-javascript "^2.1.2" - webpack-log "^2.0.0" +deepmerge@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= - -core-js@^2.4.0, core-js@^2.5.0: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -create-ecdh@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" - integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== - dependencies: - bn.js "^4.1.0" - elliptic "^6.5.3" - -create-hash@^1.1.0, create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hash@~1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" - integrity sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - ripemd160 "^2.0.0" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -css-loader@^3.5.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" - integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== - dependencies: - camelcase "^5.3.1" - cssesc "^3.0.0" - icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.32" - postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.2.0" - postcss-modules-values "^3.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^2.7.0" - semver "^6.3.0" - -css-select@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" - -css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cyclist@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" - integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= - -d3@^3.5.6: - version "3.5.17" - resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" - integrity sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g= - -debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -debug@^4.0.1, debug@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -decamelize@^1.0.0, decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decode-uri-component@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= - -des.js@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" - integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= - -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= - dependencies: - repeating "^2.0.0" +dequal@^2.0.0, dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== detect-libc@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== -detective@^4.3.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" - integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig== - dependencies: - acorn "^5.2.1" - defined "^1.0.0" - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -dir-glob@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" - integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== - dependencies: - path-type "^3.0.0" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dom-converter@^0.2: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" - -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - -domelementtype@1, domelementtype@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== - -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== - dependencies: - domelementtype "1" - -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^1.5.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - -dot-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" - integrity sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA== - dependencies: - no-case "^3.0.3" - tslib "^1.10.0" - -dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== - dependencies: - is-obj "^2.0.0" - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -ejs-include-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ejs-include-regex/-/ejs-include-regex-1.0.0.tgz#e2f71575cbfd551ac800b2474c1f62a38e70093a" - integrity sha1-4vcVdcv9VRrIALJHTB9io45wCTo= - -ejs-lint@^1.0.1: +devlop@^1.0.0, devlop@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/ejs-lint/-/ejs-lint-1.1.0.tgz#f3fd7b164fde618bcefb8e5ece8f9ac206739ce0" - integrity sha512-SnOxzUtJug5C92wtFTZ+Zyb+eoqOlN+WyqDk6HDSXgY5FseDgm8MhZ/r70nkzrh0AMPpifzebep3gGILzAlQNg== + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== dependencies: - chalk "^4.0.0" - ejs "3.0.1" - ejs-include-regex "^1.0.0" - globby "^11.0.0" - read-input "^0.3.1" - rewire "^5.0.0" - syntax-error "^1.1.6" - yargs "^15.0.0" + dequal "^2.0.0" -ejs-loader@^0.3.6: - version "0.3.7" - resolved "https://registry.yarnpkg.com/ejs-loader/-/ejs-loader-0.3.7.tgz#82d3cd0a3d3f64d519332b95f9b8a7897c9fcaf4" - integrity sha512-K1HBDWXQZkcIAnP5h65kWsD7o7NABvHswOH49rVHX7POGaTM2kYQfkFZVn4ZQeiRnzqbtf07LxSitOVRdR98GA== +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + +dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== dependencies: - loader-utils "^0.2.7" - lodash "^4.17.15" + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" -ejs-webpack-loader@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ejs-webpack-loader/-/ejs-webpack-loader-2.2.2.tgz#0536acdd79ba4cdbefb4248fcbe7441e264955d7" - integrity sha512-fuZ5djtVnvoMv4xlyQs3sh9JfIh167iPg7Q1ABFdQIbPHqRgeRWQCvodGybQhiRRCUIeqH9HPtfB8hJimPSPbA== +electron-to-chromium@^1.5.211: + version "1.5.213" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz#f434187f227fb7e67bfcf8243b959cf3ce14013e" + integrity sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q== + +entities@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== + +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== dependencies: - ejs "^2.0.0" - html-minifier "^3" - loader-utils "^0.2.7" - merge "^1.2.0" - uglify-js "~2.6.1" + is-arrayish "^0.2.1" -ejs@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.0.1.tgz#30c8f6ee9948502cc32e85c37a3f8b39b5a614a5" - integrity sha512-cuIMtJwxvzumSAkqaaoGY/L6Fc/t6YvoP9/VIaK0V/CyqKLEQ8sqODmYfy/cjXEdZ9+OOL8TecbJu+1RsofGDw== +es-module-lexer@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== -ejs@^2.0.0: - version "2.7.4" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" - integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== +esbuild@^0.25.0: + version "0.25.9" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.9.tgz#15ab8e39ae6cdc64c24ff8a2c0aef5b3fd9fa976" + integrity sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.25.9" + "@esbuild/android-arm" "0.25.9" + "@esbuild/android-arm64" "0.25.9" + "@esbuild/android-x64" "0.25.9" + "@esbuild/darwin-arm64" "0.25.9" + "@esbuild/darwin-x64" "0.25.9" + "@esbuild/freebsd-arm64" "0.25.9" + "@esbuild/freebsd-x64" "0.25.9" + "@esbuild/linux-arm" "0.25.9" + "@esbuild/linux-arm64" "0.25.9" + "@esbuild/linux-ia32" "0.25.9" + "@esbuild/linux-loong64" "0.25.9" + "@esbuild/linux-mips64el" "0.25.9" + "@esbuild/linux-ppc64" "0.25.9" + "@esbuild/linux-riscv64" "0.25.9" + "@esbuild/linux-s390x" "0.25.9" + "@esbuild/linux-x64" "0.25.9" + "@esbuild/netbsd-arm64" "0.25.9" + "@esbuild/netbsd-x64" "0.25.9" + "@esbuild/openbsd-arm64" "0.25.9" + "@esbuild/openbsd-x64" "0.25.9" + "@esbuild/openharmony-arm64" "0.25.9" + "@esbuild/sunos-x64" "0.25.9" + "@esbuild/win32-arm64" "0.25.9" + "@esbuild/win32-ia32" "0.25.9" + "@esbuild/win32-x64" "0.25.9" -electron-to-chromium@^1.3.47: - version "1.3.522" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.522.tgz#4a6485ad187ffd31913bba0747d0e36405f405d6" - integrity sha512-67V62Z4CFOiAtox+o+tosGfVk0QX4DJgH609tjT8QymbJZVAI/jWnAthnr8c5hnRNziIRwkc9EMQYejiVz3/9Q== - -elliptic@^6.5.3, elliptic@^6.5.4: - version "6.6.1" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" - integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@^4.1.1, enhanced-resolve@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" - integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -entities@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== - -entities@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== - -envify@^3.0.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/envify/-/envify-3.4.1.tgz#d7122329e8df1688ba771b12501917c9ce5cbce8" - integrity sha1-1xIjKejfFoi6dxsSUBkXyc5cvOg= - dependencies: - jstransform "^11.0.3" - through "~2.3.4" - -errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== - dependencies: - prr "~1.0.1" - -es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: - version "1.17.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-define-property@^1.0.0, es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-scope@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" - integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-utils@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint@^6.8.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" - integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== - dependencies: - "@babel/code-frame" "^7.0.0" - ajv "^6.10.0" - chalk "^2.1.0" - cross-spawn "^6.0.5" - debug "^4.0.1" - doctrine "^3.0.0" - eslint-scope "^5.0.0" - eslint-utils "^1.4.3" - eslint-visitor-keys "^1.1.0" - espree "^6.1.2" - esquery "^1.0.1" - esutils "^2.0.2" - file-entry-cache "^5.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - inquirer "^7.0.0" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.14" - minimatch "^3.0.4" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.3" - progress "^2.0.0" - regexpp "^2.0.1" - semver "^6.1.2" - strip-ansi "^5.2.0" - strip-json-comments "^3.0.1" - table "^5.2.3" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -espree@^6.1.2: - version "6.2.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" - integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== - dependencies: - acorn "^7.1.1" - acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.1.0" - -esprima-fb@^15001.1.0-dev-harmony-fb: - version "15001.1.0-dev-harmony-fb" - resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" - integrity sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE= - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esprima@~3.1.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - -esquery@^1.0.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== - dependencies: - estraverse "^4.1.0" - -estraverse@^4.1.0, estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -events@^3.0.0: +escalade@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" - integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" + "@types/estree" "^1.0.0" -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" +expect-type@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.2.tgz#c030a329fb61184126c8447585bc75a7ec6fbff3" + integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA== -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= - dependencies: - homedir-polyfill "^1.0.1" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: +extend@^3.0.0: version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1: - version "3.2.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" - integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" - merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fastq@^1.6.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" - integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== - dependencies: - reusify "^1.0.4" - -fbjs@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.6.1.tgz#9636b7705f5ba9684d44b72f78321254afc860f7" - integrity sha1-lja3cF9bqWhNRLcveDISVK/IYPc= - dependencies: - core-js "^1.0.0" - loose-envify "^1.0.0" - promise "^7.0.3" - ua-parser-js "^0.7.9" - whatwg-fetch "^0.9.0" - -figgy-pudding@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" - integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== - -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== - dependencies: - flat-cache "^2.0.1" - -file-loader@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f" - integrity sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ== - dependencies: - loader-utils "^2.0.0" - schema-utils "^2.6.5" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" +fdir@^6.4.4, fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== fill-range@^7.1.1: version "7.1.1" @@ -3002,441 +1547,66 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" -find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== +filter-obj@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed" + integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng== + +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +formik@^2.4.6: + version "2.4.6" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.6.tgz#4da75ca80f1a827ab35b08fd98d5a76e928c9686" + integrity sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g== dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" + "@types/hoist-non-react-statics" "^3.3.1" + deepmerge "^2.1.1" + hoist-non-react-statics "^3.3.0" + lodash "^4.17.21" + lodash-es "^4.17.21" + react-fast-compare "^2.0.1" + tiny-warning "^1.0.2" + tslib "^2.0.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== - dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== - -flush-write-stream@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - -for-each@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" - integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== - dependencies: - is-callable "^1.2.7" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - -gensync@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" - integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== - -get-caller-file@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.4, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - -glob@^5.0.15: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-dirs@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" - integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== - dependencies: - ini "^1.3.5" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== - dependencies: - type-fest "^0.8.1" - -globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== - -globby@^11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" - integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - -globby@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" - integrity sha1-+yzP+UAfhgCUXfral0QMypcrhoA= - dependencies: - array-union "^1.0.1" - dir-glob "^2.0.0" - glob "^7.1.2" - ignore "^3.3.5" - pify "^3.0.0" - slash "^1.0.0" - -gopd@^1.0.1, gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has-symbols@^1.0.3, has-symbols@^1.1.0: +generate-password-browser@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + resolved "https://registry.yarnpkg.com/generate-password-browser/-/generate-password-browser-1.1.0.tgz#ec1661d0f3ce0b36e2ffdf6099578725a43d12e9" + integrity sha512-qsQve0rVbCqGqAfKgZwjxKUfI1d1nyd22dz+kE8gn1iw1LxGkR+Slsl79XXfm2wxuK27IkopTs5KXcOEQnhg0w== dependencies: - has-symbols "^1.0.3" + buffer "^6.0.3" + randombytes "^2.0.5" -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + +happy-dom@^18.0.1: + version "18.0.1" + resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-18.0.1.tgz#8f8a2199462842deb675f54e405af55f3b674540" + integrity sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA== dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash-base@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" - integrity sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw== - dependencies: - inherits "^2.0.1" - -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" + "@types/node" "^20.0.0" + "@types/whatwg-mimetype" "^3.0.2" + whatwg-mimetype "^3.0.0" hasown@^2.0.2: version "2.0.2" @@ -3445,382 +1615,190 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" -he@1.2.x, he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= +hast-util-from-html@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz#485c74785358beb80c4ba6346299311ac4c49c82" + integrity sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw== dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" + "@types/hast" "^3.0.0" + devlop "^1.1.0" + hast-util-from-parse5 "^8.0.0" + parse5 "^7.0.0" + vfile "^6.0.0" + vfile-message "^4.0.0" -home-or-tmp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" - integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= +hast-util-from-parse5@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e" + integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg== dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.1" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + hastscript "^9.0.0" + property-information "^7.0.0" + vfile "^6.0.0" + vfile-location "^5.0.0" + web-namespaces "^2.0.0" -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== +hast-util-parse-selector@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz#25ab00ae9e75cbc62cf7a901f68a247eade659e2" + integrity sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA== dependencies: - parse-passwd "^1.0.0" + "@types/hast" "^2.0.0" -html-minifier-terser@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" - integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== dependencies: - camel-case "^4.1.1" - clean-css "^4.2.3" - commander "^4.1.1" - he "^1.2.0" - param-case "^3.0.3" - relateurl "^0.2.7" - terser "^4.6.3" + "@types/hast" "^3.0.0" -html-minifier@^3: - version "3.5.21" - resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c" - integrity sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA== +hast-util-to-html@^9.0.0: + version "9.0.5" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005" + integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw== dependencies: - camel-case "3.0.x" - clean-css "4.2.x" - commander "2.17.x" - he "1.2.x" - param-case "2.1.x" - relateurl "0.2.x" - uglify-js "3.4.x" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-whitespace "^3.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + stringify-entities "^4.0.0" + zwitch "^2.0.4" -html-webpack-plugin@^4.0.4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.3.0.tgz#53bf8f6d696c4637d5b656d3d9863d89ce8174fd" - integrity sha512-C0fzKN8yQoVLTelcJxZfJCE+aAvQiY2VUf3UuKrR4a9k5UMWYOtpDLsaXwATbcVCnI05hUS7L9ULQHWLZhyi3w== +hast-util-to-string@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz#a4f15e682849326dd211c97129c94b0c3e76527c" + integrity sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A== dependencies: - "@types/html-minifier-terser" "^5.0.0" - "@types/tapable" "^1.0.5" - "@types/webpack" "^4.41.8" - html-minifier-terser "^5.0.1" - loader-utils "^1.2.3" - lodash "^4.17.15" - pretty-error "^2.1.1" - tapable "^1.1.3" - util.promisify "1.0.0" + "@types/hast" "^3.0.0" -htmlparser2@^3.3.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" + "@types/hast" "^3.0.0" -http-cache-semantics@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= - -iconv-lite@^0.4.24, iconv-lite@^0.4.5: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== +hastscript@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-7.2.0.tgz#0eafb7afb153d047077fa2a833dc9b7ec604d10b" + integrity sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw== dependencies: - safer-buffer ">= 2.1.2 < 3" + "@types/hast" "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^3.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" -icss-utils@^4.0.0, icss-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" - integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== +hastscript@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff" + integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w== dependencies: - postcss "^7.0.14" + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== -ignore-by-default@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" - integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= +humps@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa" + integrity sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g== -ignore@^3.3.5: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" - integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== - -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -ignore@^5.1.4: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== immutable@^5.0.2: version "5.1.3" resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.3.tgz#e6486694c8b76c37c063cca92399fa64098634d4" integrity sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg== -import-fresh@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== +intl-messageformat@10.7.16: + version "10.7.16" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.7.16.tgz#d909f9f9f4ab857fbe681d559b958dd4dd9f665a" + integrity sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug== dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" + "@formatjs/ecma402-abstract" "2.3.4" + "@formatjs/fast-memoize" "2.2.7" + "@formatjs/icu-messageformat-parser" "2.11.2" + tslib "^2.8.0" -imports-loader@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-0.8.0.tgz#030ea51b8ca05977c40a3abfd9b4088fe0be9a69" - integrity sha512-kXWL7Scp8KQ4552ZcdVTeaQCZSLW+e6nJfp3cwUMB673T7Hr98Xjx5JK+ql7ADlJUvj1JS5O01RLbKoutN5QDQ== - dependencies: - loader-utils "^1.0.2" - source-map "^0.6.1" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - -infer-owner@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -inquirer@^7.0.0: - version "7.3.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" - integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.19" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.6.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -interpret@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -invariant@^2.2.2: +invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== dependencies: loose-envify "^1.0.0" -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== dependencies: - kind-of "^3.0.2" + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: - kind-of "^6.0.0" + hasown "^2.0.2" -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.4, is-callable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" - integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== - -is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-finite@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" - integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-glob@^4.0.3: version "4.0.3" @@ -3829,361 +1807,77 @@ is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" -is-installed-globally@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== - dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" - -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" - integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== - -is-plain-obj@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== - dependencies: - has-symbols "^1.0.1" - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -is-typed-array@^1.1.14: - version "1.1.15" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" - integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== - dependencies: - which-typed-array "^1.1.16" - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-windows@^1.0.1, is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= - -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -jquery-mask-plugin@^1.14.16: - version "1.14.16" - resolved "https://registry.yarnpkg.com/jquery-mask-plugin/-/jquery-mask-plugin-1.14.16.tgz#9ebb55947d984da5aade45315b2fe6b113e28aae" - integrity sha512-reywdHlYEkPbzWjTpcc1fk9XQ3PLvO5dzEAVqy8zI7NTF22tB1HbeU3iboZTLdkBEPaWAqeI2HtEjsGQ4roZKw== - -jquery-serializejson@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/jquery-serializejson/-/jquery-serializejson-2.9.0.tgz#03e3764e3a4b42c1c5aae9f93d7f19320c5f35a6" - integrity sha512-xR7rjl0tRKIVioV5lOkOSv7K8BHMvGzYzC7Ech1iAYuZiYf0ksEpLC8OqjA5VApXf/qn/49O9hTmW70+/EA0vA== - -jquery@^3.5.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" - integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= +js-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== -js-yaml@^3.13.1: - version "3.14.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" - integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" - integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - -json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -json5@^0.5.0, json5@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - -json5@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== - dependencies: - minimist "^1.2.5" - -jstransform@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223" - integrity sha1-CaeJk+CuTU70SH9hVakfYZDLQiM= - dependencies: - base62 "^1.1.0" - commoner "^0.10.1" - esprima-fb "^15001.1.0-dev-harmony-fb" - object-assign "^2.0.0" - source-map "^0.4.2" - -keyv@^3.0.0: +jsesc@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== -klona@^2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" - integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -latest-version@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= - -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -loader-runner@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" - integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== - -loader-utils@^0.2.7: - version "0.2.17" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" - integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" - object-assign "^4.0.1" - -loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4: +lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" - integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= - -loose-envify@^1.0.0: +loose-envify@^1.0.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: js-tokens "^3.0.0 || ^4.0.0" -lower-case@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" - integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= - -lower-case@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" - integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== - dependencies: - tslib "^1.10.0" - -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +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: version "5.1.1" @@ -4192,154 +1886,69 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -make-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + +magic-string@^0.30.17: + version "0.30.18" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.18.tgz#905bfbbc6aa5692703a93db26a9edcaa0007d2bb" + integrity sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ== dependencies: - pify "^4.0.1" - semver "^5.6.0" + "@jridgewell/sourcemap-codec" "^1.5.5" -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== +mdast-util-to-hast@^13.0.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4" + integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== dependencies: - semver "^6.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" -make-plural@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-4.3.0.tgz#f23de08efdb0cac2e0c9ba9f315b0dff6b4c2735" - integrity sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA== - optionalDependencies: - minimist "^1.2.0" +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== dependencies: - object-visit "^1.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -marionette.approuter@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/marionette.approuter/-/marionette.approuter-1.0.2.tgz#bd45b801762fea4ec5caa9505640413596cc432c" - integrity sha512-XjcKb1Y6KROCmdZxO/rtOdRhd3Hfrs+7zWjtfiuCFS3VZa2IQjNgKUuIGmaKDZte2AmKRRMaPvXMh22nKYFh8A== - -marionette.templatecache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/marionette.templatecache/-/marionette.templatecache-1.0.0.tgz#579b9a53b1b6428f8f0a0071cff2175a2d71e65b" - integrity sha1-V5uaU7G2Qo+PCgBxz/IXWi1x5ls= - dependencies: - backbone.marionette "^4.0.0, 4.0.0-beta.1" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" - integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -merge@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" - integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== - -messageformat-convert@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/messageformat-convert/-/messageformat-convert-0.3.1.tgz#03b5453ee87e66da6eef1670ce0caf997ee64e51" - integrity sha512-fpNfsvFNj5VCAMN0Hpu9D4zhnGkEHL3cILJBAOydkzzdyrSoFlYIHAPRWRajOcHDgYXt+g0NIEzq0bfW3sd5Bw== - dependencies: - common-prefix "1.1.0" - make-plural "^4.3.0" - -messageformat-formatters@^2.0.1: +micromark-util-encode@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz#0492c1402a48775f751c9b17c0354e92be012b08" - integrity sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg== + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== -messageformat-loader@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/messageformat-loader/-/messageformat-loader-0.8.1.tgz#709a8f38e36257b19a9492dbfdbc743c03351fa0" - integrity sha512-hk721fJttjqoIfW6cMcLjFPsJ7C2bL9lj7Jy2btfWf7zsu6gWlLFeTKIUfi7S1v4U1gP4dkzHa1giYsAwD+aVA== +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== dependencies: - loader-utils "^1.2.3" - messageformat-convert "^0.3.1" - yaml "^1.6.0" + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" -messageformat-parser@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/messageformat-parser/-/messageformat-parser-4.1.3.tgz#b824787f57fcda7d50769f5b63e8d4fda68f5b9e" - integrity sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg== +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== -messageformat@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/messageformat/-/messageformat-2.3.0.tgz#de263c49029d5eae65d7ee25e0754f57f425ad91" - integrity sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w== - dependencies: - make-plural "^4.3.0" - messageformat-formatters "^2.0.1" - messageformat-parser "^4.1.2" - -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" +micromark-util-types@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== micromatch@^4.0.5: version "4.0.8" @@ -4349,438 +1958,43 @@ micromatch@^4.0.5: braces "^3.0.3" picomatch "^2.3.1" -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-response@^1.0.0, mimic-response@^1.0.1: +min-indent@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -mini-css-extract-plugin@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" - integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A== - dependencies: - loader-utils "^1.1.0" - normalize-url "1.9.1" - schema-utils "^1.0.0" - webpack-sources "^1.1.0" +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - -"minimatch@2 || 3", minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -moment@^2.30.1: - version "2.30.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" - integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== - -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -nan@^2.12.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" - integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -no-case@^2.2.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" - integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== - dependencies: - lower-case "^1.1.1" - -no-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" - integrity sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw== - dependencies: - lower-case "^2.0.1" - tslib "^1.10.0" +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== 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-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +npm-run-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-6.0.0.tgz#25cfdc4eae04976f3349c0b1afc089052c362537" + integrity sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA== dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" + path-key "^4.0.0" + unicorn-magic "^0.3.0" -nodemon@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" - integrity sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ== - dependencies: - chokidar "^3.2.2" - debug "^3.2.6" - ignore-by-default "^1.0.1" - minimatch "^3.0.4" - pstree.remy "^1.1.7" - semver "^5.7.1" - supports-color "^5.5.0" - touch "^3.1.0" - undefsafe "^2.0.2" - update-notifier "^4.0.0" - -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= - dependencies: - abbrev "1" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-url@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== - -nth-check@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -numeral@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" - integrity sha1-StCAk21EPCVhrtnyGX7//iX05QY= - -object-assign@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" - integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo= - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-inspect@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.getownpropertydescriptors@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.1.tgz#5c8016847b0d67fcedb7eef254751cfcdc7e9418" - integrity sha512-ZpZpjcJeugQfWsfyQlshVoowIIQ1qBGSVll4rfDq6JJVO//fesjoX808hXWfBjY+ROZgpKDI5TRSRBSoJiZ8eg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - -p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - -pako@~1.0.5: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parallel-transform@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" - integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== - dependencies: - cyclist "^1.0.1" - inherits "^2.0.3" - readable-stream "^2.1.5" - -param-case@2.1.x: - version "2.1.1" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" - integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= - dependencies: - no-case "^2.2.0" - -param-case@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" - integrity sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA== - dependencies: - dot-case "^3.0.3" - tslib "^1.10.0" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== parent-module@^1.0.0: version "1.0.1" @@ -4789,733 +2003,402 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0: - version "5.1.5" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" - integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" + "@types/unist" "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" -parse-asn1@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" - integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: - asn1.js "^5.2.0" - browserify-aes "^1.0.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= +parse-numeric-range@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" + integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== -pascal-case@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" - integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== +parse5@^7.0.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== dependencies: - no-case "^3.0.3" - tslib "^1.10.0" + entities "^6.0.0" -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -path-exists@^4.0.0: +path-key@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-parse@^1.0.6: +path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pbkdf2@^3.0.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.3.tgz#8be674d591d65658113424592a95d1517318dd4b" - integrity sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA== - dependencies: - create-hash "~1.1.3" - create-hmac "^1.1.7" - ripemd160 "=2.0.1" - safe-buffer "^5.2.1" - sha.js "^2.4.11" - to-buffer "^1.2.0" +pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +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: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + +picocolors@1.1.1, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -possible-typed-array-names@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" - integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== - -postcss-modules-extract-imports@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" - integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== - dependencies: - postcss "^7.0.5" - -postcss-modules-local-by-default@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" - integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== - dependencies: - icss-utils "^4.1.1" - postcss "^7.0.32" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" - integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^6.0.0" - -postcss-modules-values@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" - integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== - dependencies: - icss-utils "^4.0.0" - postcss "^7.0.6" - -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" - integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -postcss@^7.0.14, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.36" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb" - integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -prepend-http@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - -pretty-error@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" - integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= - dependencies: - renderkid "^2.0.1" - utila "~0.4" - -private@^0.1.6, private@^0.1.8, private@~0.1.5: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= - -promise@^7.0.3: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -pstree.remy@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" - integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== - -public-encrypt@^4.0.0: +picomatch@^4.0.2, picomatch@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +postcss-simple-vars@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz#836b3097a54dcd13dbd3c36a5dbdd512fad2954c" + integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A== + +postcss@^8.5.6: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== +prop-types-extra@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" + integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" + react-is "^16.3.2" + warning "^4.0.0" -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= +property-information@^6.0.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" + integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== -punycode@^1.2.4: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= +property-information@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d" + integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -pupa@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" - integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== +query-string@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.3.1.tgz#d0c93e6c7fb7c17bdf04aa09e382114580ede270" + integrity sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw== dependencies: - escape-goat "^2.0.0" + decode-uri-component "^0.4.1" + filter-obj "^5.1.0" + split-on-first "^3.0.0" -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" + performance-now "^2.1.0" -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: +randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== +react-bootstrap@^2.10.10: + version "2.10.10" + resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-2.10.10.tgz#be0b0d951a69987152d75c0e6986c80425efdf21" + integrity sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ== dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" + "@babel/runtime" "^7.24.7" + "@restart/hooks" "^0.4.9" + "@restart/ui" "^1.9.4" + "@types/prop-types" "^15.7.12" + "@types/react-transition-group" "^4.4.6" + classnames "^2.3.2" + dom-helpers "^5.2.1" + invariant "^2.2.4" + prop-types "^15.8.1" + prop-types-extra "^1.1.0" + react-transition-group "^4.4.5" + uncontrollable "^7.2.1" + warning "^4.0.3" -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== +react-dom@^19.1.1: + version "19.1.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.1.tgz#2daa9ff7f3ae384aeb30e76d5ee38c046dc89893" + integrity sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw== dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" + scheduler "^0.26.0" -react-dom@^0.14.0: - version "0.14.9" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.14.9.tgz#05064a3dcf0fb1880a3b2bfc9d58c55d8d9f6293" - integrity sha1-BQZKPc8PsYgKOyv8nVjFXY2fYpM= +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== -react@^0.14.0: - version "0.14.9" - resolved "https://registry.yarnpkg.com/react/-/react-0.14.9.tgz#9110a6497c49d44ba1c0edd317aec29c2e0d91d1" - integrity sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE= +react-intl@^7.1.11: + version "7.1.11" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-7.1.11.tgz#6155602c46621ad9b67dd31750d00908e1e0b516" + integrity sha512-tnVoRCWvW5Ie2ikYSdPF7z3+880yCe/9xPmitFeRPw3RYDcCfR4m8ZYa4MBq19W4adt9Z+PQA4FaMBCJ7E+HCQ== dependencies: - envify "^3.0.0" - fbjs "^0.6.1" + "@formatjs/ecma402-abstract" "2.3.4" + "@formatjs/icu-messageformat-parser" "2.11.2" + "@formatjs/intl" "3.1.6" + "@types/hoist-non-react-statics" "^3.3.1" + "@types/react" "16 || 17 || 18 || 19" + hoist-non-react-statics "^3.3.2" + intl-messageformat "10.7.16" + tslib "^2.8.0" -read-input@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/read-input/-/read-input-0.3.1.tgz#5b3169308013464ffda6ec92e58d2d3cea948df1" - integrity sha1-WzFpMIATRk/9puyS5Y0tPOqUjfE= +react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-refresh@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53" + integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ== + +react-router-dom@^7.9.1: + version "7.9.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.9.1.tgz#48044923701773da6362f9003ec46f308f293f15" + integrity sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw== dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" + react-router "7.9.1" -readable-stream@^3.1.1, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== +react-router@7.9.1: + version "7.9.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.9.1.tgz#b227410c31f24dd416c939ca5d0f8d5c8a1404d4" + integrity sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g== dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" + cookie "^1.0.1" + set-cookie-parser "^2.6.0" -readable-stream@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== +react-select@^5.10.2: + version "5.10.2" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.10.2.tgz#8dffc69dfd7d74684d9613e6eb27204e3b99e127" + integrity sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ== dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" + "@babel/runtime" "^7.12.0" + "@emotion/cache" "^11.4.0" + "@emotion/react" "^11.8.1" + "@floating-ui/dom" "^1.0.1" + "@types/react-transition-group" "^4.4.0" + memoize-one "^6.0.0" + prop-types "^15.6.0" + react-transition-group "^4.3.0" + use-isomorphic-layout-effect "^1.2.0" -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== +react-toastify@^11.0.5: + version "11.0.5" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-11.0.5.tgz#ce4c42d10eeb433988ab2264d3e445c4e9d13313" + integrity sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA== dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" + clsx "^2.1.1" + +react-transition-group@^4.3.0, react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react@^19.1.1: + version "19.1.1" + resolved "https://registry.yarnpkg.com/react/-/react-19.1.1.tgz#06d9149ec5e083a67f9a1e39ce97b06a03b644af" + integrity sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ== readdirp@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== - dependencies: - picomatch "^2.2.1" - -recast@^0.11.17: - version "0.11.23" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" - integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= - dependencies: - ast-types "0.9.6" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - -regenerate@^1.2.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" - integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regenerator-transform@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" - integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q== - dependencies: - babel-runtime "^6.18.0" - babel-types "^6.19.0" - private "^0.1.6" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== - -regexpu-core@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" - integrity sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA= - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" - -registry-auth-token@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.0.tgz#1d37dffda72bbecd0f581e4715540213a65eb7da" - integrity sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w== - dependencies: - rc "^1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - -regjsgen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" - integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= - -regjsparser@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" - integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= - dependencies: - jsesc "~0.5.0" - -relateurl@0.2.x, relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -renderkid@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149" - integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA== - dependencies: - css-select "^1.1.0" - dom-converter "^0.2" - htmlparser2 "^3.3.0" - strip-ansi "^3.0.0" - utila "^0.4.0" - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.5.2, repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - dependencies: - resolve-from "^3.0.0" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-from@^3.0.0: +redent@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +refractor@^4.8.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-4.9.0.tgz#2e1c7af0157230cdd2f9086660912eadc5f68323" + integrity sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og== + dependencies: + "@types/hast" "^2.0.0" + "@types/prismjs" "^1.0.0" + hastscript "^7.0.0" + parse-entities "^4.0.0" + +rehype-parse@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-9.0.1.tgz#9993bda129acc64c417a9d3654a7be38b2a94c20" + integrity sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag== + dependencies: + "@types/hast" "^3.0.0" + hast-util-from-html "^2.0.0" + unified "^11.0.0" + +rehype-prism-plus@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/rehype-prism-plus/-/rehype-prism-plus-2.0.0.tgz#75b1e2d0dd7496125987a1732cb7d560de02a0fd" + integrity sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ== + dependencies: + hast-util-to-string "^3.0.0" + parse-numeric-range "^1.3.0" + refractor "^4.8.0" + rehype-parse "^9.0.0" + unist-util-filter "^5.0.0" + unist-util-visit "^5.0.0" + +rehype-stringify@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-10.0.1.tgz#2ec1ebc56c6aba07905d3b4470bdf0f684f30b75" + integrity sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA== + dependencies: + "@types/hast" "^3.0.0" + hast-util-to-html "^9.0.0" + unified "^11.0.0" + +rehype@~13.0.0: + version "13.0.2" + resolved "https://registry.yarnpkg.com/rehype/-/rehype-13.0.2.tgz#ab0b3ac26573d7b265a0099feffad450e4cf1952" + integrity sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A== + dependencies: + "@types/hast" "^3.0.0" + rehype-parse "^9.0.0" + rehype-stringify "^10.0.0" + unified "^11.0.0" resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.3.2: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== +resolve@^1.19.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== dependencies: - path-parse "^1.0.6" + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= +rollup@^4.43.0: + version "4.50.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.50.0.tgz#6f237f598b7163ede33ce827af8534c929aaa186" + integrity sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw== dependencies: - lowercase-keys "^1.0.0" + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.50.0" + "@rollup/rollup-android-arm64" "4.50.0" + "@rollup/rollup-darwin-arm64" "4.50.0" + "@rollup/rollup-darwin-x64" "4.50.0" + "@rollup/rollup-freebsd-arm64" "4.50.0" + "@rollup/rollup-freebsd-x64" "4.50.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.50.0" + "@rollup/rollup-linux-arm-musleabihf" "4.50.0" + "@rollup/rollup-linux-arm64-gnu" "4.50.0" + "@rollup/rollup-linux-arm64-musl" "4.50.0" + "@rollup/rollup-linux-loongarch64-gnu" "4.50.0" + "@rollup/rollup-linux-ppc64-gnu" "4.50.0" + "@rollup/rollup-linux-riscv64-gnu" "4.50.0" + "@rollup/rollup-linux-riscv64-musl" "4.50.0" + "@rollup/rollup-linux-s390x-gnu" "4.50.0" + "@rollup/rollup-linux-x64-gnu" "4.50.0" + "@rollup/rollup-linux-x64-musl" "4.50.0" + "@rollup/rollup-openharmony-arm64" "4.50.0" + "@rollup/rollup-win32-arm64-msvc" "4.50.0" + "@rollup/rollup-win32-ia32-msvc" "4.50.0" + "@rollup/rollup-win32-x64-msvc" "4.50.0" + fsevents "~2.3.2" -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== +rooks@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/rooks/-/rooks-9.3.0.tgz#7a3eb44192b7d5b1321acdf15fafdee37987c348" + integrity sha512-ez9ReItW+a/GXsA92Lfh5KWTjhbzlp354KOlC5oh9RHVD5fs/GaWwBq72F2xkDXVblgCgFs0Nel1hxojtszhgw== dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" + fast-deep-equal "^3.1.3" + lodash.debounce "^4.0.8" + raf "^3.4.1" + use-sync-external-store "^1.4.0" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rewire@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/rewire/-/rewire-5.0.0.tgz#c4e6558206863758f6234d8f11321793ada2dbff" - integrity sha512-1zfitNyp9RH5UDyGGLe9/1N0bMlPQ0WrX0Tmg11kMHBpqwPJI4gfPpP7YngFyLbFmhXh19SToAG0sKKEFcOIJA== - dependencies: - eslint "^6.8.0" - -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" - integrity sha1-YTObci/mo1FWiSENJOFMlhSGE+8= - dependencies: - align-text "^0.1.1" - -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - -rimraf@^2.5.4, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -ripemd160@=2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" - integrity sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w== - dependencies: - hash-base "^2.0.0" - inherits "^2.0.1" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-parallel@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" - integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== - -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" - integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= - dependencies: - aproba "^1.1.1" - -rxjs@^6.6.0: - version "6.6.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2" - integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg== - dependencies: - tslib "^1.9.0" - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sass-loader@^10.0.0: - version "10.5.2" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.5.2.tgz#1ca30534fff296417b853c7597ca3b0bbe8c37d0" - integrity sha512-vMUoSNOUKJILHpcNCCyD23X34gve1TS7Rjd9uXHeKqhvBG39x6XbswFDtpbTElj6XdMFezoWhkh5vtKudf2cgQ== - dependencies: - klona "^2.0.4" - loader-utils "^2.0.0" - neo-async "^2.6.2" - schema-utils "^3.0.0" - semver "^7.3.2" - -sass@^1.92.1: - version "1.92.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.92.1.tgz#07fb1fec5647d7b712685d1090628bf52456fe86" - integrity sha512-ffmsdbwqb3XeyR8jJR6KelIXARM9bFQe8A6Q3W4Klmwy5Ckd5gz7jgUNHo4UOqutU5Sk1DtKLbpDP0nLCg1xqQ== +sass@^1.93.0: + version "1.93.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.93.0.tgz#8252f61405be295f4755d1ed5df48bf118587aa5" + integrity sha512-CQi5/AzCwiubU3dSqRDJ93RfOfg/hhpW1l6wCIvolmehfwgCI35R/0QDs1+R+Ygrl8jFawwwIojE2w47/mf94A== dependencies: chokidar "^4.0.0" immutable "^5.0.2" @@ -5523,553 +2406,150 @@ sass@^1.92.1: optionalDependencies: "@parcel/watcher" "^2.4.1" -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" +scheduler@^0.26.0: + version "0.26.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337" + integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== -schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - -schema-utils@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2: - version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" - integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== +set-cookie-parser@^2.6.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943" + integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== -serialize-javascript@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" - integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== - -serialize-javascript@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" - integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== - dependencies: - randombytes "^2.1.0" - -set-blocking@^2.0.0: +siginfo@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== -set-function-length@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= - -sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: - version "2.4.12" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f" - integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w== - dependencies: - inherits "^2.0.4" - safe-buffer "^5.2.1" - to-buffer "^1.2.0" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -signal-exit@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== - dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= - dependencies: - is-plain-obj "^1.0.0" - -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -"source-map-js@>=0.6.2 <2.0.0": +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@^0.4.15: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== - dependencies: - source-map "^0.5.6" - -source-map-support@~0.5.12: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - integrity sha1-66T12pwNyZneaAMti092FzZSA2s= - dependencies: - amdefine ">=0.0.4" - -source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1: +source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -ssri@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" - integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== - dependencies: - figgy-pudding "^3.5.1" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -stream-browserify@^2.0.1: +space-separated-tokens@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + +split-on-first@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" + integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.9.0.tgz#1a6f7243b339dca4c9fd55e1c7504c77ef23e8f1" + integrity sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw== + +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" -stream-each@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" - integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-json-comments@^3.0.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -style-loader@^1.1.3: - version "1.2.1" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a" - integrity sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg== - dependencies: - loader-utils "^2.0.0" - schema-utils "^2.6.6" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^5.3.0, supports-color@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: +strip-ansi@^7.1.0: version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== dependencies: - has-flag "^4.0.0" + ansi-regex "^6.0.1" -syntax-error@^1.1.6: - version "1.4.0" - resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" - integrity sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w== +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== dependencies: - acorn-node "^1.2.0" + min-indent "^1.0.0" -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== +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: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" + js-tokens "^9.0.1" -"tabler-ui@git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813": - version "0.0.31" - resolved "git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813" - dependencies: - bootstrap "^4.0.0" +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== -tapable@^1.0.0, tapable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - -term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== - -terser-webpack-plugin@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz#2c63544347324baafa9a56baaddf1634c8abfc2f" - integrity sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^3.1.0" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser@^4.1.2, terser@^4.6.3: - version "4.8.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f" - integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through@^2.3.6, through@~2.3.4: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== - dependencies: - setimmediate "^1.0.4" - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= - -to-buffer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.1.tgz#2ce650cdb262e9112a18e65dc29dcb513c8155e0" - integrity sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ== - dependencies: - isarray "^2.0.5" - safe-buffer "^5.2.1" - typed-array-buffer "^1.0.3" - -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-readable-stream@^1.0.0: +supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= +tiny-invariant@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinyglobby@^0.2.14: + version "0.2.14" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" + integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" + fdir "^6.4.4" + picomatch "^4.0.2" + +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + +tinypool@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.1.1.tgz#059f2d042bd37567fbc017d3d426bdd2a2612591" + integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + +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: + version "0.2.5" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" + integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== to-regex-range@^5.0.1: version "5.0.1" @@ -6078,537 +2558,291 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +trough@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" + integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== + +tsconfck@^3.0.3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.6.tgz#da1f0b10d82237ac23422374b3fce1edb23c3ead" + integrity sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w== + +tslib@^2.0.0, tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +typescript@5.9.2: + version "5.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" + integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== + +uncontrollable@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" + integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" + "@babel/runtime" "^7.6.3" + "@types/react" ">=16.9.11" + invariant "^2.2.4" + react-lifecycles-compat "^3.0.4" -touch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" - integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== +uncontrollable@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-8.0.4.tgz#a0a8307f638795162fafd0550f4a1efa0f8c5eb6" + integrity sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +unicorn-magic@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz#4efd45c85a69e0dd576d25532fbfa22aa5c8a104" + integrity sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA== + +unified@^11.0.0: + version "11.0.5" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== dependencies: - nopt "~1.0.10" + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - -tslib@^1.10.0, tslib@^1.9.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" - integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= +unist-util-filter@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/unist-util-filter/-/unist-util-filter-5.0.1.tgz#f9f3a0bdee007e040964c274dda27bac663d0a39" + integrity sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw== dependencies: - prelude-ls "~1.1.2" + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -typed-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" - integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - is-typed-array "^1.1.14" + "@types/unist" "^3.0.0" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== dependencies: - is-typedarray "^1.0.0" + "@types/unist" "^3.0.0" -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -ua-parser-js@^0.7.9: - version "0.7.33" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" - integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== - -uglify-js@3.4.x: - version "3.4.10" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f" - integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw== - dependencies: - commander "~2.19.0" - source-map "~0.6.1" - -uglify-js@~2.6.1: - version "2.6.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.6.4.tgz#65ea2fb3059c9394692f15fed87c2b36c16b9adf" - integrity sha1-ZeovswWck5RpLxX+2HwrNsFrmt8= - dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.0" - -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc= - -undefsafe@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" - integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== - dependencies: - debug "^2.2.0" - -underscore@>=1.8.3, underscore@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e" - integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== - -update-notifier@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" - integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== - dependencies: - boxen "^4.2.0" - chalk "^3.0.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.0.0" - pupa "^2.0.1" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -upper-case@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" - integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -util.promisify@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" - integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== - dependencies: - define-properties "^1.1.2" - object.getownpropertydescriptors "^2.0.3" - -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== - dependencies: - inherits "2.0.3" - -utila@^0.4.0, utila@~0.4: - version "0.4.0" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= - -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" - integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== - -vm-browserify@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" - integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== - -watchpack-chokidar2@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" - integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== - dependencies: - chokidar "^2.1.8" - -watchpack@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b" - integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg== - dependencies: - graceful-fs "^4.1.2" - neo-async "^2.5.0" - optionalDependencies: - chokidar "^3.4.1" - watchpack-chokidar2 "^2.0.0" - -webpack-cli@^3.3.11: - version "3.3.12" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" - integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== - dependencies: - chalk "^2.4.2" - cross-spawn "^6.0.5" - enhanced-resolve "^4.1.1" - findup-sync "^3.0.0" - global-modules "^2.0.0" - import-local "^2.0.0" - interpret "^1.4.0" - loader-utils "^1.4.0" - supports-color "^6.1.0" - v8-compile-cache "^2.1.1" - yargs "^13.3.2" - -webpack-log@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" - integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== - dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" - -webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack-visualizer-plugin@^0.1.11: - version "0.1.11" - resolved "https://registry.yarnpkg.com/webpack-visualizer-plugin/-/webpack-visualizer-plugin-0.1.11.tgz#b8770ad86b4f652612c68b1b782253faf9f8a34e" - integrity sha1-uHcK2GtPZSYSxosbeCJT+vn4o04= - dependencies: - d3 "^3.5.6" - mkdirp "^0.5.1" - react "^0.14.0" - react-dom "^0.14.0" - -webpack@^4.42.1: - version "4.44.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.1.tgz#17e69fff9f321b8f117d1fda714edfc0b939cc21" - integrity sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.4.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.3.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.7.4" - webpack-sources "^1.4.1" - -whatwg-fetch@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0" - integrity sha1-DjaExsuZlbQ+/J3wPkw2XZX9nMA= - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which-typed-array@^1.1.16: - version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" - integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.8" - call-bound "^1.0.4" - for-each "^0.3.5" - get-proto "^1.0.1" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - -which@^1.2.14, which@^1.2.9, which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0= - -word-wrap@~1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" - integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== - -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= - -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== - dependencies: - errno "~0.1.7" - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - -xdg-basedir@^4.0.0: +unist-util-stringify-position@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" -xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +unist-util-visit-parents@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" -y18n@^4.0.0: +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +use-isomorphic-layout-effect@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz#2f11a525628f56424521c748feabc2ffcc962fce" + integrity sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA== + +use-sync-external-store@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" + integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== + +vfile-location@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3" + integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== + dependencies: + "@types/unist" "^3.0.0" + vfile "^6.0.0" + +vfile-message@^4.0.0: version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" + integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.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.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.10.3.tgz#ddc609aebe098b23aea685daba5548bea75adf80" + integrity sha512-f4sekUcDPF+T+GdbbE8idb1i2YplBAoH+SfRS0e/WRBWb2rYb1Jf5Pimll0Rj+3JgIYWwG2K5LtBPCXxoibkLg== + dependencies: + "@babel/code-frame" "^7.27.1" + chokidar "^4.0.3" + npm-run-path "^6.0.0" + picocolors "^1.1.1" + picomatch "^4.0.3" + strip-ansi "^7.1.0" + tiny-invariant "^1.3.3" + tinyglobby "^0.2.14" + vscode-uri "^3.1.0" + +vite-tsconfig-paths@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz#d9a71106a7ff2c1c840c6f1708042f76a9212ed4" + integrity sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w== + dependencies: + debug "^4.1.1" + globrex "^0.1.2" + tsconfck "^3.0.3" + +"vite@^5.0.0 || ^6.0.0 || ^7.0.0-0": + version "7.1.4" + resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.4.tgz#354944affb55e1aff0157406b74e0d0a3232df9a" + integrity sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw== + 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.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.6.tgz#336806d29983135677f498a05efb0fd46c5eef2d" + integrity sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ== + dependencies: + esbuild "^0.25.0" + fdir "^6.5.0" + picomatch "^4.0.3" + postcss "^8.5.6" + rollup "^4.43.0" + tinyglobby "^0.2.15" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.2.4.tgz#0637b903ad79d1539a25bc34c0ed54b5c67702ea" + integrity sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A== + dependencies: + "@types/chai" "^5.2.2" + "@vitest/expect" "3.2.4" + "@vitest/mocker" "3.2.4" + "@vitest/pretty-format" "^3.2.4" + "@vitest/runner" "3.2.4" + "@vitest/snapshot" "3.2.4" + "@vitest/spy" "3.2.4" + "@vitest/utils" "3.2.4" + chai "^5.2.0" + debug "^4.4.1" + expect-type "^1.2.1" + magic-string "^0.30.17" + pathe "^2.0.3" + picomatch "^4.0.2" + std-env "^3.9.0" + tinybench "^2.9.0" + tinyexec "^0.3.2" + tinyglobby "^0.2.14" + tinypool "^1.1.1" + tinyrainbow "^2.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" + +vscode-uri@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c" + integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ== + +warning@^4.0.0, warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +web-namespaces@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" + integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yaml@^1.6.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs@^13.3.2: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yargs@^15.0.0: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" - -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - integrity sha1-9+572FfdfB0tOMDnTvvWgdFDH9E= - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" +zwitch@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== diff --git a/scripts/ci/frontend-build b/scripts/ci/frontend-build index 9bcf1a7e..e1d92923 100755 --- a/scripts/ci/frontend-build +++ b/scripts/ci/frontend-build @@ -17,7 +17,7 @@ if hash docker 2>/dev/null; then -v "$(pwd)/frontend:/app/frontend" \ -v "$(pwd)/global:/app/global" \ -w /app/frontend "${DOCKER_IMAGE}" \ - sh -c "yarn install && 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" echo -e "${BLUE}❯ ${GREEN}Building Frontend Complete${RESET}" else From 61a92906f38346be750e47df9440331dcb5b43bc Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 3 Sep 2025 18:01:00 +1000 Subject: [PATCH 11/82] Notification toasts, nicer loading, add new user support --- backend/internal/token.js | 16 +- backend/internal/user.js | 6 +- backend/models/token.js | 6 +- frontend/src/App.css | 7 + frontend/src/App.tsx | 10 + frontend/src/api/backend/index.ts | 1 + frontend/src/components/HasPermission.tsx | 24 +- ...dingPage.module.css => Loading.module.css} | 0 frontend/src/components/Loading.tsx | 22 ++ frontend/src/components/LoadingPage.tsx | 16 +- frontend/src/components/index.ts | 1 + frontend/src/hooks/useUser.ts | 20 +- frontend/src/locale/lang/en.json | 4 + frontend/src/locale/src/en.json | 12 + frontend/src/modals/UserModal.tsx | 353 +++++++++--------- frontend/src/notifications/Msg.module.css | 14 + frontend/src/notifications/Msg.tsx | 36 ++ frontend/src/notifications/helpers.tsx | 27 ++ frontend/src/notifications/index.ts | 1 + frontend/src/pages/Access/index.tsx | 2 +- frontend/src/pages/AuditLog/index.tsx | 2 +- frontend/src/pages/Certificates/index.tsx | 2 +- frontend/src/pages/Nginx/DeadHosts/index.tsx | 2 +- frontend/src/pages/Nginx/ProxyHosts/index.tsx | 2 +- .../pages/Nginx/RedirectionHosts/index.tsx | 2 +- frontend/src/pages/Nginx/Streams/index.tsx | 2 +- frontend/src/pages/Settings/index.tsx | 2 +- frontend/src/pages/Users/Empty.tsx | 7 +- frontend/src/pages/Users/Table.tsx | 10 +- frontend/src/pages/Users/TableWrapper.tsx | 5 +- frontend/src/pages/Users/index.tsx | 2 +- 31 files changed, 401 insertions(+), 215 deletions(-) rename frontend/src/components/{LoadingPage.module.css => Loading.module.css} (100%) create mode 100644 frontend/src/components/Loading.tsx create mode 100644 frontend/src/notifications/Msg.module.css create mode 100644 frontend/src/notifications/Msg.tsx create mode 100644 frontend/src/notifications/helpers.tsx create mode 100644 frontend/src/notifications/index.ts diff --git a/backend/internal/token.js b/backend/internal/token.js index 810ac6aa..f1d2b370 100644 --- a/backend/internal/token.js +++ b/backend/internal/token.js @@ -134,24 +134,24 @@ export default { * @param {Object} user * @returns {Promise} */ - getTokenFromUser: (user) => { + getTokenFromUser: async (user) => { const expire = "1d"; const Token = new TokenModel(); const expiry = parseDatePeriod(expire); - return Token.create({ + const signed = await Token.create({ iss: "api", attrs: { id: user.id, }, scope: ["user"], expiresIn: expire, - }).then((signed) => { - return { - token: signed.token, - expires: expiry.toISOString(), - user: user, - }; }); + + return { + token: signed.token, + expires: expiry.toISOString(), + user: user, + }; }, }; diff --git a/backend/internal/user.js b/backend/internal/user.js index e3d7ca4c..1c1f3a86 100644 --- a/backend/internal/user.js +++ b/backend/internal/user.js @@ -337,11 +337,11 @@ const internalUser = { * @param {Integer} [id_requested] * @returns {[String]} */ - getUserOmisionsByAccess: (access, id_requested) => { + getUserOmisionsByAccess: (access, idRequested) => { let response = []; // Admin response - if (!access.token.hasScope("admin") && access.token.getUserId(0) !== id_requested) { - response = ["roles", "is_deleted"]; // Restricted response + if (!access.token.hasScope("admin") && access.token.getUserId(0) !== idRequested) { + response = ["is_deleted"]; // Restricted response } return response; diff --git a/backend/models/token.js b/backend/models/token.js index 5edad90d..fb40e2a0 100644 --- a/backend/models/token.js +++ b/backend/models/token.js @@ -123,16 +123,16 @@ export default () => { }, /** - * @param [default_value] + * @param [defaultValue] * @returns {Integer} */ - getUserId: (default_value) => { + getUserId: (defaultValue) => { const attrs = self.get("attrs"); if (attrs && typeof attrs.id !== "undefined" && attrs.id) { return attrs.id; } - return default_value || 0; + return defaultValue || 0; }, }; diff --git a/frontend/src/App.css b/frontend/src/App.css index 1e642918..59677e8c 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -5,3 +5,10 @@ .domain-name { font-family: monospace; } + +.mr-1 { + margin-right: 0.25rem; +} +.ml-1 { + margin-left: 0.25rem; +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index aa2fb873..f2312f3f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { RawIntlProvider } from "react-intl"; +import { ToastContainer } from "react-toastify"; import { AuthProvider, LocaleProvider, ThemeProvider } from "src/context"; import { intl } from "src/locale"; import Router from "src/Router.tsx"; @@ -16,6 +17,15 @@ function App() { + diff --git a/frontend/src/api/backend/index.ts b/frontend/src/api/backend/index.ts index 745ce3ce..1bfccb4b 100644 --- a/frontend/src/api/backend/index.ts +++ b/frontend/src/api/backend/index.ts @@ -4,6 +4,7 @@ export * from "./createDeadHost"; export * from "./createProxyHost"; export * from "./createRedirectionHost"; export * from "./createStream"; +export * from "./createUser"; export * from "./deleteAccessList"; export * from "./deleteCertificate"; export * from "./deleteDeadHost"; diff --git a/frontend/src/components/HasPermission.tsx b/frontend/src/components/HasPermission.tsx index 9b45e7f6..c4779b9e 100644 --- a/frontend/src/components/HasPermission.tsx +++ b/frontend/src/components/HasPermission.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react"; import Alert from "react-bootstrap/Alert"; +import { Loading, LoadingPage } from "src/components"; import { useUser } from "src/hooks"; import { intl } from "src/locale"; @@ -8,11 +9,30 @@ interface Props { type: "manage" | "view"; hideError?: boolean; children?: ReactNode; + pageLoading?: boolean; + loadingNoLogo?: boolean; } -function HasPermission({ permission, type, children, hideError = false }: Props) { - const { data } = useUser("me"); +function HasPermission({ + permission, + type, + children, + hideError = false, + pageLoading = false, + loadingNoLogo = false, +}: Props) { + const { data, isLoading } = useUser("me"); const perms = data?.permissions; + if (isLoading) { + if (hideError) { + return null; + } + if (pageLoading) { + return ; + } + return ; + } + let allowed = permission === ""; const acceptable = ["manage", type]; diff --git a/frontend/src/components/LoadingPage.module.css b/frontend/src/components/Loading.module.css similarity index 100% rename from frontend/src/components/LoadingPage.module.css rename to frontend/src/components/Loading.module.css diff --git a/frontend/src/components/Loading.tsx b/frontend/src/components/Loading.tsx new file mode 100644 index 00000000..35a054db --- /dev/null +++ b/frontend/src/components/Loading.tsx @@ -0,0 +1,22 @@ +import { intl } from "src/locale"; +import styles from "./Loading.module.css"; + +interface Props { + label?: string; + noLogo?: boolean; +} +export function Loading({ label, noLogo }: Props) { + return ( +
    + {noLogo ? null : ( +
    + +
    + )} +
    {label || intl.formatMessage({ id: "loading" })}
    +
    +
    +
    +
    + ); +} diff --git a/frontend/src/components/LoadingPage.tsx b/frontend/src/components/LoadingPage.tsx index 6be77ebb..7d3bec1e 100644 --- a/frontend/src/components/LoadingPage.tsx +++ b/frontend/src/components/LoadingPage.tsx @@ -1,6 +1,4 @@ -import { Page } from "src/components"; -import { intl } from "src/locale"; -import styles from "./LoadingPage.module.css"; +import { Loading, Page } from "src/components"; interface Props { label?: string; @@ -10,17 +8,7 @@ export function LoadingPage({ label, noLogo }: Props) { return (
    -
    - {noLogo ? null : ( -
    - -
    - )} -
    {label || intl.formatMessage({ id: "loading" })}
    -
    -
    -
    -
    +
    ); diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index 66e12a74..be87e068 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -2,6 +2,7 @@ export * from "./Button"; export * from "./ErrorNotFound"; export * from "./Flag"; export * from "./HasPermission"; +export * from "./Loading"; export * from "./LoadingPage"; export * from "./LocalePicker"; export * from "./NavLink"; diff --git a/frontend/src/hooks/useUser.ts b/frontend/src/hooks/useUser.ts index 180032c2..fb991438 100644 --- a/frontend/src/hooks/useUser.ts +++ b/frontend/src/hooks/useUser.ts @@ -1,7 +1,20 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { getUser, type User, updateUser } from "src/api/backend"; +import { createUser, getUser, type User, updateUser } from "src/api/backend"; const fetchUser = (id: number | string) => { + if (id === "new") { + return Promise.resolve({ + id: 0, + createdOn: "", + modifiedOn: "", + isDisabled: false, + email: "", + name: "", + nickname: "", + roles: [], + avatar: "", + } as User); + } return getUser(id, { expand: "permissions" }); }; @@ -17,8 +30,11 @@ const useUser = (id: string | number, options = {}) => { const useSetUser = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (values: User) => updateUser(values), + mutationFn: (values: User) => (values.id ? updateUser(values) : createUser(values)), onMutate: (values: User) => { + if (!values.id) { + return; + } const previousObject = queryClient.getQueryData(["user", values.id]); queryClient.setQueryData(["user", values.id], (old: User) => ({ ...old, diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json index eae027b8..f59a6d4d 100644 --- a/frontend/src/locale/lang/en.json +++ b/frontend/src/locale/lang/en.json @@ -51,6 +51,9 @@ "notfound.action": "Take me home", "notfound.text": "We are sorry but the page you are looking for was not found", "notfound.title": "Oops… You just found an error page", + "notification.error": "Error", + "notification.success": "Success", + "notification.user-saved": "User has been saved", "offline": "Offline", "online": "Online", "password": "Password", @@ -82,6 +85,7 @@ "user.edit-profile": "Edit Profile", "user.full-name": "Full Name", "user.logout": "Logout", + "user.new": "New User", "user.new-password": "New Password", "user.nickname": "Nickname", "user.switch-dark": "Switch to Dark mode", diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index b3e881de..d37553e9 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -155,6 +155,15 @@ "notfound.title": { "defaultMessage": "Oops… You just found an error page" }, + "notification.error": { + "defaultMessage": "Error" + }, + "notification.user-saved": { + "defaultMessage": "User has been saved" + }, + "notification.success": { + "defaultMessage": "Success" + }, "offline": { "defaultMessage": "Offline" }, @@ -248,6 +257,9 @@ "user.logout": { "defaultMessage": "Logout" }, + "user.new": { + "defaultMessage": "New User" + }, "user.new-password": { "defaultMessage": "New Password" }, diff --git a/frontend/src/modals/UserModal.tsx b/frontend/src/modals/UserModal.tsx index 0f25817a..293b2ae9 100644 --- a/frontend/src/modals/UserModal.tsx +++ b/frontend/src/modals/UserModal.tsx @@ -2,31 +2,35 @@ import { Field, Form, Formik } from "formik"; import { useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; -import { Button } from "src/components"; +import { Button, Loading } from "src/components"; import { useSetUser, useUser } from "src/hooks"; import { intl } from "src/locale"; import { validateEmail, validateString } from "src/modules/Validations"; +import { showSuccess } from "src/notifications"; interface Props { - userId: number | "me"; + userId: number | "me" | "new"; onClose: () => void; } export function UserModal({ userId, onClose }: Props) { - const { data } = useUser(userId); - const { data: currentUser } = useUser("me"); + const { data, isLoading, error } = useUser(userId); + const { data: currentUser, isLoading: currentIsLoading } = useUser("me"); const { mutate: setUser } = useSetUser(); - const [error, setError] = useState(null); + const [errorMsg, setErrorMsg] = useState(null); + + if (data && currentUser) { + console.log("DATA:", data); + console.log("CURRENT:", currentUser); + } const onSubmit = async (values: any, { setSubmitting }: any) => { - setError(null); + setErrorMsg(null); const { ...payload } = { - id: userId, + id: userId === "new" ? undefined : userId, roles: [], ...values, }; - console.log("values", values); - if (data?.id === currentUser?.id) { // Prevent user from locking themselves out delete payload.isDisabled; @@ -39,175 +43,188 @@ export function UserModal({ userId, onClose }: Props) { delete payload.isAdmin; setUser(payload, { - onError: (err: any) => setError(err.message), - onSuccess: () => onClose(), + onError: (err: any) => setErrorMsg(err.message), + onSuccess: () => { + showSuccess(intl.formatMessage({ id: "notification.user-saved" })); + onClose(); + }, onSettled: () => setSubmitting(false), }); }; return ( - - {({ isSubmitting }) => ( -
    - - {intl.formatMessage({ id: "user.edit" })} - - - setError(null)} dismissible> - {error} - -
    -
    -
    - - {({ field, form }: any) => ( -
    - - - {form.errors.name ? ( -
    - {form.errors.name && form.touched.name - ? form.errors.name - : null} -
    - ) : null} -
    - )} -
    -
    -
    -
    -
    - - {({ field, form }: any) => ( -
    - - - {form.errors.nickname ? ( -
    - {form.errors.nickname && form.touched.nickname - ? form.errors.nickname - : null} -
    - ) : null} -
    - )} -
    -
    -
    -
    -
    - - {({ field, form }: any) => ( -
    - - - {form.errors.email ? ( -
    - {form.errors.email && form.touched.email ? form.errors.email : null} -
    - ) : null} + {!isLoading && error && {error?.message || "Unknown error"}} + {(isLoading || currentIsLoading) && } + {!isLoading && !currentIsLoading && data && currentUser && ( + + {({ isSubmitting }) => ( + + + + {intl.formatMessage({ id: data?.id ? "user.edit" : "user.new" })} + + + + setErrorMsg(null)} dismissible> + {errorMsg} + +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.name ? ( +
    + {form.errors.name && form.touched.name + ? form.errors.name + : null} +
    + ) : null} +
    + )} +
    - )} - -
    - {currentUser && data && currentUser?.id !== data?.id ? ( -
    -

    Properties

    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.nickname ? ( +
    + {form.errors.nickname && form.touched.nickname + ? form.errors.nickname + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.email ? ( +
    + {form.errors.email && form.touched.email + ? form.errors.email + : null} +
    + ) : null} +
    + )} +
    +
    + {currentUser && data && currentUser?.id !== data?.id ? ( +
    +

    Properties

    -
    -
    - -
    -
    - +
    +
    + +
    +
    + +
    -
    - ) : null} - - - - - - - )} - + ) : null} + + + + + + + )} + + )} ); } diff --git a/frontend/src/notifications/Msg.module.css b/frontend/src/notifications/Msg.module.css new file mode 100644 index 00000000..24c55921 --- /dev/null +++ b/frontend/src/notifications/Msg.module.css @@ -0,0 +1,14 @@ +.toaster { + padding: 0; + background: transparent !important; + box-shadow: none !important; + border: none !important; + + &.toast { + border-radius: 0; + box-shadow: none; + font-size: 14px; + padding: 16px 24px; + background: transparent; + } +} diff --git a/frontend/src/notifications/Msg.tsx b/frontend/src/notifications/Msg.tsx new file mode 100644 index 00000000..b68b8671 --- /dev/null +++ b/frontend/src/notifications/Msg.tsx @@ -0,0 +1,36 @@ +import { IconCheck, IconExclamationCircle } from "@tabler/icons-react"; +import cn from "classnames"; +import type { ReactNode } from "react"; + +function Msg({ data }: any) { + const cns = cn("toast", "show", data.type || null); + + let icon: ReactNode = null; + switch (data.type) { + case "success": + icon = ; + break; + case "error": + icon = ; + break; + } + + return ( +
    + {data.title && ( +
    + {icon} {data.title} +
    + )} +
    {data.message}
    +
    + ); +} +export { Msg }; diff --git a/frontend/src/notifications/helpers.tsx b/frontend/src/notifications/helpers.tsx new file mode 100644 index 00000000..99334c0f --- /dev/null +++ b/frontend/src/notifications/helpers.tsx @@ -0,0 +1,27 @@ +import { toast } from "react-toastify"; +import { intl } from "src/locale"; +import { Msg } from "./Msg"; +import styles from "./Msg.module.css"; + +const showSuccess = (message: string) => { + toast(Msg, { + className: styles.toaster, + data: { + type: "success", + title: intl.formatMessage({ id: "notification.success" }), + message, + }, + }); +}; + +const showError = (message: string) => { + toast(, { + data: { + type: "error", + title: intl.formatMessage({ id: "notification.error" }), + message, + }, + }); +}; + +export { showSuccess, showError }; diff --git a/frontend/src/notifications/index.ts b/frontend/src/notifications/index.ts new file mode 100644 index 00000000..d4e09d7b --- /dev/null +++ b/frontend/src/notifications/index.ts @@ -0,0 +1 @@ +export * from "./helpers"; diff --git a/frontend/src/pages/Access/index.tsx b/frontend/src/pages/Access/index.tsx index 2708f85f..71fd23fe 100644 --- a/frontend/src/pages/Access/index.tsx +++ b/frontend/src/pages/Access/index.tsx @@ -3,7 +3,7 @@ import TableWrapper from "./TableWrapper"; const Access = () => { return ( - + ); diff --git a/frontend/src/pages/AuditLog/index.tsx b/frontend/src/pages/AuditLog/index.tsx index 3680ecfb..4e251871 100644 --- a/frontend/src/pages/AuditLog/index.tsx +++ b/frontend/src/pages/AuditLog/index.tsx @@ -3,7 +3,7 @@ import AuditTable from "./AuditTable"; const AuditLog = () => { return ( - + ); diff --git a/frontend/src/pages/Certificates/index.tsx b/frontend/src/pages/Certificates/index.tsx index b4dca48c..7f1bd3a0 100644 --- a/frontend/src/pages/Certificates/index.tsx +++ b/frontend/src/pages/Certificates/index.tsx @@ -3,7 +3,7 @@ import CertificateTable from "./CertificateTable"; const Certificates = () => { return ( - + ); diff --git a/frontend/src/pages/Nginx/DeadHosts/index.tsx b/frontend/src/pages/Nginx/DeadHosts/index.tsx index 94e0ab68..99693045 100644 --- a/frontend/src/pages/Nginx/DeadHosts/index.tsx +++ b/frontend/src/pages/Nginx/DeadHosts/index.tsx @@ -3,7 +3,7 @@ import TableWrapper from "./TableWrapper"; const DeadHosts = () => { return ( - + ); diff --git a/frontend/src/pages/Nginx/ProxyHosts/index.tsx b/frontend/src/pages/Nginx/ProxyHosts/index.tsx index aa0e4774..80a8fd09 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/index.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/index.tsx @@ -3,7 +3,7 @@ import TableWrapper from "./TableWrapper"; const ProxyHosts = () => { return ( - + ); diff --git a/frontend/src/pages/Nginx/RedirectionHosts/index.tsx b/frontend/src/pages/Nginx/RedirectionHosts/index.tsx index bc636b35..3df72827 100644 --- a/frontend/src/pages/Nginx/RedirectionHosts/index.tsx +++ b/frontend/src/pages/Nginx/RedirectionHosts/index.tsx @@ -3,7 +3,7 @@ import TableWrapper from "./TableWrapper"; const RedirectionHosts = () => { return ( - + ); diff --git a/frontend/src/pages/Nginx/Streams/index.tsx b/frontend/src/pages/Nginx/Streams/index.tsx index ab3f8fc6..c997ea69 100644 --- a/frontend/src/pages/Nginx/Streams/index.tsx +++ b/frontend/src/pages/Nginx/Streams/index.tsx @@ -3,7 +3,7 @@ import TableWrapper from "./TableWrapper"; const Streams = () => { return ( - + ); diff --git a/frontend/src/pages/Settings/index.tsx b/frontend/src/pages/Settings/index.tsx index bb2e84e2..2774c795 100644 --- a/frontend/src/pages/Settings/index.tsx +++ b/frontend/src/pages/Settings/index.tsx @@ -3,7 +3,7 @@ import SettingTable from "./SettingTable"; const Settings = () => { return ( - + ); diff --git a/frontend/src/pages/Users/Empty.tsx b/frontend/src/pages/Users/Empty.tsx index 162e7094..f17b1f66 100644 --- a/frontend/src/pages/Users/Empty.tsx +++ b/frontend/src/pages/Users/Empty.tsx @@ -4,15 +4,18 @@ import { intl } from "src/locale"; interface Props { tableInstance: ReactTable; + onNewUser?: () => void; } -export default function Empty({ tableInstance }: Props) { +export default function Empty({ tableInstance, onNewUser }: Props) { return (
    diff --git a/frontend/src/pages/Users/Table.tsx b/frontend/src/pages/Users/Table.tsx index 6f09892d..e011528c 100644 --- a/frontend/src/pages/Users/Table.tsx +++ b/frontend/src/pages/Users/Table.tsx @@ -12,8 +12,9 @@ interface Props { isFetching?: boolean; currentUserId?: number; onEditUser?: (id: number) => void; + onNewUser?: () => void; } -export default function Table({ data, isFetching, currentUserId, onEditUser }: Props) { +export default function Table({ data, isFetching, currentUserId, onEditUser, onNewUser }: Props) { const columnHelper = createColumnHelper(); const columns = useMemo( () => [ @@ -124,5 +125,10 @@ export default function Table({ data, isFetching, currentUserId, onEditUser }: P enableSortingRemoval: false, }); - return } />; + return ( + } + /> + ); } diff --git a/frontend/src/pages/Users/TableWrapper.tsx b/frontend/src/pages/Users/TableWrapper.tsx index 4ec62e1e..be8eecea 100644 --- a/frontend/src/pages/Users/TableWrapper.tsx +++ b/frontend/src/pages/Users/TableWrapper.tsx @@ -8,7 +8,7 @@ import { UserModal } from "src/modals"; import Table from "./Table"; export default function TableWrapper() { - const [editUserId, setEditUserId] = useState(0); + const [editUserId, setEditUserId] = useState(0 as number | "new"); const { isFetching, isLoading, isError, error, data } = useUsers(["permissions"]); const { data: currentUser } = useUser("me"); @@ -42,7 +42,7 @@ export default function TableWrapper() { autoComplete="off" /> - @@ -54,6 +54,7 @@ export default function TableWrapper() { isFetching={isFetching} currentUserId={currentUser?.id} onEditUser={(id: number) => setEditUserId(id)} + onNewUser={() => setEditUserId("new")} /> {editUserId ? setEditUserId(0)} /> : null} diff --git a/frontend/src/pages/Users/index.tsx b/frontend/src/pages/Users/index.tsx index 64e8e6d7..3f20b165 100644 --- a/frontend/src/pages/Users/index.tsx +++ b/frontend/src/pages/Users/index.tsx @@ -3,7 +3,7 @@ import TableWrapper from "./TableWrapper"; const Users = () => { return ( - + ); From 6ab7198e61905de46a00649905cd200106cbd559 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 3 Sep 2025 19:13:00 +1000 Subject: [PATCH 12/82] User table polishing, user delete modal --- backend/models/token.js | 22 +++---- frontend/src/components/SiteHeader.tsx | 4 +- .../Table/Formatter/DomainsFormatter.tsx | 4 +- .../Table/Formatter/EmailFormatter.tsx | 10 +++ .../Table/Formatter/RolesFormatter.tsx | 20 ++++++ .../Formatter/ValueWithDateFormatter.tsx | 11 ++-- .../src/components/Table/Formatter/index.ts | 2 + frontend/src/locale/lang/en.json | 9 ++- frontend/src/locale/src/en.json | 27 ++++++-- frontend/src/modals/DeleteConfirmModal.tsx | 65 +++++++++++++++++++ frontend/src/modals/UserModal.tsx | 16 ++--- frontend/src/modals/index.ts | 1 + frontend/src/pages/Users/Table.tsx | 28 ++++++-- frontend/src/pages/Users/TableWrapper.tsx | 21 +++++- 14 files changed, 197 insertions(+), 43 deletions(-) create mode 100644 frontend/src/components/Table/Formatter/EmailFormatter.tsx create mode 100644 frontend/src/components/Table/Formatter/RolesFormatter.tsx create mode 100644 frontend/src/modals/DeleteConfirmModal.tsx diff --git a/backend/models/token.js b/backend/models/token.js index fb40e2a0..81a8700b 100644 --- a/backend/models/token.js +++ b/backend/models/token.js @@ -13,7 +13,7 @@ import { global as logger } from "../logger.js"; const ALGO = "RS256"; export default () => { - let token_data = {}; + let tokenData = {}; const self = { /** @@ -37,7 +37,7 @@ export default () => { if (err) { reject(err); } else { - token_data = payload; + tokenData = payload; resolve({ token: token, payload: payload, @@ -72,18 +72,18 @@ export default () => { reject(err); } } else { - token_data = result; + tokenData = result; // Hack: some tokens out in the wild have a scope of 'all' instead of 'user'. // For 30 days at least, we need to replace 'all' with user. if ( - typeof token_data.scope !== "undefined" && - _.indexOf(token_data.scope, "all") !== -1 + typeof tokenData.scope !== "undefined" && + _.indexOf(tokenData.scope, "all") !== -1 ) { - token_data.scope = ["user"]; + tokenData.scope = ["user"]; } - resolve(token_data); + resolve(tokenData); } }, ); @@ -100,15 +100,15 @@ export default () => { * @param {String} scope * @returns {Boolean} */ - hasScope: (scope) => typeof token_data.scope !== "undefined" && _.indexOf(token_data.scope, scope) !== -1, + hasScope: (scope) => typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, scope) !== -1, /** * @param {String} key * @return {*} */ get: (key) => { - if (typeof token_data[key] !== "undefined") { - return token_data[key]; + if (typeof tokenData[key] !== "undefined") { + return tokenData[key]; } return null; @@ -119,7 +119,7 @@ export default () => { * @param {*} value */ set: (key, value) => { - token_data[key] = value; + tokenData[key] = value; }, /** diff --git a/frontend/src/components/SiteHeader.tsx b/frontend/src/components/SiteHeader.tsx index f3491fc3..d60e26e5 100644 --- a/frontend/src/components/SiteHeader.tsx +++ b/frontend/src/components/SiteHeader.tsx @@ -66,7 +66,9 @@ export function SiteHeader() {
    {currentUser?.nickname}
    - {intl.formatMessage({ id: isAdmin ? "administrator" : "standard-user" })} + {intl.formatMessage({ + id: isAdmin ? "role.admin" : "role.standard-user", + })}
    diff --git a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx index 5ab339f2..daa7f08e 100644 --- a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx +++ b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx @@ -10,9 +10,9 @@ export function DomainsFormatter({ domains, createdOn }: Props) {
    {domains.map((domain: string) => ( - + {domain} - + ))}
    {createdOn ? ( diff --git a/frontend/src/components/Table/Formatter/EmailFormatter.tsx b/frontend/src/components/Table/Formatter/EmailFormatter.tsx new file mode 100644 index 00000000..3371c66a --- /dev/null +++ b/frontend/src/components/Table/Formatter/EmailFormatter.tsx @@ -0,0 +1,10 @@ +interface Props { + email: string; +} +export function EmailFormatter({ email }: Props) { + return ( + + {email} + + ); +} diff --git a/frontend/src/components/Table/Formatter/RolesFormatter.tsx b/frontend/src/components/Table/Formatter/RolesFormatter.tsx new file mode 100644 index 00000000..a2464863 --- /dev/null +++ b/frontend/src/components/Table/Formatter/RolesFormatter.tsx @@ -0,0 +1,20 @@ +import { intl } from "src/locale"; + +interface Props { + roles: string[]; +} +export function RolesFormatter({ roles }: Props) { + const r = roles || []; + if (r.length === 0) { + r[0] = "standard-user"; + } + return ( + <> + {r.map((role: string) => ( + + {intl.formatMessage({ id: `role.${role}` })} + + ))} + + ); +} diff --git a/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx index 0754c633..db8d60ef 100644 --- a/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx +++ b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx @@ -4,16 +4,19 @@ import { intl } from "src/locale"; interface Props { value: string; createdOn?: string; + disabled?: boolean; } -export function ValueWithDateFormatter({ value, createdOn }: Props) { +export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) { return (
    -
    {value}
    +
    {value}
    {createdOn ? ( -
    - {intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })} +
    + {disabled + ? intl.formatMessage({ id: "disabled" }) + : intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })}
    ) : null}
    diff --git a/frontend/src/components/Table/Formatter/index.ts b/frontend/src/components/Table/Formatter/index.ts index 2d67aa2e..60bcbf73 100644 --- a/frontend/src/components/Table/Formatter/index.ts +++ b/frontend/src/components/Table/Formatter/index.ts @@ -1,5 +1,7 @@ export * from "./CertificateFormatter"; export * from "./DomainsFormatter"; +export * from "./EmailFormatter"; export * from "./GravatarFormatter"; +export * from "./RolesFormatter"; export * from "./StatusFormatter"; export * from "./ValueWithDateFormatter"; diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json index f59a6d4d..d71431d1 100644 --- a/frontend/src/locale/lang/en.json +++ b/frontend/src/locale/lang/en.json @@ -12,7 +12,6 @@ "action.edit": "Edit", "action.enable": "Enable", "action.permissions": "Permissions", - "administrator": "Administrator", "auditlog.title": "Audit Log", "cancel": "Cancel", "certificates.title": "SSL Certificates", @@ -37,6 +36,7 @@ "dead-hosts.count": "{count} 404 Hosts", "dead-hosts.empty": "There are no 404 Hosts", "dead-hosts.title": "404 Hosts", + "disabled": "Disabled", "email-address": "Email address", "empty-subtitle": "Why don't you create one?", "error.invalid-auth": "Invalid email or password", @@ -53,6 +53,7 @@ "notfound.title": "Oops… You just found an error page", "notification.error": "Error", "notification.success": "Success", + "notification.user-deleted": "User has been deleted", "notification.user-saved": "User has been saved", "offline": "Offline", "online": "Online", @@ -67,10 +68,11 @@ "redirection-hosts.count": "{count} Redirection Hosts", "redirection-hosts.empty": "There are no Redirection Hosts", "redirection-hosts.title": "Redirection Hosts", + "role.admin": "Administrator", + "role.standard-user": "Standard User", "save": "Save", "settings.title": "Settings", "sign-in": "Sign in", - "standard-user": "Apache Helicopter", "streams.actions-title": "Stream #{id}", "streams.add": "Add Stream", "streams.count": "{count} Streams", @@ -81,8 +83,11 @@ "user.change-password": "Change Password", "user.confirm-password": "Confirm Password", "user.current-password": "Current Password", + "user.delete.content": "Are you sure you want to delete this user?", + "user.delete.title": "Delete User", "user.edit": "Edit User", "user.edit-profile": "Edit Profile", + "user.flags.title": "Properties", "user.full-name": "Full Name", "user.logout": "Logout", "user.new": "New User", diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index d37553e9..dd20f393 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -38,9 +38,6 @@ "action.permissions": { "defaultMessage": "Permissions" }, - "administrator": { - "defaultMessage": "Administrator" - }, "auditlog.title": { "defaultMessage": "Audit Log" }, @@ -113,6 +110,9 @@ "dead-hosts.title": { "defaultMessage": "404 Hosts" }, + "disabled": { + "defaultMessage": "Disabled" + }, "email-address": { "defaultMessage": "Email address" }, @@ -158,6 +158,9 @@ "notification.error": { "defaultMessage": "Error" }, + "notification.user-deleted": { + "defaultMessage": "User has been deleted" + }, "notification.user-saved": { "defaultMessage": "User has been saved" }, @@ -203,6 +206,12 @@ "redirection-hosts.title": { "defaultMessage": "Redirection Hosts" }, + "role.admin": { + "defaultMessage": "Administrator" + }, + "role.standard-user": { + "defaultMessage": "Standard User" + }, "save": { "defaultMessage": "Save" }, @@ -212,9 +221,6 @@ "sign-in": { "defaultMessage": "Sign in" }, - "standard-user": { - "defaultMessage": "Apache Helicopter" - }, "streams.actions-title": { "defaultMessage": "Stream #{id}" }, @@ -245,12 +251,21 @@ "user.current-password": { "defaultMessage": "Current Password" }, + "user.delete.title": { + "defaultMessage": "Delete User" + }, + "user.delete.content": { + "defaultMessage": "Are you sure you want to delete this user?" + }, "user.edit": { "defaultMessage": "Edit User" }, "user.edit-profile": { "defaultMessage": "Edit Profile" }, + "user.flags.title": { + "defaultMessage": "Properties" + }, "user.full-name": { "defaultMessage": "Full Name" }, diff --git a/frontend/src/modals/DeleteConfirmModal.tsx b/frontend/src/modals/DeleteConfirmModal.tsx new file mode 100644 index 00000000..fee9e046 --- /dev/null +++ b/frontend/src/modals/DeleteConfirmModal.tsx @@ -0,0 +1,65 @@ +import { useQueryClient } from "@tanstack/react-query"; +import { type ReactNode, useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { Button } from "src/components"; +import { intl } from "src/locale"; + +interface Props { + title: string; + children: ReactNode; + onConfirm: () => Promise | void; + onClose: () => void; + invalidations?: any[]; +} +export function DeleteConfirmModal({ title, children, onConfirm, onClose, invalidations }: Props) { + const queryClient = useQueryClient(); + const [error, setError] = useState(null); + const [submitting, setSubmitting] = useState(false); + + const onSubmit = async () => { + setSubmitting(true); + setError(null); + try { + await onConfirm(); + onClose(); + // invalidate caches as requested + invalidations?.forEach((inv) => { + queryClient.invalidateQueries({ queryKey: inv }); + }); + } catch (err: any) { + setError(intl.formatMessage({ id: err.message })); + } + setSubmitting(false); + }; + + return ( + + + {title} + + + setError(null)} dismissible> + {error} + + {children} + + + + + + + ); +} diff --git a/frontend/src/modals/UserModal.tsx b/frontend/src/modals/UserModal.tsx index 293b2ae9..b7131a85 100644 --- a/frontend/src/modals/UserModal.tsx +++ b/frontend/src/modals/UserModal.tsx @@ -18,11 +18,6 @@ export function UserModal({ userId, onClose }: Props) { const { mutate: setUser } = useSetUser(); const [errorMsg, setErrorMsg] = useState(null); - if (data && currentUser) { - console.log("DATA:", data); - console.log("CURRENT:", currentUser); - } - const onSubmit = async (values: any, { setSubmitting }: any) => { setErrorMsg(null); const { ...payload } = { @@ -161,12 +156,13 @@ export function UserModal({ userId, onClose }: Props) {
    {currentUser && data && currentUser?.id !== data?.id ? (
    -

    Properties

    - +

    {intl.formatMessage({ id: "user.flags.title" })}

    ); From 429046f32eb2585f2ff8802f1de86c0a1e50bf4b Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Sun, 14 Sep 2025 14:00:00 +1000 Subject: [PATCH 20/82] Audit log table and modal --- backend/internal/audit-log.js | 29 ++++++++ backend/routes/audit-log.js | 52 +++++++++++++ frontend/src/api/backend/getAuditLog.ts | 5 +- frontend/src/api/backend/getAuditLogs.ts | 14 ++++ frontend/src/api/backend/index.ts | 1 + frontend/src/api/backend/models.ts | 2 + .../Table/Formatter/DomainsFormatter.tsx | 5 +- .../Table/Formatter/EventFormatter.tsx | 53 +++++++++++++ .../Formatter/ValueWithDateFormatter.tsx | 5 +- .../src/components/Table/Formatter/index.ts | 1 + frontend/src/hooks/index.ts | 2 + frontend/src/hooks/useAuditLog.ts | 17 +++++ frontend/src/hooks/useAuditLogs.ts | 17 +++++ frontend/src/locale/DateTimeFormat.ts | 15 ++++ frontend/src/locale/index.ts | 1 + frontend/src/locale/lang/en.json | 6 ++ frontend/src/locale/src/en.json | 18 +++++ frontend/src/modals/EventDetailsModal.tsx | 50 +++++++++++++ frontend/src/modals/PermissionsModal.tsx | 6 +- frontend/src/modals/UserModal.tsx | 6 +- frontend/src/modals/index.ts | 1 + frontend/src/pages/AuditLog/Table.tsx | 74 +++++++++++++++++++ frontend/src/pages/AuditLog/TableWrapper.tsx | 53 +++++++++++++ frontend/src/pages/AuditLog/index.tsx | 4 +- 24 files changed, 425 insertions(+), 12 deletions(-) create mode 100644 frontend/src/api/backend/getAuditLogs.ts create mode 100644 frontend/src/components/Table/Formatter/EventFormatter.tsx create mode 100644 frontend/src/hooks/useAuditLog.ts create mode 100644 frontend/src/hooks/useAuditLogs.ts create mode 100644 frontend/src/locale/DateTimeFormat.ts create mode 100644 frontend/src/modals/EventDetailsModal.tsx create mode 100644 frontend/src/pages/AuditLog/Table.tsx create mode 100644 frontend/src/pages/AuditLog/TableWrapper.tsx diff --git a/backend/internal/audit-log.js b/backend/internal/audit-log.js index 6d3ee9ac..02700dc5 100644 --- a/backend/internal/audit-log.js +++ b/backend/internal/audit-log.js @@ -36,6 +36,35 @@ const internalAuditLog = { return await query; }, + /** + * @param {Access} access + * @param {Object} [data] + * @param {Integer} [data.id] Defaults to the token user + * @param {Array} [data.expand] + * @return {Promise} + */ + get: async (access, data) => { + await access.can("auditlog:list"); + + const query = auditLogModel + .query() + .andWhere("id", data.id) + .allowGraph("[user]") + .first(); + + if (typeof data.expand !== "undefined" && data.expand !== null) { + query.withGraphFetched(`[${data.expand.join(", ")}]`); + } + + const row = await query; + + if (!row?.id) { + throw new errs.ItemNotFoundError(data.id); + } + + return row; + }, + /** * This method should not be publicly used, it doesn't check certain things. It will be assumed * that permission to add to audit log is already considered, however the access token is used for diff --git a/backend/routes/audit-log.js b/backend/routes/audit-log.js index 0c51e38a..7cd232df 100644 --- a/backend/routes/audit-log.js +++ b/backend/routes/audit-log.js @@ -52,4 +52,56 @@ router } }); +/** + * Specific audit log entry + * + * /api/audit-log/123 + */ +router + .route("/:event_id") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * GET /api/audit-log/123 + * + * Retrieve a specific entry + */ + .get(async (req, res, next) => { + try { + const data = await validator( + { + required: ["event_id"], + additionalProperties: false, + properties: { + event_id: { + $ref: "common#/properties/id", + }, + expand: { + $ref: "common#/properties/expand", + }, + }, + }, + { + event_id: req.params.event_id, + expand: + typeof req.query.expand === "string" + ? req.query.expand.split(",") + : null, + }, + ); + + const item = await internalAuditLog.get(res.locals.access, { + id: data.event_id, + expand: data.expand, + }); + res.status(200).send(item); + } catch (err) { + logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + export default router; diff --git a/frontend/src/api/backend/getAuditLog.ts b/frontend/src/api/backend/getAuditLog.ts index e798b8fb..cea0872d 100644 --- a/frontend/src/api/backend/getAuditLog.ts +++ b/frontend/src/api/backend/getAuditLog.ts @@ -1,9 +1,10 @@ import * as api from "./base"; +import type { AuditLogExpansion } from "./getAuditLogs"; import type { AuditLog } from "./models"; -export async function getAuditLog(expand?: string[], params = {}): Promise { +export async function getAuditLog(id: number, expand?: AuditLogExpansion[], params = {}): Promise { return await api.get({ - url: "/audit-log", + url: `/audit-log/${id}`, params: { expand: expand?.join(","), ...params, diff --git a/frontend/src/api/backend/getAuditLogs.ts b/frontend/src/api/backend/getAuditLogs.ts new file mode 100644 index 00000000..e1be7c28 --- /dev/null +++ b/frontend/src/api/backend/getAuditLogs.ts @@ -0,0 +1,14 @@ +import * as api from "./base"; +import type { AuditLog } from "./models"; + +export type AuditLogExpansion = "user"; + +export async function getAuditLogs(expand?: AuditLogExpansion[], params = {}): Promise { + return await api.get({ + url: "/audit-log", + params: { + expand: expand?.join(","), + ...params, + }, + }); +} diff --git a/frontend/src/api/backend/index.ts b/frontend/src/api/backend/index.ts index 9cd5b526..f5e63edc 100644 --- a/frontend/src/api/backend/index.ts +++ b/frontend/src/api/backend/index.ts @@ -16,6 +16,7 @@ export * from "./downloadCertificate"; export * from "./getAccessList"; export * from "./getAccessLists"; export * from "./getAuditLog"; +export * from "./getAuditLogs"; export * from "./getCertificate"; export * from "./getCertificates"; export * from "./getDeadHost"; diff --git a/frontend/src/api/backend/models.ts b/frontend/src/api/backend/models.ts index 328c8fc6..7a8037a7 100644 --- a/frontend/src/api/backend/models.ts +++ b/frontend/src/api/backend/models.ts @@ -40,6 +40,8 @@ export interface AuditLog { objectId: number; action: string; meta: Record; + // Expansions: + user?: User; } export interface AccessList { diff --git a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx index daa7f08e..6f5a037f 100644 --- a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx +++ b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx @@ -1,5 +1,4 @@ -import { intlFormat, parseISO } from "date-fns"; -import { intl } from "src/locale"; +import { DateTimeFormat, intl } from "src/locale"; interface Props { domains: string[]; @@ -17,7 +16,7 @@ export function DomainsFormatter({ domains, createdOn }: Props) {
    {createdOn ? (
    - {intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })} + {intl.formatMessage({ id: "created-on" }, { date: DateTimeFormat(createdOn) })}
    ) : null}
    diff --git a/frontend/src/components/Table/Formatter/EventFormatter.tsx b/frontend/src/components/Table/Formatter/EventFormatter.tsx new file mode 100644 index 00000000..77892b3d --- /dev/null +++ b/frontend/src/components/Table/Formatter/EventFormatter.tsx @@ -0,0 +1,53 @@ +import { IconUser } from "@tabler/icons-react"; +import type { AuditLog } from "src/api/backend"; +import { DateTimeFormat, intl } from "src/locale"; + +const getEventTitle = (event: AuditLog) => ( + {intl.formatMessage({ id: `event.${event.action}-${event.objectType}` })} +); + +const getEventValue = (event: AuditLog) => { + switch (event.objectType) { + case "user": + return event.meta?.name; + default: + return `UNKNOWN EVENT TYPE: ${event.objectType}`; + } +}; + +const getColorForAction = (action: string) => { + switch (action) { + case "created": + return "text-lime"; + case "deleted": + return "text-red"; + default: + return "text-blue"; + } +}; + +const getIcon = (row: AuditLog) => { + const c = getColorForAction(row.action); + let ico = null; + switch (row.objectType) { + case "user": + ico = ; + break; + } + + return ico; +}; + +interface Props { + row: AuditLog; +} +export function EventFormatter({ row }: Props) { + return ( +
    +
    + {getIcon(row)} {getEventTitle(row)} — {getEventValue(row)} +
    +
    {DateTimeFormat(row.createdOn)}
    +
    + ); +} diff --git a/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx index db8d60ef..a01ab640 100644 --- a/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx +++ b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx @@ -1,5 +1,4 @@ -import { intlFormat, parseISO } from "date-fns"; -import { intl } from "src/locale"; +import { DateTimeFormat, intl } from "src/locale"; interface Props { value: string; @@ -16,7 +15,7 @@ export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) {
    {disabled ? intl.formatMessage({ id: "disabled" }) - : intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })} + : intl.formatMessage({ id: "created-on" }, { date: DateTimeFormat(createdOn) })}
    ) : null}
    diff --git a/frontend/src/components/Table/Formatter/index.ts b/frontend/src/components/Table/Formatter/index.ts index 60bcbf73..33f447b2 100644 --- a/frontend/src/components/Table/Formatter/index.ts +++ b/frontend/src/components/Table/Formatter/index.ts @@ -1,6 +1,7 @@ export * from "./CertificateFormatter"; export * from "./DomainsFormatter"; export * from "./EmailFormatter"; +export * from "./EventFormatter"; export * from "./GravatarFormatter"; export * from "./RolesFormatter"; export * from "./StatusFormatter"; diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index 2c9b4921..f163b904 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -1,4 +1,6 @@ export * from "./useAccessLists"; +export * from "./useAuditLog"; +export * from "./useAuditLogs"; export * from "./useDeadHosts"; export * from "./useHealth"; export * from "./useHostReport"; diff --git a/frontend/src/hooks/useAuditLog.ts b/frontend/src/hooks/useAuditLog.ts new file mode 100644 index 00000000..95e08ebc --- /dev/null +++ b/frontend/src/hooks/useAuditLog.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type AuditLog, getAuditLog } from "src/api/backend"; + +const fetchAuditLog = (id: number) => { + return getAuditLog(id, ["user"]); +}; + +const useAuditLog = (id: number, options = {}) => { + return useQuery({ + queryKey: ["audit-log", id], + queryFn: () => fetchAuditLog(id), + staleTime: 5 * 60 * 1000, // 5 minutes + ...options, + }); +}; + +export { useAuditLog }; diff --git a/frontend/src/hooks/useAuditLogs.ts b/frontend/src/hooks/useAuditLogs.ts new file mode 100644 index 00000000..bbe8b506 --- /dev/null +++ b/frontend/src/hooks/useAuditLogs.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type AuditLog, type AuditLogExpansion, getAuditLogs } from "src/api/backend"; + +const fetchAuditLogs = (expand?: AuditLogExpansion[]) => { + return getAuditLogs(expand); +}; + +const useAuditLogs = (expand?: AuditLogExpansion[], options = {}) => { + return useQuery({ + queryKey: ["audit-logs", { expand }], + queryFn: () => fetchAuditLogs(expand), + staleTime: 10 * 1000, + ...options, + }); +}; + +export { fetchAuditLogs, useAuditLogs }; diff --git a/frontend/src/locale/DateTimeFormat.ts b/frontend/src/locale/DateTimeFormat.ts new file mode 100644 index 00000000..fb8e66c8 --- /dev/null +++ b/frontend/src/locale/DateTimeFormat.ts @@ -0,0 +1,15 @@ +import { intlFormat, parseISO } from "date-fns"; + +const DateTimeFormat = (isoDate: string) => + intlFormat(parseISO(isoDate), { + weekday: "long", + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric", + hour12: true, + }); + +export { DateTimeFormat }; diff --git a/frontend/src/locale/index.ts b/frontend/src/locale/index.ts index c6dca46d..6d9ac03c 100644 --- a/frontend/src/locale/index.ts +++ b/frontend/src/locale/index.ts @@ -1 +1,2 @@ +export * from "./DateTimeFormat"; export * from "./IntlProvider"; diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json index e1e7cc5e..c857c08a 100644 --- a/frontend/src/locale/lang/en.json +++ b/frontend/src/locale/lang/en.json @@ -12,13 +12,16 @@ "action.edit": "Edit", "action.enable": "Enable", "action.permissions": "Permissions", + "action.view-details": "View Details", "auditlog.title": "Audit Log", "cancel": "Cancel", "certificates.title": "SSL Certificates", + "close": "Close", "column.access": "Access", "column.authorization": "Authorization", "column.destination": "Destination", "column.email": "Email", + "column.event": "Event", "column.http-code": "Access", "column.incoming-port": "Incoming Port", "column.name": "Name", @@ -41,6 +44,9 @@ "empty-subtitle": "Why don't you create one?", "error.invalid-auth": "Invalid email or password", "error.passwords-must-match": "Passwords must match", + "event.created-user": "Created User", + "event.deleted-user": "Deleted User", + "event.updated-user": "Updated User", "footer.github-fork": "Fork me on Github", "hosts.title": "Hosts", "http-only": "HTTP Only", diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index 951a47ba..b67e8207 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -41,12 +41,18 @@ "auditlog.title": { "defaultMessage": "Audit Log" }, + "action.view-details": { + "defaultMessage": "View Details" + }, "cancel": { "defaultMessage": "Cancel" }, "certificates.title": { "defaultMessage": "SSL Certificates" }, + "close": { + "defaultMessage": "Close" + }, "created-on": { "defaultMessage": "Created: {date}" }, @@ -62,6 +68,9 @@ "column.email": { "defaultMessage": "Email" }, + "column.event": { + "defaultMessage": "Event" + }, "column.http-code": { "defaultMessage": "Access" }, @@ -122,6 +131,15 @@ "error.invalid-auth": { "defaultMessage": "Invalid email or password" }, + "event.created-user": { + "defaultMessage": "Created User" + }, + "event.deleted-user": { + "defaultMessage": "Deleted User" + }, + "event.updated-user": { + "defaultMessage": "Updated User" + }, "footer.github-fork": { "defaultMessage": "Fork me on Github" }, diff --git a/frontend/src/modals/EventDetailsModal.tsx b/frontend/src/modals/EventDetailsModal.tsx new file mode 100644 index 00000000..c105c3f5 --- /dev/null +++ b/frontend/src/modals/EventDetailsModal.tsx @@ -0,0 +1,50 @@ +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { Button, EventFormatter, GravatarFormatter, Loading } from "src/components"; +import { useAuditLog } from "src/hooks"; +import { intl } from "src/locale"; + +interface Props { + id: number; + onClose: () => void; +} +export function EventDetailsModal({ id, onClose }: Props) { + const { data, isLoading, error } = useAuditLog(id); + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + {!isLoading && data && ( + <> + + {intl.formatMessage({ id: "action.view-details" })} + + +
    +
    + +
    +
    + +
    +
    +
    +								{JSON.stringify(data.meta, null, 2)}
    +							
    +
    +
    + + + + + )} +
    + ); +} diff --git a/frontend/src/modals/PermissionsModal.tsx b/frontend/src/modals/PermissionsModal.tsx index e3368418..6e8e04cd 100644 --- a/frontend/src/modals/PermissionsModal.tsx +++ b/frontend/src/modals/PermissionsModal.tsx @@ -83,7 +83,11 @@ export function PermissionsModal({ userId, onClose }: Props) { return ( - {!isLoading && error && {error?.message || "Unknown error"}} + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} {isLoading && } {!isLoading && data && ( - {!isLoading && error && {error?.message || "Unknown error"}} + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} {(isLoading || currentIsLoading) && } {!isLoading && !currentIsLoading && data && currentUser && ( void; +} +export default function Table({ data, isFetching, onSelectItem }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: AuditLog) => row.user, { + id: "user.avatar", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: AuditLog) => row.user?.name, { + id: "user.name", + header: intl.formatMessage({ id: "column.name" }), + }), + columnHelper.accessor((row: AuditLog) => row, { + id: "objectType", + header: intl.formatMessage({ id: "column.event" }), + cell: (info: any) => { + return ; + }, + }), + columnHelper.display({ + id: "id", + cell: (info: any) => { + return ( + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper, onSelectItem], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return ; +} diff --git a/frontend/src/pages/AuditLog/TableWrapper.tsx b/frontend/src/pages/AuditLog/TableWrapper.tsx new file mode 100644 index 00000000..17422f2c --- /dev/null +++ b/frontend/src/pages/AuditLog/TableWrapper.tsx @@ -0,0 +1,53 @@ +import { IconSearch } from "@tabler/icons-react"; +import { useState } from "react"; +import Alert from "react-bootstrap/Alert"; +import { LoadingPage } from "src/components"; +import { useAuditLogs } from "src/hooks"; +import { intl } from "src/locale"; +import { EventDetailsModal } from "src/modals"; +import Table from "./Table"; + +export default function TableWrapper() { + const [eventId, setEventId] = useState(0); + const { isFetching, isLoading, isError, error, data } = useAuditLogs(["user"]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + return ( +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "auditlog.title" })}

    +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "proxy-hosts.empty" })}

    {intl.formatMessage({ id: "empty-subtitle" })}

    - +
    + {eventId ? setEventId(0)} /> : null} + + + ); +} diff --git a/frontend/src/pages/AuditLog/index.tsx b/frontend/src/pages/AuditLog/index.tsx index 4e251871..af124229 100644 --- a/frontend/src/pages/AuditLog/index.tsx +++ b/frontend/src/pages/AuditLog/index.tsx @@ -1,10 +1,10 @@ import { HasPermission } from "src/components"; -import AuditTable from "./AuditTable"; +import TableWrapper from "./TableWrapper"; const AuditLog = () => { return ( - + ); }; From efcefe0c1771200a6e9dc9de614219a55e9a0de6 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 15 Sep 2025 17:34:00 +1000 Subject: [PATCH 21/82] Fix custom cert writes, fix schema --- backend/internal/certificate.js | 2 +- backend/schema/components/audit-log-list.json | 7 ++ .../schema/components/audit-log-object.json | 14 +++- backend/schema/components/stream-object.json | 2 +- backend/schema/paths/audit-log/get.json | 6 +- backend/schema/paths/audit-log/id/get.json | 73 +++++++++++++++++++ backend/schema/swagger.json | 5 ++ 7 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 backend/schema/components/audit-log-list.json create mode 100644 backend/schema/paths/audit-log/id/get.json diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index f776474d..76eb8fa5 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -584,7 +584,7 @@ const internalCertificate = { } }); - const certificate = internalCertificate.update(access, { + const certificate = await internalCertificate.update(access, { id: data.id, expires_on: moment(validations.certificate.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), domain_names: [validations.certificate.cn], diff --git a/backend/schema/components/audit-log-list.json b/backend/schema/components/audit-log-list.json new file mode 100644 index 00000000..74368528 --- /dev/null +++ b/backend/schema/components/audit-log-list.json @@ -0,0 +1,7 @@ +{ + "type": "array", + "description": "Audit Log list", + "items": { + "$ref": "./audit-log-object.json" + } +} diff --git a/backend/schema/components/audit-log-object.json b/backend/schema/components/audit-log-object.json index 3e5e8594..4ed25e5c 100644 --- a/backend/schema/components/audit-log-object.json +++ b/backend/schema/components/audit-log-object.json @@ -1,7 +1,16 @@ { "type": "object", "description": "Audit Log object", - "required": ["id", "created_on", "modified_on", "user_id", "object_type", "object_id", "action", "meta"], + "required": [ + "id", + "created_on", + "modified_on", + "user_id", + "object_type", + "object_id", + "action", + "meta" + ], "additionalProperties": false, "properties": { "id": { @@ -27,6 +36,9 @@ }, "meta": { "type": "object" + }, + "user": { + "$ref": "./user-object.json" } } } diff --git a/backend/schema/components/stream-object.json b/backend/schema/components/stream-object.json index 848c30e6..d4ba0a27 100644 --- a/backend/schema/components/stream-object.json +++ b/backend/schema/components/stream-object.json @@ -31,7 +31,7 @@ }, { "type": "string", - "format": "ipv4" + "format": "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$" }, { "type": "string", diff --git a/backend/schema/paths/audit-log/get.json b/backend/schema/paths/audit-log/get.json index bc43e29d..ecda9cef 100644 --- a/backend/schema/paths/audit-log/get.json +++ b/backend/schema/paths/audit-log/get.json @@ -1,6 +1,6 @@ { - "operationId": "getAuditLog", - "summary": "Get Audit Log", + "operationId": "getAuditLogs", + "summary": "Get Audit Logs", "tags": ["Audit Log"], "security": [ { @@ -44,7 +44,7 @@ } }, "schema": { - "$ref": "../../components/audit-log-object.json" + "$ref": "../../components/audit-log-list.json" } } } diff --git a/backend/schema/paths/audit-log/id/get.json b/backend/schema/paths/audit-log/id/get.json new file mode 100644 index 00000000..74f59723 --- /dev/null +++ b/backend/schema/paths/audit-log/id/get.json @@ -0,0 +1,73 @@ +{ + "operationId": "getAuditLog", + "summary": "Get Audit Log Event", + "tags": [ + "Audit Log" + ], + "security": [ + { + "BearerAuth": [ + "audit-log" + ] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "example": 1 + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "id": 1, + "created_on": "2025-09-15T17:27:45.000Z", + "modified_on": "2025-09-15T17:27:45.000Z", + "user_id": 1, + "object_type": "user", + "object_id": 1, + "action": "created", + "meta": { + "id": 1, + "created_on": "2025-09-15T17:27:45.000Z", + "modified_on": "2025-09-15T17:27:45.000Z", + "is_disabled": false, + "email": "jc@jc21.com", + "name": "Jamie", + "nickname": "Jamie", + "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", + "roles": [ + "admin" + ], + "permissions": { + "visibility": "all", + "proxy_hosts": "manage", + "redirection_hosts": "manage", + "dead_hosts": "manage", + "streams": "manage", + "access_lists": "manage", + "certificates": "manage" + } + } + } + } + }, + "schema": { + "$ref": "../../../components/audit-log-object.json" + } + } + } + } + } +} diff --git a/backend/schema/swagger.json b/backend/schema/swagger.json index 4a502b4e..b1076916 100644 --- a/backend/schema/swagger.json +++ b/backend/schema/swagger.json @@ -29,6 +29,11 @@ "$ref": "./paths/audit-log/get.json" } }, + "/audit-log/{id}": { + "get": { + "$ref": "./paths/audit-log/id/get.json" + } + }, "/nginx/access-lists": { "get": { "$ref": "./paths/nginx/access-lists/get.json" From 058f49ceea03ccbe6d9fa6fadc64c852f1ca93c2 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 17 Sep 2025 18:01:00 +1000 Subject: [PATCH 22/82] Certificates react table basis --- backend/internal/certificate.js | 5 +- frontend/src/api/backend/getCertificates.ts | 4 +- frontend/src/hooks/index.ts | 1 + frontend/src/hooks/useCertificates.ts | 17 +++ frontend/src/locale/lang/en.json | 6 + frontend/src/locale/src/en.json | 18 +++ frontend/src/pages/Certificates/Empty.tsx | 36 ++++++ frontend/src/pages/Certificates/Table.tsx | 116 ++++++++++++++++++ .../src/pages/Certificates/TableWrapper.tsx | 71 +++++++++++ frontend/src/pages/Certificates/index.tsx | 4 +- 10 files changed, 271 insertions(+), 7 deletions(-) create mode 100644 frontend/src/hooks/useCertificates.ts create mode 100644 frontend/src/pages/Certificates/Empty.tsx create mode 100644 frontend/src/pages/Certificates/Table.tsx create mode 100644 frontend/src/pages/Certificates/TableWrapper.tsx diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 76eb8fa5..ec1abf26 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -406,10 +406,7 @@ const internalCertificate = { .query() .where("is_deleted", 0) .groupBy("id") - .allowGraph("[owner]") - .allowGraph("[proxy_hosts]") - .allowGraph("[redirection_hosts]") - .allowGraph("[dead_hosts]") + .allowGraph("[owner,proxy_hosts,redirection_hosts,dead_hosts]") .orderBy("nice_name", "ASC"); if (accessData.permission_visibility !== "all") { diff --git a/frontend/src/api/backend/getCertificates.ts b/frontend/src/api/backend/getCertificates.ts index 15660b97..9389fe44 100644 --- a/frontend/src/api/backend/getCertificates.ts +++ b/frontend/src/api/backend/getCertificates.ts @@ -1,7 +1,9 @@ import * as api from "./base"; import type { Certificate } from "./models"; -export async function getCertificates(expand?: string[], params = {}): Promise { +export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts"; + +export async function getCertificates(expand?: CertificateExpansion[], params = {}): Promise { return await api.get({ url: "/nginx/certificates", params: { diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index f163b904..91738be9 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -1,6 +1,7 @@ export * from "./useAccessLists"; export * from "./useAuditLog"; export * from "./useAuditLogs"; +export * from "./useCertificates"; export * from "./useDeadHosts"; export * from "./useHealth"; export * from "./useHostReport"; diff --git a/frontend/src/hooks/useCertificates.ts b/frontend/src/hooks/useCertificates.ts new file mode 100644 index 00000000..261c79d8 --- /dev/null +++ b/frontend/src/hooks/useCertificates.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { type Certificate, type CertificateExpansion, getCertificates } from "src/api/backend"; + +const fetchCertificates = (expand?: CertificateExpansion[]) => { + return getCertificates(expand); +}; + +const useCertificates = (expand?: CertificateExpansion[], options = {}) => { + return useQuery({ + queryKey: ["certificates", { expand }], + queryFn: () => fetchCertificates(expand), + staleTime: 60 * 1000, + ...options, + }); +}; + +export { fetchCertificates, useCertificates }; diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json index c857c08a..cabcdcde 100644 --- a/frontend/src/locale/lang/en.json +++ b/frontend/src/locale/lang/en.json @@ -15,6 +15,10 @@ "action.view-details": "View Details", "auditlog.title": "Audit Log", "cancel": "Cancel", + "certificates.actions-title": "Certificate #{id}", + "certificates.add": "Add Certificate", + "certificates.custom": "Custom Certificate", + "certificates.empty": "There are no Certificates", "certificates.title": "SSL Certificates", "close": "Close", "column.access": "Access", @@ -22,10 +26,12 @@ "column.destination": "Destination", "column.email": "Email", "column.event": "Event", + "column.expires": "Expires", "column.http-code": "Access", "column.incoming-port": "Incoming Port", "column.name": "Name", "column.protocol": "Protocol", + "column.provider": "Provider", "column.roles": "Roles", "column.satisfy": "Satisfy", "column.scheme": "Scheme", diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index b67e8207..78a2bd16 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -47,6 +47,18 @@ "cancel": { "defaultMessage": "Cancel" }, + "certificates.actions-title": { + "defaultMessage": "Certificate #{id}" + }, + "certificates.add": { + "defaultMessage": "Add Certificate" + }, + "certificates.custom": { + "defaultMessage": "Custom Certificate" + }, + "certificates.empty": { + "defaultMessage": "There are no Certificates" + }, "certificates.title": { "defaultMessage": "SSL Certificates" }, @@ -71,6 +83,9 @@ "column.event": { "defaultMessage": "Event" }, + "column.expires": { + "defaultMessage": "Expires" + }, "column.http-code": { "defaultMessage": "Access" }, @@ -83,6 +98,9 @@ "column.protocol": { "defaultMessage": "Protocol" }, + "column.provider": { + "defaultMessage": "Provider" + }, "column.roles": { "defaultMessage": "Roles" }, diff --git a/frontend/src/pages/Certificates/Empty.tsx b/frontend/src/pages/Certificates/Empty.tsx new file mode 100644 index 00000000..ba96e86a --- /dev/null +++ b/frontend/src/pages/Certificates/Empty.tsx @@ -0,0 +1,36 @@ +import type { Table as ReactTable } from "@tanstack/react-table"; +import { intl } from "src/locale"; + +/** + * This component should never render as there should always be 1 user minimum, + * but I'm keeping it for consistency. + */ + +interface Props { + tableInstance: ReactTable; +} +export default function Empty({ tableInstance }: Props) { + return ( + + + + ); +} diff --git a/frontend/src/pages/Certificates/Table.tsx b/frontend/src/pages/Certificates/Table.tsx new file mode 100644 index 00000000..77007031 --- /dev/null +++ b/frontend/src/pages/Certificates/Table.tsx @@ -0,0 +1,116 @@ +import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react"; +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { useMemo } from "react"; +import type { Certificate } from "src/api/backend"; +import { DomainsFormatter, GravatarFormatter } from "src/components"; +import { TableLayout } from "src/components/Table/TableLayout"; +import { intl } from "src/locale"; +import Empty from "./Empty"; + +interface Props { + data: Certificate[]; + isFetching?: boolean; +} +export default function Table({ data, isFetching }: Props) { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: any) => row.owner, { + id: "owner", + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + meta: { + className: "w-1", + }, + }), + columnHelper.accessor((row: any) => row, { + id: "domainNames", + header: intl.formatMessage({ id: "column.name" }), + cell: (info: any) => { + const value = info.getValue(); + return ; + }, + }), + columnHelper.accessor((row: any) => row.provider, { + id: "provider", + header: intl.formatMessage({ id: "column.provider" }), + cell: (info: any) => { + return info.getValue(); + }, + }), + columnHelper.accessor((row: any) => row.expires_on, { + id: "expires_on", + header: intl.formatMessage({ id: "column.expires" }), + cell: (info: any) => { + return info.getValue(); + }, + }), + columnHelper.accessor((row: any) => row, { + id: "id", + header: intl.formatMessage({ id: "column.status" }), + cell: (info: any) => { + return info.getValue(); + }, + }), + columnHelper.display({ + id: "id", // todo: not needed for a display? + cell: (info: any) => { + return ( + + +
    + + {intl.formatMessage( + { + id: "certificates.actions-title", + }, + { id: info.row.original.id }, + )} + + + + {intl.formatMessage({ id: "action.edit" })} + + + + {intl.formatMessage({ id: "action.disable" })} + + + + ); + }, + meta: { + className: "text-end w-1", + }, + }), + ], + [columnHelper], + ); + + const tableInstance = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + rowCount: data.length, + meta: { + isFetching, + }, + enableSortingRemoval: false, + }); + + return } />; +} diff --git a/frontend/src/pages/Certificates/TableWrapper.tsx b/frontend/src/pages/Certificates/TableWrapper.tsx new file mode 100644 index 00000000..06588ebc --- /dev/null +++ b/frontend/src/pages/Certificates/TableWrapper.tsx @@ -0,0 +1,71 @@ +import { IconSearch } from "@tabler/icons-react"; +import Alert from "react-bootstrap/Alert"; +import { LoadingPage } from "src/components"; +import { useCertificates } from "src/hooks"; +import { intl } from "src/locale"; +import Table from "./Table"; + +export default function TableWrapper() { + const { isFetching, isLoading, isError, error, data } = useCertificates([ + "owner", + "dead_hosts", + "proxy_hosts", + "redirection_hosts", + ]); + + if (isLoading) { + return ; + } + + if (isError) { + return {error?.message || "Unknown error"}; + } + + return ( +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "certificates.title" })}

    +
    +
    +
    +
    + + + + +
    + +
    +
    +
    +
    +
    +
    +

    {intl.formatMessage({ id: "certificates.empty" })}

    +

    {intl.formatMessage({ id: "empty-subtitle" })}

    + +
    +
    + + + ); +} diff --git a/frontend/src/pages/Certificates/index.tsx b/frontend/src/pages/Certificates/index.tsx index 7f1bd3a0..f667e9af 100644 --- a/frontend/src/pages/Certificates/index.tsx +++ b/frontend/src/pages/Certificates/index.tsx @@ -1,10 +1,10 @@ import { HasPermission } from "src/components"; -import CertificateTable from "./CertificateTable"; +import TableWrapper from "./TableWrapper"; const Certificates = () => { return ( - + ); }; From 54e036276af7c701cc8612170fa0791cd76e1a4a Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Sun, 21 Sep 2025 17:16:46 +1000 Subject: [PATCH 23/82] API lib cleanup, 404 hosts WIP --- frontend/src/api/backend/createAccessList.ts | 15 +- frontend/src/api/backend/createCertificate.ts | 15 +- frontend/src/api/backend/createDeadHost.ts | 15 +- frontend/src/api/backend/createProxyHost.ts | 15 +- .../src/api/backend/createRedirectionHost.ts | 18 +- frontend/src/api/backend/createStream.ts | 15 +- frontend/src/api/backend/createUser.ts | 17 +- frontend/src/api/backend/deleteAccessList.ts | 11 +- frontend/src/api/backend/deleteCertificate.ts | 11 +- frontend/src/api/backend/deleteDeadHost.ts | 11 +- frontend/src/api/backend/deleteProxyHost.ts | 11 +- .../src/api/backend/deleteRedirectionHost.ts | 11 +- frontend/src/api/backend/deleteStream.ts | 11 +- frontend/src/api/backend/deleteUser.ts | 11 +- .../src/api/backend/downloadCertificate.ts | 11 +- frontend/src/api/backend/expansions.ts | 6 + frontend/src/api/backend/getAccessList.ts | 14 +- frontend/src/api/backend/getAccessLists.ts | 3 +- frontend/src/api/backend/getAuditLog.ts | 2 +- frontend/src/api/backend/getAuditLogs.ts | 3 +- frontend/src/api/backend/getCertificate.ts | 14 +- frontend/src/api/backend/getCertificates.ts | 3 +- frontend/src/api/backend/getDeadHost.ts | 14 +- frontend/src/api/backend/getDeadHosts.ts | 5 +- frontend/src/api/backend/getHealth.ts | 11 +- frontend/src/api/backend/getHostsReport.ts | 11 +- frontend/src/api/backend/getProxyHost.ts | 14 +- frontend/src/api/backend/getProxyHosts.ts | 3 +- .../src/api/backend/getRedirectionHost.ts | 14 +- .../src/api/backend/getRedirectionHosts.ts | 7 +- frontend/src/api/backend/getSetting.ts | 13 +- frontend/src/api/backend/getStream.ts | 14 +- frontend/src/api/backend/getStreams.ts | 5 +- frontend/src/api/backend/getToken.ts | 20 +- frontend/src/api/backend/getUser.ts | 8 +- frontend/src/api/backend/getUsers.ts | 3 +- frontend/src/api/backend/index.ts | 1 + frontend/src/api/backend/refreshToken.ts | 11 +- frontend/src/api/backend/renewCertificate.ts | 11 +- frontend/src/api/backend/setPermissions.ts | 17 +- .../src/api/backend/testHttpCertificate.ts | 18 +- frontend/src/api/backend/toggleDeadHost.ts | 15 +- frontend/src/api/backend/toggleProxyHost.ts | 15 +- .../src/api/backend/toggleRedirectionHost.ts | 15 +- frontend/src/api/backend/toggleStream.ts | 11 +- frontend/src/api/backend/updateAccessList.ts | 13 +- frontend/src/api/backend/updateAuth.ts | 18 +- frontend/src/api/backend/updateDeadHost.ts | 13 +- frontend/src/api/backend/updateProxyHost.ts | 13 +- .../src/api/backend/updateRedirectionHost.ts | 16 +- frontend/src/api/backend/updateSetting.ts | 13 +- frontend/src/api/backend/updateStream.ts | 13 +- frontend/src/api/backend/updateUser.ts | 13 +- frontend/src/api/backend/uploadCertificate.ts | 12 +- .../src/api/backend/validateCertificate.ts | 12 +- frontend/src/components/ErrorNotFound.tsx | 6 +- .../src/components/Form/DomainNamesField.tsx | 119 ++++++++ .../components/Form/SSLCertificateField.tsx | 112 +++++++ frontend/src/components/Form/index.ts | 2 + frontend/src/components/Table/TableBody.tsx | 6 +- frontend/src/components/index.ts | 1 + frontend/src/context/AuthContext.tsx | 2 +- frontend/src/hooks/index.ts | 1 + frontend/src/hooks/useDeadHost.ts | 57 ++++ frontend/src/hooks/useDeadHosts.ts | 6 +- frontend/src/hooks/useRedirectionHosts.ts | 6 +- frontend/src/hooks/useStreams.ts | 6 +- frontend/src/hooks/useUser.ts | 2 +- frontend/src/locale/lang/en.json | 9 +- frontend/src/locale/src/en.json | 21 ++ frontend/src/modals/DeadHostModal.tsx | 285 ++++++++++++++++++ frontend/src/modals/index.ts | 1 + .../pages/Certificates/CertificateTable.tsx | 132 -------- frontend/src/pages/Nginx/DeadHosts/Empty.tsx | 7 +- frontend/src/pages/Nginx/DeadHosts/Table.tsx | 19 +- .../pages/Nginx/DeadHosts/TableWrapper.tsx | 30 +- 76 files changed, 921 insertions(+), 544 deletions(-) create mode 100644 frontend/src/api/backend/expansions.ts create mode 100644 frontend/src/components/Form/DomainNamesField.tsx create mode 100644 frontend/src/components/Form/SSLCertificateField.tsx create mode 100644 frontend/src/components/Form/index.ts create mode 100644 frontend/src/hooks/useDeadHost.ts create mode 100644 frontend/src/modals/DeadHostModal.tsx delete mode 100644 frontend/src/pages/Certificates/CertificateTable.tsx diff --git a/frontend/src/api/backend/createAccessList.ts b/frontend/src/api/backend/createAccessList.ts index d90f7c97..8135a976 100644 --- a/frontend/src/api/backend/createAccessList.ts +++ b/frontend/src/api/backend/createAccessList.ts @@ -1,13 +1,10 @@ import * as api from "./base"; import type { AccessList } from "./models"; -export async function createAccessList(item: AccessList, abortController?: AbortController): Promise { - return await api.post( - { - url: "/nginx/access-lists", - // todo: only use whitelist of fields for this data - data: item, - }, - abortController, - ); +export async function createAccessList(item: AccessList): Promise { + return await api.post({ + url: "/nginx/access-lists", + // todo: only use whitelist of fields for this data + data: item, + }); } diff --git a/frontend/src/api/backend/createCertificate.ts b/frontend/src/api/backend/createCertificate.ts index ba9655a3..57f89478 100644 --- a/frontend/src/api/backend/createCertificate.ts +++ b/frontend/src/api/backend/createCertificate.ts @@ -1,13 +1,10 @@ import * as api from "./base"; import type { Certificate } from "./models"; -export async function createCertificate(item: Certificate, abortController?: AbortController): Promise { - return await api.post( - { - url: "/nginx/certificates", - // todo: only use whitelist of fields for this data - data: item, - }, - abortController, - ); +export async function createCertificate(item: Certificate): Promise { + return await api.post({ + url: "/nginx/certificates", + // todo: only use whitelist of fields for this data + data: item, + }); } diff --git a/frontend/src/api/backend/createDeadHost.ts b/frontend/src/api/backend/createDeadHost.ts index ab090446..521075a3 100644 --- a/frontend/src/api/backend/createDeadHost.ts +++ b/frontend/src/api/backend/createDeadHost.ts @@ -1,13 +1,10 @@ import * as api from "./base"; import type { DeadHost } from "./models"; -export async function createDeadHost(item: DeadHost, abortController?: AbortController): Promise { - return await api.post( - { - url: "/nginx/dead-hosts", - // todo: only use whitelist of fields for this data - data: item, - }, - abortController, - ); +export async function createDeadHost(item: DeadHost): Promise { + return await api.post({ + url: "/nginx/dead-hosts", + // todo: only use whitelist of fields for this data + data: item, + }); } diff --git a/frontend/src/api/backend/createProxyHost.ts b/frontend/src/api/backend/createProxyHost.ts index 6b548b87..830fb22e 100644 --- a/frontend/src/api/backend/createProxyHost.ts +++ b/frontend/src/api/backend/createProxyHost.ts @@ -1,13 +1,10 @@ import * as api from "./base"; import type { ProxyHost } from "./models"; -export async function createProxyHost(item: ProxyHost, abortController?: AbortController): Promise { - return await api.post( - { - url: "/nginx/proxy-hosts", - // todo: only use whitelist of fields for this data - data: item, - }, - abortController, - ); +export async function createProxyHost(item: ProxyHost): Promise { + return await api.post({ + url: "/nginx/proxy-hosts", + // todo: only use whitelist of fields for this data + data: item, + }); } diff --git a/frontend/src/api/backend/createRedirectionHost.ts b/frontend/src/api/backend/createRedirectionHost.ts index ea73a226..83ae6914 100644 --- a/frontend/src/api/backend/createRedirectionHost.ts +++ b/frontend/src/api/backend/createRedirectionHost.ts @@ -1,16 +1,10 @@ import * as api from "./base"; import type { RedirectionHost } from "./models"; -export async function createRedirectionHost( - item: RedirectionHost, - abortController?: AbortController, -): Promise { - return await api.post( - { - url: "/nginx/redirection-hosts", - // todo: only use whitelist of fields for this data - data: item, - }, - abortController, - ); +export async function createRedirectionHost(item: RedirectionHost): Promise { + return await api.post({ + url: "/nginx/redirection-hosts", + // todo: only use whitelist of fields for this data + data: item, + }); } diff --git a/frontend/src/api/backend/createStream.ts b/frontend/src/api/backend/createStream.ts index c8a6cf1f..e5f84605 100644 --- a/frontend/src/api/backend/createStream.ts +++ b/frontend/src/api/backend/createStream.ts @@ -1,13 +1,10 @@ import * as api from "./base"; import type { Stream } from "./models"; -export async function createStream(item: Stream, abortController?: AbortController): Promise { - return await api.post( - { - url: "/nginx/streams", - // todo: only use whitelist of fields for this data - data: item, - }, - abortController, - ); +export async function createStream(item: Stream): Promise { + return await api.post({ + url: "/nginx/streams", + // todo: only use whitelist of fields for this data + data: item, + }); } diff --git a/frontend/src/api/backend/createUser.ts b/frontend/src/api/backend/createUser.ts index 1aadda34..dcaa5feb 100644 --- a/frontend/src/api/backend/createUser.ts +++ b/frontend/src/api/backend/createUser.ts @@ -15,14 +15,11 @@ export interface NewUser { roles?: string[]; } -export async function createUser(item: NewUser, noAuth?: boolean, abortController?: AbortController): Promise { - return await api.post( - { - url: "/users", - // todo: only use whitelist of fields for this data - data: item, - noAuth, - }, - abortController, - ); +export async function createUser(item: NewUser, noAuth?: boolean): Promise { + return await api.post({ + url: "/users", + // todo: only use whitelist of fields for this data + data: item, + noAuth, + }); } diff --git a/frontend/src/api/backend/deleteAccessList.ts b/frontend/src/api/backend/deleteAccessList.ts index 96b51dbf..838c940b 100644 --- a/frontend/src/api/backend/deleteAccessList.ts +++ b/frontend/src/api/backend/deleteAccessList.ts @@ -1,10 +1,7 @@ import * as api from "./base"; -export async function deleteAccessList(id: number, abortController?: AbortController): Promise { - return await api.del( - { - url: `/nginx/access-lists/${id}`, - }, - abortController, - ); +export async function deleteAccessList(id: number): Promise { + return await api.del({ + url: `/nginx/access-lists/${id}`, + }); } diff --git a/frontend/src/api/backend/deleteCertificate.ts b/frontend/src/api/backend/deleteCertificate.ts index 97debf87..2257316c 100644 --- a/frontend/src/api/backend/deleteCertificate.ts +++ b/frontend/src/api/backend/deleteCertificate.ts @@ -1,10 +1,7 @@ import * as api from "./base"; -export async function deleteCertificate(id: number, abortController?: AbortController): Promise { - return await api.del( - { - url: `/nginx/certificates/${id}`, - }, - abortController, - ); +export async function deleteCertificate(id: number): Promise { + return await api.del({ + url: `/nginx/certificates/${id}`, + }); } diff --git a/frontend/src/api/backend/deleteDeadHost.ts b/frontend/src/api/backend/deleteDeadHost.ts index 50ba6fa0..8e72ca84 100644 --- a/frontend/src/api/backend/deleteDeadHost.ts +++ b/frontend/src/api/backend/deleteDeadHost.ts @@ -1,10 +1,7 @@ import * as api from "./base"; -export async function deleteDeadHost(id: number, abortController?: AbortController): Promise { - return await api.del( - { - url: `/nginx/dead-hosts/${id}`, - }, - abortController, - ); +export async function deleteDeadHost(id: number): Promise { + return await api.del({ + url: `/nginx/dead-hosts/${id}`, + }); } diff --git a/frontend/src/api/backend/deleteProxyHost.ts b/frontend/src/api/backend/deleteProxyHost.ts index f5ddc4d4..7b7f2d82 100644 --- a/frontend/src/api/backend/deleteProxyHost.ts +++ b/frontend/src/api/backend/deleteProxyHost.ts @@ -1,10 +1,7 @@ import * as api from "./base"; -export async function deleteProxyHost(id: number, abortController?: AbortController): Promise { - return await api.del( - { - url: `/nginx/proxy-hosts/${id}`, - }, - abortController, - ); +export async function deleteProxyHost(id: number): Promise { + return await api.del({ + url: `/nginx/proxy-hosts/${id}`, + }); } diff --git a/frontend/src/api/backend/deleteRedirectionHost.ts b/frontend/src/api/backend/deleteRedirectionHost.ts index 0fe5cc2d..7c594b68 100644 --- a/frontend/src/api/backend/deleteRedirectionHost.ts +++ b/frontend/src/api/backend/deleteRedirectionHost.ts @@ -1,10 +1,7 @@ import * as api from "./base"; -export async function deleteRedirectionHost(id: number, abortController?: AbortController): Promise { - return await api.del( - { - url: `/nginx/redirection-hosts/${id}`, - }, - abortController, - ); +export async function deleteRedirectionHost(id: number): Promise { + return await api.del({ + url: `/nginx/redirection-hosts/${id}`, + }); } diff --git a/frontend/src/api/backend/deleteStream.ts b/frontend/src/api/backend/deleteStream.ts index 17871d13..db9e11f1 100644 --- a/frontend/src/api/backend/deleteStream.ts +++ b/frontend/src/api/backend/deleteStream.ts @@ -1,10 +1,7 @@ import * as api from "./base"; -export async function deleteStream(id: number, abortController?: AbortController): Promise { - return await api.del( - { - url: `/nginx/streams/${id}`, - }, - abortController, - ); +export async function deleteStream(id: number): Promise { + return await api.del({ + url: `/nginx/streams/${id}`, + }); } diff --git a/frontend/src/api/backend/deleteUser.ts b/frontend/src/api/backend/deleteUser.ts index 30faa701..2a04a6e9 100644 --- a/frontend/src/api/backend/deleteUser.ts +++ b/frontend/src/api/backend/deleteUser.ts @@ -1,10 +1,7 @@ import * as api from "./base"; -export async function deleteUser(id: number, abortController?: AbortController): Promise { - return await api.del( - { - url: `/users/${id}`, - }, - abortController, - ); +export async function deleteUser(id: number): Promise { + return await api.del({ + url: `/users/${id}`, + }); } diff --git a/frontend/src/api/backend/downloadCertificate.ts b/frontend/src/api/backend/downloadCertificate.ts index aa980071..dc7aa312 100644 --- a/frontend/src/api/backend/downloadCertificate.ts +++ b/frontend/src/api/backend/downloadCertificate.ts @@ -1,11 +1,8 @@ import * as api from "./base"; import type { Binary } from "./responseTypes"; -export async function downloadCertificate(id: number, abortController?: AbortController): Promise { - return await api.get( - { - url: `/nginx/certificates/${id}/download`, - }, - abortController, - ); +export async function downloadCertificate(id: number): Promise { + return await api.get({ + url: `/nginx/certificates/${id}/download`, + }); } diff --git a/frontend/src/api/backend/expansions.ts b/frontend/src/api/backend/expansions.ts new file mode 100644 index 00000000..2f31e4d0 --- /dev/null +++ b/frontend/src/api/backend/expansions.ts @@ -0,0 +1,6 @@ +export type AccessListExpansion = "owner" | "items" | "clients"; +export type AuditLogExpansion = "user"; +export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts"; +export type HostExpansion = "owner" | "certificate"; +export type ProxyHostExpansion = "owner" | "access_list" | "certificate"; +export type UserExpansion = "permissions"; diff --git a/frontend/src/api/backend/getAccessList.ts b/frontend/src/api/backend/getAccessList.ts index 75fea51e..a1afd350 100644 --- a/frontend/src/api/backend/getAccessList.ts +++ b/frontend/src/api/backend/getAccessList.ts @@ -1,11 +1,13 @@ import * as api from "./base"; +import type { AccessListExpansion } from "./expansions"; import type { AccessList } from "./models"; -export async function getAccessList(id: number, abortController?: AbortController): Promise { - return await api.get( - { - url: `/nginx/access-lists/${id}`, +export async function getAccessList(id: number, expand?: AccessListExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/access-lists/${id}`, + params: { + expand: expand?.join(","), + ...params, }, - abortController, - ); + }); } diff --git a/frontend/src/api/backend/getAccessLists.ts b/frontend/src/api/backend/getAccessLists.ts index 96eaa91a..515f1e21 100644 --- a/frontend/src/api/backend/getAccessLists.ts +++ b/frontend/src/api/backend/getAccessLists.ts @@ -1,8 +1,7 @@ import * as api from "./base"; +import type { AccessListExpansion } from "./expansions"; import type { AccessList } from "./models"; -export type AccessListExpansion = "owner" | "items" | "clients"; - export async function getAccessLists(expand?: AccessListExpansion[], params = {}): Promise { return await api.get({ url: "/nginx/access-lists", diff --git a/frontend/src/api/backend/getAuditLog.ts b/frontend/src/api/backend/getAuditLog.ts index cea0872d..d2d01f70 100644 --- a/frontend/src/api/backend/getAuditLog.ts +++ b/frontend/src/api/backend/getAuditLog.ts @@ -1,5 +1,5 @@ import * as api from "./base"; -import type { AuditLogExpansion } from "./getAuditLogs"; +import type { AuditLogExpansion } from "./expansions"; import type { AuditLog } from "./models"; export async function getAuditLog(id: number, expand?: AuditLogExpansion[], params = {}): Promise { diff --git a/frontend/src/api/backend/getAuditLogs.ts b/frontend/src/api/backend/getAuditLogs.ts index e1be7c28..bbdb2f16 100644 --- a/frontend/src/api/backend/getAuditLogs.ts +++ b/frontend/src/api/backend/getAuditLogs.ts @@ -1,8 +1,7 @@ import * as api from "./base"; +import type { AuditLogExpansion } from "./expansions"; import type { AuditLog } from "./models"; -export type AuditLogExpansion = "user"; - export async function getAuditLogs(expand?: AuditLogExpansion[], params = {}): Promise { return await api.get({ url: "/audit-log", diff --git a/frontend/src/api/backend/getCertificate.ts b/frontend/src/api/backend/getCertificate.ts index fc6a4c6d..13de898d 100644 --- a/frontend/src/api/backend/getCertificate.ts +++ b/frontend/src/api/backend/getCertificate.ts @@ -1,11 +1,13 @@ import * as api from "./base"; +import type { CertificateExpansion } from "./expansions"; import type { Certificate } from "./models"; -export async function getCertificate(id: number, abortController?: AbortController): Promise { - return await api.get( - { - url: `/nginx/certificates/${id}`, +export async function getCertificate(id: number, expand?: CertificateExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/certificates/${id}`, + params: { + expand: expand?.join(","), + ...params, }, - abortController, - ); + }); } diff --git a/frontend/src/api/backend/getCertificates.ts b/frontend/src/api/backend/getCertificates.ts index 9389fe44..d6d215b5 100644 --- a/frontend/src/api/backend/getCertificates.ts +++ b/frontend/src/api/backend/getCertificates.ts @@ -1,8 +1,7 @@ import * as api from "./base"; +import type { CertificateExpansion } from "./expansions"; import type { Certificate } from "./models"; -export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts"; - export async function getCertificates(expand?: CertificateExpansion[], params = {}): Promise { return await api.get({ url: "/nginx/certificates", diff --git a/frontend/src/api/backend/getDeadHost.ts b/frontend/src/api/backend/getDeadHost.ts index 7759b094..d6c062b9 100644 --- a/frontend/src/api/backend/getDeadHost.ts +++ b/frontend/src/api/backend/getDeadHost.ts @@ -1,11 +1,13 @@ import * as api from "./base"; +import type { HostExpansion } from "./expansions"; import type { DeadHost } from "./models"; -export async function getDeadHost(id: number, abortController?: AbortController): Promise { - return await api.get( - { - url: `/nginx/dead-hosts/${id}`, +export async function getDeadHost(id: number, expand?: HostExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/dead-hosts/${id}`, + params: { + expand: expand?.join(","), + ...params, }, - abortController, - ); + }); } diff --git a/frontend/src/api/backend/getDeadHosts.ts b/frontend/src/api/backend/getDeadHosts.ts index 398221b6..1ca410e4 100644 --- a/frontend/src/api/backend/getDeadHosts.ts +++ b/frontend/src/api/backend/getDeadHosts.ts @@ -1,9 +1,8 @@ import * as api from "./base"; +import type { HostExpansion } from "./expansions"; import type { DeadHost } from "./models"; -export type DeadHostExpansion = "owner" | "certificate"; - -export async function getDeadHosts(expand?: DeadHostExpansion[], params = {}): Promise { +export async function getDeadHosts(expand?: HostExpansion[], params = {}): Promise { return await api.get({ url: "/nginx/dead-hosts", params: { diff --git a/frontend/src/api/backend/getHealth.ts b/frontend/src/api/backend/getHealth.ts index b423f5de..055681ce 100644 --- a/frontend/src/api/backend/getHealth.ts +++ b/frontend/src/api/backend/getHealth.ts @@ -1,11 +1,8 @@ import * as api from "./base"; import type { HealthResponse } from "./responseTypes"; -export async function getHealth(abortController?: AbortController): Promise { - return await api.get( - { - url: "/", - }, - abortController, - ); +export async function getHealth(): Promise { + return await api.get({ + url: "/", + }); } diff --git a/frontend/src/api/backend/getHostsReport.ts b/frontend/src/api/backend/getHostsReport.ts index b396e291..938d3a74 100644 --- a/frontend/src/api/backend/getHostsReport.ts +++ b/frontend/src/api/backend/getHostsReport.ts @@ -1,10 +1,7 @@ import * as api from "./base"; -export async function getHostsReport(abortController?: AbortController): Promise> { - return await api.get( - { - url: "/reports/hosts", - }, - abortController, - ); +export async function getHostsReport(): Promise> { + return await api.get({ + url: "/reports/hosts", + }); } diff --git a/frontend/src/api/backend/getProxyHost.ts b/frontend/src/api/backend/getProxyHost.ts index f51795ca..911b89c3 100644 --- a/frontend/src/api/backend/getProxyHost.ts +++ b/frontend/src/api/backend/getProxyHost.ts @@ -1,11 +1,13 @@ import * as api from "./base"; +import type { ProxyHostExpansion } from "./expansions"; import type { ProxyHost } from "./models"; -export async function getProxyHost(id: number, abortController?: AbortController): Promise { - return await api.get( - { - url: `/nginx/proxy-hosts/${id}`, +export async function getProxyHost(id: number, expand?: ProxyHostExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/proxy-hosts/${id}`, + params: { + expand: expand?.join(","), + ...params, }, - abortController, - ); + }); } diff --git a/frontend/src/api/backend/getProxyHosts.ts b/frontend/src/api/backend/getProxyHosts.ts index 5db1779c..ba9c864a 100644 --- a/frontend/src/api/backend/getProxyHosts.ts +++ b/frontend/src/api/backend/getProxyHosts.ts @@ -1,8 +1,7 @@ import * as api from "./base"; +import type { ProxyHostExpansion } from "./expansions"; import type { ProxyHost } from "./models"; -export type ProxyHostExpansion = "owner" | "access_list" | "certificate"; - export async function getProxyHosts(expand?: ProxyHostExpansion[], params = {}): Promise { return await api.get({ url: "/nginx/proxy-hosts", diff --git a/frontend/src/api/backend/getRedirectionHost.ts b/frontend/src/api/backend/getRedirectionHost.ts index fa537130..9b188741 100644 --- a/frontend/src/api/backend/getRedirectionHost.ts +++ b/frontend/src/api/backend/getRedirectionHost.ts @@ -1,11 +1,13 @@ import * as api from "./base"; +import type { HostExpansion } from "./expansions"; import type { ProxyHost } from "./models"; -export async function getRedirectionHost(id: number, abortController?: AbortController): Promise { - return await api.get( - { - url: `/nginx/redirection-hosts/${id}`, +export async function getRedirectionHost(id: number, expand?: HostExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/redirection-hosts/${id}`, + params: { + expand: expand?.join(","), + ...params, }, - abortController, - ); + }); } diff --git a/frontend/src/api/backend/getRedirectionHosts.ts b/frontend/src/api/backend/getRedirectionHosts.ts index 4e44c6ee..63e292f7 100644 --- a/frontend/src/api/backend/getRedirectionHosts.ts +++ b/frontend/src/api/backend/getRedirectionHosts.ts @@ -1,11 +1,8 @@ import * as api from "./base"; +import type { HostExpansion } from "./expansions"; import type { RedirectionHost } from "./models"; -export type RedirectionHostExpansion = "owner" | "certificate"; -export async function getRedirectionHosts( - expand?: RedirectionHostExpansion[], - params = {}, -): Promise { +export async function getRedirectionHosts(expand?: HostExpansion[], params = {}): Promise { return await api.get({ url: "/nginx/redirection-hosts", params: { diff --git a/frontend/src/api/backend/getSetting.ts b/frontend/src/api/backend/getSetting.ts index f04f7fad..daa54fee 100644 --- a/frontend/src/api/backend/getSetting.ts +++ b/frontend/src/api/backend/getSetting.ts @@ -1,11 +1,12 @@ import * as api from "./base"; import type { Setting } from "./models"; -export async function getSetting(id: string, abortController?: AbortController): Promise { - return await api.get( - { - url: `/settings/${id}`, +export async function getSetting(id: string, expand?: string[], params = {}): Promise { + return await api.get({ + url: `/settings/${id}`, + params: { + expand: expand?.join(","), + ...params, }, - abortController, - ); + }); } diff --git a/frontend/src/api/backend/getStream.ts b/frontend/src/api/backend/getStream.ts index 61927b68..82e10a04 100644 --- a/frontend/src/api/backend/getStream.ts +++ b/frontend/src/api/backend/getStream.ts @@ -1,11 +1,13 @@ import * as api from "./base"; +import type { HostExpansion } from "./expansions"; import type { Stream } from "./models"; -export async function getStream(id: number, abortController?: AbortController): Promise { - return await api.get( - { - url: `/nginx/streams/${id}`, +export async function getStream(id: number, expand?: HostExpansion[], params = {}): Promise { + return await api.get({ + url: `/nginx/streams/${id}`, + params: { + expand: expand?.join(","), + ...params, }, - abortController, - ); + }); } diff --git a/frontend/src/api/backend/getStreams.ts b/frontend/src/api/backend/getStreams.ts index 8468a55d..b5e9379b 100644 --- a/frontend/src/api/backend/getStreams.ts +++ b/frontend/src/api/backend/getStreams.ts @@ -1,9 +1,8 @@ import * as api from "./base"; +import type { HostExpansion } from "./expansions"; import type { Stream } from "./models"; -export type StreamExpansion = "owner" | "certificate"; - -export async function getStreams(expand?: StreamExpansion[], params = {}): Promise { +export async function getStreams(expand?: HostExpansion[], params = {}): Promise { return await api.get({ url: "/nginx/streams", params: { diff --git a/frontend/src/api/backend/getToken.ts b/frontend/src/api/backend/getToken.ts index be20e112..600f0529 100644 --- a/frontend/src/api/backend/getToken.ts +++ b/frontend/src/api/backend/getToken.ts @@ -1,19 +1,9 @@ import * as api from "./base"; import type { TokenResponse } from "./responseTypes"; -interface Options { - payload: { - identity: string; - secret: string; - }; -} - -export async function getToken({ payload }: Options, abortController?: AbortController): Promise { - return await api.post( - { - url: "/tokens", - data: payload, - }, - abortController, - ); +export async function getToken(identity: string, secret: string): Promise { + return await api.post({ + url: "/tokens", + data: { identity, secret }, + }); } diff --git a/frontend/src/api/backend/getUser.ts b/frontend/src/api/backend/getUser.ts index ae57783f..a006782a 100644 --- a/frontend/src/api/backend/getUser.ts +++ b/frontend/src/api/backend/getUser.ts @@ -1,10 +1,14 @@ import * as api from "./base"; +import type { UserExpansion } from "./expansions"; import type { User } from "./models"; -export async function getUser(id: number | string = "me", params = {}): Promise { +export async function getUser(id: number | string = "me", expand?: UserExpansion[], params = {}): Promise { const userId = id ? id : "me"; return await api.get({ url: `/users/${userId}`, - params, + params: { + expand: expand?.join(","), + ...params, + }, }); } diff --git a/frontend/src/api/backend/getUsers.ts b/frontend/src/api/backend/getUsers.ts index a4a03f7c..dab584f4 100644 --- a/frontend/src/api/backend/getUsers.ts +++ b/frontend/src/api/backend/getUsers.ts @@ -1,8 +1,7 @@ import * as api from "./base"; +import type { UserExpansion } from "./expansions"; import type { User } from "./models"; -export type UserExpansion = "permissions"; - export async function getUsers(expand?: UserExpansion[], params = {}): Promise { return await api.get({ url: "/users", diff --git a/frontend/src/api/backend/index.ts b/frontend/src/api/backend/index.ts index f5e63edc..cb771c04 100644 --- a/frontend/src/api/backend/index.ts +++ b/frontend/src/api/backend/index.ts @@ -13,6 +13,7 @@ export * from "./deleteRedirectionHost"; export * from "./deleteStream"; export * from "./deleteUser"; export * from "./downloadCertificate"; +export * from "./expansions"; export * from "./getAccessList"; export * from "./getAccessLists"; export * from "./getAuditLog"; diff --git a/frontend/src/api/backend/refreshToken.ts b/frontend/src/api/backend/refreshToken.ts index 6502fdbe..de1848c2 100644 --- a/frontend/src/api/backend/refreshToken.ts +++ b/frontend/src/api/backend/refreshToken.ts @@ -1,11 +1,8 @@ import * as api from "./base"; import type { TokenResponse } from "./responseTypes"; -export async function refreshToken(abortController?: AbortController): Promise { - return await api.get( - { - url: "/tokens", - }, - abortController, - ); +export async function refreshToken(): Promise { + return await api.get({ + url: "/tokens", + }); } diff --git a/frontend/src/api/backend/renewCertificate.ts b/frontend/src/api/backend/renewCertificate.ts index 9eefff5d..0f3d082d 100644 --- a/frontend/src/api/backend/renewCertificate.ts +++ b/frontend/src/api/backend/renewCertificate.ts @@ -1,11 +1,8 @@ import * as api from "./base"; import type { Certificate } from "./models"; -export async function renewCertificate(id: number, abortController?: AbortController): Promise { - return await api.post( - { - url: `/nginx/certificates/${id}/renew`, - }, - abortController, - ); +export async function renewCertificate(id: number): Promise { + return await api.post({ + url: `/nginx/certificates/${id}/renew`, + }); } diff --git a/frontend/src/api/backend/setPermissions.ts b/frontend/src/api/backend/setPermissions.ts index 80616e97..47fa6306 100644 --- a/frontend/src/api/backend/setPermissions.ts +++ b/frontend/src/api/backend/setPermissions.ts @@ -1,17 +1,10 @@ import * as api from "./base"; import type { UserPermissions } from "./models"; -export async function setPermissions( - userId: number, - data: UserPermissions, - abortController?: AbortController, -): Promise { +export async function setPermissions(userId: number, data: UserPermissions): Promise { // Remove readonly fields - return await api.put( - { - url: `/users/${userId}/permissions`, - data, - }, - abortController, - ); + return await api.put({ + url: `/users/${userId}/permissions`, + data, + }); } diff --git a/frontend/src/api/backend/testHttpCertificate.ts b/frontend/src/api/backend/testHttpCertificate.ts index c6e4eaf0..a35d1fce 100644 --- a/frontend/src/api/backend/testHttpCertificate.ts +++ b/frontend/src/api/backend/testHttpCertificate.ts @@ -1,16 +1,10 @@ import * as api from "./base"; -export async function testHttpCertificate( - domains: string[], - abortController?: AbortController, -): Promise> { - return await api.get( - { - url: "/nginx/certificates/test-http", - params: { - domains: domains.join(","), - }, +export async function testHttpCertificate(domains: string[]): Promise> { + return await api.get({ + url: "/nginx/certificates/test-http", + params: { + domains: domains.join(","), }, - abortController, - ); + }); } diff --git a/frontend/src/api/backend/toggleDeadHost.ts b/frontend/src/api/backend/toggleDeadHost.ts index 967d65db..71a780ef 100644 --- a/frontend/src/api/backend/toggleDeadHost.ts +++ b/frontend/src/api/backend/toggleDeadHost.ts @@ -1,14 +1,7 @@ import * as api from "./base"; -export async function toggleDeadHost( - id: number, - enabled: boolean, - abortController?: AbortController, -): Promise { - return await api.post( - { - url: `/nginx/dead-hosts/${id}/${enabled ? "enable" : "disable"}`, - }, - abortController, - ); +export async function toggleDeadHost(id: number, enabled: boolean): Promise { + return await api.post({ + url: `/nginx/dead-hosts/${id}/${enabled ? "enable" : "disable"}`, + }); } diff --git a/frontend/src/api/backend/toggleProxyHost.ts b/frontend/src/api/backend/toggleProxyHost.ts index 2de3f2a5..376e7881 100644 --- a/frontend/src/api/backend/toggleProxyHost.ts +++ b/frontend/src/api/backend/toggleProxyHost.ts @@ -1,14 +1,7 @@ import * as api from "./base"; -export async function toggleProxyHost( - id: number, - enabled: boolean, - abortController?: AbortController, -): Promise { - return await api.post( - { - url: `/nginx/proxy-hosts/${id}/${enabled ? "enable" : "disable"}`, - }, - abortController, - ); +export async function toggleProxyHost(id: number, enabled: boolean): Promise { + return await api.post({ + url: `/nginx/proxy-hosts/${id}/${enabled ? "enable" : "disable"}`, + }); } diff --git a/frontend/src/api/backend/toggleRedirectionHost.ts b/frontend/src/api/backend/toggleRedirectionHost.ts index 4f2fe41d..0cfa5733 100644 --- a/frontend/src/api/backend/toggleRedirectionHost.ts +++ b/frontend/src/api/backend/toggleRedirectionHost.ts @@ -1,14 +1,7 @@ import * as api from "./base"; -export async function toggleRedirectionHost( - id: number, - enabled: boolean, - abortController?: AbortController, -): Promise { - return await api.post( - { - url: `/nginx/redirection-hosts/${id}/${enabled ? "enable" : "disable"}`, - }, - abortController, - ); +export async function toggleRedirectionHost(id: number, enabled: boolean): Promise { + return await api.post({ + url: `/nginx/redirection-hosts/${id}/${enabled ? "enable" : "disable"}`, + }); } diff --git a/frontend/src/api/backend/toggleStream.ts b/frontend/src/api/backend/toggleStream.ts index 3f8e2e9e..2b71f720 100644 --- a/frontend/src/api/backend/toggleStream.ts +++ b/frontend/src/api/backend/toggleStream.ts @@ -1,10 +1,7 @@ import * as api from "./base"; -export async function toggleStream(id: number, enabled: boolean, abortController?: AbortController): Promise { - return await api.post( - { - url: `/nginx/streams/${id}/${enabled ? "enable" : "disable"}`, - }, - abortController, - ); +export async function toggleStream(id: number, enabled: boolean): Promise { + return await api.post({ + url: `/nginx/streams/${id}/${enabled ? "enable" : "disable"}`, + }); } diff --git a/frontend/src/api/backend/updateAccessList.ts b/frontend/src/api/backend/updateAccessList.ts index cfae9389..7a23566b 100644 --- a/frontend/src/api/backend/updateAccessList.ts +++ b/frontend/src/api/backend/updateAccessList.ts @@ -1,15 +1,12 @@ import * as api from "./base"; import type { AccessList } from "./models"; -export async function updateAccessList(item: AccessList, abortController?: AbortController): Promise { +export async function updateAccessList(item: AccessList): Promise { // Remove readonly fields const { id, createdOn: _, modifiedOn: __, ...data } = item; - return await api.put( - { - url: `/nginx/access-lists/${id}`, - data: data, - }, - abortController, - ); + return await api.put({ + url: `/nginx/access-lists/${id}`, + data: data, + }); } diff --git a/frontend/src/api/backend/updateAuth.ts b/frontend/src/api/backend/updateAuth.ts index c63dfeeb..5b14b076 100644 --- a/frontend/src/api/backend/updateAuth.ts +++ b/frontend/src/api/backend/updateAuth.ts @@ -1,12 +1,7 @@ import * as api from "./base"; import type { User } from "./models"; -export async function updateAuth( - userId: number | "me", - newPassword: string, - current?: string, - abortController?: AbortController, -): Promise { +export async function updateAuth(userId: number | "me", newPassword: string, current?: string): Promise { const data = { type: "password", current: current, @@ -16,11 +11,8 @@ export async function updateAuth( data.current = current; } - return await api.put( - { - url: `/users/${userId}/auth`, - data, - }, - abortController, - ); + return await api.put({ + url: `/users/${userId}/auth`, + data, + }); } diff --git a/frontend/src/api/backend/updateDeadHost.ts b/frontend/src/api/backend/updateDeadHost.ts index 8afd44ab..1eb5631b 100644 --- a/frontend/src/api/backend/updateDeadHost.ts +++ b/frontend/src/api/backend/updateDeadHost.ts @@ -1,15 +1,12 @@ import * as api from "./base"; import type { DeadHost } from "./models"; -export async function updateDeadHost(item: DeadHost, abortController?: AbortController): Promise { +export async function updateDeadHost(item: DeadHost): Promise { // Remove readonly fields const { id, createdOn: _, modifiedOn: __, ...data } = item; - return await api.put( - { - url: `/nginx/dead-hosts/${id}`, - data: data, - }, - abortController, - ); + return await api.put({ + url: `/nginx/dead-hosts/${id}`, + data: data, + }); } diff --git a/frontend/src/api/backend/updateProxyHost.ts b/frontend/src/api/backend/updateProxyHost.ts index 55cdc24c..e7ee3d90 100644 --- a/frontend/src/api/backend/updateProxyHost.ts +++ b/frontend/src/api/backend/updateProxyHost.ts @@ -1,15 +1,12 @@ import * as api from "./base"; import type { ProxyHost } from "./models"; -export async function updateProxyHost(item: ProxyHost, abortController?: AbortController): Promise { +export async function updateProxyHost(item: ProxyHost): Promise { // Remove readonly fields const { id, createdOn: _, modifiedOn: __, ...data } = item; - return await api.put( - { - url: `/nginx/proxy-hosts/${id}`, - data: data, - }, - abortController, - ); + return await api.put({ + url: `/nginx/proxy-hosts/${id}`, + data: data, + }); } diff --git a/frontend/src/api/backend/updateRedirectionHost.ts b/frontend/src/api/backend/updateRedirectionHost.ts index e66aabb9..4cc36f1e 100644 --- a/frontend/src/api/backend/updateRedirectionHost.ts +++ b/frontend/src/api/backend/updateRedirectionHost.ts @@ -1,18 +1,12 @@ import * as api from "./base"; import type { RedirectionHost } from "./models"; -export async function updateRedirectionHost( - item: RedirectionHost, - abortController?: AbortController, -): Promise { +export async function updateRedirectionHost(item: RedirectionHost): Promise { // Remove readonly fields const { id, createdOn: _, modifiedOn: __, ...data } = item; - return await api.put( - { - url: `/nginx/redirection-hosts/${id}`, - data: data, - }, - abortController, - ); + return await api.put({ + url: `/nginx/redirection-hosts/${id}`, + data: data, + }); } diff --git a/frontend/src/api/backend/updateSetting.ts b/frontend/src/api/backend/updateSetting.ts index e257528a..bcb94059 100644 --- a/frontend/src/api/backend/updateSetting.ts +++ b/frontend/src/api/backend/updateSetting.ts @@ -1,15 +1,12 @@ import * as api from "./base"; import type { Setting } from "./models"; -export async function updateSetting(item: Setting, abortController?: AbortController): Promise { +export async function updateSetting(item: Setting): Promise { // Remove readonly fields const { id, ...data } = item; - return await api.put( - { - url: `/settings/${id}`, - data: data, - }, - abortController, - ); + return await api.put({ + url: `/settings/${id}`, + data: data, + }); } diff --git a/frontend/src/api/backend/updateStream.ts b/frontend/src/api/backend/updateStream.ts index 18da6ff4..508bcece 100644 --- a/frontend/src/api/backend/updateStream.ts +++ b/frontend/src/api/backend/updateStream.ts @@ -1,15 +1,12 @@ import * as api from "./base"; import type { Stream } from "./models"; -export async function updateStream(item: Stream, abortController?: AbortController): Promise { +export async function updateStream(item: Stream): Promise { // Remove readonly fields const { id, createdOn: _, modifiedOn: __, ...data } = item; - return await api.put( - { - url: `/nginx/streams/${id}`, - data: data, - }, - abortController, - ); + return await api.put({ + url: `/nginx/streams/${id}`, + data: data, + }); } diff --git a/frontend/src/api/backend/updateUser.ts b/frontend/src/api/backend/updateUser.ts index 0df5c279..d063e127 100644 --- a/frontend/src/api/backend/updateUser.ts +++ b/frontend/src/api/backend/updateUser.ts @@ -1,15 +1,12 @@ import * as api from "./base"; import type { User } from "./models"; -export async function updateUser(item: User, abortController?: AbortController): Promise { +export async function updateUser(item: User): Promise { // Remove readonly fields const { id, createdOn: _, modifiedOn: __, ...data } = item; - return await api.put( - { - url: `/users/${id}`, - data: data, - }, - abortController, - ); + return await api.put({ + url: `/users/${id}`, + data: data, + }); } diff --git a/frontend/src/api/backend/uploadCertificate.ts b/frontend/src/api/backend/uploadCertificate.ts index 6c920538..94549a79 100644 --- a/frontend/src/api/backend/uploadCertificate.ts +++ b/frontend/src/api/backend/uploadCertificate.ts @@ -6,13 +6,9 @@ export async function uploadCertificate( certificate: string, certificateKey: string, intermediateCertificate?: string, - abortController?: AbortController, ): Promise { - return await api.post( - { - url: `/nginx/certificates/${id}/upload`, - data: { certificate, certificateKey, intermediateCertificate }, - }, - abortController, - ); + return await api.post({ + url: `/nginx/certificates/${id}/upload`, + data: { certificate, certificateKey, intermediateCertificate }, + }); } diff --git a/frontend/src/api/backend/validateCertificate.ts b/frontend/src/api/backend/validateCertificate.ts index c4065076..a1901e03 100644 --- a/frontend/src/api/backend/validateCertificate.ts +++ b/frontend/src/api/backend/validateCertificate.ts @@ -5,13 +5,9 @@ export async function validateCertificate( certificate: string, certificateKey: string, intermediateCertificate?: string, - abortController?: AbortController, ): Promise { - return await api.post( - { - url: "/nginx/certificates/validate", - data: { certificate, certificateKey, intermediateCertificate }, - }, - abortController, - ); + return await api.post({ + url: "/nginx/certificates/validate", + data: { certificate, certificateKey, intermediateCertificate }, + }); } diff --git a/frontend/src/components/ErrorNotFound.tsx b/frontend/src/components/ErrorNotFound.tsx index ab864dad..5b92b6d5 100644 --- a/frontend/src/components/ErrorNotFound.tsx +++ b/frontend/src/components/ErrorNotFound.tsx @@ -1,6 +1,6 @@ -import { intl } from "src/locale"; import { useNavigate } from "react-router-dom"; import { Button } from "src/components"; +import { intl } from "src/locale"; export function ErrorNotFound() { const navigate = useNavigate(); @@ -9,9 +9,7 @@ export function ErrorNotFound() {

    {intl.formatMessage({ id: "notfound.title" })}

    -

    - {intl.formatMessage({ id: "notfound.text" })} -

    +

    {intl.formatMessage({ id: "notfound.text" })}

    - + {emptyState ? emptyState : } ); } diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index be87e068..c8ab1762 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -1,6 +1,7 @@ export * from "./Button"; export * from "./ErrorNotFound"; export * from "./Flag"; +export * from "./Form"; export * from "./HasPermission"; export * from "./Loading"; export * from "./LoadingPage"; diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx index f940722e..d26d5713 100644 --- a/frontend/src/context/AuthContext.tsx +++ b/frontend/src/context/AuthContext.tsx @@ -30,7 +30,7 @@ function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props) }; const login = async (identity: string, secret: string) => { - const response = await getToken({ payload: { identity, secret } }); + const response = await getToken(identity, secret); handleTokenUpdate(response); }; diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index 91738be9..b3a9b980 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -2,6 +2,7 @@ export * from "./useAccessLists"; export * from "./useAuditLog"; export * from "./useAuditLogs"; export * from "./useCertificates"; +export * from "./useDeadHost"; export * from "./useDeadHosts"; export * from "./useHealth"; export * from "./useHostReport"; diff --git a/frontend/src/hooks/useDeadHost.ts b/frontend/src/hooks/useDeadHost.ts new file mode 100644 index 00000000..87615c75 --- /dev/null +++ b/frontend/src/hooks/useDeadHost.ts @@ -0,0 +1,57 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { createDeadHost, type DeadHost, getDeadHost, updateDeadHost } from "src/api/backend"; + +const fetchDeadHost = (id: number | "new") => { + if (id === "new") { + return Promise.resolve({ + id: 0, + createdOn: "", + modifiedOn: "", + ownerUserId: 0, + domainNames: [], + certificateId: 0, + sslForced: false, + advancedConfig: "", + meta: {}, + http2Support: false, + enabled: true, + hstsEnabled: false, + hstsSubdomains: false, + } as DeadHost); + } + return getDeadHost(id, ["owner"]); +}; + +const useDeadHost = (id: number | "new", options = {}) => { + return useQuery({ + queryKey: ["dead-host", id], + queryFn: () => fetchDeadHost(id), + staleTime: 60 * 1000, // 1 minute + ...options, + }); +}; + +const useSetDeadHost = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (values: DeadHost) => (values.id ? updateDeadHost(values) : createDeadHost(values)), + onMutate: (values: DeadHost) => { + if (!values.id) { + return; + } + const previousObject = queryClient.getQueryData(["dead-host", values.id]); + queryClient.setQueryData(["dead-host", values.id], (old: DeadHost) => ({ + ...old, + ...values, + })); + return () => queryClient.setQueryData(["dead-host", values.id], previousObject); + }, + onError: (_, __, rollback: any) => rollback(), + onSuccess: async ({ id }: DeadHost) => { + queryClient.invalidateQueries({ queryKey: ["dead-host", id] }); + queryClient.invalidateQueries({ queryKey: ["dead-hosts"] }); + }, + }); +}; + +export { useDeadHost, useSetDeadHost }; diff --git a/frontend/src/hooks/useDeadHosts.ts b/frontend/src/hooks/useDeadHosts.ts index f08ed0ee..744d28c4 100644 --- a/frontend/src/hooks/useDeadHosts.ts +++ b/frontend/src/hooks/useDeadHosts.ts @@ -1,11 +1,11 @@ import { useQuery } from "@tanstack/react-query"; -import { type DeadHost, type DeadHostExpansion, getDeadHosts } from "src/api/backend"; +import { type DeadHost, getDeadHosts, type HostExpansion } from "src/api/backend"; -const fetchDeadHosts = (expand?: DeadHostExpansion[]) => { +const fetchDeadHosts = (expand?: HostExpansion[]) => { return getDeadHosts(expand); }; -const useDeadHosts = (expand?: DeadHostExpansion[], options = {}) => { +const useDeadHosts = (expand?: HostExpansion[], options = {}) => { return useQuery({ queryKey: ["dead-hosts", { expand }], queryFn: () => fetchDeadHosts(expand), diff --git a/frontend/src/hooks/useRedirectionHosts.ts b/frontend/src/hooks/useRedirectionHosts.ts index 03a66238..3b7eda60 100644 --- a/frontend/src/hooks/useRedirectionHosts.ts +++ b/frontend/src/hooks/useRedirectionHosts.ts @@ -1,11 +1,11 @@ import { useQuery } from "@tanstack/react-query"; -import { getRedirectionHosts, type RedirectionHost, type RedirectionHostExpansion } from "src/api/backend"; +import { getRedirectionHosts, type HostExpansion, type RedirectionHost } from "src/api/backend"; -const fetchRedirectionHosts = (expand?: RedirectionHostExpansion[]) => { +const fetchRedirectionHosts = (expand?: HostExpansion[]) => { return getRedirectionHosts(expand); }; -const useRedirectionHosts = (expand?: RedirectionHostExpansion[], options = {}) => { +const useRedirectionHosts = (expand?: HostExpansion[], options = {}) => { return useQuery({ queryKey: ["redirection-hosts", { expand }], queryFn: () => fetchRedirectionHosts(expand), diff --git a/frontend/src/hooks/useStreams.ts b/frontend/src/hooks/useStreams.ts index 8612050d..0f0129de 100644 --- a/frontend/src/hooks/useStreams.ts +++ b/frontend/src/hooks/useStreams.ts @@ -1,11 +1,11 @@ import { useQuery } from "@tanstack/react-query"; -import { getStreams, type Stream, type StreamExpansion } from "src/api/backend"; +import { getStreams, type HostExpansion, type Stream } from "src/api/backend"; -const fetchStreams = (expand?: StreamExpansion[]) => { +const fetchStreams = (expand?: HostExpansion[]) => { return getStreams(expand); }; -const useStreams = (expand?: StreamExpansion[], options = {}) => { +const useStreams = (expand?: HostExpansion[], options = {}) => { return useQuery({ queryKey: ["streams", { expand }], queryFn: () => fetchStreams(expand), diff --git a/frontend/src/hooks/useUser.ts b/frontend/src/hooks/useUser.ts index fb991438..92e0f63a 100644 --- a/frontend/src/hooks/useUser.ts +++ b/frontend/src/hooks/useUser.ts @@ -15,7 +15,7 @@ const fetchUser = (id: number | string) => { avatar: "", } as User); } - return getUser(id, { expand: "permissions" }); + return getUser(id, ["permissions"]); }; const useUser = (id: string | number, options = {}) => { diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json index cabcdcde..13915f30 100644 --- a/frontend/src/locale/lang/en.json +++ b/frontend/src/locale/lang/en.json @@ -24,6 +24,7 @@ "column.access": "Access", "column.authorization": "Authorization", "column.destination": "Destination", + "column.details": "Details", "column.email": "Email", "column.event": "Event", "column.expires": "Expires", @@ -40,12 +41,15 @@ "column.status": "Status", "created-on": "Created: {date}", "dashboard.title": "Dashboard", + "dead-host.edit": "Edit 404 Host", + "dead-host.new": "New 404 Host", "dead-hosts.actions-title": "404 Host #{id}", "dead-hosts.add": "Add 404 Host", "dead-hosts.count": "{count} 404 Hosts", "dead-hosts.empty": "There are no 404 Hosts", "dead-hosts.title": "404 Hosts", "disabled": "Disabled", + "domain-names": "Domain Names", "email-address": "Email address", "empty-subtitle": "Why don't you create one?", "error.invalid-auth": "Invalid email or password", @@ -96,6 +100,7 @@ "setup.preamble": "Get started by creating your admin account.", "setup.title": "Welcome!", "sign-in": "Sign in", + "ssl-certificate": "SSL Certificate", "streams.actions-title": "Stream #{id}", "streams.add": "Add Stream", "streams.count": "{count} Streams", @@ -122,5 +127,7 @@ "user.switch-light": "Switch to Light mode", "users.actions-title": "User #{id}", "users.add": "Add User", - "users.title": "Users" + "users.title": "Users", + "wildcards-not-permitted": "Wildcards not permitted for this type", + "wildcards-not-supported": "Wildcards not supported for this CA" } \ No newline at end of file diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index 78a2bd16..4f74fd0c 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -77,6 +77,9 @@ "column.destination": { "defaultMessage": "Destination" }, + "column.details": { + "defaultMessage": "Details" + }, "column.email": { "defaultMessage": "Email" }, @@ -131,15 +134,24 @@ "dead-hosts.count": { "defaultMessage": "{count} 404 Hosts" }, + "dead-host.edit": { + "defaultMessage": "Edit 404 Host" + }, "dead-hosts.empty": { "defaultMessage": "There are no 404 Hosts" }, + "dead-host.new": { + "defaultMessage": "New 404 Host" + }, "dead-hosts.title": { "defaultMessage": "404 Hosts" }, "disabled": { "defaultMessage": "Disabled" }, + "domain-names": { + "defaultMessage": "Domain Names" + }, "email-address": { "defaultMessage": "Email address" }, @@ -290,6 +302,9 @@ "sign-in": { "defaultMessage": "Sign in" }, + "ssl-certificate": { + "defaultMessage": "SSL Certificate" + }, "streams.actions-title": { "defaultMessage": "Stream #{id}" }, @@ -370,5 +385,11 @@ }, "users.title": { "defaultMessage": "Users" + }, + "wildcards-not-permitted": { + "defaultMessage": "Wildcards not permitted for this type" + }, + "wildcards-not-supported": { + "defaultMessage": "Wildcards not supported for this CA" } } diff --git a/frontend/src/modals/DeadHostModal.tsx b/frontend/src/modals/DeadHostModal.tsx new file mode 100644 index 00000000..3983df4d --- /dev/null +++ b/frontend/src/modals/DeadHostModal.tsx @@ -0,0 +1,285 @@ +import { IconSettings } from "@tabler/icons-react"; +import { Form, Formik } from "formik"; +import { useState } from "react"; +import { Alert } from "react-bootstrap"; +import Modal from "react-bootstrap/Modal"; +import { Button, DomainNamesField, Loading, SSLCertificateField } from "src/components"; +import { useDeadHost } from "src/hooks"; +import { intl } from "src/locale"; + +interface Props { + id: number | "new"; + onClose: () => void; +} +export function DeadHostModal({ id, onClose }: Props) { + const { data, isLoading, error } = useDeadHost(id); + // const { mutate: setDeadHost } = useSetDeadHost(); + const [errorMsg, setErrorMsg] = useState(null); + + const onSubmit = async (values: any, { setSubmitting }: any) => { + setSubmitting(true); + setErrorMsg(null); + console.log("SUBMIT:", values); + setSubmitting(false); + // const { ...payload } = { + // id: id === "new" ? undefined : id, + // roles: [], + // ...values, + // }; + + // setDeadHost(payload, { + // onError: (err: any) => setErrorMsg(err.message), + // onSuccess: () => { + // showSuccess(intl.formatMessage({ id: "notification.dead-host-saved" })); + // onClose(); + // }, + // onSettled: () => setSubmitting(false), + // }); + }; + + return ( + + {!isLoading && error && ( + + {error?.message || "Unknown error"} + + )} + {isLoading && } + {!isLoading && data && ( + + {({ isSubmitting }) => ( +
    + + + {intl.formatMessage({ id: data?.id ? "dead-host.edit" : "dead-host.new" })} + + + + setErrorMsg(null)} dismissible> + {errorMsg} + + + + + {/*
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.name ? ( +
    + {form.errors.name && form.touched.name + ? form.errors.name + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.nickname ? ( +
    + {form.errors.nickname && form.touched.nickname + ? form.errors.nickname + : null} +
    + ) : null} +
    + )} +
    +
    +
    +
    +
    + + {({ field, form }: any) => ( +
    + + + {form.errors.email ? ( +
    + {form.errors.email && form.touched.email + ? form.errors.email + : null} +
    + ) : null} +
    + )} +
    +
    + {currentUser && data && currentUser?.id !== data?.id ? ( +
    +

    {intl.formatMessage({ id: "user.flags.title" })}

    +
    +
    + +
    +
    + +
    +
    +
    + ) : null} */} +
    + + + + + + )} +
    + )} +
    + ); +} diff --git a/frontend/src/modals/index.ts b/frontend/src/modals/index.ts index c8e89fb5..a5c7fa86 100644 --- a/frontend/src/modals/index.ts +++ b/frontend/src/modals/index.ts @@ -1,4 +1,5 @@ export * from "./ChangePasswordModal"; +export * from "./DeadHostModal"; export * from "./DeleteConfirmModal"; export * from "./EventDetailsModal"; export * from "./PermissionsModal"; diff --git a/frontend/src/pages/Certificates/CertificateTable.tsx b/frontend/src/pages/Certificates/CertificateTable.tsx deleted file mode 100644 index df607d13..00000000 --- a/frontend/src/pages/Certificates/CertificateTable.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { IconDotsVertical, IconEdit, IconPower, IconSearch, IconTrash } from "@tabler/icons-react"; -import { Button } from "src/components"; -import { intl } from "src/locale"; - -export default function CertificateTable() { - return ( -
    -
    -
    -
    -
    -
    -

    {intl.formatMessage({ id: "certificates.title" })}

    -
    -
    -
    -
    - - - - -
    - -
    -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - -
    -
    - -
    -
    -
    -
    - blog.jc21.com -
    -
    Created: 20th September 2024
    -
    -
    http://172.17.0.1:3001Let's EncryptPublic - Online - - - -
    - Proxy Host #2 - - - Edit - - - - Disable - - - -
    -
    -
    -
    -
    - ); -} diff --git a/frontend/src/pages/Nginx/DeadHosts/Empty.tsx b/frontend/src/pages/Nginx/DeadHosts/Empty.tsx index 48865c88..59a22279 100644 --- a/frontend/src/pages/Nginx/DeadHosts/Empty.tsx +++ b/frontend/src/pages/Nginx/DeadHosts/Empty.tsx @@ -4,15 +4,18 @@ import { intl } from "src/locale"; interface Props { tableInstance: ReactTable; + onNew?: () => void; } -export default function Empty({ tableInstance }: Props) { +export default function Empty({ tableInstance, onNew }: Props) { return (

    {intl.formatMessage({ id: "dead-hosts.empty" })}

    {intl.formatMessage({ id: "empty-subtitle" })}

    - +
    diff --git a/frontend/src/pages/Nginx/DeadHosts/Table.tsx b/frontend/src/pages/Nginx/DeadHosts/Table.tsx index d9531956..77d9ae91 100644 --- a/frontend/src/pages/Nginx/DeadHosts/Table.tsx +++ b/frontend/src/pages/Nginx/DeadHosts/Table.tsx @@ -10,8 +10,10 @@ import Empty from "./Empty"; interface Props { data: DeadHost[]; isFetching?: boolean; + onDelete?: (id: number) => void; + onNew?: () => void; } -export default function Table({ data, isFetching }: Props) { +export default function Table({ data, isFetching, onDelete, onNew }: Props) { const columnHelper = createColumnHelper(); const columns = useMemo( () => [ @@ -78,7 +80,14 @@ export default function Table({ data, isFetching }: Props) { {intl.formatMessage({ id: "action.disable" })}
    - + { + e.preventDefault(); + onDelete?.(info.row.original.id); + }} + > {intl.formatMessage({ id: "action.delete" })} @@ -91,7 +100,7 @@ export default function Table({ data, isFetching }: Props) { }, }), ], - [columnHelper], + [columnHelper, onDelete], ); const tableInstance = useReactTable({ @@ -105,5 +114,7 @@ export default function Table({ data, isFetching }: Props) { enableSortingRemoval: false, }); - return } />; + return ( + } /> + ); } diff --git a/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx index 5badc3b1..ad8c072d 100644 --- a/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx +++ b/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx @@ -1,11 +1,16 @@ import { IconSearch } from "@tabler/icons-react"; +import { useState } from "react"; import Alert from "react-bootstrap/Alert"; import { Button, LoadingPage } from "src/components"; import { useDeadHosts } from "src/hooks"; import { intl } from "src/locale"; +import { DeadHostModal, DeleteConfirmModal } from "src/modals"; +import { showSuccess } from "src/notifications"; import Table from "./Table"; export default function TableWrapper() { + const [deleteId, setDeleteId] = useState(0); + const [editId, setEditId] = useState(0 as number | "new"); const { isFetching, isLoading, isError, error, data } = useDeadHosts(["owner", "certificate"]); if (isLoading) { @@ -16,6 +21,11 @@ export default function TableWrapper() { return {error?.message || "Unknown error"}; } + const handleDelete = async () => { + // await deleteUser(deleteId); + showSuccess(intl.formatMessage({ id: "notification.host-deleted" })); + }; + return (
    @@ -38,14 +48,30 @@ export default function TableWrapper() { autoComplete="off" />
    -
    - +
    setDeleteId(id)} + onNew={() => setEditId("new")} + /> + {editId ? setEditId(0)} /> : null} + {deleteId ? ( + setDeleteId(0)} + invalidations={[["dead-hosts"], ["dead-host", deleteId]]} + > + {intl.formatMessage({ id: "user.delete.content" })} + + ) : null} ); From 94375bbc5f8a7e3b035c078d02e539eb03dbe5b6 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 22 Sep 2025 22:19:18 +1000 Subject: [PATCH 24/82] DNS Provider configuration --- backend/routes/nginx/certificates.js | 102 +++++++++----- backend/routes/reports.js | 3 +- .../api/backend/getCertificateDNSProviders.ts | 9 ++ frontend/src/api/backend/index.ts | 1 + frontend/src/api/backend/models.ts | 6 + .../Form/DNSProviderFields.module.css | 16 +++ .../src/components/Form/DNSProviderFields.tsx | 114 ++++++++++++++++ .../components/Form/SSLCertificateField.tsx | 27 +++- .../src/components/Form/SSLOptionsFields.tsx | 128 ++++++++++++++++++ frontend/src/components/Form/index.ts | 2 + frontend/src/hooks/index.ts | 1 + frontend/src/hooks/useDnsProviders.ts | 17 +++ frontend/src/modals/DeadHostModal.tsx | 3 +- 13 files changed, 387 insertions(+), 42 deletions(-) create mode 100644 frontend/src/api/backend/getCertificateDNSProviders.ts create mode 100644 frontend/src/components/Form/DNSProviderFields.module.css create mode 100644 frontend/src/components/Form/DNSProviderFields.tsx create mode 100644 frontend/src/components/Form/SSLOptionsFields.tsx create mode 100644 frontend/src/hooks/useDnsProviders.ts diff --git a/backend/routes/nginx/certificates.js b/backend/routes/nginx/certificates.js index 16d04f38..47a827b0 100644 --- a/backend/routes/nginx/certificates.js +++ b/backend/routes/nginx/certificates.js @@ -1,4 +1,5 @@ import express from "express"; +import dnsPlugins from "../../global/certbot-dns-plugins.json" with { type: "json" }; import internalCertificate from "../../internal/certificate.js"; import errs from "../../lib/error.js"; import jwtdecode from "../../lib/express/jwt-decode.js"; @@ -72,6 +73,38 @@ router } }); +/** + * /api/nginx/certificates/dns-providers + */ +router + .route("/dns-providers") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * GET /api/nginx/certificates/dns-providers + * + * Get list of all supported DNS providers + */ + .get(async (req, res, next) => { + try { + if (!res.locals.access.token.getUserId()) { + throw new errs.PermissionError("Login required"); + } + const clean = Object.keys(dnsPlugins).map((key) => ({ + id: key, + name: dnsPlugins[key].name, + credentials: dnsPlugins[key].credentials, + })); + res.status(200).send(clean); + } catch (err) { + logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + /** * Test HTTP challenge for domains * @@ -107,6 +140,41 @@ router } }); + +/** + * Validate Certs before saving + * + * /api/nginx/certificates/validate + */ +router + .route("/validate") + .options((_, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * POST /api/nginx/certificates/validate + * + * Validate certificates + */ + .post(async (req, res, next) => { + if (!req.files) { + res.status(400).send({ error: "No files were uploaded" }); + return; + } + + try { + const result = await internalCertificate.validate({ + files: req.files, + }); + res.status(200).send(result); + } catch (err) { + logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); + next(err); + } + }); + /** * Specific certificate * @@ -266,38 +334,4 @@ router } }); -/** - * Validate Certs before saving - * - * /api/nginx/certificates/validate - */ -router - .route("/validate") - .options((_, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/certificates/validate - * - * Validate certificates - */ - .post(async (req, res, next) => { - if (!req.files) { - res.status(400).send({ error: "No files were uploaded" }); - return; - } - - try { - const result = await internalCertificate.validate({ - files: req.files, - }); - res.status(200).send(result); - } catch (err) { - logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); - next(err); - } - }); - export default router; diff --git a/backend/routes/reports.js b/backend/routes/reports.js index a5ac3de7..bd3a91fe 100644 --- a/backend/routes/reports.js +++ b/backend/routes/reports.js @@ -14,11 +14,12 @@ router .options((_, res) => { res.sendStatus(204); }) + .all(jwtdecode()) /** * GET /reports/hosts */ - .get(jwtdecode(), async (req, res, next) => { + .get(async (req, res, next) => { try { const data = await internalReport.getHostsReport(res.locals.access); res.status(200).send(data); diff --git a/frontend/src/api/backend/getCertificateDNSProviders.ts b/frontend/src/api/backend/getCertificateDNSProviders.ts new file mode 100644 index 00000000..03e3afa2 --- /dev/null +++ b/frontend/src/api/backend/getCertificateDNSProviders.ts @@ -0,0 +1,9 @@ +import * as api from "./base"; +import type { DNSProvider } from "./models"; + +export async function getCertificateDNSProviders(params = {}): Promise { + return await api.get({ + url: "/nginx/certificates/dns-providers", + params, + }); +} diff --git a/frontend/src/api/backend/index.ts b/frontend/src/api/backend/index.ts index cb771c04..65210cb8 100644 --- a/frontend/src/api/backend/index.ts +++ b/frontend/src/api/backend/index.ts @@ -19,6 +19,7 @@ export * from "./getAccessLists"; export * from "./getAuditLog"; export * from "./getAuditLogs"; export * from "./getCertificate"; +export * from "./getCertificateDNSProviders"; export * from "./getCertificates"; export * from "./getDeadHost"; export * from "./getDeadHosts"; diff --git a/frontend/src/api/backend/models.ts b/frontend/src/api/backend/models.ts index 7a8037a7..6526fcc4 100644 --- a/frontend/src/api/backend/models.ts +++ b/frontend/src/api/backend/models.ts @@ -193,3 +193,9 @@ export interface Setting { value: string; meta: Record; } + +export interface DNSProvider { + id: string; + name: string; + credentials: string; +} diff --git a/frontend/src/components/Form/DNSProviderFields.module.css b/frontend/src/components/Form/DNSProviderFields.module.css new file mode 100644 index 00000000..ba4ac629 --- /dev/null +++ b/frontend/src/components/Form/DNSProviderFields.module.css @@ -0,0 +1,16 @@ +.dnsChallengeWarning { + border: 1px solid #fecaca; /* Tailwind's red-300 */ + padding: 1rem; + border-radius: 0.375rem; /* Tailwind's rounded-md */ + margin-top: 1rem; +} + +.textareaMono { + font-family: 'Courier New', Courier, monospace !important; + /* background-color: #f9fafb; + border: 1px solid #d1d5db; + padding: 0.5rem; + border-radius: 0.375rem; + width: 100%; */ + resize: vertical; +} diff --git a/frontend/src/components/Form/DNSProviderFields.tsx b/frontend/src/components/Form/DNSProviderFields.tsx new file mode 100644 index 00000000..3ac36eaf --- /dev/null +++ b/frontend/src/components/Form/DNSProviderFields.tsx @@ -0,0 +1,114 @@ +import cn from "classnames"; +import { Field, useFormikContext } from "formik"; +import { useState } from "react"; +import Select, { type ActionMeta } from "react-select"; +import type { DNSProvider } from "src/api/backend"; +import { useDnsProviders } from "src/hooks"; +import styles from "./DNSProviderFields.module.css"; + +interface DNSProviderOption { + readonly value: string; + readonly label: string; + readonly credentials: string; +} + +export function DNSProviderFields() { + const { values, setFieldValue } = useFormikContext(); + const { data: dnsProviders, isLoading } = useDnsProviders(); + const [dnsProviderId, setDnsProviderId] = useState(null); + + const v: any = values || {}; + + const handleChange = (newValue: any, _actionMeta: ActionMeta) => { + setFieldValue("dnsProvider", newValue?.value); + setFieldValue("dnsProviderCredentials", newValue?.credentials); + setDnsProviderId(newValue?.value); + }; + + const options: DNSProviderOption[] = + dnsProviders?.map((p: DNSProvider) => ({ + value: p.id, + label: p.name, + credentials: p.credentials, + })) || []; + + return ( +
    +

    + This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective + plugins documentation. +

    + + + {({ field }: any) => ( +
    + +