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 @@ <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> + + <% if (active_domain_names().length > 0) { %> + <%- i18n('certificates', 'in-use') %> + <% } else { %> + <%- i18n('certificates', 'inactive') %> + <% } %> + <% if (canManage) { %>

-<% } %> +<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/certificates/list/item.js b/frontend/js/app/nginx/certificates/list/item.js index 7fa1c68..b9a927a 100644 --- a/frontend/js/app/nginx/certificates/list/item.js +++ b/frontend/js/app/nginx/certificates/list/item.js @@ -44,14 +44,24 @@ module.exports = Mn.View.extend({ }, }, - templateContext: { - canManage: App.Cache.User.canManage('certificates'), - isExpired: function () { - return moment(this.expires_on).isBefore(moment()); - }, - dns_providers: dns_providers + templateContext: function () { + return { + canManage: App.Cache.User.canManage('certificates'), + isExpired: function () { + return moment(this.expires_on).isBefore(moment()); + }, + dns_providers: dns_providers, + active_domain_names: function () { + const { proxy_hosts = [], redirect_hosts = [], dead_hosts = [] } = this; + return [...proxy_hosts, ...redirect_hosts, ...dead_hosts].reduce((acc, host) => { + acc.push(...(host.domain_names || [])); + return acc; + }, []); + } + }; }, + initialize: function () { this.listenTo(this.model, 'change', this.render); } diff --git a/frontend/js/app/nginx/certificates/list/main.ejs b/frontend/js/app/nginx/certificates/list/main.ejs index aa49a27..329b584 100644 --- a/frontend/js/app/nginx/certificates/list/main.ejs +++ b/frontend/js/app/nginx/certificates/list/main.ejs @@ -3,6 +3,7 @@ <%- i18n('str', 'name') %> <%- i18n('all-hosts', 'cert-provider') %> <%- i18n('str', 'expires') %> + <%- i18n('str', 'status') %> <% if (canManage) { %>   <% } %> diff --git a/frontend/js/app/nginx/certificates/main.js b/frontend/js/app/nginx/certificates/main.js index 8956276..3f9f022 100644 --- a/frontend/js/app/nginx/certificates/main.js +++ b/frontend/js/app/nginx/certificates/main.js @@ -74,7 +74,7 @@ module.exports = Mn.View.extend({ e.preventDefault(); let query = this.ui.query.val(); - this.fetch(['owner'], query) + this.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'], query) .then(response => this.showData(response)) .catch(err => { this.showError(err); @@ -89,7 +89,7 @@ module.exports = Mn.View.extend({ onRender: function () { let view = this; - view.fetch(['owner']) + view.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts']) .then(response => { if (!view.isDestroyed()) { if (response && response.length) { diff --git a/frontend/js/app/nginx/stream/form.ejs b/frontend/js/app/nginx/stream/form.ejs index 1fc4f13..800945f 100644 --- a/frontend/js/app/nginx/stream/form.ejs +++ b/frontend/js/app/nginx/stream/form.ejs @@ -3,48 +3,187 @@ -