Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
330993f
Convert backend to ESM
jc21 Sep 2, 2025
fadec97
React
jc21 Sep 2, 2025
61a9290
Notification toasts, nicer loading, add new user support
jc21 Sep 3, 2025
6ab7198
User table polishing, user delete modal
jc21 Sep 3, 2025
0b2fa82
Introducing the Setup Wizard for creating the first user
jc21 Sep 8, 2025
7a6efd8
User Permissions Modal
jc21 Sep 8, 2025
fb2708d
Fix cypress tests following user wizard changes
jc21 Sep 10, 2025
d40e290
Biome update
jc21 Sep 10, 2025
1928e55
Fix proxy hosts routes throwing errors
jc21 Sep 10, 2025
038de3e
Refactor from Promises to async/await
jc21 Sep 10, 2025
8ad95c5
Set password for users
jc21 Sep 13, 2025
429046f
Audit log table and modal
jc21 Sep 14, 2025
efcefe0
Fix custom cert writes, fix schema
jc21 Sep 15, 2025
058f49c
Certificates react table basis
jc21 Sep 17, 2025
54e0362
API lib cleanup, 404 hosts WIP
jc21 Sep 21, 2025
94375bb
DNS Provider configuration
jc21 Sep 22, 2025
d85e515
Dark UI for react-select
jc21 Sep 22, 2025
18537b9
404 hosts add update complete, fix certbot renewals
jc21 Sep 23, 2025
da68fe2
404 hosts polish
jc21 Sep 24, 2025
fc8a5e8
404 hosts search
jc21 Sep 24, 2025
a3d1724
User table polish and audit log updates
jc21 Sep 24, 2025
8884e3b
TZ for dev db
jc21 Sep 24, 2025
4866988
Fix stream creation with new ssl cert
jc21 Sep 24, 2025
100a7e3
Streams modal
jc21 Sep 24, 2025
9339626
Streams polish
jc21 Sep 24, 2025
e36c1b9
Redirection hosts ui
jc21 Sep 25, 2025
abdf886
Auto sorting of locale files
jc21 Sep 25, 2025
d0767ba
Proxy host modal basis, other improvements
jc21 Sep 30, 2025
fcb08d3
Bump version
jc21 Oct 1, 2025
227e818
Wrap intl in span identifying translation
jc21 Oct 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.12.6
2.13.0
10 changes: 1 addition & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.12.6-green.svg?style=for-the-badge">
<img src="https://img.shields.io/badge/version-2.13.0-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>
Expand Down Expand Up @@ -88,14 +88,6 @@ Sometimes this can take a little bit because of the entropy of keys.

[http://127.0.0.1:81](http://127.0.0.1:81)

Default Admin User:
```
Email: admin@example.com
Password: changeme
```

Immediately after logging in with this default user you will be asked to modify your details and change your password.


## Contributing

Expand Down
73 changes: 0 additions & 73 deletions backend/.eslintrc.json

This file was deleted.

11 changes: 0 additions & 11 deletions backend/.prettierrc

This file was deleted.

88 changes: 45 additions & 43 deletions backend/app.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
const express = require('express');
const bodyParser = require('body-parser');
const fileUpload = require('express-fileupload');
const compression = require('compression');
const config = require('./lib/config');
const log = require('./logger').express;
import bodyParser from "body-parser";
import compression from "compression";
import express from "express";
import fileUpload from "express-fileupload";
import { isDebugMode } from "./lib/config.js";
import cors from "./lib/express/cors.js";
import jwt from "./lib/express/jwt.js";
import { express as logger } from "./logger.js";
import mainRoutes from "./routes/main.js";

/**
* App
*/
const app = express();
app.use(fileUpload());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.urlencoded({ extended: true }));

// Gzip
app.use(compression());
Expand All @@ -20,71 +23,70 @@ app.use(compression());
* General Logging, BEFORE routes
*/

app.disable('x-powered-by');
app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
app.enable('strict routing');
app.disable("x-powered-by");
app.enable("trust proxy", ["loopback", "linklocal", "uniquelocal"]);
app.enable("strict routing");

// pretty print JSON when not live
if (config.debug()) {
app.set('json spaces', 2);
if (isDebugMode()) {
app.set("json spaces", 2);
}

// CORS for everything
app.use(require('./lib/express/cors'));
app.use(cors);

// General security/cache related headers + server header
app.use(function (req, res, next) {
let x_frame_options = 'DENY';
app.use((_, res, next) => {
let x_frame_options = "DENY";

if (typeof process.env.X_FRAME_OPTIONS !== 'undefined' && process.env.X_FRAME_OPTIONS) {
if (typeof process.env.X_FRAME_OPTIONS !== "undefined" && process.env.X_FRAME_OPTIONS) {
x_frame_options = process.env.X_FRAME_OPTIONS;
}

res.set({
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': x_frame_options,
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
Pragma: 'no-cache',
Expires: 0
"X-XSS-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": x_frame_options,
"Cache-Control": "no-cache, no-store, max-age=0, must-revalidate",
Pragma: "no-cache",
Expires: 0,
});
next();
});

app.use(require('./lib/express/jwt')());
app.use('/', require('./routes/main'));
app.use(jwt());
app.use("/", mainRoutes);

// production error handler
// no stacktraces leaked to user
// eslint-disable-next-line
app.use(function (err, req, res, next) {

let payload = {
app.use((err, req, res, _) => {
const payload = {
error: {
code: err.status,
message: err.public ? err.message : 'Internal Error'
}
code: err.status,
message: err.public ? err.message : "Internal Error",
},
};

if (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) {
if (typeof err.message_i18n !== "undefined") {
payload.error.message_i18n = err.message_i18n;
}

if (isDebugMode() || (req.baseUrl + req.path).includes("nginx/certificates")) {
payload.debug = {
stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
previous: err.previous
stack: typeof err.stack !== "undefined" && err.stack ? err.stack.split("\n") : null,
previous: err.previous,
};
}

// Not every error is worth logging - but this is good for now until it gets annoying.
if (typeof err.stack !== 'undefined' && err.stack) {
if (config.debug()) {
log.debug(err.stack);
} else if (typeof err.public == 'undefined' || !err.public) {
log.warn(err.message);
if (typeof err.stack !== "undefined" && err.stack) {
logger.debug(err.stack);
if (typeof err.public === "undefined" || !err.public) {
logger.warn(err.message);
}
}

res
.status(err.status || 500)
.send(payload);
res.status(err.status || 500).send(payload);
});

module.exports = app;
export default app;
91 changes: 91 additions & 0 deletions backend/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"includes": [
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx",
"!**/dist/**/*"
]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 4,
"lineWidth": 120,
"formatWithErrors": true
},
"assist": {
"actions": {
"source": {
"organizeImports": {
"level": "on",
"options": {
"groups": [
":BUN:",
":NODE:",
[
"npm:*",
"npm:*/**"
],
":PACKAGE_WITH_PROTOCOL:",
":URL:",
":PACKAGE:",
[
"/src/*",
"/src/**"
],
[
"/**"
],
[
"#*",
"#*/**"
],
":PATH:"
]
}
}
}
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"useUniqueElementIds": "off"
},
"suspicious": {
"noExplicitAny": "off"
},
"performance": {
"noDelete": "off"
},
"nursery": "off",
"a11y": {
"useSemanticElements": "off",
"useValidAnchor": "off"
},
"style": {
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
}
}
}
}
35 changes: 20 additions & 15 deletions backend/db.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
const config = require('./lib/config');
import knex from "knex";
import {configGet, configHas} from "./lib/config.js";

if (!config.has('database')) {
throw new Error('Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/');
}
const generateDbConfig = () => {
if (!configHas("database")) {
throw new Error(
"Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/",
);
}

const cfg = configGet("database");

function generateDbConfig() {
const cfg = config.get('database');
if (cfg.engine === 'knex-native') {
if (cfg.engine === "knex-native") {
return cfg.knex;
}

return {
client: cfg.engine,
client: cfg.engine,
connection: {
host: cfg.host,
user: cfg.user,
host: cfg.host,
user: cfg.user,
password: cfg.password,
database: cfg.name,
port: cfg.port
port: cfg.port,
},
migrations: {
tableName: 'migrations'
}
tableName: "migrations",
},
};
}
};

module.exports = require('knex')(generateDbConfig());
export default knex(generateDbConfig());
Loading