Compare commits

...

40 Commits

Author SHA1 Message Date
jc21
e88d55f1d2 Merge pull request #4839 from NginxProxyManager/develop
v2.13.1
2025-11-05 15:40:32 +10:00
Jamie Curnow
4cb85f6480 Fix #4833 supports the usual proxy env vars for outgoing admin related requests
All checks were successful
Close stale issues and PRs / stale (push) Successful in 20s
2025-11-05 15:16:42 +10:00
jc21
df7dea2d16 Merge branch 'master' into develop 2025-11-05 12:35:06 +10:00
Jamie Curnow
23f4948bde Bump version 2025-11-05 12:33:59 +10:00
Jamie Curnow
0ceb7d0892 Fix #4838 when showing avatars of deleted users 2025-11-05 12:33:13 +10:00
Jamie Curnow
f35671db21 Fix #4837 for those with older config 2025-11-05 10:56:23 +10:00
Jamie Curnow
a3a0614948 Fix #4828 showing incorrect certicificate value 2025-11-05 10:21:55 +10:00
Jamie Curnow
06b67ed4bc Remove user name column from audit log
All checks were successful
Close stale issues and PRs / stale (push) Successful in 20s
2025-11-04 14:57:10 +10:00
Jamie Curnow
4a0e27572e Fix missing translation for renew cert dialog 2025-11-04 14:54:02 +10:00
jc21
fbea8dfa9e Merge pull request #4825 from NginxProxyManager/develop
v2.13.0
2025-11-04 14:23:00 +10:00
Jamie Curnow
8c37348b65 Properly wrap debug calls 2025-11-04 13:43:52 +10:00
Jamie Curnow
2b3e9d72f4 Updated docs screenshots 2025-11-04 13:05:21 +10:00
jc21
a3e5235d81 Merge branch 'master' into develop
All checks were successful
Close stale issues and PRs / stale (push) Successful in 26s
2025-11-04 07:47:04 +10:00
jc21
9875fa92f1 Merge pull request #4794 from Johno-ACSLive/develop
Add basic MySQL TLS support
2025-11-04 07:13:15 +10:00
jc21
ef5156b613 Merge pull request #4813 from potatojuicemachine/develop
Adds Hetzner Cloud to available plugins
2025-11-03 13:38:11 +10:00
Jamie Curnow
b9a34ebb7e Revert to cypress 14, 15 was causing problems with executing external commands 2025-11-03 12:53:23 +10:00
Jamie Curnow
7642d0a000 Cleanup cypress tests 2025-11-03 12:35:58 +10:00
Jamie Curnow
7a6a9de0ea Update frontend deps
All checks were successful
Close stale issues and PRs / stale (push) Successful in 19s
2025-11-03 10:53:46 +10:00
Jamie Curnow
a5d50f9588 Update test deps 2025-11-03 10:52:53 +10:00
Jamie Curnow
612695c2e8 Upgrade biomejs 2025-11-03 10:51:16 +10:00
Jonathon Aroutsidis
71a2277b9b Replace spaces with tabs 2025-11-03 10:48:14 +11:00
Jonathon Aroutsidis
5acf287ea7 Aligned Assignments and arrow-parens 2025-11-03 10:48:14 +11:00
Jonathon Aroutsidis
e34206b526 Include SSL Options for MySQL 2025-11-03 10:46:20 +11:00
jc21
6b00adf8b9 Merge pull request #4725 from NginxProxyManager/dependabot/npm_and_yarn/test/eslint/plugin-kit-0.3.5
Bump @eslint/plugin-kit from 0.3.2 to 0.3.5 in /test
2025-11-03 08:49:30 +10:00
jc21
a93558278e Merge pull request #4763 from NginxProxyManager/dependabot/npm_and_yarn/test/axios-1.12.0
Bump axios from 1.10.0 to 1.12.0 in /test
2025-11-03 08:37:03 +10:00
jc21
bc2867b357 Merge pull request #4803 from NginxProxyManager/dependabot/npm_and_yarn/docs/vite-5.4.21
Bump vite from 5.4.19 to 5.4.21 in /docs
2025-11-03 08:18:00 +10:00
jc21
52093ba258 Merge pull request #4805 from vlauciani/patch-1
Update PostgreSQL volume path in setup documentation for 18+
2025-11-03 08:15:23 +10:00
jc21
24216f1f2f Merge pull request #4785 from NginxProxyManager/react
v2.13.0 React UI
2025-11-02 22:48:16 +10:00
Jamie Curnow
52e528f217 Remove incomplete languages and cleanup 2025-11-02 21:28:25 +10:00
Jamie Curnow
4709f9826c Permissions polish for restricted users 2025-10-31 12:50:54 +10:00
Jamie Curnow
74a8c5d806 Fix app crash when do unautorized things 2025-10-30 15:03:01 +10:00
Jamie Curnow
82a1a86c3a Log in as user support 2025-10-30 14:45:22 +10:00
Tim Burr
e0985bee43 Merge remote-tracking branch 'base/react' into develop 2025-10-29 13:15:58 +01:00
Tim Burr
51dd6e6a1b Sets postgres version to 17 2025-10-29 10:59:01 +01:00
Tim Burr
a2ea63a539 Adds Hetzner Cloud 2025-10-27 13:48:41 +01:00
Valentino Lauciani
bfcd057755 Update PostgreSQL volume path in setup documentation for 18+ 2025-10-24 09:30:19 +02:00
dependabot[bot]
08bdc23131 Bump vite from 5.4.19 to 5.4.21 in /docs
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.19 to 5.4.21.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.21/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.21/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 5.4.21
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-21 07:13:05 +00:00
dependabot[bot]
c9aba0c928 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] <support@github.com>
2025-09-13 15:18:55 +00:00
dependabot[bot]
4397f57a51 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] <support@github.com>
2025-08-22 02:38:28 +00:00
jc21
356eaa0691 Merge pull request #4653 from NginxProxyManager/develop
v2.12.6
2025-07-10 07:18:53 +10:00
125 changed files with 1401 additions and 918 deletions

View File

@@ -1 +1 @@
2.13.0
2.13.1

View File

@@ -1,7 +1,7 @@
<p align="center">
<img src="https://nginxproxymanager.com/github.png">
<br><br>
<img src="https://img.shields.io/badge/version-2.13.0-green.svg?style=for-the-badge">
<img src="https://img.shields.io/badge/version-2.13.1-green.svg?style=for-the-badge">
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
</a>

View File

@@ -5,7 +5,7 @@ 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 { debug, express as logger } from "./logger.js";
import mainRoutes from "./routes/main.js";
/**
@@ -80,7 +80,7 @@ app.use((err, req, res, _) => {
// Not every error is worth logging - but this is good for now until it gets annoying.
if (typeof err.stack !== "undefined" && err.stack) {
logger.debug(err.stack);
debug(logger, err.stack);
if (typeof err.public === "undefined" || !err.public) {
logger.warn(err.message);
}

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.1/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.2/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",

View File

@@ -295,6 +295,14 @@
"credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef",
"full_plugin_name": "dns-hetzner"
},
"hetzner-cloud": {
"name": "Hetzner Cloud",
"package_name": "certbot-dns-hetzner-cloud",
"version": "~=1.0.4",
"dependencies": "",
"credentials": "dns_hetzner_cloud_api_token = your_api_token_here",
"full_plugin_name": "dns-hetzner-cloud"
},
"hostingnl": {
"name": "Hosting.nl",
"package_name": "certbot-dns-hostingnl",

View File

@@ -21,7 +21,8 @@ const generateDbConfig = () => {
user: cfg.user,
password: cfg.password,
database: cfg.name,
port: cfg.port,
port: cfg.port,
...(cfg.ssl ? { ssl: cfg.ssl } : {})
},
migrations: {
tableName: "migrations",

View File

@@ -4,13 +4,14 @@ import path from "path";
import archiver from "archiver";
import _ from "lodash";
import moment from "moment";
import { ProxyAgent } from "proxy-agent";
import tempWrite from "temp-write";
import dnsPlugins from "../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 { debug, ssl as logger } from "../logger.js";
import certificateModel from "../models/certificate.js";
import tokenModel from "../models/token.js";
import userModel from "../models/user.js";
@@ -355,7 +356,7 @@ const internalCertificate = {
const opName = `/tmp/${downloadName}`;
await internalCertificate.zipFiles(certFiles, opName);
logger.debug("zip completed : ", opName);
debug(logger, "zip completed : ", opName);
return {
fileName: opName,
};
@@ -375,7 +376,7 @@ const internalCertificate = {
return new Promise((resolve, reject) => {
source.map((fl) => {
const fileName = path.basename(fl);
logger.debug(fl, "added to certificate zip");
debug(logger, fl, "added to certificate zip");
archive.file(fl, { name: fileName });
return true;
});
@@ -1114,6 +1115,7 @@ const internalCertificate = {
performTestForDomain: async (domain) => {
logger.info(`Testing http challenge for ${domain}`);
const agent = new ProxyAgent();
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 = {
@@ -1123,6 +1125,7 @@ const internalCertificate = {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": Buffer.byteLength(formBody),
},
agent,
};
const result = await new Promise((resolve) => {

View File

@@ -2,6 +2,7 @@ import fs from "node:fs";
import https from "node:https";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { ProxyAgent } from "proxy-agent";
import errs from "../lib/error.js";
import utils from "../lib/utils.js";
import { ipRanges as logger } from "../logger.js";
@@ -29,10 +30,11 @@ const internalIpRanges = {
},
fetchUrl: (url) => {
const agent = new ProxyAgent();
return new Promise((resolve, reject) => {
logger.info(`Fetching ${url}`);
return https
.get(url, (res) => {
.get(url, { agent }, (res) => {
res.setEncoding("utf8");
let raw_data = "";
res.on("data", (chunk) => {

View File

@@ -4,7 +4,7 @@ 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";
import { debug, nginx as logger } from "../logger.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -68,7 +68,7 @@ const internalNginx = {
return true;
});
logger.debug("Nginx test failed:", valid_lines.join("\n"));
debug(logger, "Nginx test failed:", valid_lines.join("\n"));
// config is bad, update meta and delete config
combined_meta = _.assign({}, host.meta, {
@@ -102,7 +102,7 @@ const internalNginx = {
* @returns {Promise}
*/
test: () => {
logger.debug("Testing Nginx configuration");
debug(logger, "Testing Nginx configuration");
return utils.execFile("/usr/sbin/nginx", ["-t", "-g", "error_log off;"]);
},
@@ -190,7 +190,7 @@ const internalNginx = {
const host = JSON.parse(JSON.stringify(host_row));
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
logger.debug(`Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2));
debug(logger, `Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2));
const renderEngine = utils.getRenderEngine();
@@ -241,7 +241,7 @@ const internalNginx = {
.parseAndRender(template, host)
.then((config_text) => {
fs.writeFileSync(filename, config_text, { encoding: "utf8" });
logger.debug("Wrote config:", filename, config_text);
debug(logger, "Wrote config:", filename, config_text);
// Restore locations array
host.locations = origLocations;
@@ -249,7 +249,7 @@ const internalNginx = {
resolve(true);
})
.catch((err) => {
logger.debug(`Could not write ${filename}:`, err.message);
debug(logger, `Could not write ${filename}:`, err.message);
reject(new errs.ConfigurationError(err.message));
});
});
@@ -265,7 +265,7 @@ const internalNginx = {
* @returns {Promise}
*/
generateLetsEncryptRequestConfig: (certificate) => {
logger.debug("Generating LetsEncrypt Request Config:", certificate);
debug(logger, "Generating LetsEncrypt Request Config:", certificate);
const renderEngine = utils.getRenderEngine();
return new Promise((resolve, reject) => {
@@ -285,11 +285,11 @@ const internalNginx = {
.parseAndRender(template, certificate)
.then((config_text) => {
fs.writeFileSync(filename, config_text, { encoding: "utf8" });
logger.debug("Wrote config:", filename, config_text);
debug(logger, "Wrote config:", filename, config_text);
resolve(true);
})
.catch((err) => {
logger.debug(`Could not write ${filename}:`, err.message);
debug(logger, `Could not write ${filename}:`, err.message);
reject(new errs.ConfigurationError(err.message));
});
});
@@ -305,10 +305,10 @@ const internalNginx = {
return;
}
try {
logger.debug(`Deleting file: ${filename}`);
debug(logger, `Deleting file: ${filename}`);
fs.unlinkSync(filename);
} catch (err) {
logger.debug("Could not delete file:", JSON.stringify(err, null, 2));
debug(logger, "Could not delete file:", JSON.stringify(err, null, 2));
}
},

View File

@@ -265,7 +265,7 @@ export default function (tokenString) {
schemas: [roleSchema, permsSchema, objectSchema, permissionSchema],
});
const valid = ajv.validate("permissions", dataSchema);
const valid = await ajv.validate("permissions", dataSchema);
return valid && dataSchema[permission];
} catch (err) {
err.permission = permission;

View File

@@ -25,15 +25,26 @@ const configure = () => {
if (configData?.database) {
logger.info(`Using configuration from file: ${filename}`);
// Migrate those who have "mysql" engine to "mysql2"
if (configData.database.engine === "mysql") {
configData.database.engine = mysqlEngine;
}
instance = configData;
instance.keys = getKeys();
return;
}
}
const envMysqlHost = process.env.DB_MYSQL_HOST || null;
const envMysqlUser = process.env.DB_MYSQL_USER || null;
const envMysqlName = process.env.DB_MYSQL_NAME || null;
const toBool = (v) => /^(1|true|yes|on)$/i.test((v || '').trim());
const envMysqlHost = process.env.DB_MYSQL_HOST || null;
const envMysqlUser = process.env.DB_MYSQL_USER || null;
const envMysqlName = process.env.DB_MYSQL_NAME || null;
const envMysqlSSL = toBool(process.env.DB_MYSQL_SSL);
const envMysqlSSLRejectUnauthorized = process.env.DB_MYSQL_SSL_REJECT_UNAUTHORIZED === undefined ? true : toBool(process.env.DB_MYSQL_SSL_REJECT_UNAUTHORIZED);
const envMysqlSSLVerifyIdentity = process.env.DB_MYSQL_SSL_VERIFY_IDENTITY === undefined ? true : toBool(process.env.DB_MYSQL_SSL_VERIFY_IDENTITY);
if (envMysqlHost && envMysqlUser && envMysqlName) {
// we have enough mysql creds to go with mysql
logger.info("Using MySQL configuration");
@@ -44,7 +55,8 @@ const configure = () => {
port: process.env.DB_MYSQL_PORT || 3306,
user: envMysqlUser,
password: process.env.DB_MYSQL_PASSWORD,
name: envMysqlName,
name: envMysqlName,
ssl: envMysqlSSL ? { rejectUnauthorized: envMysqlSSLRejectUnauthorized, verifyIdentity: envMysqlSSLVerifyIdentity } : false,
},
keys: getKeys(),
};
@@ -90,7 +102,9 @@ const configure = () => {
const getKeys = () => {
// Get keys from file
logger.debug("Cheecking for keys file:", keysFile);
if (isDebugMode()) {
logger.debug("Checking for keys file:", keysFile);
}
if (!fs.existsSync(keysFile)) {
generateKeys();
} else if (process.env.DEBUG) {

View File

@@ -3,14 +3,14 @@ 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 { debug, global as logger } from "../logger.js";
import errs from "./error.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const exec = async (cmd, options = {}) => {
logger.debug("CMD:", cmd);
debug(logger, "CMD:", cmd);
const { stdout, stderr } = await new Promise((resolve, reject) => {
const child = nodeExec(cmd, options, (isError, stdout, stderr) => {
if (isError) {
@@ -34,7 +34,7 @@ const exec = async (cmd, options = {}) => {
* @returns {Promise}
*/
const execFile = (cmd, args, options) => {
logger.debug(`CMD: ${cmd} ${args ? args.join(" ") : ""}`);
debug(logger, `CMD: ${cmd} ${args ? args.join(" ") : ""}`);
const opts = options || {};
return new Promise((resolve, reject) => {

View File

@@ -1,4 +1,5 @@
import signale from "signale";
import { isDebugMode } from "./lib/config.js";
const opts = {
logLevel: "info",
@@ -15,4 +16,10 @@ 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 };
const debug = (logger, ...args) => {
if (isDebugMode()) {
logger.debug(...args);
}
};
export { debug, global, migrate, express, access, nginx, ssl, certbot, importer, setup, ipRanges };

View File

@@ -20,9 +20,9 @@
"body-parser": "^1.20.3",
"compression": "^1.7.4",
"express": "^4.20.0",
"express-fileupload": "^1.1.9",
"gravatar": "^1.8.0",
"jsonwebtoken": "^9.0.0",
"express-fileupload": "^1.5.2",
"gravatar": "^1.8.2",
"jsonwebtoken": "^9.0.2",
"knex": "2.4.2",
"liquidjs": "10.6.1",
"lodash": "^4.17.21",
@@ -32,13 +32,14 @@
"objection": "3.0.1",
"path": "^0.12.7",
"pg": "^8.16.3",
"proxy-agent": "^6.5.0",
"signale": "1.4.0",
"sqlite3": "^5.1.7",
"temp-write": "^4.0.0"
},
"devDependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"@biomejs/biome": "^2.3.1",
"@biomejs/biome": "^2.3.2",
"chalk": "4.1.2",
"nodemon": "^2.0.2"
},

View File

@@ -2,7 +2,7 @@ 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";
import { express as logger } from "../logger.js";
import { debug, express as logger } from "../logger.js";
const router = express.Router({
caseSensitive: true,
@@ -47,7 +47,7 @@ router
const rows = await internalAuditLog.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -99,7 +99,7 @@ router
});
res.status(200).send(item);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -3,7 +3,7 @@ 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 { express as logger } from "../../logger.js";
import { debug, express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({
@@ -49,7 +49,7 @@ router
const rows = await internalAccessList.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -65,7 +65,7 @@ router
const result = await internalAccessList.create(res.locals.access, payload);
res.status(201).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -113,7 +113,7 @@ router
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -130,7 +130,7 @@ router
const result = await internalAccessList.update(res.locals.access, payload);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -147,7 +147,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -5,7 +5,7 @@ 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 { express as logger } from "../../logger.js";
import { debug, express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({
@@ -58,7 +58,7 @@ router
);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -81,7 +81,7 @@ router
);
res.status(201).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -115,7 +115,7 @@ router
clean.sort((a, b) => a.name.localeCompare(b.name));
res.status(200).send(clean);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -151,7 +151,7 @@ router
);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -185,7 +185,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -236,7 +236,7 @@ router
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -253,7 +253,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -288,7 +288,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -318,7 +318,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -347,7 +347,7 @@ router
});
res.status(200).download(result.fileName);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -3,7 +3,7 @@ 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 { express as logger } from "../../logger.js";
import { debug, express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({
@@ -49,7 +49,7 @@ router
const rows = await internalDeadHost.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -65,7 +65,7 @@ router
const result = await internalDeadHost.create(res.locals.access, payload);
res.status(201).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -113,7 +113,7 @@ router
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -130,7 +130,7 @@ router
const result = await internalDeadHost.update(res.locals.access, payload);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -147,7 +147,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -174,7 +174,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -199,7 +199,7 @@ router
const result = internalDeadHost.disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) });
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -3,7 +3,7 @@ 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 { express as logger } from "../../logger.js";
import { debug, express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({
@@ -49,7 +49,7 @@ router
const rows = await internalProxyHost.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -65,7 +65,7 @@ router
const result = await internalProxyHost.create(res.locals.access, payload);
res.status(201).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err} ${JSON.stringify(err.debug, null, 2)}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err} ${JSON.stringify(err.debug, null, 2)}`);
next(err);
}
});
@@ -113,7 +113,7 @@ router
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -130,7 +130,7 @@ router
const result = await internalProxyHost.update(res.locals.access, payload);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -147,7 +147,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -174,7 +174,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -201,7 +201,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -3,7 +3,7 @@ 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 { express as logger } from "../../logger.js";
import { debug, express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({
@@ -49,7 +49,7 @@ router
const rows = await internalRedirectionHost.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -65,7 +65,7 @@ router
const result = await internalRedirectionHost.create(res.locals.access, payload);
res.status(201).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -113,7 +113,7 @@ router
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -133,7 +133,7 @@ router
const result = await internalRedirectionHost.update(res.locals.access, payload);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -150,7 +150,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -177,7 +177,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -204,7 +204,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -3,7 +3,7 @@ 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 { express as logger } from "../../logger.js";
import { debug, express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({
@@ -49,7 +49,7 @@ router
const rows = await internalStream.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -65,7 +65,7 @@ router
const result = await internalStream.create(res.locals.access, payload);
res.status(201).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -113,7 +113,7 @@ router
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -130,7 +130,7 @@ router
const result = await internalStream.update(res.locals.access, payload);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -147,7 +147,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -174,7 +174,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -201,7 +201,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -1,7 +1,7 @@
import express from "express";
import internalReport from "../internal/report.js";
import jwtdecode from "../lib/express/jwt-decode.js";
import { express as logger } from "../logger.js";
import { debug, express as logger } from "../logger.js";
const router = express.Router({
caseSensitive: true,
@@ -24,7 +24,7 @@ router
const data = await internalReport.getHostsReport(res.locals.access);
res.status(200).send(data);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -1,5 +1,5 @@
import express from "express";
import { express as logger } from "../logger.js";
import { debug, express as logger } from "../logger.js";
import PACKAGE from "../package.json" with { type: "json" };
import { getCompiledSchema } from "../schema/index.js";
@@ -36,7 +36,7 @@ router
swaggerJSON.servers[0].url = `${origin}/api`;
res.status(200).send(swaggerJSON);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -3,7 +3,7 @@ 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 { express as logger } from "../logger.js";
import { debug, express as logger } from "../logger.js";
import { getValidationSchema } from "../schema/index.js";
const router = express.Router({
@@ -32,7 +32,7 @@ router
const rows = await internalSetting.getAll(res.locals.access);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -76,7 +76,7 @@ router
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -93,7 +93,7 @@ router
const result = await internalSetting.update(res.locals.access, payload);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -2,7 +2,7 @@ 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 { express as logger } from "../logger.js";
import { debug, express as logger } from "../logger.js";
import { getValidationSchema } from "../schema/index.js";
const router = express.Router({
@@ -32,7 +32,7 @@ router
});
res.status(200).send(data);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -48,7 +48,7 @@ router
const result = await internalToken.getTokenFromEmail(data);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -7,7 +7,7 @@ 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 { express as logger } from "../logger.js";
import { debug, express as logger } from "../logger.js";
import { getValidationSchema } from "../schema/index.js";
import { isSetup } from "../setup.js";
@@ -61,7 +61,7 @@ router
);
res.status(200).send(users);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -101,7 +101,7 @@ router
const user = await internalUser.create(res.locals.access, payload);
res.status(201).send(user);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -124,7 +124,7 @@ router
await internalUser.deleteAll();
res.status(200).send(true);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
return;
@@ -185,7 +185,7 @@ router
});
res.status(200).send(user);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -205,7 +205,7 @@ router
const result = await internalUser.update(res.locals.access, payload);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
@@ -222,7 +222,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -255,7 +255,7 @@ router
const result = await internalUser.setPassword(res.locals.access, payload);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -291,7 +291,7 @@ router
);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
@@ -320,7 +320,7 @@ router
});
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});

View File

@@ -43,59 +43,59 @@
ajv-draft-04 "^1.0.0"
call-me-maybe "^1.0.2"
"@biomejs/biome@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.3.1.tgz#d1a9284f52986324f288cdaf450331a0f3fb1da7"
integrity sha512-A29evf1R72V5bo4o2EPxYMm5mtyGvzp2g+biZvRFx29nWebGyyeOSsDWGx3tuNNMFRepGwxmA9ZQ15mzfabK2w==
"@biomejs/biome@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.3.2.tgz#aeeb5f12c39571a18f36a919be63ba7dbc7b290a"
integrity sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg==
optionalDependencies:
"@biomejs/cli-darwin-arm64" "2.3.1"
"@biomejs/cli-darwin-x64" "2.3.1"
"@biomejs/cli-linux-arm64" "2.3.1"
"@biomejs/cli-linux-arm64-musl" "2.3.1"
"@biomejs/cli-linux-x64" "2.3.1"
"@biomejs/cli-linux-x64-musl" "2.3.1"
"@biomejs/cli-win32-arm64" "2.3.1"
"@biomejs/cli-win32-x64" "2.3.1"
"@biomejs/cli-darwin-arm64" "2.3.2"
"@biomejs/cli-darwin-x64" "2.3.2"
"@biomejs/cli-linux-arm64" "2.3.2"
"@biomejs/cli-linux-arm64-musl" "2.3.2"
"@biomejs/cli-linux-x64" "2.3.2"
"@biomejs/cli-linux-x64-musl" "2.3.2"
"@biomejs/cli-win32-arm64" "2.3.2"
"@biomejs/cli-win32-x64" "2.3.2"
"@biomejs/cli-darwin-arm64@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.1.tgz#607835f8ef043e1a80f9ad2a232c9e860941ab60"
integrity sha512-ombSf3MnTUueiYGN1SeI9tBCsDUhpWzOwS63Dove42osNh0PfE1cUtHFx6eZ1+MYCCLwXzlFlYFdrJ+U7h6LcA==
"@biomejs/cli-darwin-arm64@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.2.tgz#93f866161abe32e702987ccbddf492c1aabe016f"
integrity sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew==
"@biomejs/cli-darwin-x64@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.1.tgz#654fe4aaa8ea5d5bde5457db4961ad5d214713ac"
integrity sha512-pcOfwyoQkrkbGvXxRvZNe5qgD797IowpJPovPX5biPk2FwMEV+INZqfCaz4G5bVq9hYnjwhRMamg11U4QsRXrQ==
"@biomejs/cli-darwin-x64@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.2.tgz#9c3dffdac12e4f4d8db7680ca20f58ace1f38c23"
integrity sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA==
"@biomejs/cli-linux-arm64-musl@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.1.tgz#5fe502082a575c31ef808cf080cbcd4485964167"
integrity sha512-+DZYv8l7FlUtTrWs1Tdt1KcNCAmRO87PyOnxKGunbWm5HKg1oZBSbIIPkjrCtDZaeqSG1DiGx7qF+CPsquQRcg==
"@biomejs/cli-linux-arm64-musl@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.2.tgz#a0424d2fe355cc43c375b3fbf3e42d39b7221d0e"
integrity sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw==
"@biomejs/cli-linux-arm64@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.1.tgz#81c02547905d379dbb312e6ff24b04908c2e320f"
integrity sha512-td5O8pFIgLs8H1sAZsD6v+5quODihyEw4nv2R8z7swUfIK1FKk+15e4eiYVLcAE4jUqngvh4j3JCNgg0Y4o4IQ==
"@biomejs/cli-linux-arm64@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.2.tgz#f85717c04d420ede20523d173a1fc10df60d4d37"
integrity sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw==
"@biomejs/cli-linux-x64-musl@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.1.tgz#c7c00beb5eda1ad25185544897e66eeec6be3b0b"
integrity sha512-Y3Ob4nqgv38Mh+6EGHltuN+Cq8aj/gyMTJYzkFZV2AEj+9XzoXB9VNljz9pjfFNHUxvLEV4b55VWyxozQTBaUQ==
"@biomejs/cli-linux-x64-musl@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.2.tgz#d3e114c744c32d2c50a77c13476bd941819c92d8"
integrity sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA==
"@biomejs/cli-linux-x64@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.1.tgz#7481d2e7be98d4de574df233766a5bdda037c897"
integrity sha512-PYWgEO7up7XYwSAArOpzsVCiqxBCXy53gsReAb1kKYIyXaoAlhBaBMvxR/k2Rm9aTuZ662locXUmPk/Aj+Xu+Q==
"@biomejs/cli-linux-x64@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.2.tgz#f66ce85d2d757d45e6edecce04753a805bd816f0"
integrity sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA==
"@biomejs/cli-win32-arm64@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.1.tgz#dac8c7c7223e97f86cd0eed7aa95584984761481"
integrity sha512-RHIG/zgo+69idUqVvV3n8+j58dKYABRpMyDmfWu2TITC+jwGPiEaT0Q3RKD+kQHiS80mpBrST0iUGeEXT0bU9A==
"@biomejs/cli-win32-arm64@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.2.tgz#b46f8b47a3d97e766cc5ad5eb67d90eeb230b2cb"
integrity sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg==
"@biomejs/cli-win32-x64@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.1.tgz#f8818ab2c1e3a6e2ed8a656935173e5ce4c720be"
integrity sha512-izl30JJ5Dp10mi90Eko47zhxE6pYyWPcnX1NQxKpL/yMhXxf95oLTzfpu4q+MDBh/gemNqyJEwjBpe0MT5iWPA==
"@biomejs/cli-win32-x64@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.2.tgz#a14f5e220dd496705278315ee3e5e028dd657344"
integrity sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ==
"@gar/promisify@^1.0.1":
version "1.1.3"
@@ -143,6 +143,11 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@tootallnate/quickjs-emscripten@^0.23.0":
version "0.23.0"
resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c"
integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==
"@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"
@@ -168,6 +173,11 @@ agent-base@6, agent-base@^6.0.2:
dependencies:
debug "4"
agent-base@^7.1.0, agent-base@^7.1.2:
version "7.1.4"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8"
integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
agentkeepalive@^4.1.3:
version "4.6.0"
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a"
@@ -308,6 +318,13 @@ asn1@^0.2.4:
dependencies:
safer-buffer "~2.1.0"
ast-types@^0.13.4:
version "0.13.4"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782"
integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==
dependencies:
tslib "^2.0.1"
async@^3.2.4:
version "3.2.6"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
@@ -328,6 +345,11 @@ base64-js@^1.3.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
basic-ftp@^5.0.2:
version "5.0.5"
resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.0.5.tgz#14a474f5fffecca1f4f406f1c26b18f800225ac0"
integrity sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==
batchflow@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/batchflow/-/batchflow-0.4.0.tgz#7d419df79b6b7587b06f9ea34f96ccef6f74e5b5"
@@ -667,6 +689,11 @@ crc32-stream@^4.0.2:
crc-32 "^1.2.0"
readable-stream "^3.4.0"
data-uri-to-buffer@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz#8a58bb67384b261a38ef18bea1810cb01badd28b"
integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==
db-errors@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/db-errors/-/db-errors-0.2.3.tgz#a6a38952e00b20e790f2695a6446b3c65497ffa2"
@@ -700,6 +727,13 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
debug@^4.3.4:
version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -717,6 +751,15 @@ 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==
degenerator@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-5.0.1.tgz#9403bf297c6dad9a1ece409b37db27954f91f2f5"
integrity sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==
dependencies:
ast-types "^0.13.4"
escodegen "^2.1.0"
esprima "^4.0.1"
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@@ -846,11 +889,37 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
escodegen@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17"
integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==
dependencies:
esprima "^4.0.1"
estraverse "^5.2.0"
esutils "^2.0.2"
optionalDependencies:
source-map "~0.6.1"
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.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
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"
@@ -861,7 +930,7 @@ expand-template@^2.0.3:
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
express-fileupload@^1.1.9:
express-fileupload@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.5.2.tgz#4da70ba6f2ffd4c736eab0776445865a9dbd9bfa"
integrity sha512-wxUJn2vTHvj/kZCVmc5/bJO15C7aSMyHeuXYY3geKpeKibaAoQGcEv5+sM6nHS2T7VF+QHS4hTWPiY2mKofEdg==
@@ -1069,6 +1138,15 @@ get-proto@^1.0.1:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
get-uri@^6.0.1:
version "6.0.5"
resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.5.tgz#714892aa4a871db671abc5395e5e9447bc306a16"
integrity sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==
dependencies:
basic-ftp "^5.0.2"
data-uri-to-buffer "^6.0.2"
debug "^4.3.4"
getopts@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4"
@@ -1108,7 +1186,7 @@ graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.0, 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==
gravatar@^1.8.0:
gravatar@^1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/gravatar/-/gravatar-1.8.2.tgz#f298642b1562ed685af2ae938dbe31ec0c542cc1"
integrity sha512-GdRwLM3oYpFQKy47MKuluw9hZ2gaCtiKPbDGdcDEuYDKlc8eNnW27KYL9LVbIDzEsx88WtDWQm2ClBcsgBnj6w==
@@ -1170,6 +1248,14 @@ http-proxy-agent@^4.0.1:
agent-base "6"
debug "4"
http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1:
version "7.0.2"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e"
integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==
dependencies:
agent-base "^7.1.0"
debug "^4.3.4"
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"
@@ -1178,6 +1264,14 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
https-proxy-agent@^7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
dependencies:
agent-base "^7.1.2"
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"
@@ -1352,7 +1446,7 @@ json-schema-traverse@^1.0.0:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
jsonwebtoken@^9.0.0:
jsonwebtoken@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
@@ -1747,6 +1841,11 @@ negotiator@^0.6.2, negotiator@~0.6.4:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
netmask@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7"
integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==
node-abi@^3.3.0:
version "3.78.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.78.0.tgz#fd0ecbd0aa89857b98da06bd3909194abb0821ba"
@@ -1924,6 +2023,28 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
pac-proxy-agent@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz#9cfaf33ff25da36f6147a20844230ec92c06e5df"
integrity sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==
dependencies:
"@tootallnate/quickjs-emscripten" "^0.23.0"
agent-base "^7.1.2"
debug "^4.3.4"
get-uri "^6.0.1"
http-proxy-agent "^7.0.0"
https-proxy-agent "^7.0.6"
pac-resolver "^7.0.1"
socks-proxy-agent "^8.0.5"
pac-resolver@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.1.tgz#54675558ea368b64d210fd9c92a640b5f3b8abb6"
integrity sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==
dependencies:
degenerator "^5.0.0"
netmask "^2.0.2"
parse-json@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
@@ -2120,6 +2241,25 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
proxy-agent@^6.5.0:
version "6.5.0"
resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.5.0.tgz#9e49acba8e4ee234aacb539f89ed9c23d02f232d"
integrity sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==
dependencies:
agent-base "^7.1.2"
debug "^4.3.4"
http-proxy-agent "^7.0.1"
https-proxy-agent "^7.0.6"
lru-cache "^7.14.1"
pac-proxy-agent "^7.1.0"
proxy-from-env "^1.1.0"
socks-proxy-agent "^8.0.5"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
pstree.remy@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
@@ -2422,7 +2562,16 @@ socks-proxy-agent@^6.0.0:
debug "^4.3.3"
socks "^2.6.2"
socks@^2.6.2:
socks-proxy-agent@^8.0.5:
version "8.0.5"
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz#b9cdb4e7e998509d7659d689ce7697ac21645bee"
integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==
dependencies:
agent-base "^7.1.2"
debug "^4.3.4"
socks "^2.8.3"
socks@^2.6.2, socks@^2.8.3:
version "2.8.7"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea"
integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==
@@ -2430,6 +2579,11 @@ socks@^2.6.2:
ip-address "^10.0.1"
smart-buffer "^4.2.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==
split2@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
@@ -2609,6 +2763,11 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
tslib@^2.0.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"

View File

@@ -24,4 +24,5 @@
.inline-img img {
display: inline;
margin-right: 8px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

View File

@@ -4,17 +4,44 @@ outline: deep
# Screenshots
### Light Mode
::: raw
<div class="inline-img">
<a href="/screenshots/login.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/login.png" alt="Login" title="Login" width="200"/></a>
<a href="/screenshots/dashboard.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dashboard.png" alt="Dashboard" title="Dashboard" width="200"/></a>
<a href="/screenshots/proxy-hosts.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/proxy-hosts.png" alt="Proxy Hosts" title="Proxy Hosts" width="200"/></a>
<a href="/screenshots/proxy-hosts-add.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/proxy-hosts-add.png" alt="Add Proxy Host" title="Add Proxy Host" width="200"/></a>
<a href="/screenshots/redirection-hosts.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/redirection-hosts.png" alt="Redirection Hosts" title="Redirection Hosts" width="200"/></a>
<a href="/screenshots/dead-hosts.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dead-hosts.png" alt="404 Hosts" title="404 Hosts" width="200"/></a>
<a href="/screenshots/permissions.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/permissions.png" alt="User Permissions" title="User Permissions" width="200"/></a>
<a href="/screenshots/certificates.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/certificates.png" alt="Certificates" title="Certificates" width="200"/></a>
<a href="/screenshots/audit-log.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/audit-log.png" alt="Audit Log" title="Audit Log" width="200"/></a>
<a href="/screenshots/custom-settings.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/custom-settings.png" alt="Custom Settings" title="Custom Settings" width="200"/></a>
<a href="/screenshots/light/01_first-user.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/01_first-user.png" alt="Setup" title="Setup" width="200"/></a>
<a href="/screenshots/light/02_login.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/02_login.png" alt="Login" title="Login" width="200"/></a>
<a href="/screenshots/light/03_dashboard.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/03_dashboard.png" alt="Dashboard" title="Dashboard" width="200"/></a>
<a href="/screenshots/light/04_proxy-hosts.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/04_proxy-hosts.png" alt="Proxy Hosts" title="Proxy Hosts" width="200"/></a>
<a href="/screenshots/light/05_redirection_hosts.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/05_redirection_hosts.png" alt="Redirection Hosts" title="Redirection Hosts" width="200"/></a>
<a href="/screenshots/light/06_streams.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/06_streams.png" alt="Streams" title="Streams" width="200"/></a>
<a href="/screenshots/light/07_404_hosts.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/07_404_hosts.png" alt="404 Hosts" title="404 Hosts" width="200"/></a>
<a href="/screenshots/light/08_access-lists.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/08_access-lists.png" alt="Access Lists" title="Access Lists" width="200"/></a>
<a href="/screenshots/light/09_certificates.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/09_certificates.png" alt="Certificates" title="Certificates" width="200"/></a>
<a href="/screenshots/light/10_users.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/10_users.png" alt="Users" title="Users" width="200"/></a>
<a href="/screenshots/light/11_audit-logs.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/11_audit-logs.png" alt="Audit Logs" title="Audit Logs" width="200"/></a>
<a href="/screenshots/light/12_settings.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/12_settings.png" alt="Settings" title="Settings" width="200"/></a>
<a href="/screenshots/light/13_add-proxy_host.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/13_add-proxy_host.png" alt="Add Proxy Host" title="Add Proxy Host" width="200"/></a>
<a href="/screenshots/light/14_add_proxy_host_dns.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/light/14_add_proxy_host_dns.png" alt="Add Proxy Host with DNS" title="Add Proxy Host with DNS" width="200"/></a>
</div>
:::
### Dark Mode
::: raw
<div class="inline-img">
<a href="/screenshots/dark/01_first-user.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/01_first-user.png" alt="Setup" title="Setup" width="200"/></a>
<a href="/screenshots/dark/02_login.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/02_login.png" alt="Login" title="Login" width="200"/></a>
<a href="/screenshots/dark/03_dashboard.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/03_dashboard.png" alt="Dashboard" title="Dashboard" width="200"/></a>
<a href="/screenshots/dark/04_proxy-hosts.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/04_proxy-hosts.png" alt="Proxy Hosts" title="Proxy Hosts" width="200"/></a>
<a href="/screenshots/dark/05_redirection_hosts.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/05_redirection_hosts.png" alt="Redirection Hosts" title="Redirection Hosts" width="200"/></a>
<a href="/screenshots/dark/06_streams.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/06_streams.png" alt="Streams" title="Streams" width="200"/></a>
<a href="/screenshots/dark/07_404_hosts.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/07_404_hosts.png" alt="404 Hosts" title="404 Hosts" width="200"/></a>
<a href="/screenshots/dark/08_access-lists.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/08_access-lists.png" alt="Access Lists" title="Access Lists" width="200"/></a>
<a href="/screenshots/dark/09_certificates.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/09_certificates.png" alt="Certificates" title="Certificates" width="200"/></a>
<a href="/screenshots/dark/10_users.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/10_users.png" alt="Users" title="Users" width="200"/></a>
<a href="/screenshots/dark/11_audit-logs.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/11_audit-logs.png" alt="Audit Logs" title="Audit Logs" width="200"/></a>
<a href="/screenshots/dark/12_settings.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/12_settings.png" alt="Settings" title="Settings" width="200"/></a>
<a href="/screenshots/dark/13_add-proxy_host.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/13_add-proxy_host.png" alt="Add Proxy Host" title="Add Proxy Host" width="200"/></a>
<a href="/screenshots/dark/14_add_proxy_host_dns.png" target="_blank"><img class="no-medium-zoom zooming" src="/screenshots/dark/14_add_proxy_host_dns.png" alt="Add Proxy Host with DNS" title="Add Proxy Host with DNS" width="200"/></a>
</div>
:::

View File

@@ -75,6 +75,10 @@ services:
DB_MYSQL_USER: "npm"
DB_MYSQL_PASSWORD: "npm"
DB_MYSQL_NAME: "npm"
# Optional SSL (see section below)
# DB_MYSQL_SSL: 'true'
# DB_MYSQL_SSL_REJECT_UNAUTHORIZED: 'true'
# DB_MYSQL_SSL_VERIFY_IDENTITY: 'true'
# Uncomment this if IPv6 is not enabled on your host
# DISABLE_IPV6: 'true'
volumes:
@@ -102,6 +106,16 @@ Please note, that `DB_MYSQL_*` environment variables will take precedent over `D
:::
### Optional: MySQL / MariaDB SSL
You can enable TLS for the MySQL/MariaDB connection with these environment variables:
- DB_MYSQL_SSL: Enable SSL when set to true. If unset or false, SSL disabled (previous default behaviour).
- DB_MYSQL_SSL_REJECT_UNAUTHORIZED: (default: true) Validate the server certificate chain. Set to false to allow selfsigned/unknown CA.
- DB_MYSQL_SSL_VERIFY_IDENTITY: (default: true) Performs host name / identity verification.
Enabling SSL using a self-signed cert (not recommended for production).
## Using Postgres database
Similar to the MySQL server setup:
@@ -141,7 +155,7 @@ services:
POSTGRES_PASSWORD: 'npmpass'
POSTGRES_DB: 'npm'
volumes:
- ./postgres:/var/lib/postgresql/data
- ./postgresql:/var/lib/postgresql
```
::: warning

View File

@@ -1065,9 +1065,9 @@ vfile@^6.0.0:
vfile-message "^4.0.0"
vite@^5.4.8:
version "5.4.19"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.19.tgz#20efd060410044b3ed555049418a5e7d1998f959"
integrity sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==
version "5.4.21"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.21.tgz#84a4f7c5d860b071676d39ba513c0d598fdc7027"
integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==
dependencies:
esbuild "^0.21.3"
postcss "^8.4.43"

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.1/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.2/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",

View File

@@ -18,7 +18,7 @@
"dependencies": {
"@tabler/core": "^1.4.0",
"@tabler/icons-react": "^3.35.0",
"@tanstack/react-query": "^5.90.5",
"@tanstack/react-query": "^5.90.6",
"@tanstack/react-table": "^8.21.3",
"@uiw/react-textarea-code-editor": "^3.1.1",
"classnames": "^2.5.1",
@@ -34,13 +34,13 @@
"react-dom": "^19.2.0",
"react-intl": "^7.1.14",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.9.4",
"react-router-dom": "^7.9.5",
"react-select": "^5.10.2",
"react-toastify": "^11.0.5",
"rooks": "^9.3.0"
},
"devDependencies": {
"@biomejs/biome": "^2.3.1",
"@biomejs/biome": "^2.3.2",
"@formatjs/cli": "^6.7.4",
"@tanstack/react-query-devtools": "^5.90.2",
"@testing-library/dom": "^10.4.1",
@@ -52,15 +52,15 @@
"@types/react-dom": "^19.2.2",
"@types/react-table": "^7.7.20",
"@vitejs/plugin-react": "^5.1.0",
"happy-dom": "^20.0.8",
"happy-dom": "^20.0.10",
"postcss": "^8.5.6",
"postcss-simple-vars": "^7.0.1",
"sass": "^1.93.2",
"sass": "^1.93.3",
"tmp": "^0.2.5",
"typescript": "5.9.3",
"vite": "^7.1.12",
"vite-plugin-checker": "^0.11.0",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^4.0.3"
"vitest": "^4.0.6"
}
}

View File

@@ -37,6 +37,7 @@ export * from "./getToken";
export * from "./getUser";
export * from "./getUsers";
export * from "./helpers";
export * from "./loginAsUser";
export * from "./models";
export * from "./refreshToken";
export * from "./renewCertificate";

View File

@@ -0,0 +1,8 @@
import * as api from "./base";
import type { LoginAsTokenResponse } from "./responseTypes";
export async function loginAsUser(id: number): Promise<LoginAsTokenResponse> {
return await api.post({
url: `/users/${id}/login`,
});
}

View File

@@ -1,4 +1,4 @@
import type { AppVersion } from "./models";
import type { AppVersion, User } from "./models";
export interface HealthResponse {
status: string;
@@ -15,3 +15,7 @@ export interface ValidatedCertificateResponse {
certificate: Record<string, any>;
certificateKey: boolean;
}
export interface LoginAsTokenResponse extends TokenResponse {
user: User;
}

View File

@@ -1,8 +1,9 @@
import type { Table as ReactTable } from "@tanstack/react-table";
import cn from "classnames";
import type { ReactNode } from "react";
import { Button } from "src/components";
import { Button, HasPermission } from "src/components";
import { T } from "src/locale";
import { type ADMIN, MANAGE, type Permission, type Section } from "src/modules/Permissions";
interface Props {
tableInstance: ReactTable<any>;
@@ -12,8 +13,20 @@ interface Props {
objects: string;
color?: string;
customAddBtn?: ReactNode;
permissionSection?: Section | typeof ADMIN;
permission?: Permission;
}
function EmptyData({ tableInstance, onNew, isFiltered, object, objects, color = "primary", customAddBtn }: Props) {
function EmptyData({
tableInstance,
onNew,
isFiltered,
object,
objects,
color = "primary",
customAddBtn,
permissionSection,
permission,
}: Props) {
return (
<tr>
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
@@ -27,16 +40,18 @@ function EmptyData({ tableInstance, onNew, isFiltered, object, objects, color =
<h2>
<T id="object.empty" tData={{ objects }} />
</h2>
<p className="text-muted">
<T id="empty-subtitle" />
</p>
{customAddBtn ? (
customAddBtn
) : (
<Button className={cn("my-3", `btn-${color}`)} onClick={onNew}>
<T id="object.add" tData={{ object }} />
</Button>
)}
<HasPermission section={permissionSection} permission={permission || MANAGE} hideError>
<p className="text-muted">
<T id="empty-subtitle" />
</p>
{customAddBtn ? (
customAddBtn
) : (
<Button className={cn("my-3", `btn-${color}`)} onClick={onNew}>
<T id="object.add" tData={{ object }} />
</Button>
)}
</HasPermission>
</>
)}
</div>

View File

@@ -3,25 +3,29 @@ import Alert from "react-bootstrap/Alert";
import { Loading, LoadingPage } from "src/components";
import { useUser } from "src/hooks";
import { T } from "src/locale";
import { type ADMIN, hasPermission, type Permission, type Section } from "src/modules/Permissions";
interface Props {
permission: string;
type: "manage" | "view";
section?: Section | typeof ADMIN;
permission: Permission;
hideError?: boolean;
children?: ReactNode;
pageLoading?: boolean;
loadingNoLogo?: boolean;
}
function HasPermission({
section,
permission,
type,
children,
hideError = false,
pageLoading = false,
loadingNoLogo = false,
}: Props) {
const { data, isLoading } = useUser("me");
const perms = data?.permissions;
if (!section) {
return <>{children}</>;
}
if (isLoading) {
if (hideError) {
@@ -33,33 +37,7 @@ function HasPermission({
return <Loading noLogo={loadingNoLogo} />;
}
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;
}
const allowed = hasPermission(section, permission, data?.permissions, data?.roles);
if (allowed) {
return <>{children}</>;
}

View File

@@ -1,5 +1,5 @@
import { IconLock, IconLogout, IconUser } from "@tabler/icons-react";
import { LocalePicker, ThemeSwitcher, NavLink } from "src/components";
import { LocalePicker, NavLink, ThemeSwitcher } from "src/components";
import { useAuthState } from "src/context";
import { useUser } from "src/hooks";
import { T } from "src/locale";
@@ -26,18 +26,18 @@ export function SiteHeader() {
<span className="navbar-toggler-icon" />
</button>
<div className="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
<NavLink to="/">
<div className={styles.logo}>
<img
src="/images/logo-no-text.svg"
width={40}
height={40}
className="navbar-brand-image"
alt="Logo"
/>
</div>
Nginx Proxy Manager
</NavLink>
<NavLink to="/">
<div className={styles.logo}>
<img
src="/images/logo-no-text.svg"
width={40}
height={40}
className="navbar-brand-image"
alt="Logo"
/>
</div>
Nginx Proxy Manager
</NavLink>
</div>
<div className="navbar-nav flex-row order-md-last">
<div className="d-none d-md-flex">

View File

@@ -11,14 +11,26 @@ import cn from "classnames";
import React from "react";
import { HasPermission, NavLink } from "src/components";
import { T } from "src/locale";
import {
ACCESS_LISTS,
ADMIN,
CERTIFICATES,
DEAD_HOSTS,
type MANAGE,
PROXY_HOSTS,
REDIRECTION_HOSTS,
type Section,
STREAMS,
VIEW,
} from "src/modules/Permissions";
interface MenuItem {
label: string;
icon?: React.ElementType;
to?: string;
items?: MenuItem[];
permission?: string;
permissionType?: "view" | "manage";
permissionSection?: Section | typeof ADMIN;
permission?: typeof VIEW | typeof MANAGE;
}
const menuItems: MenuItem[] = [
@@ -34,26 +46,26 @@ const menuItems: MenuItem[] = [
{
to: "/nginx/proxy",
label: "proxy-hosts",
permission: "proxyHosts",
permissionType: "view",
permissionSection: PROXY_HOSTS,
permission: VIEW,
},
{
to: "/nginx/redirection",
label: "redirection-hosts",
permission: "redirectionHosts",
permissionType: "view",
permissionSection: REDIRECTION_HOSTS,
permission: VIEW,
},
{
to: "/nginx/stream",
label: "streams",
permission: "streams",
permissionType: "view",
permissionSection: STREAMS,
permission: VIEW,
},
{
to: "/nginx/404",
label: "dead-hosts",
permission: "deadHosts",
permissionType: "view",
permissionSection: DEAD_HOSTS,
permission: VIEW,
},
],
},
@@ -61,33 +73,33 @@ const menuItems: MenuItem[] = [
to: "/access",
icon: IconLock,
label: "access-lists",
permission: "accessLists",
permissionType: "view",
permissionSection: ACCESS_LISTS,
permission: VIEW,
},
{
to: "/certificates",
icon: IconShield,
label: "certificates",
permission: "certificates",
permissionType: "view",
permissionSection: CERTIFICATES,
permission: VIEW,
},
{
to: "/users",
icon: IconUser,
label: "users",
permission: "admin",
permissionSection: ADMIN,
},
{
to: "/audit-log",
icon: IconBook,
label: "auditlogs",
permission: "admin",
permissionSection: ADMIN,
},
{
to: "/settings",
icon: IconSettings,
label: "settings",
permission: "admin",
permissionSection: ADMIN,
},
];
@@ -99,8 +111,8 @@ const getMenuItem = (item: MenuItem, onClick?: () => void) => {
return (
<HasPermission
key={`item-${item.label}`}
permission={item.permission || ""}
type={item.permissionType || "view"}
section={item.permissionSection}
permission={item.permission || VIEW}
hideError
>
<li className="nav-item">
@@ -122,8 +134,8 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
return (
<HasPermission
key={`item-${item.label}`}
permission={item.permission || ""}
type={item.permissionType || "view"}
section={item.permissionSection}
permission={item.permission || VIEW}
hideError
>
<li className={cns}>
@@ -147,8 +159,8 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
return (
<HasPermission
key={`${idx}-${subitem.to}`}
permission={subitem.permission || ""}
type={subitem.permissionType || "view"}
section={subitem.permissionSection}
permission={subitem.permission || VIEW}
hideError
>
<NavLink to={subitem.to} isDropdownItem onClick={onClick}>

View File

@@ -5,5 +5,14 @@ interface Props {
certificate?: Certificate;
}
export function CertificateFormatter({ certificate }: Props) {
return <T id={certificate ? "lets-encrypt" : "http-only"} />;
let translation = "http-only";
if (certificate) {
translation = certificate.provider;
if (translation === "letsencrypt") {
translation = "lets-encrypt";
} else if (translation === "other") {
translation = "certificates.custom";
}
}
return <T id={translation} />;
}

View File

@@ -1,5 +1,7 @@
const defaultImg = "/images/default-avatar.jpg";
interface Props {
url: string;
url?: string;
name?: string;
}
export function GravatarFormatter({ url, name }: Props) {
@@ -9,7 +11,7 @@ export function GravatarFormatter({ url, name }: Props) {
title={name}
className="avatar avatar-2 me-2"
style={{
backgroundImage: `url(${url})`,
backgroundImage: `url(${url || defaultImg})`,
}}
/>
</div>

View File

@@ -1,13 +1,14 @@
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 { getToken, loginAsUser, 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<void>;
loginAs: (id: number) => Promise<void>;
logout: () => void;
token?: string;
}
@@ -34,7 +35,20 @@ function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props)
handleTokenUpdate(response);
};
const loginAs = async (id: number) => {
const response = await loginAsUser(id);
AuthStore.add(response);
queryClient.clear();
window.location.reload();
};
const logout = () => {
if (AuthStore.count() >= 2) {
AuthStore.drop();
queryClient.clear();
window.location.reload();
return;
}
AuthStore.clear();
setAuthenticated(false);
queryClient.clear();
@@ -55,7 +69,7 @@ function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props)
true,
);
const value = { authenticated, login, logout };
const value = { authenticated, login, logout, loginAs };
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

View File

@@ -1,21 +1,15 @@
import { createIntl, createIntlCache } from "react-intl";
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"],
["fa", "fa-IR"],
];
const localeOptions = [["en", "en-US"]];
const loadMessages = (locale?: string): typeof langList & typeof langEn => {
const thisLocale = locale || "en";
switch (thisLocale.slice(0, 2)) {
case "fa":
return Object.assign({}, langList, langEn, langFa);
default:
return Object.assign({}, langList, langEn);
}
@@ -23,9 +17,6 @@ const loadMessages = (locale?: string): typeof langList & typeof langEn => {
const getFlagCodeForLocale = (locale?: string) => {
switch (locale) {
case "fa-IR":
case "fa":
return "IR";
default:
return "EN";
}

View File

@@ -32,6 +32,7 @@
"certificate.none.subtitle.for-http": "This host will not use HTTPS",
"certificate.none.title": "None",
"certificate.not-in-use": "Not Used",
"certificate.renew": "Renew Certificate",
"certificates": "Certificates",
"certificates.custom": "Custom Certificate",
"certificates.custom.warning": "Key files protected with a passphrase are not supported.",
@@ -201,6 +202,7 @@
"user.current-password": "Current Password",
"user.edit-profile": "Edit Profile",
"user.full-name": "Full Name",
"user.login-as": "Sign in as {name}",
"user.logout": "Logout",
"user.new-password": "New Password",
"user.nickname": "Nickname",

View File

@@ -1,3 +0,0 @@
{
"dashboard": "داشبورد"
}

View File

@@ -1,5 +1,3 @@
{
"locale-de-DE": "Deutsch",
"locale-en-US": "English",
"locale-fa-IR": "فارسی"
"locale-en-US": "English"
}

View File

@@ -98,6 +98,9 @@
"certificate.not-in-use": {
"defaultMessage": "Not Used"
},
"certificate.renew": {
"defaultMessage": "Renew Certificate"
},
"certificates": {
"defaultMessage": "Certificates"
},
@@ -605,6 +608,9 @@
"user.full-name": {
"defaultMessage": "Full Name"
},
"user.login-as": {
"defaultMessage": "Sign in as {name}"
},
"user.logout": {
"defaultMessage": "Logout"
},

View File

@@ -1,5 +0,0 @@
{
"dashboard": {
"defaultMessage": "داشبورد"
}
}

View File

@@ -1,11 +1,5 @@
{
"locale-de-DE": {
"defaultMessage": "Deutsch"
},
"locale-en-US": {
"defaultMessage": "English"
},
"locale-fa-IR": {
"defaultMessage": "فارسی"
}
}

View File

@@ -47,11 +47,41 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
});
};
// given the field and clicked permission, intelligently set the value, and
// other values that depends on it.
const handleChange = (form: any, field: any, perm: string) => {
if (field.name === "proxyHosts" && perm !== "hidden" && form.values.accessLists === "hidden") {
form.setFieldValue("accessLists", "view");
}
// certs are required for proxy and redirection hosts, and streams
if (
["proxyHosts", "redirectionHosts", "deadHosts", "streams"].includes(field.name) &&
perm !== "hidden" &&
form.values.certificates === "hidden"
) {
form.setFieldValue("certificates", "view");
}
form.setFieldValue(field.name, perm);
};
const getPermissionButtons = (field: any, form: any) => {
const isManage = field.value === "manage";
const isView = field.value === "view";
const isHidden = field.value === "hidden";
let hiddenDisabled = false;
if (field.name === "accessLists") {
hiddenDisabled = form.values.proxyHosts !== "hidden";
}
if (field.name === "certificates") {
hiddenDisabled =
form.values.proxyHosts !== "hidden" ||
form.values.redirectionHosts !== "hidden" ||
form.values.deadHosts !== "hidden" ||
form.values.streams !== "hidden";
}
return (
<div>
<div className="btn-group w-100" role="group">
@@ -63,7 +93,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
autoComplete="off"
value="manage"
checked={field.value === "manage"}
onChange={() => form.setFieldValue(field.name, "manage")}
onChange={() => handleChange(form, field, "manage")}
/>
<label htmlFor={`${field.name}-manage`} className={getClasses(isManage)}>
<T id="permissions.manage" />
@@ -76,7 +106,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
autoComplete="off"
value="view"
checked={field.value === "view"}
onChange={() => form.setFieldValue(field.name, "view")}
onChange={() => handleChange(form, field, "view")}
/>
<label htmlFor={`${field.name}-view`} className={getClasses(isView)}>
<T id="permissions.view" />
@@ -89,7 +119,8 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
autoComplete="off"
value="hidden"
checked={field.value === "hidden"}
onChange={() => form.setFieldValue(field.name, "hidden")}
disabled={hiddenDisabled}
onChange={() => handleChange(form, field, "hidden")}
/>
<label htmlFor={`${field.name}-hidden`} className={getClasses(isHidden)}>
<T id="permissions.hidden" />

View File

@@ -9,14 +9,16 @@ import {
AccessField,
Button,
DomainNamesField,
HasPermission,
Loading,
LocationsFields,
NginxConfigField,
SSLCertificateField,
SSLOptionsFields,
} from "src/components";
import { useProxyHost, useSetProxyHost } from "src/hooks";
import { useProxyHost, useSetProxyHost, useUser } from "src/hooks";
import { T } from "src/locale";
import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions";
import { validateNumber, validateString } from "src/modules/Validations";
import { showObjectSuccess } from "src/notifications";
@@ -28,6 +30,7 @@ interface Props extends InnerModalProps {
id: number | "new";
}
const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
const { data: currentUser, isLoading: userIsLoading, error: userError } = useUser("me");
const { data, isLoading, error } = useProxyHost(id);
const { mutate: setProxyHost } = useSetProxyHost();
const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null);
@@ -58,13 +61,13 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
return (
<Modal show={visible} onHide={remove}>
{!isLoading && error && (
{!isLoading && (error || userError) && (
<Alert variant="danger" className="m-3">
{error?.message || "Unknown error"}
{error?.message || userError?.message || "Unknown error"}
</Alert>
)}
{isLoading && <Loading noLogo />}
{!isLoading && data && (
{isLoading || (userIsLoading && <Loading noLogo />)}
{!isLoading && !userIsLoading && data && currentUser && (
<Formik
initialValues={
{
@@ -349,16 +352,18 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
<T id="cancel" />
</Button>
<Button
type="submit"
actionType="primary"
className="ms-auto bg-lime"
data-bs-dismiss="modal"
isLoading={isSubmitting}
disabled={isSubmitting}
>
<T id="save" />
</Button>
<HasPermission section={PROXY_HOSTS} permission={MANAGE} hideError>
<Button
type="submit"
actionType="primary"
className="ms-auto bg-lime"
data-bs-dismiss="modal"
isLoading={isSubmitting}
disabled={isSubmitting}
>
<T id="save" />
</Button>
</HasPermission>
</Modal.Footer>
</Form>
)}

View File

@@ -47,7 +47,7 @@ const RenewCertificateModal = EasyModal.create(({ id, visible, remove }: Props)
<Modal show={visible} onHide={isSubmitting ? undefined : remove}>
<Modal.Header closeButton={!isSubmitting}>
<Modal.Title>
<T id="renew-certificate" />
<T id="certificate.renew" />
</Modal.Title>
</Modal.Header>
<Modal.Body>

View File

@@ -44,6 +44,7 @@ export class AuthStore {
// const t = this.tokens;
// return t.length > 0;
// }
// Start from the END of the stack and work backwards
hasActiveToken() {
const t = this.tokens;
if (!t.length) {
@@ -68,22 +69,27 @@ export class AuthStore {
localStorage.setItem(TOKEN_KEY, JSON.stringify([{ token, expires }]));
}
// Add a token to the stack
// Add a token to the END of 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 a token from the END of the stack
drop() {
const t = this.tokens;
localStorage.setItem(TOKEN_KEY, JSON.stringify(t.splice(-1, 1)));
t.splice(-1, 1);
localStorage.setItem(TOKEN_KEY, JSON.stringify(t));
}
clear() {
localStorage.removeItem(TOKEN_KEY);
}
count() {
return this.tokens.length;
}
}
export default new AuthStore();

View File

@@ -0,0 +1,49 @@
import type { UserPermissions } from "src/api/backend";
export const ADMIN = "admin";
export const VISIBILITY = "visibility";
export const PROXY_HOSTS = "proxyHosts";
export const REDIRECTION_HOSTS = "redirectionHosts";
export const DEAD_HOSTS = "deadHosts";
export const STREAMS = "streams";
export const CERTIFICATES = "certificates";
export const ACCESS_LISTS = "accessLists";
export const MANAGE = "manage";
export const VIEW = "view";
export const HIDDEN = "hidden";
export const ALL = "all";
export const USER = "user";
export type Section =
| typeof ADMIN
| typeof VISIBILITY
| typeof PROXY_HOSTS
| typeof REDIRECTION_HOSTS
| typeof DEAD_HOSTS
| typeof STREAMS
| typeof CERTIFICATES
| typeof ACCESS_LISTS;
export type Permission = typeof MANAGE | typeof VIEW;
const hasPermission = (
section: Section,
perm: Permission,
userPerms: UserPermissions | undefined,
roles: string[] | undefined,
): boolean => {
if (!userPerms) return false;
if (isAdmin(roles)) return true;
const acceptable = [MANAGE, perm];
// @ts-expect-error 7053
const v = typeof userPerms[section] !== "undefined" ? userPerms[section] : HIDDEN;
return acceptable.indexOf(v) !== -1;
};
const isAdmin = (roles: string[] | undefined): boolean => {
return roles?.includes("admin") || false;
};
export { hasPermission, isAdmin };

View File

@@ -2,9 +2,10 @@ 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 { EmptyData, GravatarFormatter, ValueWithDateFormatter } from "src/components";
import { EmptyData, GravatarFormatter, HasPermission, ValueWithDateFormatter } from "src/components";
import { TableLayout } from "src/components/Table/TableLayout";
import { intl, T } from "src/locale";
import { ACCESS_LISTS, MANAGE } from "src/modules/Permissions";
interface Props {
data: AccessList[];
@@ -20,7 +21,10 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
() => [
columnHelper.accessor((row: any) => row.owner, {
id: "owner",
cell: (info: any) => <GravatarFormatter url={info.getValue().avatar} name={info.getValue().name} />,
cell: (info: any) => {
const value = info.getValue();
return <GravatarFormatter url={value ? value.avatar : ""} name={value ? value.name : ""} />;
},
meta: {
className: "w-1",
},
@@ -84,18 +88,20 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
<IconEdit size={16} />
<T id="action.edit" />
</a>
<div className="dropdown-divider" />
<a
className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault();
onDelete?.(info.row.original.id);
}}
>
<IconTrash size={16} />
<T id="action.delete" />
</a>
<HasPermission section={ACCESS_LISTS} permission={MANAGE} hideError>
<div className="dropdown-divider" />
<a
className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault();
onDelete?.(info.row.original.id);
}}
>
<IconTrash size={16} />
<T id="action.delete" />
</a>
</HasPermission>
</div>
</span>
);
@@ -130,6 +136,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
onNew={onNew}
isFiltered={isFiltered}
color="cyan"
permissionSection={ACCESS_LISTS}
/>
}
/>

View File

@@ -2,10 +2,11 @@ import { IconHelp, IconSearch } from "@tabler/icons-react";
import { useState } from "react";
import Alert from "react-bootstrap/Alert";
import { deleteAccessList } from "src/api/backend";
import { Button, LoadingPage } from "src/components";
import { Button, HasPermission, LoadingPage } from "src/components";
import { useAccessLists } from "src/hooks";
import { T } from "src/locale";
import { showAccessListModal, showDeleteConfirmModal, showHelpModal } from "src/modals";
import { ACCESS_LISTS, MANAGE } from "src/modules/Permissions";
import { showObjectSuccess } from "src/notifications";
import Table from "./Table";
@@ -67,11 +68,17 @@ export default function TableWrapper() {
<Button size="sm" onClick={() => showHelpModal("AccessLists", "cyan")}>
<IconHelp size={20} />
</Button>
{data?.length ? (
<Button size="sm" className="btn-cyan" onClick={() => showAccessListModal("new")}>
<T id="object.add" tData={{ object: "access-list" }} />
</Button>
) : null}
<HasPermission section={ACCESS_LISTS} permission={MANAGE} hideError>
{data?.length ? (
<Button
size="sm"
className="btn-cyan"
onClick={() => showAccessListModal("new")}
>
<T id="object.add" tData={{ object: "access-list" }} />
</Button>
) : null}
</HasPermission>
</div>
</div>
</div>

View File

@@ -1,9 +1,10 @@
import { HasPermission } from "src/components";
import { ACCESS_LISTS, VIEW } from "src/modules/Permissions";
import TableWrapper from "./TableWrapper";
const Access = () => {
return (
<HasPermission permission="accessLists" type="view" pageLoading loadingNoLogo>
<HasPermission section={ACCESS_LISTS} permission={VIEW} pageLoading loadingNoLogo>
<TableWrapper />
</HasPermission>
);

View File

@@ -18,16 +18,12 @@ export default function Table({ data, isFetching, onSelectItem }: Props) {
id: "user.avatar",
cell: (info: any) => {
const value = info.getValue();
return <GravatarFormatter url={value.avatar} name={value.name} />;
return <GravatarFormatter url={value ? value.avatar : ""} name={value ? value.name : ""} />;
},
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" }),

View File

@@ -1,9 +1,10 @@
import { HasPermission } from "src/components";
import { ADMIN, VIEW } from "src/modules/Permissions";
import TableWrapper from "./TableWrapper";
const AuditLog = () => {
return (
<HasPermission permission="admin" type="manage" pageLoading loadingNoLogo>
<HasPermission section={ADMIN} permission={VIEW} pageLoading loadingNoLogo>
<TableWrapper />
</HasPermission>
);

View File

@@ -8,10 +8,12 @@ import {
DomainsFormatter,
EmptyData,
GravatarFormatter,
HasPermission,
} from "src/components";
import { TableLayout } from "src/components/Table/TableLayout";
import { intl, T } from "src/locale";
import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals";
import { CERTIFICATES, MANAGE } from "src/modules/Permissions";
interface Props {
data: Certificate[];
@@ -29,7 +31,7 @@ export default function Table({ data, isFetching, onDelete, onRenew, onDownload,
id: "owner",
cell: (info: any) => {
const value = info.getValue();
return <GravatarFormatter url={value.avatar} name={value.name} />;
return <GravatarFormatter url={value ? value.avatar : ""} name={value ? value.name : ""} />;
},
meta: {
className: "w-1",
@@ -125,29 +127,31 @@ export default function Table({ data, isFetching, onDelete, onRenew, onDownload,
<IconRefresh size={16} />
<T id="action.renew" />
</a>
<a
className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault();
onDownload?.(info.row.original.id);
}}
>
<IconDownload size={16} />
<T id="action.download" />
</a>
<div className="dropdown-divider" />
<a
className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault();
onDelete?.(info.row.original.id);
}}
>
<IconTrash size={16} />
<T id="action.delete" />
</a>
<HasPermission section={CERTIFICATES} permission={MANAGE} hideError>
<a
className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault();
onDownload?.(info.row.original.id);
}}
>
<IconDownload size={16} />
<T id="action.download" />
</a>
<div className="dropdown-divider" />
<a
className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault();
onDelete?.(info.row.original.id);
}}
>
<IconTrash size={16} />
<T id="action.delete" />
</a>
</HasPermission>
</div>
</span>
);
@@ -223,6 +227,7 @@ export default function Table({ data, isFetching, onDelete, onRenew, onDownload,
isFiltered={isFiltered}
color="pink"
customAddBtn={customAddBtn}
permissionSection={CERTIFICATES}
/>
}
/>

Some files were not shown because too many files have changed in this diff Show More