diff --git a/.version b/.version index 371a952..ccc99d0 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.12.2 +2.12.3 diff --git a/Jenkinsfile b/Jenkinsfile index 224138b..66ed7cb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -128,7 +128,7 @@ pipeline { sh 'docker-compose down --remove-orphans --volumes -t 30 || true' } unstable { - dir(path: 'testing/results') { + dir(path: 'test/results') { archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') } } @@ -161,7 +161,7 @@ pipeline { sh 'docker-compose down --remove-orphans --volumes -t 30 || true' } unstable { - dir(path: 'testing/results') { + dir(path: 'test/results') { archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') } } @@ -199,7 +199,7 @@ pipeline { sh 'docker-compose down --remove-orphans --volumes -t 30 || true' } unstable { - dir(path: 'testing/results') { + dir(path: 'test/results') { archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') } } diff --git a/README-en.md b/README-en.md index 10b9238..fe66bb9 100644 --- a/README-en.md +++ b/README-en.md @@ -1,7 +1,7 @@
-
+
diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js
index 41c975e..f6043e1 100644
--- a/backend/internal/access-list.js
+++ b/backend/internal/access-list.js
@@ -258,6 +258,7 @@ const internalAccessList = {
})
.where('access_list.is_deleted', 0)
.andWhere('access_list.id', data.id)
+ .groupBy('access_list.id')
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
.first();
@@ -507,8 +508,13 @@ const internalAccessList = {
if (typeof item.password !== 'undefined' && item.password.length) {
logger.info('Adding: ' + item.username);
- utils.execFile('/usr/bin/htpasswd', ['-b', htpasswd_file, item.username, item.password])
- .then((/*result*/) => {
+ utils.execFile('openssl', ['passwd', '-apr1', item.password])
+ .then((res) => {
+ try {
+ fs.appendFileSync(htpasswd_file, item.username + ':' + res + '\n', {encoding: 'utf8'});
+ } catch (err) {
+ reject(err);
+ }
next();
})
.catch((err) => {
diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js
index 34b8fdf..f2e845a 100644
--- a/backend/internal/certificate.js
+++ b/backend/internal/certificate.js
@@ -313,6 +313,9 @@ const internalCertificate = {
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner]')
+ .allowGraph('[proxy_hosts]')
+ .allowGraph('[redirection_hosts]')
+ .allowGraph('[dead_hosts]')
.first();
if (access_data.permission_visibility !== 'all') {
@@ -464,6 +467,9 @@ const internalCertificate = {
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner]')
+ .allowGraph('[proxy_hosts]')
+ .allowGraph('[redirection_hosts]')
+ .allowGraph('[dead_hosts]')
.orderBy('nice_name', 'ASC');
if (access_data.permission_visibility !== 'all') {
diff --git a/backend/internal/stream.js b/backend/internal/stream.js
index 9f76a1d..4d49bc3 100644
--- a/backend/internal/stream.js
+++ b/backend/internal/stream.js
@@ -1,13 +1,15 @@
-const _ = require('lodash');
-const error = require('../lib/error');
-const utils = require('../lib/utils');
-const streamModel = require('../models/stream');
-const internalNginx = require('./nginx');
-const internalAuditLog = require('./audit-log');
-const {castJsonIfNeed} = require('../lib/helpers');
+const _ = require('lodash');
+const error = require('../lib/error');
+const utils = require('../lib/utils');
+const streamModel = require('../models/stream');
+const internalNginx = require('./nginx');
+const internalAuditLog = require('./audit-log');
+const internalCertificate = require('./certificate');
+const internalHost = require('./host');
+const {castJsonIfNeed} = require('../lib/helpers');
function omissions () {
- return ['is_deleted'];
+ return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted'];
}
const internalStream = {
@@ -18,6 +20,12 @@ const internalStream = {
* @returns {Promise}
*/
create: (access, data) => {
+ const create_certificate = data.certificate_id === 'new';
+
+ if (create_certificate) {
+ delete data.certificate_id;
+ }
+
return access.can('streams:create', data)
.then((/*access_data*/) => {
// TODO: At this point the existing ports should have been checked
@@ -27,16 +35,44 @@ const internalStream = {
data.meta = {};
}
+ // streams aren't routed by domain name so don't store domain names in the DB
+ let data_no_domains = structuredClone(data);
+ delete data_no_domains.domain_names;
+
return streamModel
.query()
- .insertAndFetch(data)
+ .insertAndFetch(data_no_domains)
.then(utils.omitRow(omissions()));
})
+ .then((row) => {
+ if (create_certificate) {
+ return internalCertificate.createQuickCertificate(access, data)
+ .then((cert) => {
+ // update host with cert id
+ return internalStream.update(access, {
+ id: row.id,
+ certificate_id: cert.id
+ });
+ })
+ .then(() => {
+ return row;
+ });
+ } else {
+ return row;
+ }
+ })
+ .then((row) => {
+ // re-fetch with cert
+ return internalStream.get(access, {
+ id: row.id,
+ expand: ['certificate', 'owner']
+ });
+ })
.then((row) => {
// Configure nginx
return internalNginx.configure(streamModel, 'stream', row)
.then(() => {
- return internalStream.get(access, {id: row.id, expand: ['owner']});
+ return row;
});
})
.then((row) => {
@@ -60,6 +96,12 @@ const internalStream = {
* @return {Promise}
*/
update: (access, data) => {
+ const create_certificate = data.certificate_id === 'new';
+
+ if (create_certificate) {
+ delete data.certificate_id;
+ }
+
return access.can('streams:update', data.id)
.then((/*access_data*/) => {
// TODO: at this point the existing streams should have been checked
@@ -71,16 +113,32 @@ const internalStream = {
throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
}
+ if (create_certificate) {
+ return internalCertificate.createQuickCertificate(access, {
+ domain_names: data.domain_names || row.domain_names,
+ meta: _.assign({}, row.meta, data.meta)
+ })
+ .then((cert) => {
+ // update host with cert id
+ data.certificate_id = cert.id;
+ })
+ .then(() => {
+ return row;
+ });
+ } else {
+ return row;
+ }
+ })
+ .then((row) => {
+ // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
+ data = _.assign({}, {
+ domain_names: row.domain_names
+ }, data);
+
return streamModel
.query()
.patchAndFetchById(row.id, data)
.then(utils.omitRow(omissions()))
- .then((saved_row) => {
- return internalNginx.configure(streamModel, 'stream', saved_row)
- .then(() => {
- return internalStream.get(access, {id: row.id, expand: ['owner']});
- });
- })
.then((saved_row) => {
// Add to audit log
return internalAuditLog.add(access, {
@@ -93,6 +151,17 @@ const internalStream = {
return saved_row;
});
});
+ })
+ .then(() => {
+ return internalStream.get(access, {id: data.id, expand: ['owner', 'certificate']})
+ .then((row) => {
+ return internalNginx.configure(streamModel, 'stream', row)
+ .then((new_meta) => {
+ row.meta = new_meta;
+ row = internalHost.cleanRowCertificateMeta(row);
+ return _.omit(row, omissions());
+ });
+ });
});
},
@@ -115,7 +184,7 @@ const internalStream = {
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
- .allowGraph('[owner]')
+ .allowGraph('[owner,certificate]')
.first();
if (access_data.permission_visibility !== 'all') {
@@ -132,6 +201,7 @@ const internalStream = {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
+ row = internalHost.cleanRowCertificateMeta(row);
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
row = _.omit(row, data.omit);
@@ -197,14 +267,14 @@ const internalStream = {
.then(() => {
return internalStream.get(access, {
id: data.id,
- expand: ['owner']
+ expand: ['certificate', 'owner']
});
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
- throw new error.ValidationError('Host is already enabled');
+ throw new error.ValidationError('Stream is already enabled');
}
row.enabled = 1;
@@ -250,7 +320,7 @@ const internalStream = {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
- throw new error.ValidationError('Host is already disabled');
+ throw new error.ValidationError('Stream is already disabled');
}
row.enabled = 0;
@@ -298,7 +368,7 @@ const internalStream = {
.query()
.where('is_deleted', 0)
.groupBy('id')
- .allowGraph('[owner]')
+ .allowGraph('[owner,certificate]')
.orderByRaw('CAST(incoming_port AS INTEGER) ASC');
if (access_data.permission_visibility !== 'all') {
@@ -317,6 +387,13 @@ const internalStream = {
}
return query.then(utils.omitRows(omissions()));
+ })
+ .then((rows) => {
+ if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
+ return internalHost.cleanAllRowsCertificateMeta(rows);
+ }
+
+ return rows;
});
},
diff --git a/backend/migrations/20240427161436_stream_ssl.js b/backend/migrations/20240427161436_stream_ssl.js
new file mode 100644
index 0000000..5f47b18
--- /dev/null
+++ b/backend/migrations/20240427161436_stream_ssl.js
@@ -0,0 +1,38 @@
+const migrate_name = 'stream_ssl';
+const logger = require('../logger').migrate;
+
+/**
+ * Migrate
+ *
+ * @see http://knexjs.org/#Schema
+ *
+ * @param {Object} knex
+ * @returns {Promise}
+ */
+exports.up = function (knex) {
+ logger.info('[' + migrate_name + '] Migrating Up...');
+
+ return knex.schema.table('stream', (table) => {
+ table.integer('certificate_id').notNull().unsigned().defaultTo(0);
+ })
+ .then(function () {
+ logger.info('[' + migrate_name + '] stream Table altered');
+ });
+};
+
+/**
+ * Undo Migrate
+ *
+ * @param {Object} knex
+ * @returns {Promise}
+ */
+exports.down = function (knex) {
+ logger.info('[' + migrate_name + '] Migrating Down...');
+
+ return knex.schema.table('stream', (table) => {
+ table.dropColumn('certificate_id');
+ })
+ .then(function () {
+ logger.info('[' + migrate_name + '] stream Table altered');
+ });
+};
diff --git a/backend/models/certificate.js b/backend/models/certificate.js
index 534d927..d4ea21a 100644
--- a/backend/models/certificate.js
+++ b/backend/models/certificate.js
@@ -4,7 +4,6 @@
const db = require('../db');
const helpers = require('../lib/helpers');
const Model = require('objection').Model;
-const User = require('./user');
const now = require('./now_helper');
Model.knex(db);
@@ -68,6 +67,11 @@ class Certificate extends Model {
}
static get relationMappings () {
+ const ProxyHost = require('./proxy_host');
+ const DeadHost = require('./dead_host');
+ const User = require('./user');
+ const RedirectionHost = require('./redirection_host');
+
return {
owner: {
relation: Model.HasOneRelation,
@@ -79,6 +83,39 @@ class Certificate extends Model {
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
+ },
+ proxy_hosts: {
+ relation: Model.HasManyRelation,
+ modelClass: ProxyHost,
+ join: {
+ from: 'certificate.id',
+ to: 'proxy_host.certificate_id'
+ },
+ modify: function (qb) {
+ qb.where('proxy_host.is_deleted', 0);
+ }
+ },
+ dead_hosts: {
+ relation: Model.HasManyRelation,
+ modelClass: DeadHost,
+ join: {
+ from: 'certificate.id',
+ to: 'dead_host.certificate_id'
+ },
+ modify: function (qb) {
+ qb.where('dead_host.is_deleted', 0);
+ }
+ },
+ redirection_hosts: {
+ relation: Model.HasManyRelation,
+ modelClass: RedirectionHost,
+ join: {
+ from: 'certificate.id',
+ to: 'redirection_host.certificate_id'
+ },
+ modify: function (qb) {
+ qb.where('redirection_host.is_deleted', 0);
+ }
}
};
}
diff --git a/backend/models/stream.js b/backend/models/stream.js
index b96ca5a..dbec2dc 100644
--- a/backend/models/stream.js
+++ b/backend/models/stream.js
@@ -1,15 +1,14 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const helpers = require('../lib/helpers');
-const Model = require('objection').Model;
-const User = require('./user');
-const now = require('./now_helper');
+const Model = require('objection').Model;
+const db = require('../db');
+const helpers = require('../lib/helpers');
+const User = require('./user');
+const Certificate = require('./certificate');
+const now = require('./now_helper');
Model.knex(db);
const boolFields = [
+ 'enabled',
'is_deleted',
'tcp_forwarding',
'udp_forwarding',
@@ -64,6 +63,17 @@ class Stream extends Model {
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
+ },
+ certificate: {
+ relation: Model.HasOneRelation,
+ modelClass: Certificate,
+ join: {
+ from: 'stream.certificate_id',
+ to: 'certificate.id'
+ },
+ modify: function (qb) {
+ qb.where('certificate.is_deleted', 0);
+ }
}
};
}
diff --git a/backend/schema/components/stream-object.json b/backend/schema/components/stream-object.json
index e177499..848c30e 100644
--- a/backend/schema/components/stream-object.json
+++ b/backend/schema/components/stream-object.json
@@ -19,9 +19,7 @@
"incoming_port": {
"type": "integer",
"minimum": 1,
- "maximum": 65535,
- "if": {"properties": {"tcp_forwarding": {"const": true}}},
- "then": {"not": {"oneOf": [{"const": 80}, {"const": 443}]}}
+ "maximum": 65535
},
"forwarding_host": {
"anyOf": [
@@ -55,8 +53,24 @@
"enabled": {
"$ref": "../common.json#/properties/enabled"
},
+ "certificate_id": {
+ "$ref": "../common.json#/properties/certificate_id"
+ },
"meta": {
"type": "object"
+ },
+ "owner": {
+ "$ref": "./user-object.json"
+ },
+ "certificate": {
+ "oneOf": [
+ {
+ "type": "null"
+ },
+ {
+ "$ref": "./certificate-object.json"
+ }
+ ]
}
}
}
diff --git a/backend/schema/components/token-object.json b/backend/schema/components/token-object.json
index a7044bc..6ec4e43 100644
--- a/backend/schema/components/token-object.json
+++ b/backend/schema/components/token-object.json
@@ -5,10 +5,9 @@
"additionalProperties": false,
"properties": {
"expires": {
- "description": "Token Expiry Unix Time",
- "example": 1566540249,
- "minimum": 1,
- "type": "number"
+ "description": "Token Expiry ISO Time String",
+ "example": "2025-02-04T20:40:46.340Z",
+ "type": "string"
},
"token": {
"description": "JWT Token",
diff --git a/backend/schema/paths/nginx/streams/get.json b/backend/schema/paths/nginx/streams/get.json
index 596afc6..17969ee 100644
--- a/backend/schema/paths/nginx/streams/get.json
+++ b/backend/schema/paths/nginx/streams/get.json
@@ -14,7 +14,7 @@
"description": "Expansions",
"schema": {
"type": "string",
- "enum": ["access_list", "owner", "certificate"]
+ "enum": ["owner", "certificate"]
}
}
],
@@ -40,7 +40,8 @@
"nginx_online": true,
"nginx_err": null
},
- "enabled": true
+ "enabled": true,
+ "certificate_id": 0
}
]
}
diff --git a/backend/schema/paths/nginx/streams/post.json b/backend/schema/paths/nginx/streams/post.json
index 9f3514e..d26996b 100644
--- a/backend/schema/paths/nginx/streams/post.json
+++ b/backend/schema/paths/nginx/streams/post.json
@@ -32,6 +32,9 @@
"udp_forwarding": {
"$ref": "../../../components/stream-object.json#/properties/udp_forwarding"
},
+ "certificate_id": {
+ "$ref": "../../../components/stream-object.json#/properties/certificate_id"
+ },
"meta": {
"$ref": "../../../components/stream-object.json#/properties/meta"
}
@@ -73,7 +76,8 @@
"nickname": "Admin",
"avatar": "",
"roles": ["admin"]
- }
+ },
+ "certificate_id": 0
}
}
},
diff --git a/backend/schema/paths/nginx/streams/streamID/get.json b/backend/schema/paths/nginx/streams/streamID/get.json
index 6547656..801af13 100644
--- a/backend/schema/paths/nginx/streams/streamID/get.json
+++ b/backend/schema/paths/nginx/streams/streamID/get.json
@@ -40,7 +40,8 @@
"nginx_online": true,
"nginx_err": null
},
- "enabled": true
+ "enabled": true,
+ "certificate_id": 0
}
}
},
diff --git a/backend/schema/paths/nginx/streams/streamID/put.json b/backend/schema/paths/nginx/streams/streamID/put.json
index fbfdc90..14adb16 100644
--- a/backend/schema/paths/nginx/streams/streamID/put.json
+++ b/backend/schema/paths/nginx/streams/streamID/put.json
@@ -29,56 +29,26 @@
"additionalProperties": false,
"minProperties": 1,
"properties": {
- "domain_names": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/domain_names"
+ "incoming_port": {
+ "$ref": "../../../../components/stream-object.json#/properties/incoming_port"
},
- "forward_scheme": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/forward_scheme"
+ "forwarding_host": {
+ "$ref": "../../../../components/stream-object.json#/properties/forwarding_host"
},
- "forward_host": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/forward_host"
+ "forwarding_port": {
+ "$ref": "../../../../components/stream-object.json#/properties/forwarding_port"
},
- "forward_port": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/forward_port"
+ "tcp_forwarding": {
+ "$ref": "../../../../components/stream-object.json#/properties/tcp_forwarding"
+ },
+ "udp_forwarding": {
+ "$ref": "../../../../components/stream-object.json#/properties/udp_forwarding"
},
"certificate_id": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/certificate_id"
- },
- "ssl_forced": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/hsts_subdomains"
- },
- "http2_support": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/http2_support"
- },
- "block_exploits": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/block_exploits"
- },
- "caching_enabled": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/caching_enabled"
- },
- "allow_websocket_upgrade": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/allow_websocket_upgrade"
- },
- "access_list_id": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/access_list_id"
- },
- "advanced_config": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/advanced_config"
- },
- "enabled": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/enabled"
+ "$ref": "../../../../components/stream-object.json#/properties/certificate_id"
},
"meta": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/meta"
- },
- "locations": {
- "$ref": "../../../../components/proxy-host-object.json#/properties/locations"
+ "$ref": "../../../../components/stream-object.json#/properties/meta"
}
}
}
@@ -94,42 +64,32 @@
"default": {
"value": {
"id": 1,
- "created_on": "2024-10-08T23:23:03.000Z",
- "modified_on": "2024-10-08T23:26:37.000Z",
+ "created_on": "2024-10-09T02:33:45.000Z",
+ "modified_on": "2024-10-09T02:33:45.000Z",
"owner_user_id": 1,
- "domain_names": ["test.example.com"],
- "forward_host": "192.168.0.10",
- "forward_port": 8989,
- "access_list_id": 0,
- "certificate_id": 0,
- "ssl_forced": false,
- "caching_enabled": false,
- "block_exploits": false,
- "advanced_config": "",
+ "incoming_port": 9090,
+ "forwarding_host": "router.internal",
+ "forwarding_port": 80,
+ "tcp_forwarding": true,
+ "udp_forwarding": false,
"meta": {
"nginx_online": true,
"nginx_err": null
},
- "allow_websocket_upgrade": false,
- "http2_support": false,
- "forward_scheme": "http",
"enabled": true,
- "hsts_enabled": false,
- "hsts_subdomains": false,
"owner": {
"id": 1,
- "created_on": "2024-10-07T22:43:55.000Z",
- "modified_on": "2024-10-08T12:52:54.000Z",
+ "created_on": "2024-10-09T02:33:16.000Z",
+ "modified_on": "2024-10-09T02:33:16.000Z",
"is_deleted": false,
"is_disabled": false,
"email": "admin@example.com",
"name": "Administrator",
- "nickname": "some guy",
- "avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm",
+ "nickname": "Admin",
+ "avatar": "",
"roles": ["admin"]
},
- "certificate": null,
- "access_list": null
+ "certificate_id": 0
}
}
},
diff --git a/backend/schema/paths/tokens/get.json b/backend/schema/paths/tokens/get.json
index 859bc61..ef842ea 100644
--- a/backend/schema/paths/tokens/get.json
+++ b/backend/schema/paths/tokens/get.json
@@ -15,7 +15,7 @@
"examples": {
"default": {
"value": {
- "expires": 1566540510,
+ "expires": "2025-02-04T20:40:46.340Z",
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
}
}
diff --git a/backend/schema/paths/tokens/post.json b/backend/schema/paths/tokens/post.json
index dece6b6..99703ff 100644
--- a/backend/schema/paths/tokens/post.json
+++ b/backend/schema/paths/tokens/post.json
@@ -38,7 +38,7 @@
"default": {
"value": {
"result": {
- "expires": 1566540510,
+ "expires": "2025-02-04T20:40:46.340Z",
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
}
}
diff --git a/backend/templates/_certificates.conf b/backend/templates/_certificates.conf
index 06ca7bb..efcca5c 100644
--- a/backend/templates/_certificates.conf
+++ b/backend/templates/_certificates.conf
@@ -2,6 +2,7 @@
{% if certificate.provider == "letsencrypt" %}
# Let's Encrypt SSL
include conf.d/include/letsencrypt-acme-challenge.conf;
+ include conf.d/include/ssl-cache.conf;
include conf.d/include/ssl-ciphers.conf;
ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem;
diff --git a/backend/templates/_certificates_stream.conf b/backend/templates/_certificates_stream.conf
new file mode 100644
index 0000000..ba7812f
--- /dev/null
+++ b/backend/templates/_certificates_stream.conf
@@ -0,0 +1,13 @@
+{% if certificate and certificate_id > 0 %}
+{% if certificate.provider == "letsencrypt" %}
+ # Let's Encrypt SSL
+ include conf.d/include/ssl-cache-stream.conf;
+ include conf.d/include/ssl-ciphers.conf;
+ ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem;
+{%- else %}
+ # Custom SSL
+ ssl_certificate /data/custom_ssl/npm-{{ certificate_id }}/fullchain.pem;
+ ssl_certificate_key /data/custom_ssl/npm-{{ certificate_id }}/privkey.pem;
+{%- endif -%}
+{%- endif -%}
diff --git a/backend/templates/stream.conf b/backend/templates/stream.conf
index 76159a6..7333aae 100644
--- a/backend/templates/stream.conf
+++ b/backend/templates/stream.conf
@@ -5,12 +5,10 @@
{% if enabled %}
{% if tcp_forwarding == 1 or tcp_forwarding == true -%}
server {
- listen {{ incoming_port }};
-{% if ipv6 -%}
- listen [::]:{{ incoming_port }};
-{% else -%}
- #listen [::]:{{ incoming_port }};
-{% endif %}
+ listen {{ incoming_port }} {%- if certificate %} ssl {%- endif %};
+ {% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} {%- if certificate %} ssl {%- endif %};
+
+ {%- include "_certificates_stream.conf" %}
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
@@ -19,14 +17,12 @@ server {
include /data/nginx/custom/server_stream_tcp[.]conf;
}
{% endif %}
-{% if udp_forwarding == 1 or udp_forwarding == true %}
+
+{% if udp_forwarding == 1 or udp_forwarding == true -%}
server {
listen {{ incoming_port }} udp;
-{% if ipv6 -%}
- listen [::]:{{ incoming_port }} udp;
-{% else -%}
- #listen [::]:{{ incoming_port }} udp;
-{% endif %}
+ {% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} udp;
+
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
# Custom
diff --git a/docker/Dockerfile-zh b/docker/Dockerfile-zh
index 24d5914..9c48703 100644
--- a/docker/Dockerfile-zh
+++ b/docker/Dockerfile-zh
@@ -1,4 +1,4 @@
-FROM jc21/nginx-proxy-manager:2.12.2
+FROM jc21/nginx-proxy-manager:2.12.3
ENV NPM_LANGUAGE="zh"
diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml
index bb68858..280a054 100644
--- a/docker/docker-compose.ci.yml
+++ b/docker/docker-compose.ci.yml
@@ -22,6 +22,10 @@ services:
test: ["CMD", "/usr/bin/check-health"]
interval: 10s
timeout: 3s
+ expose:
+ - '80-81/tcp'
+ - '443/tcp'
+ - '1500-1503/tcp'
networks:
fulltest:
aliases:
@@ -40,7 +44,7 @@ services:
- ca.internal
pdns:
- image: pschiffe/pdns-mysql
+ image: pschiffe/pdns-mysql:4.8
volumes:
- '/etc/localtime:/etc/localtime:ro'
environment:
@@ -97,7 +101,7 @@ services:
HTTP_PROXY: 'squid:3128'
HTTPS_PROXY: 'squid:3128'
volumes:
- - 'cypress_logs:/results'
+ - 'cypress_logs:/test/results'
- './dev/resolv.conf:/etc/resolv.conf:ro'
- '/etc/localtime:/etc/localtime:ro'
command: cypress run --browser chrome --config-file=cypress/config/ci.js
diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml
index 50ca555..5abe057 100644
--- a/docker/docker-compose.dev.yml
+++ b/docker/docker-compose.dev.yml
@@ -132,7 +132,7 @@ services:
- 8128:3128
pdns:
- image: pschiffe/pdns-mysql
+ image: pschiffe/pdns-mysql:4.8
container_name: npm2dev.pdns
volumes:
- '/etc/localtime:/etc/localtime:ro'
@@ -218,7 +218,7 @@ services:
env_file:
- ci.env
ports:
- - 9000:9000
+ - 9000:9000
depends_on:
- authentik-redis
- db-postgres
diff --git a/docker/rootfs/etc/nginx/conf.d/include/ssl-cache-stream.conf b/docker/rootfs/etc/nginx/conf.d/include/ssl-cache-stream.conf
new file mode 100644
index 0000000..433555d
--- /dev/null
+++ b/docker/rootfs/etc/nginx/conf.d/include/ssl-cache-stream.conf
@@ -0,0 +1,2 @@
+ssl_session_timeout 5m;
+ssl_session_cache shared:SSL_stream:50m;
diff --git a/docker/rootfs/etc/nginx/conf.d/include/ssl-cache.conf b/docker/rootfs/etc/nginx/conf.d/include/ssl-cache.conf
new file mode 100644
index 0000000..aa7ba2c
--- /dev/null
+++ b/docker/rootfs/etc/nginx/conf.d/include/ssl-cache.conf
@@ -0,0 +1,2 @@
+ssl_session_timeout 5m;
+ssl_session_cache shared:SSL:50m;
diff --git a/docker/rootfs/etc/nginx/conf.d/include/ssl-ciphers.conf b/docker/rootfs/etc/nginx/conf.d/include/ssl-ciphers.conf
index 233abb6..b5dacfb 100644
--- a/docker/rootfs/etc/nginx/conf.d/include/ssl-ciphers.conf
+++ b/docker/rootfs/etc/nginx/conf.d/include/ssl-ciphers.conf
@@ -1,6 +1,3 @@
-ssl_session_timeout 5m;
-ssl_session_cache shared:SSL:50m;
-
# intermediate configuration. tweak to your needs.
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
diff --git a/docker/scripts/install-s6 b/docker/scripts/install-s6
index 2922735..5f3b73e 100755
--- a/docker/scripts/install-s6
+++ b/docker/scripts/install-s6
@@ -8,7 +8,7 @@ BLUE='\E[1;34m'
GREEN='\E[1;32m'
RESET='\E[0m'
-S6_OVERLAY_VERSION=3.1.5.0
+S6_OVERLAY_VERSION=3.2.0.2
TARGETPLATFORM=${1:-linux/amd64}
# Determine the correct binary file for the architecture given
diff --git a/docs/yarn.lock b/docs/yarn.lock
index f89b80f..2adc44f 100644
--- a/docs/yarn.lock
+++ b/docs/yarn.lock
@@ -1065,9 +1065,9 @@ vfile@^6.0.0:
vfile-message "^4.0.0"
vite@^5.4.8:
- version "5.4.8"
- resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.8.tgz#af548ce1c211b2785478d3ba3e8da51e39a287e8"
- integrity sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==
+ version "5.4.14"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.14.tgz#ff8255edb02134df180dcfca1916c37a6abe8408"
+ integrity sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==
dependencies:
esbuild "^0.21.3"
postcss "^8.4.43"
diff --git a/frontend/js/app/controller.js b/frontend/js/app/controller.js
index ccb2978..ebddd78 100644
--- a/frontend/js/app/controller.js
+++ b/frontend/js/app/controller.js
@@ -4,444 +4,438 @@ const Tokens = require('./tokens');
module.exports = {
- /**
- * @param {String} route
- * @param {Object} [options]
- * @returns {Boolean}
- */
- navigate: function (route, options) {
- options = options || {};
- Backbone.history.navigate(route.toString(), options);
- return true;
- },
+ /**
+ * @param {String} route
+ * @param {Object} [options]
+ * @returns {Boolean}
+ */
+ navigate: function (route, options) {
+ options = options || {};
+ Backbone.history.navigate(route.toString(), options);
+ return true;
+ },
- /**
- * Login
- */
- showLogin: function () {
- window.location = '/login';
- },
+ /**
+ * Login
+ */
+ showLogin: function () {
+ window.location = '/login';
+ },
- /**
- * Users
- */
- showUsers: function () {
- let controller = this;
- if (Cache.User.isAdmin()) {
- require(['./main', './users/main'], (App, View) => {
- controller.navigate('/users');
- App.UI.showAppContent(new View());
- });
- } else {
- this.showDashboard();
- }
- },
+ /**
+ * Users
+ */
+ showUsers: function () {
+ const controller = this;
+ if (Cache.User.isAdmin()) {
+ require(['./main', './users/main'], (App, View) => {
+ controller.navigate('/users');
+ App.UI.showAppContent(new View());
+ });
+ } else {
+ this.showDashboard();
+ }
+ },
- /**
- * User Form
- *
- * @param [model]
- */
- showUserForm: function (model) {
- if (Cache.User.isAdmin()) {
- require(['./main', './user/form'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * User Form
+ *
+ * @param [model]
+ */
+ showUserForm: function (model) {
+ if (Cache.User.isAdmin()) {
+ require(['./main', './user/form'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * User Permissions Form
- *
- * @param model
- */
- showUserPermissions: function (model) {
- if (Cache.User.isAdmin()) {
- require(['./main', './user/permissions'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * User Permissions Form
+ *
+ * @param model
+ */
+ showUserPermissions: function (model) {
+ if (Cache.User.isAdmin()) {
+ require(['./main', './user/permissions'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * User Password Form
- *
- * @param model
- */
- showUserPasswordForm: function (model) {
- if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) {
- require(['./main', './user/password'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * User Password Form
+ *
+ * @param model
+ */
+ showUserPasswordForm: function (model) {
+ if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) {
+ require(['./main', './user/password'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * User Delete Confirm
- *
- * @param model
- */
- showUserDeleteConfirm: function (model) {
- if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) {
- require(['./main', './user/delete'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * User Delete Confirm
+ *
+ * @param model
+ */
+ showUserDeleteConfirm: function (model) {
+ if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) {
+ require(['./main', './user/delete'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Dashboard
- */
- showDashboard: function () {
- let controller = this;
+ /**
+ * Dashboard
+ */
+ showDashboard: function () {
+ const controller = this;
+ require(['./main', './dashboard/main'], (App, View) => {
+ controller.navigate('/');
+ App.UI.showAppContent(new View());
+ });
+ },
- require(['./main', './dashboard/main'], (App, View) => {
- controller.navigate('/');
- App.UI.showAppContent(new View());
- });
- },
+ /**
+ * Nginx Proxy Hosts
+ */
+ showNginxProxy: function () {
+ if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) {
+ const controller = this;
- /**
- * Nginx Proxy Hosts
- */
- showNginxProxy: function () {
- if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) {
- let controller = this;
+ require(['./main', './nginx/proxy/main'], (App, View) => {
+ controller.navigate('/nginx/proxy');
+ App.UI.showAppContent(new View());
+ });
+ }
+ },
- require(['./main', './nginx/proxy/main'], (App, View) => {
- controller.navigate('/nginx/proxy');
- App.UI.showAppContent(new View());
- });
- }
- },
+ /**
+ * Nginx Proxy Host Form
+ *
+ * @param [model]
+ */
+ showNginxProxyForm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
+ require(['./main', './nginx/proxy/form'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Nginx Proxy Host Form
- *
- * @param [model]
- */
- showNginxProxyForm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
- require(['./main', './nginx/proxy/form'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Proxy Host Delete Confirm
+ *
+ * @param model
+ */
+ showNginxProxyDeleteConfirm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
+ require(['./main', './nginx/proxy/delete'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Proxy Host Delete Confirm
- *
- * @param model
- */
- showNginxProxyDeleteConfirm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
- require(['./main', './nginx/proxy/delete'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Nginx Redirection Hosts
+ */
+ showNginxRedirection: function () {
+ if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) {
+ const controller = this;
+ require(['./main', './nginx/redirection/main'], (App, View) => {
+ controller.navigate('/nginx/redirection');
+ App.UI.showAppContent(new View());
+ });
+ }
+ },
- /**
- * Nginx Redirection Hosts
- */
- showNginxRedirection: function () {
- if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) {
- let controller = this;
+ /**
+ * Nginx Redirection Host Form
+ *
+ * @param [model]
+ */
+ showNginxRedirectionForm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
+ require(['./main', './nginx/redirection/form'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- require(['./main', './nginx/redirection/main'], (App, View) => {
- controller.navigate('/nginx/redirection');
- App.UI.showAppContent(new View());
- });
- }
- },
+ /**
+ * Proxy Redirection Delete Confirm
+ *
+ * @param model
+ */
+ showNginxRedirectionDeleteConfirm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
+ require(['./main', './nginx/redirection/delete'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Nginx Redirection Host Form
- *
- * @param [model]
- */
- showNginxRedirectionForm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
- require(['./main', './nginx/redirection/form'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Nginx Stream Hosts
+ */
+ showNginxStream: function () {
+ if (Cache.User.isAdmin() || Cache.User.canView('streams')) {
+ const controller = this;
+ require(['./main', './nginx/stream/main'], (App, View) => {
+ controller.navigate('/nginx/stream');
+ App.UI.showAppContent(new View());
+ });
+ }
+ },
- /**
- * Proxy Redirection Delete Confirm
- *
- * @param model
- */
- showNginxRedirectionDeleteConfirm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
- require(['./main', './nginx/redirection/delete'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Stream Form
+ *
+ * @param [model]
+ */
+ showNginxStreamForm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
+ require(['./main', './nginx/stream/form'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Nginx Stream Hosts
- */
- showNginxStream: function () {
- if (Cache.User.isAdmin() || Cache.User.canView('streams')) {
- let controller = this;
+ /**
+ * Stream Delete Confirm
+ *
+ * @param model
+ */
+ showNginxStreamDeleteConfirm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
+ require(['./main', './nginx/stream/delete'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- require(['./main', './nginx/stream/main'], (App, View) => {
- controller.navigate('/nginx/stream');
- App.UI.showAppContent(new View());
- });
- }
- },
+ /**
+ * Nginx Dead Hosts
+ */
+ showNginxDead: function () {
+ if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) {
+ const controller = this;
+ require(['./main', './nginx/dead/main'], (App, View) => {
+ controller.navigate('/nginx/404');
+ App.UI.showAppContent(new View());
+ });
+ }
+ },
- /**
- * Stream Form
- *
- * @param [model]
- */
- showNginxStreamForm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
- require(['./main', './nginx/stream/form'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Dead Host Form
+ *
+ * @param [model]
+ */
+ showNginxDeadForm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
+ require(['./main', './nginx/dead/form'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Stream Delete Confirm
- *
- * @param model
- */
- showNginxStreamDeleteConfirm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
- require(['./main', './nginx/stream/delete'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Dead Host Delete Confirm
+ *
+ * @param model
+ */
+ showNginxDeadDeleteConfirm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
+ require(['./main', './nginx/dead/delete'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Nginx Dead Hosts
- */
- showNginxDead: function () {
- if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) {
- let controller = this;
+ /**
+ * Help Dialog
+ *
+ * @param {String} title
+ * @param {String} content
+ */
+ showHelp: function (title, content) {
+ require(['./main', './help/main'], function (App, View) {
+ App.UI.showModalDialog(new View({title: title, content: content}));
+ });
+ },
- require(['./main', './nginx/dead/main'], (App, View) => {
- controller.navigate('/nginx/404');
- App.UI.showAppContent(new View());
- });
- }
- },
+ /**
+ * Nginx Access
+ */
+ showNginxAccess: function () {
+ if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) {
+ const controller = this;
+ require(['./main', './nginx/access/main'], (App, View) => {
+ controller.navigate('/nginx/access');
+ App.UI.showAppContent(new View());
+ });
+ }
+ },
- /**
- * Dead Host Form
- *
- * @param [model]
- */
- showNginxDeadForm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
- require(['./main', './nginx/dead/form'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Nginx Access List Form
+ *
+ * @param [model]
+ */
+ showNginxAccessListForm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
+ require(['./main', './nginx/access/form'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Dead Host Delete Confirm
- *
- * @param model
- */
- showNginxDeadDeleteConfirm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
- require(['./main', './nginx/dead/delete'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Access List Delete Confirm
+ *
+ * @param model
+ */
+ showNginxAccessListDeleteConfirm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
+ require(['./main', './nginx/access/delete'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Help Dialog
- *
- * @param {String} title
- * @param {String} content
- */
- showHelp: function (title, content) {
- require(['./main', './help/main'], function (App, View) {
- App.UI.showModalDialog(new View({title: title, content: content}));
- });
- },
+ /**
+ * Nginx Certificates
+ */
+ showNginxCertificates: function () {
+ if (Cache.User.isAdmin() || Cache.User.canView('certificates')) {
+ const controller = this;
+ require(['./main', './nginx/certificates/main'], (App, View) => {
+ controller.navigate('/nginx/certificates');
+ App.UI.showAppContent(new View());
+ });
+ }
+ },
- /**
- * Nginx Access
- */
- showNginxAccess: function () {
- if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) {
- let controller = this;
+ /**
+ * Nginx Certificate Form
+ *
+ * @param [model]
+ */
+ showNginxCertificateForm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
+ require(['./main', './nginx/certificates/form'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- require(['./main', './nginx/access/main'], (App, View) => {
- controller.navigate('/nginx/access');
- App.UI.showAppContent(new View());
- });
- }
- },
+ /**
+ * Certificate Renew
+ *
+ * @param model
+ */
+ showNginxCertificateRenew: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
+ require(['./main', './nginx/certificates/renew'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Nginx Access List Form
- *
- * @param [model]
- */
- showNginxAccessListForm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
- require(['./main', './nginx/access/form'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Certificate Delete Confirm
+ *
+ * @param model
+ */
+ showNginxCertificateDeleteConfirm: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
+ require(['./main', './nginx/certificates/delete'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Access List Delete Confirm
- *
- * @param model
- */
- showNginxAccessListDeleteConfirm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
- require(['./main', './nginx/access/delete'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Certificate Test Reachability
+ *
+ * @param model
+ */
+ showNginxCertificateTestReachability: function (model) {
+ if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
+ require(['./main', './nginx/certificates/test'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Nginx Certificates
- */
- showNginxCertificates: function () {
- if (Cache.User.isAdmin() || Cache.User.canView('certificates')) {
- let controller = this;
+ /**
+ * Audit Log
+ */
+ showAuditLog: function () {
+ const controller = this;
+ if (Cache.User.isAdmin()) {
+ require(['./main', './audit-log/main'], (App, View) => {
+ controller.navigate('/audit-log');
+ App.UI.showAppContent(new View());
+ });
+ } else {
+ this.showDashboard();
+ }
+ },
- require(['./main', './nginx/certificates/main'], (App, View) => {
- controller.navigate('/nginx/certificates');
- App.UI.showAppContent(new View());
- });
- }
- },
+ /**
+ * Audit Log Metadata
+ *
+ * @param model
+ */
+ showAuditMeta: function (model) {
+ if (Cache.User.isAdmin()) {
+ require(['./main', './audit-log/meta'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ },
- /**
- * Nginx Certificate Form
- *
- * @param [model]
- */
- showNginxCertificateForm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
- require(['./main', './nginx/certificates/form'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Settings
+ */
+ showSettings: function () {
+ const controller = this;
+ if (Cache.User.isAdmin()) {
+ require(['./main', './settings/main'], (App, View) => {
+ controller.navigate('/settings');
+ App.UI.showAppContent(new View());
+ });
+ } else {
+ this.showDashboard();
+ }
+ },
- /**
- * Certificate Renew
- *
- * @param model
- */
- showNginxCertificateRenew: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
- require(['./main', './nginx/certificates/renew'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
+ /**
+ * Settings Item Form
+ *
+ * @param model
+ */
+ showSettingForm: function (model) {
+ if (Cache.User.isAdmin()) {
+ if (model.get('id') === 'default-site') {
+ require(['./main', './settings/default-site/main'], function (App, View) {
+ App.UI.showModalDialog(new View({model: model}));
+ });
+ }
+ }
+ },
- /**
- * Certificate Delete Confirm
- *
- * @param model
- */
- showNginxCertificateDeleteConfirm: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
- require(['./main', './nginx/certificates/delete'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
-
- /**
- * Certificate Test Reachability
- *
- * @param model
- */
- showNginxCertificateTestReachability: function (model) {
- if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
- require(['./main', './nginx/certificates/test'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
-
- /**
- * Audit Log
- */
- showAuditLog: function () {
- let controller = this;
- if (Cache.User.isAdmin()) {
- require(['./main', './audit-log/main'], (App, View) => {
- controller.navigate('/audit-log');
- App.UI.showAppContent(new View());
- });
- } else {
- this.showDashboard();
- }
- },
-
- /**
- * Audit Log Metadata
- *
- * @param model
- */
- showAuditMeta: function (model) {
- if (Cache.User.isAdmin()) {
- require(['./main', './audit-log/meta'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- },
-
- /**
- * Settings
- */
- showSettings: function () {
- let controller = this;
- if (Cache.User.isAdmin()) {
- require(['./main', './settings/main'], (App, View) => {
- controller.navigate('/settings');
- App.UI.showAppContent(new View());
- });
- } else {
- this.showDashboard();
- }
- },
-
- /**
- * Settings Item Form
- *
- * @param model
- */
- showSettingForm: function (model) {
- if (Cache.User.isAdmin()) {
- if (model.get('id') === 'default-site') {
- require(['./main', './settings/default-site/main'], function (App, View) {
- App.UI.showModalDialog(new View({model: model}));
- });
- }
- }
- },
-
- /**
- * Logout
- */
- logout: function () {
- Tokens.dropTopToken();
- this.showLogin();
- }
+ /**
+ * Logout
+ */
+ logout: function () {
+ Tokens.dropTopToken();
+ this.showLogin();
+ }
};
diff --git a/frontend/js/app/dashboard/main.js b/frontend/js/app/dashboard/main.js
index c2e82f8..ba4a99a 100644
--- a/frontend/js/app/dashboard/main.js
+++ b/frontend/js/app/dashboard/main.js
@@ -6,87 +6,85 @@ const Helpers = require('../../lib/helpers');
const template = require('./main.ejs');
module.exports = Mn.View.extend({
- template: template,
- id: 'dashboard',
- columns: 0,
+ template: template,
+ id: 'dashboard',
+ columns: 0,
- stats: {},
+ stats: {},
- ui: {
- links: 'a'
- },
+ ui: {
+ links: 'a'
+ },
- events: {
- 'click @ui.links': function (e) {
- e.preventDefault();
- Controller.navigate($(e.currentTarget).attr('href'), true);
- }
- },
+ events: {
+ 'click @ui.links': function (e) {
+ e.preventDefault();
+ Controller.navigate($(e.currentTarget).attr('href'), true);
+ }
+ },
- templateContext: function () {
- let view = this;
+ templateContext: function () {
+ const view = this;
- return {
- getUserName: function () {
- return Cache.User.get('nickname') || Cache.User.get('name');
- },
+ return {
+ getUserName: function () {
+ return Cache.User.get('nickname') || Cache.User.get('name');
+ },
- getHostStat: function (type) {
- if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') {
- return Helpers.niceNumber(view.stats.hosts[type]);
- }
+ getHostStat: function (type) {
+ if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') {
+ return Helpers.niceNumber(view.stats.hosts[type]);
+ }
- return '-';
- },
+ return '-';
+ },
- canShow: function (perm) {
- return Cache.User.isAdmin() || Cache.User.canView(perm);
- },
+ canShow: function (perm) {
+ return Cache.User.isAdmin() || Cache.User.canView(perm);
+ },
- columns: view.columns
- };
- },
+ columns: view.columns
+ };
+ },
- onRender: function () {
- let view = this;
+ onRender: function () {
+ const view = this;
+ if (typeof view.stats.hosts === 'undefined') {
+ Api.Reports.getHostStats()
+ .then(response => {
+ if (!view.isDestroyed()) {
+ view.stats.hosts = response;
+ view.render();
+ }
+ })
+ .catch(err => {
+ console.log(err);
+ });
+ }
+ },
- if (typeof view.stats.hosts === 'undefined') {
- Api.Reports.getHostStats()
- .then(response => {
- if (!view.isDestroyed()) {
- view.stats.hosts = response;
- view.render();
- }
- })
- .catch(err => {
- console.log(err);
- });
- }
- },
+ /**
+ * @param {Object} [model]
+ */
+ preRender: function (model) {
+ this.columns = 0;
- /**
- * @param {Object} [model]
- */
- preRender: function (model) {
- this.columns = 0;
+ // calculate the available columns based on permissions for the objects
+ // and store as a variable
+ const perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts'];
- // calculate the available columns based on permissions for the objects
- // and store as a variable
- //let view = this;
- let perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts'];
+ perms.map(perm => {
+ this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0;
+ });
- perms.map(perm => {
- this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0;
- });
+ // Prevent double rendering on initial calls
+ if (typeof model !== 'undefined') {
+ this.render();
+ }
+ },
- // Prevent double rendering on initial calls
- if (typeof model !== 'undefined') {
- this.render();
- }
- },
-
- initialize: function () {
- this.preRender();
- this.listenTo(Cache.User, 'change', this.preRender);
- }
+ initialize: function () {
+ this.preRender();
+ this.listenTo(Cache.User, 'change', this.preRender);
+ }
});
diff --git a/frontend/js/app/nginx/certificates/list/item.ejs b/frontend/js/app/nginx/certificates/list/item.ejs
index 9a0d6b2..179a819 100644
--- a/frontend/js/app/nginx/certificates/list/item.ejs
+++ b/frontend/js/app/nginx/certificates/list/item.ejs
@@ -33,6 +33,13 @@