Compare commits

..

31 Commits

Author SHA1 Message Date
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
118 changed files with 1197 additions and 899 deletions

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

@@ -294,6 +294,14 @@
"dependencies": "",
"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",

View File

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

View File

@@ -10,7 +10,7 @@ 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 +355,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 +375,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;
});

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

@@ -31,9 +31,14 @@ const configure = () => {
}
}
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");
@@ -45,6 +50,7 @@ const configure = () => {
user: envMysqlUser,
password: process.env.DB_MYSQL_PASSWORD,
name: envMysqlName,
ssl: envMysqlSSL ? { rejectUnauthorized: envMysqlSSLRejectUnauthorized, verifyIdentity: envMysqlSSLVerifyIdentity } : false,
},
keys: getKeys(),
};
@@ -90,7 +96,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",
@@ -38,7 +38,7 @@
},
"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"
@@ -861,7 +861,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==
@@ -1108,7 +1108,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==
@@ -1352,7 +1352,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==

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,6 +40,7 @@ function EmptyData({ tableInstance, onNew, isFiltered, object, objects, color =
<h2>
<T id="object.empty" tData={{ objects }} />
</h2>
<HasPermission section={permissionSection} permission={permission || MANAGE} hideError>
<p className="text-muted">
<T id="empty-subtitle" />
</p>
@@ -37,6 +51,7 @@ function EmptyData({ tableInstance, onNew, isFiltered, object, objects, color =
<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";

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

@@ -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

@@ -201,6 +201,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

@@ -605,6 +605,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,6 +352,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
<T id="cancel" />
</Button>
<HasPermission section={PROXY_HOSTS} permission={MANAGE} hideError>
<Button
type="submit"
actionType="primary"
@@ -359,6 +363,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
>
<T id="save" />
</Button>
</HasPermission>
</Modal.Footer>
</Form>
)}

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[];
@@ -84,6 +85,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
<IconEdit size={16} />
<T id="action.edit" />
</a>
<HasPermission section={ACCESS_LISTS} permission={MANAGE} hideError>
<div className="dropdown-divider" />
<a
className="dropdown-item"
@@ -96,6 +98,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
<IconTrash size={16} />
<T id="action.delete" />
</a>
</HasPermission>
</div>
</span>
);
@@ -130,6 +133,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>
<HasPermission section={ACCESS_LISTS} permission={MANAGE} hideError>
{data?.length ? (
<Button size="sm" className="btn-cyan" onClick={() => showAccessListModal("new")}>
<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

@@ -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[];
@@ -125,6 +127,7 @@ export default function Table({ data, isFetching, onDelete, onRenew, onDownload,
<IconRefresh size={16} />
<T id="action.renew" />
</a>
<HasPermission section={CERTIFICATES} permission={MANAGE} hideError>
<a
className="dropdown-item"
href="#"
@@ -148,6 +151,7 @@ export default function Table({ data, isFetching, onDelete, onRenew, onDownload,
<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}
/>
}
/>

View File

@@ -2,7 +2,7 @@ import { IconHelp, IconSearch } from "@tabler/icons-react";
import { useState } from "react";
import Alert from "react-bootstrap/Alert";
import { deleteCertificate, downloadCertificate } from "src/api/backend";
import { Button, LoadingPage } from "src/components";
import { Button, HasPermission, LoadingPage } from "src/components";
import { useCertificates } from "src/hooks";
import { T } from "src/locale";
import {
@@ -13,6 +13,7 @@ import {
showHTTPCertificateModal,
showRenewCertificateModal,
} from "src/modals";
import { CERTIFICATES, MANAGE } from "src/modules/Permissions";
import { showError, showObjectSuccess } from "src/notifications";
import Table from "./Table";
@@ -70,7 +71,6 @@ export default function TableWrapper() {
<T id="certificates" />
</h2>
</div>
<div className="col-md-auto col-sm-12">
<div className="ms-auto d-flex flex-wrap btn-list">
{data?.length ? (
@@ -90,6 +90,7 @@ export default function TableWrapper() {
<Button size="sm" onClick={() => showHelpModal("Certificates", "pink")}>
<IconHelp size={20} />
</Button>
<HasPermission section={CERTIFICATES} permission={MANAGE} hideError>
{data?.length ? (
<div className="dropdown">
<button
@@ -134,6 +135,7 @@ export default function TableWrapper() {
</div>
</div>
) : null}
</HasPermission>
</div>
</div>
</div>

View File

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

View File

@@ -1,7 +1,9 @@
import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc } from "@tabler/icons-react";
import { useNavigate } from "react-router-dom";
import { HasPermission } from "src/components";
import { useHostReport } from "src/hooks";
import { T } from "src/locale";
import { DEAD_HOSTS, PROXY_HOSTS, REDIRECTION_HOSTS, STREAMS, VIEW } from "src/modules/Permissions";
const Dashboard = () => {
const { data: hostReport } = useHostReport();
@@ -15,6 +17,7 @@ const Dashboard = () => {
<div className="row row-deck row-cards">
<div className="col-12 my-4">
<div className="row row-cards">
<HasPermission section={PROXY_HOSTS} permission={VIEW} hideError>
<div className="col-sm-6 col-lg-3">
<a
href="/nginx/proxy"
@@ -40,6 +43,8 @@ const Dashboard = () => {
</div>
</a>
</div>
</HasPermission>
<HasPermission section={REDIRECTION_HOSTS} permission={VIEW} hideError>
<div className="col-sm-6 col-lg-3">
<a
href="/nginx/redirection"
@@ -57,12 +62,17 @@ const Dashboard = () => {
</span>
</div>
<div className="col">
<T id="redirection-hosts.count" data={{ count: hostReport?.redirection }} />
<T
id="redirection-hosts.count"
data={{ count: hostReport?.redirection }}
/>
</div>
</div>
</div>
</a>
</div>
</HasPermission>
<HasPermission section={STREAMS} permission={VIEW} hideError>
<div className="col-sm-6 col-lg-3">
<a
href="/nginx/stream"
@@ -86,6 +96,8 @@ const Dashboard = () => {
</div>
</a>
</div>
</HasPermission>
<HasPermission section={DEAD_HOSTS} permission={VIEW} hideError>
<div className="col-sm-6 col-lg-3">
<a
href="/nginx/404"
@@ -109,23 +121,10 @@ const Dashboard = () => {
</div>
</a>
</div>
</HasPermission>
</div>
</div>
</div>
<pre>
<code>{`Todo:
- check mobile
- REDO SCREENSHOTS in docs folder
- check permissions in all places
More for api, then implement here:
- Add error message_18n for all backend errors
- properly wrap all logger.debug called in isDebug check
- add new api endpoint changes to swagger docs
`}</code>
</pre>
</div>
);
};

View File

@@ -7,10 +7,12 @@ import {
DomainsFormatter,
EmptyData,
GravatarFormatter,
HasPermission,
TrueFalseFormatter,
} from "src/components";
import { TableLayout } from "src/components/Table/TableLayout";
import { intl, T } from "src/locale";
import { DEAD_HOSTS, MANAGE } from "src/modules/Permissions";
interface Props {
data: DeadHost[];
@@ -89,6 +91,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
<IconEdit size={16} />
<T id="action.edit" />
</a>
<HasPermission section={DEAD_HOSTS} permission={MANAGE} hideError>
<a
className="dropdown-item"
href="#"
@@ -112,6 +115,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
<IconTrash size={16} />
<T id="action.delete" />
</a>
</HasPermission>
</div>
</span>
);
@@ -146,6 +150,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
onNew={onNew}
isFiltered={isFiltered}
color="red"
permissionSection={DEAD_HOSTS}
/>
}
/>

View File

@@ -3,10 +3,11 @@ import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import Alert from "react-bootstrap/Alert";
import { deleteDeadHost, toggleDeadHost } from "src/api/backend";
import { Button, LoadingPage } from "src/components";
import { Button, HasPermission, LoadingPage } from "src/components";
import { useDeadHosts } from "src/hooks";
import { T } from "src/locale";
import { showDeadHostModal, showDeleteConfirmModal, showHelpModal } from "src/modals";
import { DEAD_HOSTS, MANAGE } from "src/modules/Permissions";
import { showObjectSuccess } from "src/notifications";
import Table from "./Table";
@@ -76,11 +77,13 @@ export default function TableWrapper() {
<Button size="sm" onClick={() => showHelpModal("DeadHosts", "red")}>
<IconHelp size={20} />
</Button>
<HasPermission section={DEAD_HOSTS} permission={MANAGE} hideError>
{data?.length ? (
<Button size="sm" className="btn-red" onClick={() => showDeadHostModal("new")}>
<T id="object.add" tData={{ object: "dead-host" }} />
</Button>
) : null}
</HasPermission>
</div>
</div>
</div>

View File

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

View File

@@ -8,10 +8,12 @@ import {
DomainsFormatter,
EmptyData,
GravatarFormatter,
HasPermission,
TrueFalseFormatter,
} from "src/components";
import { TableLayout } from "src/components/Table/TableLayout";
import { intl, T } from "src/locale";
import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions";
interface Props {
data: ProxyHost[];
@@ -105,6 +107,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
<IconEdit size={16} />
<T id="action.edit" />
</a>
<HasPermission section={PROXY_HOSTS} permission={MANAGE} hideError>
<a
className="dropdown-item"
href="#"
@@ -128,6 +131,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
<IconTrash size={16} />
<T id="action.delete" />
</a>
</HasPermission>
</div>
</span>
);
@@ -162,6 +166,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
onNew={onNew}
isFiltered={isFiltered}
color="lime"
permissionSection={PROXY_HOSTS}
/>
}
/>

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