mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-10-04 11:50:09 +00:00
Compare commits
117 Commits
v2.12.1
...
7fe117602e
Author | SHA1 | Date | |
---|---|---|---|
|
7fe117602e | ||
|
79d28f03d0 | ||
|
c4df89df1f | ||
|
34c703f8b4 | ||
|
0a05d8f0ad | ||
|
0a9141fad5 | ||
|
42836774b7 | ||
|
2a07544f58 | ||
|
dc9d884743 | ||
|
0d5d2b1b7c | ||
|
df48b835c4 | ||
|
8a1557154a | ||
|
a6af5ec2c7 | ||
|
14d7c35fd7 | ||
|
cfcf78aaee | ||
|
3a01b2c84f | ||
|
e1c84a5c10 | ||
|
c56c95a59a | ||
|
6a60627833 | ||
|
b4793d3c16 | ||
|
68a7803513 | ||
|
2657af97cf | ||
|
4452f014b9 | ||
|
cd80cc8e4d | ||
|
ee4250d770 | ||
|
3dbc70faa6 | ||
|
3091c21cae | ||
|
57cd2a1919 | ||
|
ad5936c530 | ||
|
498109addb | ||
|
3f3aacd7ec | ||
|
bb4ecf812d | ||
|
c05f9695d0 | ||
|
6343b398f0 | ||
|
59362b7477 | ||
|
aedaaa18e0 | ||
|
080bd0b749 | ||
|
9687e9e450 | ||
|
5a234bb88c | ||
|
4de4b65036 | ||
|
f1c97c7c36 | ||
|
b4f49969d6 | ||
|
ec12d8f9bf | ||
|
e50e3def9d | ||
|
6415f284f9 | ||
|
98e5997f0a | ||
|
fc30a92bd4 | ||
|
e2011ee45c | ||
|
1406e75c2c | ||
|
ca3ee98c68 | ||
|
f90d839ebe | ||
|
be5278f31e | ||
|
73110d5e1e | ||
|
356b98bf7e | ||
|
3eecf7a38b | ||
|
7f9240dda7 | ||
|
f537619ffe | ||
|
805968aac6 | ||
|
2a4093c1b8 | ||
|
ae2ac8a733 | ||
|
5d087f1256 | ||
|
c6eca2578e | ||
|
56033bee9c | ||
|
c6630e87bb | ||
|
d6b98f51b0 | ||
|
1e322804ce | ||
|
b3de76c945 | ||
|
fcf4117f8e | ||
|
d26e8c1d0c | ||
|
19ed4c1212 | ||
|
03018d252b | ||
|
8351dd41f6 | ||
|
97212f2686 | ||
|
fe068a8b51 | ||
|
61e2bde98f | ||
|
81c9038929 | ||
|
4ea50ca40c | ||
|
53ed12bcf2 | ||
|
cb3e4ed59c | ||
|
b20dc5eade | ||
|
586afc0c91 | ||
|
93ea17a9bb | ||
|
151160a834 | ||
|
2075f98cad | ||
|
07a4e5791f | ||
|
640a1eeb68 | ||
|
126d3d44ca | ||
|
20646e7bb5 | ||
|
87998a03ce | ||
|
a56342c76a | ||
|
4c89379671 | ||
|
10b9a49274 | ||
|
595a742c40 | ||
|
c171752137 | ||
|
a0b26b9e98 | ||
|
d6791f4e38 | ||
|
62c94f3099 | ||
|
25a26d6175 | ||
|
17246e418f | ||
|
f7d3ca0b07 | ||
|
a55de386e7 | ||
|
e9d4f5b827 | ||
|
1c1cee3836 | ||
|
eaf6335694 | ||
|
ffe05ebd41 | ||
|
2e9a4f1aed | ||
|
d17c85e4c8 | ||
|
dad8d0ca00 | ||
|
d7e0558a35 | ||
|
ee41bb5562 | ||
|
0cf6b9caa4 | ||
|
68a9baf206 | ||
|
d92421d098 | ||
|
96c58b203e | ||
|
d499e2bfef | ||
|
ff102f4107 | ||
|
a69d747007 |
52
Jenkinsfile
vendored
52
Jenkinsfile
vendored
@@ -43,7 +43,7 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
// Defaults to the Branch name, which is applies to all branches AND pr's
|
// Defaults to the Branch name, which is applies to all branches AND pr's
|
||||||
buildxPushTags = "-t docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}"
|
buildxPushTags = "-t docker.io/nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ pipeline {
|
|||||||
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
||||||
}
|
}
|
||||||
unstable {
|
unstable {
|
||||||
dir(path: 'testing/results') {
|
dir(path: 'test/results') {
|
||||||
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,45 @@ pipeline {
|
|||||||
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
||||||
}
|
}
|
||||||
unstable {
|
unstable {
|
||||||
dir(path: 'testing/results') {
|
dir(path: 'test/results') {
|
||||||
|
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Test Postgres') {
|
||||||
|
environment {
|
||||||
|
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_postgres"
|
||||||
|
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.postgres.yml'
|
||||||
|
}
|
||||||
|
when {
|
||||||
|
not {
|
||||||
|
equals expected: 'UNSTABLE', actual: currentBuild.result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'rm -rf ./test/results/junit/*'
|
||||||
|
sh './scripts/ci/fulltest-cypress'
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
// Dumps to analyze later
|
||||||
|
sh 'mkdir -p debug/postgres'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1'
|
||||||
|
|
||||||
|
junit 'test/results/junit/*'
|
||||||
|
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
||||||
|
}
|
||||||
|
unstable {
|
||||||
|
dir(path: 'test/results') {
|
||||||
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,7 +241,13 @@ pipeline {
|
|||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
npmGithubPrComment("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`\n\n**Note:** ensure you backup your NPM instance before testing this PR image! Especially if this PR contains database changes.", true)
|
npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on
|
||||||
|
[DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev)
|
||||||
|
as `nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}`
|
||||||
|
|
||||||
|
**Note:** ensure you backup your NPM instance before testing this image! Especially if there are database changes
|
||||||
|
**Note:** this is a different docker image namespace than the official image
|
||||||
|
""", true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://nginxproxymanager.com/github.png">
|
<img src="https://nginxproxymanager.com/github.png">
|
||||||
<br><br>
|
<br><br>
|
||||||
<img src="https://img.shields.io/badge/version-2.12.1-green.svg?style=for-the-badge">
|
<img src="https://img.shields.io/badge/version-2.12.3-green.svg?style=for-the-badge">
|
||||||
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
||||||
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
||||||
</a>
|
</a>
|
||||||
|
@@ -81,7 +81,7 @@ const internalAccessList = {
|
|||||||
|
|
||||||
return internalAccessList.build(row)
|
return internalAccessList.build(row)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (row.proxy_host_count) {
|
if (parseInt(row.proxy_host_count, 10)) {
|
||||||
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -223,7 +223,7 @@ const internalAccessList = {
|
|||||||
.then((row) => {
|
.then((row) => {
|
||||||
return internalAccessList.build(row)
|
return internalAccessList.build(row)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (row.proxy_host_count) {
|
if (parseInt(row.proxy_host_count, 10)) {
|
||||||
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
||||||
}
|
}
|
||||||
}).then(internalNginx.reload)
|
}).then(internalNginx.reload)
|
||||||
@@ -252,9 +252,13 @@ const internalAccessList = {
|
|||||||
let query = accessListModel
|
let query = accessListModel
|
||||||
.query()
|
.query()
|
||||||
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
|
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
|
||||||
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
.leftJoin('proxy_host', function() {
|
||||||
|
this.on('proxy_host.access_list_id', '=', 'access_list.id')
|
||||||
|
.andOn('proxy_host.is_deleted', '=', 0);
|
||||||
|
})
|
||||||
.where('access_list.is_deleted', 0)
|
.where('access_list.is_deleted', 0)
|
||||||
.andWhere('access_list.id', data.id)
|
.andWhere('access_list.id', data.id)
|
||||||
|
.groupBy('access_list.id')
|
||||||
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
|
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
@@ -373,7 +377,10 @@ const internalAccessList = {
|
|||||||
let query = accessListModel
|
let query = accessListModel
|
||||||
.query()
|
.query()
|
||||||
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
|
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
|
||||||
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
.leftJoin('proxy_host', function() {
|
||||||
|
this.on('proxy_host.access_list_id', '=', 'access_list.id')
|
||||||
|
.andOn('proxy_host.is_deleted', '=', 0);
|
||||||
|
})
|
||||||
.where('access_list.is_deleted', 0)
|
.where('access_list.is_deleted', 0)
|
||||||
.groupBy('access_list.id')
|
.groupBy('access_list.id')
|
||||||
.allowGraph('[owner,items,clients]')
|
.allowGraph('[owner,items,clients]')
|
||||||
@@ -501,8 +508,13 @@ const internalAccessList = {
|
|||||||
if (typeof item.password !== 'undefined' && item.password.length) {
|
if (typeof item.password !== 'undefined' && item.password.length) {
|
||||||
logger.info('Adding: ' + item.username);
|
logger.info('Adding: ' + item.username);
|
||||||
|
|
||||||
utils.execFile('/usr/bin/htpasswd', ['-b', htpasswd_file, item.username, item.password])
|
utils.execFile('openssl', ['passwd', '-apr1', item.password])
|
||||||
.then((/*result*/) => {
|
.then((res) => {
|
||||||
|
try {
|
||||||
|
fs.appendFileSync(htpasswd_file, item.username + ':' + res + '\n', {encoding: 'utf8'});
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
next();
|
next();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const auditLogModel = require('../models/audit-log');
|
const auditLogModel = require('../models/audit-log');
|
||||||
|
const {castJsonIfNeed} = require('../lib/helpers');
|
||||||
|
|
||||||
const internalAuditLog = {
|
const internalAuditLog = {
|
||||||
|
|
||||||
@@ -22,9 +23,9 @@ const internalAuditLog = {
|
|||||||
.allowGraph('[user]');
|
.allowGraph('[user]');
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string') {
|
if (typeof search_query === 'string' && search_query.length > 0) {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where('meta', 'like', '%' + search_query + '%');
|
this.where(castJsonIfNeed('meta'), 'like', '%' + search_query + '%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -313,6 +313,9 @@ const internalCertificate = {
|
|||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('id', data.id)
|
.andWhere('id', data.id)
|
||||||
.allowGraph('[owner]')
|
.allowGraph('[owner]')
|
||||||
|
.allowGraph('[proxy_hosts]')
|
||||||
|
.allowGraph('[redirection_hosts]')
|
||||||
|
.allowGraph('[dead_hosts]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@@ -464,6 +467,9 @@ const internalCertificate = {
|
|||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner]')
|
.allowGraph('[owner]')
|
||||||
|
.allowGraph('[proxy_hosts]')
|
||||||
|
.allowGraph('[redirection_hosts]')
|
||||||
|
.allowGraph('[dead_hosts]')
|
||||||
.orderBy('nice_name', 'ASC');
|
.orderBy('nice_name', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
|
@@ -6,6 +6,7 @@ const internalHost = require('./host');
|
|||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
const internalCertificate = require('./certificate');
|
const internalCertificate = require('./certificate');
|
||||||
|
const {castJsonIfNeed} = require('../lib/helpers');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@@ -409,16 +410,16 @@ const internalDeadHost = {
|
|||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner,certificate]')
|
.allowGraph('[owner,certificate]')
|
||||||
.orderBy('domain_names', 'ASC');
|
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string') {
|
if (typeof search_query === 'string' && search_query.length > 0) {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where('domain_names', 'like', '%' + search_query + '%');
|
this.where(castJsonIfNeed('domain_names'), 'like', '%' + search_query + '%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ const _ = require('lodash');
|
|||||||
const proxyHostModel = require('../models/proxy_host');
|
const proxyHostModel = require('../models/proxy_host');
|
||||||
const redirectionHostModel = require('../models/redirection_host');
|
const redirectionHostModel = require('../models/redirection_host');
|
||||||
const deadHostModel = require('../models/dead_host');
|
const deadHostModel = require('../models/dead_host');
|
||||||
|
const {castJsonIfNeed} = require('../lib/helpers');
|
||||||
|
|
||||||
const internalHost = {
|
const internalHost = {
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ const internalHost = {
|
|||||||
cleanSslHstsData: function (data, existing_data) {
|
cleanSslHstsData: function (data, existing_data) {
|
||||||
existing_data = existing_data === undefined ? {} : existing_data;
|
existing_data = existing_data === undefined ? {} : existing_data;
|
||||||
|
|
||||||
let combined_data = _.assign({}, existing_data, data);
|
const combined_data = _.assign({}, existing_data, data);
|
||||||
|
|
||||||
if (!combined_data.certificate_id) {
|
if (!combined_data.certificate_id) {
|
||||||
combined_data.ssl_forced = false;
|
combined_data.ssl_forced = false;
|
||||||
@@ -73,7 +74,7 @@ const internalHost = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
getHostsWithDomains: function (domain_names) {
|
getHostsWithDomains: function (domain_names) {
|
||||||
let promises = [
|
const promises = [
|
||||||
proxyHostModel
|
proxyHostModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0),
|
.where('is_deleted', 0),
|
||||||
@@ -125,19 +126,19 @@ const internalHost = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
isHostnameTaken: function (hostname, ignore_type, ignore_id) {
|
isHostnameTaken: function (hostname, ignore_type, ignore_id) {
|
||||||
let promises = [
|
const promises = [
|
||||||
proxyHostModel
|
proxyHostModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('domain_names', 'like', '%' + hostname + '%'),
|
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'),
|
||||||
redirectionHostModel
|
redirectionHostModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('domain_names', 'like', '%' + hostname + '%'),
|
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'),
|
||||||
deadHostModel
|
deadHostModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('domain_names', 'like', '%' + hostname + '%')
|
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%')
|
||||||
];
|
];
|
||||||
|
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
|
@@ -6,6 +6,7 @@ const internalHost = require('./host');
|
|||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
const internalCertificate = require('./certificate');
|
const internalCertificate = require('./certificate');
|
||||||
|
const {castJsonIfNeed} = require('../lib/helpers');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted', 'owner.is_deleted'];
|
return ['is_deleted', 'owner.is_deleted'];
|
||||||
@@ -416,16 +417,16 @@ const internalProxyHost = {
|
|||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner,access_list,certificate]')
|
.allowGraph('[owner,access_list,certificate]')
|
||||||
.orderBy('domain_names', 'ASC');
|
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string') {
|
if (typeof search_query === 'string' && search_query.length > 0) {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where('domain_names', 'like', '%' + search_query + '%');
|
this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ const internalHost = require('./host');
|
|||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
const internalCertificate = require('./certificate');
|
const internalCertificate = require('./certificate');
|
||||||
|
const {castJsonIfNeed} = require('../lib/helpers');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@@ -409,16 +410,16 @@ const internalRedirectionHost = {
|
|||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner,certificate]')
|
.allowGraph('[owner,certificate]')
|
||||||
.orderBy('domain_names', 'ASC');
|
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string') {
|
if (typeof search_query === 'string' && search_query.length > 0) {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where('domain_names', 'like', '%' + search_query + '%');
|
this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,12 +1,15 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
const utils = require('../lib/utils');
|
||||||
const streamModel = require('../models/stream');
|
const streamModel = require('../models/stream');
|
||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
|
const internalCertificate = require('./certificate');
|
||||||
|
const internalHost = require('./host');
|
||||||
|
const {castJsonIfNeed} = require('../lib/helpers');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const internalStream = {
|
const internalStream = {
|
||||||
@@ -17,6 +20,12 @@ const internalStream = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
create: (access, data) => {
|
create: (access, data) => {
|
||||||
|
const create_certificate = data.certificate_id === 'new';
|
||||||
|
|
||||||
|
if (create_certificate) {
|
||||||
|
delete data.certificate_id;
|
||||||
|
}
|
||||||
|
|
||||||
return access.can('streams:create', data)
|
return access.can('streams:create', data)
|
||||||
.then((/*access_data*/) => {
|
.then((/*access_data*/) => {
|
||||||
// TODO: At this point the existing ports should have been checked
|
// TODO: At this point the existing ports should have been checked
|
||||||
@@ -26,16 +35,44 @@ const internalStream = {
|
|||||||
data.meta = {};
|
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
|
return streamModel
|
||||||
.query()
|
.query()
|
||||||
.insertAndFetch(data)
|
.insertAndFetch(data_no_domains)
|
||||||
.then(utils.omitRow(omissions()));
|
.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) => {
|
.then((row) => {
|
||||||
// Configure nginx
|
// Configure nginx
|
||||||
return internalNginx.configure(streamModel, 'stream', row)
|
return internalNginx.configure(streamModel, 'stream', row)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return internalStream.get(access, {id: row.id, expand: ['owner']});
|
return row;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
@@ -59,6 +96,12 @@ const internalStream = {
|
|||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
update: (access, data) => {
|
update: (access, data) => {
|
||||||
|
const create_certificate = data.certificate_id === 'new';
|
||||||
|
|
||||||
|
if (create_certificate) {
|
||||||
|
delete data.certificate_id;
|
||||||
|
}
|
||||||
|
|
||||||
return access.can('streams:update', data.id)
|
return access.can('streams:update', data.id)
|
||||||
.then((/*access_data*/) => {
|
.then((/*access_data*/) => {
|
||||||
// TODO: at this point the existing streams should have been checked
|
// TODO: at this point the existing streams should have been checked
|
||||||
@@ -70,16 +113,32 @@ const internalStream = {
|
|||||||
throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
|
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
|
return streamModel
|
||||||
.query()
|
.query()
|
||||||
.patchAndFetchById(row.id, data)
|
.patchAndFetchById(row.id, data)
|
||||||
.then(utils.omitRow(omissions()))
|
.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) => {
|
.then((saved_row) => {
|
||||||
// Add to audit log
|
// Add to audit log
|
||||||
return internalAuditLog.add(access, {
|
return internalAuditLog.add(access, {
|
||||||
@@ -92,6 +151,17 @@ const internalStream = {
|
|||||||
return saved_row;
|
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());
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -114,7 +184,7 @@ const internalStream = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('id', data.id)
|
.andWhere('id', data.id)
|
||||||
.allowGraph('[owner]')
|
.allowGraph('[owner,certificate]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@@ -131,6 +201,7 @@ const internalStream = {
|
|||||||
if (!row || !row.id) {
|
if (!row || !row.id) {
|
||||||
throw new error.ItemNotFoundError(data.id);
|
throw new error.ItemNotFoundError(data.id);
|
||||||
}
|
}
|
||||||
|
row = internalHost.cleanRowCertificateMeta(row);
|
||||||
// Custom omissions
|
// Custom omissions
|
||||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||||
row = _.omit(row, data.omit);
|
row = _.omit(row, data.omit);
|
||||||
@@ -196,14 +267,14 @@ const internalStream = {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return internalStream.get(access, {
|
return internalStream.get(access, {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
expand: ['owner']
|
expand: ['certificate', 'owner']
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (!row || !row.id) {
|
if (!row || !row.id) {
|
||||||
throw new error.ItemNotFoundError(data.id);
|
throw new error.ItemNotFoundError(data.id);
|
||||||
} else if (row.enabled) {
|
} else if (row.enabled) {
|
||||||
throw new error.ValidationError('Host is already enabled');
|
throw new error.ValidationError('Stream is already enabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
row.enabled = 1;
|
row.enabled = 1;
|
||||||
@@ -249,7 +320,7 @@ const internalStream = {
|
|||||||
if (!row || !row.id) {
|
if (!row || !row.id) {
|
||||||
throw new error.ItemNotFoundError(data.id);
|
throw new error.ItemNotFoundError(data.id);
|
||||||
} else if (!row.enabled) {
|
} else if (!row.enabled) {
|
||||||
throw new error.ValidationError('Host is already disabled');
|
throw new error.ValidationError('Stream is already disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
row.enabled = 0;
|
row.enabled = 0;
|
||||||
@@ -293,21 +364,21 @@ const internalStream = {
|
|||||||
getAll: (access, expand, search_query) => {
|
getAll: (access, expand, search_query) => {
|
||||||
return access.can('streams:list')
|
return access.can('streams:list')
|
||||||
.then((access_data) => {
|
.then((access_data) => {
|
||||||
let query = streamModel
|
const query = streamModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner]')
|
.allowGraph('[owner,certificate]')
|
||||||
.orderBy('incoming_port', 'ASC');
|
.orderByRaw('CAST(incoming_port AS INTEGER) ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string') {
|
if (typeof search_query === 'string' && search_query.length > 0) {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where('incoming_port', 'like', '%' + search_query + '%');
|
this.where(castJsonIfNeed('incoming_port'), 'like', `%${search_query}%`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +387,13 @@ const internalStream = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return query.then(utils.omitRows(omissions()));
|
return query.then(utils.omitRows(omissions()));
|
||||||
|
})
|
||||||
|
.then((rows) => {
|
||||||
|
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
|
||||||
|
return internalHost.cleanAllRowsCertificateMeta(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -327,9 +405,9 @@ const internalStream = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
getCount: (user_id, visibility) => {
|
getCount: (user_id, visibility) => {
|
||||||
let query = streamModel
|
const query = streamModel
|
||||||
.query()
|
.query()
|
||||||
.count('id as count')
|
.count('id AS count')
|
||||||
.where('is_deleted', 0);
|
.where('is_deleted', 0);
|
||||||
|
|
||||||
if (visibility !== 'all') {
|
if (visibility !== 'all') {
|
||||||
|
@@ -5,6 +5,8 @@ const authModel = require('../models/auth');
|
|||||||
const helpers = require('../lib/helpers');
|
const helpers = require('../lib/helpers');
|
||||||
const TokenModel = require('../models/token');
|
const TokenModel = require('../models/token');
|
||||||
|
|
||||||
|
const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,15 +71,15 @@ module.exports = {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new error.AuthError('Invalid password');
|
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new error.AuthError('No password auth for user');
|
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new error.AuthError('No relevant user found');
|
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@@ -2,7 +2,10 @@ const fs = require('fs');
|
|||||||
const NodeRSA = require('node-rsa');
|
const NodeRSA = require('node-rsa');
|
||||||
const logger = require('../logger').global;
|
const logger = require('../logger').global;
|
||||||
|
|
||||||
const keysFile = '/data/keys.json';
|
const keysFile = '/data/keys.json';
|
||||||
|
const mysqlEngine = 'mysql2';
|
||||||
|
const postgresEngine = 'pg';
|
||||||
|
const sqliteClientName = 'sqlite3';
|
||||||
|
|
||||||
let instance = null;
|
let instance = null;
|
||||||
|
|
||||||
@@ -14,7 +17,7 @@ const configure = () => {
|
|||||||
let configData;
|
let configData;
|
||||||
try {
|
try {
|
||||||
configData = require(filename);
|
configData = require(filename);
|
||||||
} catch (err) {
|
} catch (_) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +37,7 @@ const configure = () => {
|
|||||||
logger.info('Using MySQL configuration');
|
logger.info('Using MySQL configuration');
|
||||||
instance = {
|
instance = {
|
||||||
database: {
|
database: {
|
||||||
engine: 'mysql2',
|
engine: mysqlEngine,
|
||||||
host: envMysqlHost,
|
host: envMysqlHost,
|
||||||
port: process.env.DB_MYSQL_PORT || 3306,
|
port: process.env.DB_MYSQL_PORT || 3306,
|
||||||
user: envMysqlUser,
|
user: envMysqlUser,
|
||||||
@@ -46,13 +49,33 @@ const configure = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const envPostgresHost = process.env.DB_POSTGRES_HOST || null;
|
||||||
|
const envPostgresUser = process.env.DB_POSTGRES_USER || null;
|
||||||
|
const envPostgresName = process.env.DB_POSTGRES_NAME || null;
|
||||||
|
if (envPostgresHost && envPostgresUser && envPostgresName) {
|
||||||
|
// we have enough postgres creds to go with postgres
|
||||||
|
logger.info('Using Postgres configuration');
|
||||||
|
instance = {
|
||||||
|
database: {
|
||||||
|
engine: postgresEngine,
|
||||||
|
host: envPostgresHost,
|
||||||
|
port: process.env.DB_POSTGRES_PORT || 5432,
|
||||||
|
user: envPostgresUser,
|
||||||
|
password: process.env.DB_POSTGRES_PASSWORD,
|
||||||
|
name: envPostgresName,
|
||||||
|
},
|
||||||
|
keys: getKeys(),
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite';
|
const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite';
|
||||||
logger.info(`Using Sqlite: ${envSqliteFile}`);
|
logger.info(`Using Sqlite: ${envSqliteFile}`);
|
||||||
instance = {
|
instance = {
|
||||||
database: {
|
database: {
|
||||||
engine: 'knex-native',
|
engine: 'knex-native',
|
||||||
knex: {
|
knex: {
|
||||||
client: 'sqlite3',
|
client: sqliteClientName,
|
||||||
connection: {
|
connection: {
|
||||||
filename: envSqliteFile
|
filename: envSqliteFile
|
||||||
},
|
},
|
||||||
@@ -143,7 +166,27 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
isSqlite: function () {
|
isSqlite: function () {
|
||||||
instance === null && configure();
|
instance === null && configure();
|
||||||
return instance.database.knex && instance.database.knex.client === 'sqlite3';
|
return instance.database.knex && instance.database.knex.client === sqliteClientName;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a mysql configuration?
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isMysql: function () {
|
||||||
|
instance === null && configure();
|
||||||
|
return instance.database.engine === mysqlEngine;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a postgres configuration?
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isPostgres: function () {
|
||||||
|
instance === null && configure();
|
||||||
|
return instance.database.engine === postgresEngine;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
const {isPostgres} = require('./config');
|
||||||
|
const {ref} = require('objection');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
@@ -45,6 +47,16 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return obj;
|
return obj;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casts a column to json if using postgres
|
||||||
|
*
|
||||||
|
* @param {string} colName
|
||||||
|
* @returns {string|Objection.ReferenceBuilder}
|
||||||
|
*/
|
||||||
|
castJsonIfNeed: function (colName) {
|
||||||
|
return isPostgres() ? ref(colName).castText() : colName;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
38
backend/migrations/20240427161436_stream_ssl.js
Normal file
38
backend/migrations/20240427161436_stream_ssl.js
Normal file
@@ -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');
|
||||||
|
});
|
||||||
|
};
|
@@ -4,7 +4,6 @@
|
|||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const helpers = require('../lib/helpers');
|
const helpers = require('../lib/helpers');
|
||||||
const Model = require('objection').Model;
|
const Model = require('objection').Model;
|
||||||
const User = require('./user');
|
|
||||||
const now = require('./now_helper');
|
const now = require('./now_helper');
|
||||||
|
|
||||||
Model.knex(db);
|
Model.knex(db);
|
||||||
@@ -68,6 +67,11 @@ class Certificate extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get relationMappings () {
|
static get relationMappings () {
|
||||||
|
const ProxyHost = require('./proxy_host');
|
||||||
|
const DeadHost = require('./dead_host');
|
||||||
|
const User = require('./user');
|
||||||
|
const RedirectionHost = require('./redirection_host');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
owner: {
|
owner: {
|
||||||
relation: Model.HasOneRelation,
|
relation: Model.HasOneRelation,
|
||||||
@@ -79,6 +83,39 @@ class Certificate extends Model {
|
|||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('user.is_deleted', 0);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,11 @@ Model.knex(db);
|
|||||||
|
|
||||||
const boolFields = [
|
const boolFields = [
|
||||||
'is_deleted',
|
'is_deleted',
|
||||||
|
'ssl_forced',
|
||||||
|
'http2_support',
|
||||||
'enabled',
|
'enabled',
|
||||||
|
'hsts_enabled',
|
||||||
|
'hsts_subdomains',
|
||||||
];
|
];
|
||||||
|
|
||||||
class DeadHost extends Model {
|
class DeadHost extends Model {
|
||||||
|
@@ -17,6 +17,9 @@ const boolFields = [
|
|||||||
'preserve_path',
|
'preserve_path',
|
||||||
'ssl_forced',
|
'ssl_forced',
|
||||||
'block_exploits',
|
'block_exploits',
|
||||||
|
'hsts_enabled',
|
||||||
|
'hsts_subdomains',
|
||||||
|
'http2_support',
|
||||||
];
|
];
|
||||||
|
|
||||||
class RedirectionHost extends Model {
|
class RedirectionHost extends Model {
|
||||||
|
@@ -1,16 +1,15 @@
|
|||||||
// Objection Docs:
|
const Model = require('objection').Model;
|
||||||
// http://vincit.github.io/objection.js/
|
const db = require('../db');
|
||||||
|
const helpers = require('../lib/helpers');
|
||||||
const db = require('../db');
|
const User = require('./user');
|
||||||
const helpers = require('../lib/helpers');
|
const Certificate = require('./certificate');
|
||||||
const Model = require('objection').Model;
|
const now = require('./now_helper');
|
||||||
const User = require('./user');
|
|
||||||
const now = require('./now_helper');
|
|
||||||
|
|
||||||
Model.knex(db);
|
Model.knex(db);
|
||||||
|
|
||||||
const boolFields = [
|
const boolFields = [
|
||||||
'is_deleted',
|
'is_deleted',
|
||||||
|
'enabled',
|
||||||
'tcp_forwarding',
|
'tcp_forwarding',
|
||||||
'udp_forwarding',
|
'udp_forwarding',
|
||||||
];
|
];
|
||||||
@@ -64,6 +63,17 @@ class Stream extends Model {
|
|||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('user.is_deleted', 0);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
"node-rsa": "^1.0.8",
|
"node-rsa": "^1.0.8",
|
||||||
"objection": "3.0.1",
|
"objection": "3.0.1",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
|
"pg": "^8.13.1",
|
||||||
"signale": "1.4.0",
|
"signale": "1.4.0",
|
||||||
"sqlite3": "5.1.6",
|
"sqlite3": "5.1.6",
|
||||||
"temp-write": "^4.0.0"
|
"temp-write": "^4.0.0"
|
||||||
|
@@ -22,8 +22,7 @@
|
|||||||
"enabled",
|
"enabled",
|
||||||
"locations",
|
"locations",
|
||||||
"hsts_enabled",
|
"hsts_enabled",
|
||||||
"hsts_subdomains",
|
"hsts_subdomains"
|
||||||
"certificate"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@@ -53,8 +53,24 @@
|
|||||||
"enabled": {
|
"enabled": {
|
||||||
"$ref": "../common.json#/properties/enabled"
|
"$ref": "../common.json#/properties/enabled"
|
||||||
},
|
},
|
||||||
|
"certificate_id": {
|
||||||
|
"$ref": "../common.json#/properties/certificate_id"
|
||||||
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"$ref": "./user-object.json"
|
||||||
|
},
|
||||||
|
"certificate": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "./certificate-object.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,10 +5,9 @@
|
|||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"expires": {
|
"expires": {
|
||||||
"description": "Token Expiry Unix Time",
|
"description": "Token Expiry ISO Time String",
|
||||||
"example": 1566540249,
|
"example": "2025-02-04T20:40:46.340Z",
|
||||||
"minimum": 1,
|
"type": "string"
|
||||||
"type": "number"
|
|
||||||
},
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"description": "JWT Token",
|
"description": "JWT Token",
|
||||||
|
@@ -49,8 +49,7 @@
|
|||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"minLength": 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
"description": "Expansions",
|
"description": "Expansions",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["access_list", "owner", "certificate"]
|
"enum": ["owner", "certificate"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -40,7 +40,8 @@
|
|||||||
"nginx_online": true,
|
"nginx_online": true,
|
||||||
"nginx_err": null
|
"nginx_err": null
|
||||||
},
|
},
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"certificate_id": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -32,6 +32,9 @@
|
|||||||
"udp_forwarding": {
|
"udp_forwarding": {
|
||||||
"$ref": "../../../components/stream-object.json#/properties/udp_forwarding"
|
"$ref": "../../../components/stream-object.json#/properties/udp_forwarding"
|
||||||
},
|
},
|
||||||
|
"certificate_id": {
|
||||||
|
"$ref": "../../../components/stream-object.json#/properties/certificate_id"
|
||||||
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"$ref": "../../../components/stream-object.json#/properties/meta"
|
"$ref": "../../../components/stream-object.json#/properties/meta"
|
||||||
}
|
}
|
||||||
@@ -73,7 +76,8 @@
|
|||||||
"nickname": "Admin",
|
"nickname": "Admin",
|
||||||
"avatar": "",
|
"avatar": "",
|
||||||
"roles": ["admin"]
|
"roles": ["admin"]
|
||||||
}
|
},
|
||||||
|
"certificate_id": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -40,7 +40,8 @@
|
|||||||
"nginx_online": true,
|
"nginx_online": true,
|
||||||
"nginx_err": null
|
"nginx_err": null
|
||||||
},
|
},
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"certificate_id": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -29,56 +29,26 @@
|
|||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"minProperties": 1,
|
"minProperties": 1,
|
||||||
"properties": {
|
"properties": {
|
||||||
"domain_names": {
|
"incoming_port": {
|
||||||
"$ref": "../../../../components/proxy-host-object.json#/properties/domain_names"
|
"$ref": "../../../../components/stream-object.json#/properties/incoming_port"
|
||||||
},
|
},
|
||||||
"forward_scheme": {
|
"forwarding_host": {
|
||||||
"$ref": "../../../../components/proxy-host-object.json#/properties/forward_scheme"
|
"$ref": "../../../../components/stream-object.json#/properties/forwarding_host"
|
||||||
},
|
},
|
||||||
"forward_host": {
|
"forwarding_port": {
|
||||||
"$ref": "../../../../components/proxy-host-object.json#/properties/forward_host"
|
"$ref": "../../../../components/stream-object.json#/properties/forwarding_port"
|
||||||
},
|
},
|
||||||
"forward_port": {
|
"tcp_forwarding": {
|
||||||
"$ref": "../../../../components/proxy-host-object.json#/properties/forward_port"
|
"$ref": "../../../../components/stream-object.json#/properties/tcp_forwarding"
|
||||||
|
},
|
||||||
|
"udp_forwarding": {
|
||||||
|
"$ref": "../../../../components/stream-object.json#/properties/udp_forwarding"
|
||||||
},
|
},
|
||||||
"certificate_id": {
|
"certificate_id": {
|
||||||
"$ref": "../../../../components/proxy-host-object.json#/properties/certificate_id"
|
"$ref": "../../../../components/stream-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"
|
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"$ref": "../../../../components/proxy-host-object.json#/properties/meta"
|
"$ref": "../../../../components/stream-object.json#/properties/meta"
|
||||||
},
|
|
||||||
"locations": {
|
|
||||||
"$ref": "../../../../components/proxy-host-object.json#/properties/locations"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,42 +64,32 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"value": {
|
"value": {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"created_on": "2024-10-08T23:23:03.000Z",
|
"created_on": "2024-10-09T02:33:45.000Z",
|
||||||
"modified_on": "2024-10-08T23:26:37.000Z",
|
"modified_on": "2024-10-09T02:33:45.000Z",
|
||||||
"owner_user_id": 1,
|
"owner_user_id": 1,
|
||||||
"domain_names": ["test.example.com"],
|
"incoming_port": 9090,
|
||||||
"forward_host": "192.168.0.10",
|
"forwarding_host": "router.internal",
|
||||||
"forward_port": 8989,
|
"forwarding_port": 80,
|
||||||
"access_list_id": 0,
|
"tcp_forwarding": true,
|
||||||
"certificate_id": 0,
|
"udp_forwarding": false,
|
||||||
"ssl_forced": false,
|
|
||||||
"caching_enabled": false,
|
|
||||||
"block_exploits": false,
|
|
||||||
"advanced_config": "",
|
|
||||||
"meta": {
|
"meta": {
|
||||||
"nginx_online": true,
|
"nginx_online": true,
|
||||||
"nginx_err": null
|
"nginx_err": null
|
||||||
},
|
},
|
||||||
"allow_websocket_upgrade": false,
|
|
||||||
"http2_support": false,
|
|
||||||
"forward_scheme": "http",
|
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"hsts_enabled": false,
|
|
||||||
"hsts_subdomains": false,
|
|
||||||
"owner": {
|
"owner": {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"created_on": "2024-10-07T22:43:55.000Z",
|
"created_on": "2024-10-09T02:33:16.000Z",
|
||||||
"modified_on": "2024-10-08T12:52:54.000Z",
|
"modified_on": "2024-10-09T02:33:16.000Z",
|
||||||
"is_deleted": false,
|
"is_deleted": false,
|
||||||
"is_disabled": false,
|
"is_disabled": false,
|
||||||
"email": "admin@example.com",
|
"email": "admin@example.com",
|
||||||
"name": "Administrator",
|
"name": "Administrator",
|
||||||
"nickname": "some guy",
|
"nickname": "Admin",
|
||||||
"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm",
|
"avatar": "",
|
||||||
"roles": ["admin"]
|
"roles": ["admin"]
|
||||||
},
|
},
|
||||||
"certificate": null,
|
"certificate_id": 0
|
||||||
"access_list": null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
"examples": {
|
"examples": {
|
||||||
"default": {
|
"default": {
|
||||||
"value": {
|
"value": {
|
||||||
"expires": 1566540510,
|
"expires": "2025-02-04T20:40:46.340Z",
|
||||||
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
|
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,7 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"value": {
|
"value": {
|
||||||
"result": {
|
"result": {
|
||||||
"expires": 1566540510,
|
"expires": "2025-02-04T20:40:46.340Z",
|
||||||
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
|
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,15 @@
|
|||||||
"url": "http://127.0.0.1:81/api"
|
"url": "http://127.0.0.1:81/api"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {
|
||||||
|
"bearerAuth": {
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "bearer",
|
||||||
|
"bearerFormat": "JWT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/": {
|
"/": {
|
||||||
"get": {
|
"get": {
|
||||||
|
@@ -15,18 +15,18 @@ const certbot = require('./lib/certbot');
|
|||||||
const setupDefaultUser = () => {
|
const setupDefaultUser = () => {
|
||||||
return userModel
|
return userModel
|
||||||
.query()
|
.query()
|
||||||
.select(userModel.raw('COUNT(`id`) as `count`'))
|
.select('id', )
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.first()
|
.first()
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (!row.count) {
|
if (!row || !row.id) {
|
||||||
// Create a new user and set password
|
// Create a new user and set password
|
||||||
let email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com';
|
const email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com';
|
||||||
let password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme';
|
const password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme';
|
||||||
|
|
||||||
logger.info('Creating a new user: ' + email + ' with password: ' + password);
|
logger.info('Creating a new user: ' + email + ' with password: ' + password);
|
||||||
|
|
||||||
let data = {
|
const data = {
|
||||||
is_deleted: 0,
|
is_deleted: 0,
|
||||||
email: email,
|
email: email,
|
||||||
name: 'Administrator',
|
name: 'Administrator',
|
||||||
@@ -77,11 +77,11 @@ const setupDefaultUser = () => {
|
|||||||
const setupDefaultSettings = () => {
|
const setupDefaultSettings = () => {
|
||||||
return settingModel
|
return settingModel
|
||||||
.query()
|
.query()
|
||||||
.select(settingModel.raw('COUNT(`id`) as `count`'))
|
.select('id')
|
||||||
.where({id: 'default-site'})
|
.where({id: 'default-site'})
|
||||||
.first()
|
.first()
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (!row.count) {
|
if (!row || !row.id) {
|
||||||
settingModel
|
settingModel
|
||||||
.query()
|
.query()
|
||||||
.insert({
|
.insert({
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
auth_basic "Authorization required";
|
auth_basic "Authorization required";
|
||||||
auth_basic_user_file /data/access/{{ access_list_id }};
|
auth_basic_user_file /data/access/{{ access_list_id }};
|
||||||
|
|
||||||
{% if access_list.pass_auth == 0 %}
|
{% if access_list.pass_auth == 0 or access_list.pass_auth == true %}
|
||||||
proxy_set_header Authorization "";
|
proxy_set_header Authorization "";
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
deny all;
|
deny all;
|
||||||
|
|
||||||
# Access checks must...
|
# Access checks must...
|
||||||
{% if access_list.satisfy_any == 1 %}
|
{% if access_list.satisfy_any == 1 or access_list.satisfy_any == true %}
|
||||||
satisfy any;
|
satisfy any;
|
||||||
{% else %}
|
{% else %}
|
||||||
satisfy all;
|
satisfy all;
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
{% if certificate.provider == "letsencrypt" %}
|
{% if certificate.provider == "letsencrypt" %}
|
||||||
# Let's Encrypt SSL
|
# Let's Encrypt SSL
|
||||||
include conf.d/include/letsencrypt-acme-challenge.conf;
|
include conf.d/include/letsencrypt-acme-challenge.conf;
|
||||||
|
include conf.d/include/ssl-cache.conf;
|
||||||
include conf.d/include/ssl-ciphers.conf;
|
include conf.d/include/ssl-ciphers.conf;
|
||||||
ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem;
|
||||||
|
13
backend/templates/_certificates_stream.conf
Normal file
13
backend/templates/_certificates_stream.conf
Normal file
@@ -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 -%}
|
@@ -5,11 +5,16 @@
|
|||||||
#listen [::]:80;
|
#listen [::]:80;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if certificate -%}
|
{% if certificate -%}
|
||||||
listen 443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %};
|
listen 443 ssl;
|
||||||
{% if ipv6 -%}
|
{% if ipv6 -%}
|
||||||
listen [::]:443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %};
|
listen [::]:443 ssl;
|
||||||
{% else -%}
|
{% else -%}
|
||||||
#listen [::]:443;
|
#listen [::]:443;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
server_name {{ domain_names | join: " " }};
|
server_name {{ domain_names | join: " " }};
|
||||||
|
{% if http2_support == 1 or http2_support == true %}
|
||||||
|
http2 on;
|
||||||
|
{% else -%}
|
||||||
|
http2 off;
|
||||||
|
{% endif %}
|
@@ -7,11 +7,7 @@
|
|||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
|
||||||
set $proxy_forward_scheme {{ forward_scheme }};
|
proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
|
||||||
set $proxy_server "{{ forward_host }}";
|
|
||||||
set $proxy_port {{ forward_port }};
|
|
||||||
|
|
||||||
proxy_pass $proxy_forward_scheme://$proxy_server:$proxy_port{{ forward_path }};
|
|
||||||
|
|
||||||
{% include "_access.conf" %}
|
{% include "_access.conf" %}
|
||||||
{% include "_assets.conf" %}
|
{% include "_assets.conf" %}
|
||||||
|
@@ -22,5 +22,7 @@ server {
|
|||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
# Custom
|
||||||
|
include /data/nginx/custom/server_dead[.]conf;
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@@ -5,12 +5,10 @@
|
|||||||
{% if enabled %}
|
{% if enabled %}
|
||||||
{% if tcp_forwarding == 1 or tcp_forwarding == true -%}
|
{% if tcp_forwarding == 1 or tcp_forwarding == true -%}
|
||||||
server {
|
server {
|
||||||
listen {{ incoming_port }};
|
listen {{ incoming_port }} {%- if certificate %} ssl {%- endif %};
|
||||||
{% if ipv6 -%}
|
{% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} {%- if certificate %} ssl {%- endif %};
|
||||||
listen [::]:{{ incoming_port }};
|
|
||||||
{% else -%}
|
{%- include "_certificates_stream.conf" %}
|
||||||
#listen [::]:{{ incoming_port }};
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
|
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
|
||||||
|
|
||||||
@@ -19,14 +17,12 @@ server {
|
|||||||
include /data/nginx/custom/server_stream_tcp[.]conf;
|
include /data/nginx/custom/server_stream_tcp[.]conf;
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if udp_forwarding == 1 or udp_forwarding == true %}
|
|
||||||
|
{% if udp_forwarding == 1 or udp_forwarding == true -%}
|
||||||
server {
|
server {
|
||||||
listen {{ incoming_port }} udp;
|
listen {{ incoming_port }} udp;
|
||||||
{% if ipv6 -%}
|
{% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} udp;
|
||||||
listen [::]:{{ incoming_port }} udp;
|
|
||||||
{% else -%}
|
|
||||||
#listen [::]:{{ incoming_port }} udp;
|
|
||||||
{% endif %}
|
|
||||||
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
|
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
|
||||||
|
|
||||||
# Custom
|
# Custom
|
||||||
|
@@ -830,9 +830,9 @@ crc32-stream@^4.0.2:
|
|||||||
readable-stream "^3.4.0"
|
readable-stream "^3.4.0"
|
||||||
|
|
||||||
cross-spawn@^7.0.2:
|
cross-spawn@^7.0.2:
|
||||||
version "7.0.3"
|
version "7.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key "^3.1.0"
|
path-key "^3.1.0"
|
||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
@@ -2735,11 +2735,67 @@ path@^0.12.7:
|
|||||||
process "^0.11.1"
|
process "^0.11.1"
|
||||||
util "^0.10.3"
|
util "^0.10.3"
|
||||||
|
|
||||||
|
pg-cloudflare@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98"
|
||||||
|
integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==
|
||||||
|
|
||||||
pg-connection-string@2.5.0:
|
pg-connection-string@2.5.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
|
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
|
||||||
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
|
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
|
||||||
|
|
||||||
|
pg-connection-string@^2.7.0:
|
||||||
|
version "2.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37"
|
||||||
|
integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==
|
||||||
|
|
||||||
|
pg-int8@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
|
||||||
|
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
|
||||||
|
|
||||||
|
pg-pool@^3.7.0:
|
||||||
|
version "3.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec"
|
||||||
|
integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==
|
||||||
|
|
||||||
|
pg-protocol@^1.7.0:
|
||||||
|
version "1.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93"
|
||||||
|
integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==
|
||||||
|
|
||||||
|
pg-types@^2.1.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
|
||||||
|
integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
|
||||||
|
dependencies:
|
||||||
|
pg-int8 "1.0.1"
|
||||||
|
postgres-array "~2.0.0"
|
||||||
|
postgres-bytea "~1.0.0"
|
||||||
|
postgres-date "~1.0.4"
|
||||||
|
postgres-interval "^1.1.0"
|
||||||
|
|
||||||
|
pg@^8.13.1:
|
||||||
|
version "8.13.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pg/-/pg-8.13.1.tgz#6498d8b0a87ff76c2df7a32160309d3168c0c080"
|
||||||
|
integrity sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==
|
||||||
|
dependencies:
|
||||||
|
pg-connection-string "^2.7.0"
|
||||||
|
pg-pool "^3.7.0"
|
||||||
|
pg-protocol "^1.7.0"
|
||||||
|
pg-types "^2.1.0"
|
||||||
|
pgpass "1.x"
|
||||||
|
optionalDependencies:
|
||||||
|
pg-cloudflare "^1.1.1"
|
||||||
|
|
||||||
|
pgpass@1.x:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d"
|
||||||
|
integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==
|
||||||
|
dependencies:
|
||||||
|
split2 "^4.1.0"
|
||||||
|
|
||||||
picomatch@^2.0.4, picomatch@^2.2.1:
|
picomatch@^2.0.4, picomatch@^2.2.1:
|
||||||
version "2.2.2"
|
version "2.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
||||||
@@ -2758,6 +2814,28 @@ pkg-conf@^2.1.0:
|
|||||||
find-up "^2.0.0"
|
find-up "^2.0.0"
|
||||||
load-json-file "^4.0.0"
|
load-json-file "^4.0.0"
|
||||||
|
|
||||||
|
postgres-array@~2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
|
||||||
|
integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==
|
||||||
|
|
||||||
|
postgres-bytea@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35"
|
||||||
|
integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==
|
||||||
|
|
||||||
|
postgres-date@~1.0.4:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8"
|
||||||
|
integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==
|
||||||
|
|
||||||
|
postgres-interval@^1.1.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695"
|
||||||
|
integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==
|
||||||
|
dependencies:
|
||||||
|
xtend "^4.0.0"
|
||||||
|
|
||||||
prelude-ls@^1.2.1:
|
prelude-ls@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||||
@@ -3194,6 +3272,11 @@ socks@^2.6.2:
|
|||||||
ip "^2.0.0"
|
ip "^2.0.0"
|
||||||
smart-buffer "^4.2.0"
|
smart-buffer "^4.2.0"
|
||||||
|
|
||||||
|
split2@^4.1.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
|
||||||
|
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
|
||||||
|
|
||||||
sprintf-js@~1.0.2:
|
sprintf-js@~1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
@@ -3665,6 +3748,11 @@ xdg-basedir@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
||||||
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
|
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
|
||||||
|
|
||||||
|
xtend@^4.0.0:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||||
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
|
||||||
y18n@^4.0.0:
|
y18n@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
|
||||||
|
8
docker/ci.env
Normal file
8
docker/ci.env
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
AUTHENTIK_SECRET_KEY=gl8woZe8L6IIX8SC0c5Ocsj0xPkX5uJo5DVZCFl+L/QGbzuplfutYuua2ODNLEiDD3aFd9H2ylJmrke0
|
||||||
|
AUTHENTIK_REDIS__HOST=authentik-redis
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST=db-postgres
|
||||||
|
AUTHENTIK_POSTGRESQL__USER=authentik
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME=authentik
|
||||||
|
AUTHENTIK_POSTGRESQL__PASSWORD=07EKS5NLI6Tpv68tbdvrxfvj
|
||||||
|
AUTHENTIK_BOOTSTRAP_PASSWORD=admin
|
||||||
|
AUTHENTIK_BOOTSTRAP_EMAIL=admin@example.com
|
BIN
docker/ci/postgres/authentik.sql.gz
Normal file
BIN
docker/ci/postgres/authentik.sql.gz
Normal file
Binary file not shown.
@@ -29,7 +29,8 @@ COPY scripts/install-s6 /tmp/install-s6
|
|||||||
RUN rm -f /etc/nginx/conf.d/production.conf \
|
RUN rm -f /etc/nginx/conf.d/production.conf \
|
||||||
&& chmod 644 /etc/logrotate.d/nginx-proxy-manager \
|
&& chmod 644 /etc/logrotate.d/nginx-proxy-manager \
|
||||||
&& /tmp/install-s6 "${TARGETPLATFORM}" \
|
&& /tmp/install-s6 "${TARGETPLATFORM}" \
|
||||||
&& rm -f /tmp/install-s6
|
&& rm -f /tmp/install-s6 \
|
||||||
|
&& chmod 644 -R /root/.cache
|
||||||
|
|
||||||
# Certs for testing purposes
|
# Certs for testing purposes
|
||||||
COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem
|
COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem
|
||||||
|
78
docker/docker-compose.ci.postgres.yml
Normal file
78
docker/docker-compose.ci.postgres.yml
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
|
||||||
|
services:
|
||||||
|
|
||||||
|
cypress:
|
||||||
|
environment:
|
||||||
|
CYPRESS_stack: 'postgres'
|
||||||
|
|
||||||
|
fullstack:
|
||||||
|
environment:
|
||||||
|
DB_POSTGRES_HOST: 'db-postgres'
|
||||||
|
DB_POSTGRES_PORT: '5432'
|
||||||
|
DB_POSTGRES_USER: 'npm'
|
||||||
|
DB_POSTGRES_PASSWORD: 'npmpass'
|
||||||
|
DB_POSTGRES_NAME: 'npm'
|
||||||
|
depends_on:
|
||||||
|
- db-postgres
|
||||||
|
- authentik
|
||||||
|
- authentik-worker
|
||||||
|
- authentik-ldap
|
||||||
|
|
||||||
|
db-postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: 'npm'
|
||||||
|
POSTGRES_PASSWORD: 'npmpass'
|
||||||
|
POSTGRES_DB: 'npm'
|
||||||
|
volumes:
|
||||||
|
- psql_vol:/var/lib/postgresql/data
|
||||||
|
- ./ci/postgres:/docker-entrypoint-initdb.d
|
||||||
|
networks:
|
||||||
|
- fulltest
|
||||||
|
|
||||||
|
authentik-redis:
|
||||||
|
image: 'redis:alpine'
|
||||||
|
command: --save 60 1 --loglevel warning
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD-SHELL', 'redis-cli ping | grep PONG']
|
||||||
|
start_period: 20s
|
||||||
|
interval: 30s
|
||||||
|
retries: 5
|
||||||
|
timeout: 3s
|
||||||
|
volumes:
|
||||||
|
- redis_vol:/data
|
||||||
|
|
||||||
|
authentik:
|
||||||
|
image: ghcr.io/goauthentik/server:2024.10.1
|
||||||
|
restart: unless-stopped
|
||||||
|
command: server
|
||||||
|
env_file:
|
||||||
|
- ci.env
|
||||||
|
depends_on:
|
||||||
|
- authentik-redis
|
||||||
|
- db-postgres
|
||||||
|
|
||||||
|
authentik-worker:
|
||||||
|
image: ghcr.io/goauthentik/server:2024.10.1
|
||||||
|
restart: unless-stopped
|
||||||
|
command: worker
|
||||||
|
env_file:
|
||||||
|
- ci.env
|
||||||
|
depends_on:
|
||||||
|
- authentik-redis
|
||||||
|
- db-postgres
|
||||||
|
|
||||||
|
authentik-ldap:
|
||||||
|
image: ghcr.io/goauthentik/ldap:2024.10.1
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_HOST: 'http://authentik:9000'
|
||||||
|
AUTHENTIK_INSECURE: 'true'
|
||||||
|
AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp'
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- authentik
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
psql_vol:
|
||||||
|
redis_vol:
|
@@ -22,6 +22,10 @@ services:
|
|||||||
test: ["CMD", "/usr/bin/check-health"]
|
test: ["CMD", "/usr/bin/check-health"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
|
expose:
|
||||||
|
- '80-81/tcp'
|
||||||
|
- '443/tcp'
|
||||||
|
- '1500-1503/tcp'
|
||||||
networks:
|
networks:
|
||||||
fulltest:
|
fulltest:
|
||||||
aliases:
|
aliases:
|
||||||
@@ -40,7 +44,7 @@ services:
|
|||||||
- ca.internal
|
- ca.internal
|
||||||
|
|
||||||
pdns:
|
pdns:
|
||||||
image: pschiffe/pdns-mysql
|
image: pschiffe/pdns-mysql:4.8
|
||||||
volumes:
|
volumes:
|
||||||
- '/etc/localtime:/etc/localtime:ro'
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
environment:
|
environment:
|
||||||
@@ -97,7 +101,7 @@ services:
|
|||||||
HTTP_PROXY: 'squid:3128'
|
HTTP_PROXY: 'squid:3128'
|
||||||
HTTPS_PROXY: 'squid:3128'
|
HTTPS_PROXY: 'squid:3128'
|
||||||
volumes:
|
volumes:
|
||||||
- 'cypress_logs:/results'
|
- 'cypress_logs:/test/results'
|
||||||
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
||||||
- '/etc/localtime:/etc/localtime:ro'
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
command: cypress run --browser chrome --config-file=cypress/config/ci.js
|
command: cypress run --browser chrome --config-file=cypress/config/ci.js
|
||||||
|
@@ -2,8 +2,8 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
fullstack:
|
fullstack:
|
||||||
image: nginxproxymanager:dev
|
image: npm2dev:core
|
||||||
container_name: npm_core
|
container_name: npm2dev.core
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./dev/Dockerfile
|
dockerfile: ./dev/Dockerfile
|
||||||
@@ -26,11 +26,17 @@ services:
|
|||||||
DEVELOPMENT: 'true'
|
DEVELOPMENT: 'true'
|
||||||
LE_STAGING: 'true'
|
LE_STAGING: 'true'
|
||||||
# db:
|
# db:
|
||||||
DB_MYSQL_HOST: 'db'
|
# DB_MYSQL_HOST: 'db'
|
||||||
DB_MYSQL_PORT: '3306'
|
# DB_MYSQL_PORT: '3306'
|
||||||
DB_MYSQL_USER: 'npm'
|
# DB_MYSQL_USER: 'npm'
|
||||||
DB_MYSQL_PASSWORD: 'npm'
|
# DB_MYSQL_PASSWORD: 'npm'
|
||||||
DB_MYSQL_NAME: 'npm'
|
# DB_MYSQL_NAME: 'npm'
|
||||||
|
# db-postgres:
|
||||||
|
DB_POSTGRES_HOST: 'db-postgres'
|
||||||
|
DB_POSTGRES_PORT: '5432'
|
||||||
|
DB_POSTGRES_USER: 'npm'
|
||||||
|
DB_POSTGRES_PASSWORD: 'npmpass'
|
||||||
|
DB_POSTGRES_NAME: 'npm'
|
||||||
# DB_SQLITE_FILE: "/data/database.sqlite"
|
# DB_SQLITE_FILE: "/data/database.sqlite"
|
||||||
# DISABLE_IPV6: "true"
|
# DISABLE_IPV6: "true"
|
||||||
# Required for DNS Certificate provisioning testing:
|
# Required for DNS Certificate provisioning testing:
|
||||||
@@ -49,11 +55,15 @@ services:
|
|||||||
timeout: 3s
|
timeout: 3s
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
- db-postgres
|
||||||
|
- authentik
|
||||||
|
- authentik-worker
|
||||||
|
- authentik-ldap
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: jc21/mariadb-aria
|
image: jc21/mariadb-aria
|
||||||
container_name: npm_db
|
container_name: npm2dev.db
|
||||||
ports:
|
ports:
|
||||||
- 33306:3306
|
- 33306:3306
|
||||||
networks:
|
networks:
|
||||||
@@ -66,8 +76,22 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- db_data:/var/lib/mysql
|
||||||
|
|
||||||
|
db-postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
container_name: npm2dev.db-postgres
|
||||||
|
networks:
|
||||||
|
- nginx_proxy_manager
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: 'npm'
|
||||||
|
POSTGRES_PASSWORD: 'npmpass'
|
||||||
|
POSTGRES_DB: 'npm'
|
||||||
|
volumes:
|
||||||
|
- psql_data:/var/lib/postgresql/data
|
||||||
|
- ./ci/postgres:/docker-entrypoint-initdb.d
|
||||||
|
|
||||||
stepca:
|
stepca:
|
||||||
image: jc21/testca
|
image: jc21/testca
|
||||||
|
container_name: npm2dev.stepca
|
||||||
volumes:
|
volumes:
|
||||||
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
||||||
- '/etc/localtime:/etc/localtime:ro'
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
@@ -78,6 +102,7 @@ services:
|
|||||||
|
|
||||||
dnsrouter:
|
dnsrouter:
|
||||||
image: jc21/dnsrouter
|
image: jc21/dnsrouter
|
||||||
|
container_name: npm2dev.dnsrouter
|
||||||
volumes:
|
volumes:
|
||||||
- ./dev/dnsrouter-config.json.tmp:/dnsrouter-config.json:ro
|
- ./dev/dnsrouter-config.json.tmp:/dnsrouter-config.json:ro
|
||||||
networks:
|
networks:
|
||||||
@@ -85,7 +110,7 @@ services:
|
|||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
image: swaggerapi/swagger-ui:latest
|
image: swaggerapi/swagger-ui:latest
|
||||||
container_name: npm_swagger
|
container_name: npm2dev.swagger
|
||||||
ports:
|
ports:
|
||||||
- 3082:80
|
- 3082:80
|
||||||
environment:
|
environment:
|
||||||
@@ -96,7 +121,7 @@ services:
|
|||||||
|
|
||||||
squid:
|
squid:
|
||||||
image: ubuntu/squid
|
image: ubuntu/squid
|
||||||
container_name: npm_squid
|
container_name: npm2dev.squid
|
||||||
volumes:
|
volumes:
|
||||||
- './dev/squid.conf:/etc/squid/squid.conf:ro'
|
- './dev/squid.conf:/etc/squid/squid.conf:ro'
|
||||||
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
||||||
@@ -107,7 +132,8 @@ services:
|
|||||||
- 8128:3128
|
- 8128:3128
|
||||||
|
|
||||||
pdns:
|
pdns:
|
||||||
image: pschiffe/pdns-mysql
|
image: pschiffe/pdns-mysql:4.8
|
||||||
|
container_name: npm2dev.pdns
|
||||||
volumes:
|
volumes:
|
||||||
- '/etc/localtime:/etc/localtime:ro'
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
environment:
|
environment:
|
||||||
@@ -136,6 +162,7 @@ services:
|
|||||||
|
|
||||||
pdns-db:
|
pdns-db:
|
||||||
image: mariadb
|
image: mariadb
|
||||||
|
container_name: npm2dev.pdns-db
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: 'pdns'
|
MYSQL_ROOT_PASSWORD: 'pdns'
|
||||||
MYSQL_DATABASE: 'pdns'
|
MYSQL_DATABASE: 'pdns'
|
||||||
@@ -149,7 +176,8 @@ services:
|
|||||||
- nginx_proxy_manager
|
- nginx_proxy_manager
|
||||||
|
|
||||||
cypress:
|
cypress:
|
||||||
image: "npm_dev_cypress"
|
image: npm2dev:cypress
|
||||||
|
container_name: npm2dev.cypress
|
||||||
build:
|
build:
|
||||||
context: ../
|
context: ../
|
||||||
dockerfile: test/cypress/Dockerfile
|
dockerfile: test/cypress/Dockerfile
|
||||||
@@ -164,16 +192,77 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- nginx_proxy_manager
|
- nginx_proxy_manager
|
||||||
|
|
||||||
|
authentik-redis:
|
||||||
|
image: 'redis:alpine'
|
||||||
|
container_name: npm2dev.authentik-redis
|
||||||
|
command: --save 60 1 --loglevel warning
|
||||||
|
networks:
|
||||||
|
- nginx_proxy_manager
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD-SHELL', 'redis-cli ping | grep PONG']
|
||||||
|
start_period: 20s
|
||||||
|
interval: 30s
|
||||||
|
retries: 5
|
||||||
|
timeout: 3s
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
|
||||||
|
authentik:
|
||||||
|
image: ghcr.io/goauthentik/server:2024.10.1
|
||||||
|
container_name: npm2dev.authentik
|
||||||
|
restart: unless-stopped
|
||||||
|
command: server
|
||||||
|
networks:
|
||||||
|
- nginx_proxy_manager
|
||||||
|
env_file:
|
||||||
|
- ci.env
|
||||||
|
ports:
|
||||||
|
- 9000:9000
|
||||||
|
depends_on:
|
||||||
|
- authentik-redis
|
||||||
|
- db-postgres
|
||||||
|
|
||||||
|
authentik-worker:
|
||||||
|
image: ghcr.io/goauthentik/server:2024.10.1
|
||||||
|
container_name: npm2dev.authentik-worker
|
||||||
|
restart: unless-stopped
|
||||||
|
command: worker
|
||||||
|
networks:
|
||||||
|
- nginx_proxy_manager
|
||||||
|
env_file:
|
||||||
|
- ci.env
|
||||||
|
depends_on:
|
||||||
|
- authentik-redis
|
||||||
|
- db-postgres
|
||||||
|
|
||||||
|
authentik-ldap:
|
||||||
|
image: ghcr.io/goauthentik/ldap:2024.10.1
|
||||||
|
container_name: npm2dev.authentik-ldap
|
||||||
|
networks:
|
||||||
|
- nginx_proxy_manager
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_HOST: 'http://authentik:9000'
|
||||||
|
AUTHENTIK_INSECURE: 'true'
|
||||||
|
AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp'
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- authentik
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
npm_data:
|
npm_data:
|
||||||
name: npm_core_data
|
name: npm2dev_core_data
|
||||||
le_data:
|
le_data:
|
||||||
name: npm_le_data
|
name: npm2dev_le_data
|
||||||
db_data:
|
db_data:
|
||||||
name: npm_db_data
|
name: npm2dev_db_data
|
||||||
pdns_mysql:
|
pdns_mysql:
|
||||||
name: npm_pdns_mysql
|
name: npnpm2dev_pdns_mysql
|
||||||
|
psql_data:
|
||||||
|
name: npm2dev_psql_data
|
||||||
|
redis_data:
|
||||||
|
name: npm2dev_redis_data
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
nginx_proxy_manager:
|
nginx_proxy_manager:
|
||||||
name: npm_network
|
name: npm2dev_network
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\.map|js\.map)$ {
|
location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|woff2|eot|ttf|svg|ico|css\.map|js\.map)$ {
|
||||||
if_modified_since off;
|
if_modified_since off;
|
||||||
|
|
||||||
# use the public cache
|
# use the public cache
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
ssl_session_timeout 5m;
|
||||||
|
ssl_session_cache shared:SSL_stream:50m;
|
2
docker/rootfs/etc/nginx/conf.d/include/ssl-cache.conf
Normal file
2
docker/rootfs/etc/nginx/conf.d/include/ssl-cache.conf
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ssl_session_timeout 5m;
|
||||||
|
ssl_session_cache shared:SSL:50m;
|
@@ -1,6 +1,3 @@
|
|||||||
ssl_session_timeout 5m;
|
|
||||||
ssl_session_cache shared:SSL:50m;
|
|
||||||
|
|
||||||
# intermediate configuration. tweak to your needs.
|
# intermediate configuration. tweak to your needs.
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
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';
|
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';
|
||||||
|
@@ -8,7 +8,7 @@ BLUE='\E[1;34m'
|
|||||||
GREEN='\E[1;32m'
|
GREEN='\E[1;32m'
|
||||||
RESET='\E[0m'
|
RESET='\E[0m'
|
||||||
|
|
||||||
S6_OVERLAY_VERSION=3.1.5.0
|
S6_OVERLAY_VERSION=3.2.0.2
|
||||||
TARGETPLATFORM=${1:-linux/amd64}
|
TARGETPLATFORM=${1:-linux/amd64}
|
||||||
|
|
||||||
# Determine the correct binary file for the architecture given
|
# Determine the correct binary file for the architecture given
|
||||||
|
@@ -50,7 +50,6 @@ networks:
|
|||||||
Let's look at a Portainer example:
|
Let's look at a Portainer example:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: '3.8'
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
portainer:
|
portainer:
|
||||||
@@ -92,8 +91,6 @@ This image supports the use of Docker secrets to import from files and keep sens
|
|||||||
You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name.
|
You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
# Secrets are single-line text files where the sole content is the secret
|
# Secrets are single-line text files where the sole content is the secret
|
||||||
# Paths in this example assume that secrets are kept in local folder called ".secrets"
|
# Paths in this example assume that secrets are kept in local folder called ".secrets"
|
||||||
@@ -184,6 +181,7 @@ You can add your custom configuration snippet files at `/data/nginx/custom` as f
|
|||||||
- `/data/nginx/custom/server_stream.conf`: Included at the end of every stream server block
|
- `/data/nginx/custom/server_stream.conf`: Included at the end of every stream server block
|
||||||
- `/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream server block
|
- `/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream server block
|
||||||
- `/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream server block
|
- `/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream server block
|
||||||
|
- `/data/nginx/custom/server_dead.conf`: Included at the end of every 404 server block
|
||||||
|
|
||||||
Every file is optional.
|
Every file is optional.
|
||||||
|
|
||||||
|
@@ -9,7 +9,6 @@ outline: deep
|
|||||||
Create a `docker-compose.yml` file:
|
Create a `docker-compose.yml` file:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: '3.8'
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
@@ -22,8 +21,7 @@ services:
|
|||||||
# Add any other Stream port you want to expose
|
# Add any other Stream port you want to expose
|
||||||
# - '21:21' # FTP
|
# - '21:21' # FTP
|
||||||
|
|
||||||
# Uncomment the next line if you uncomment anything in the section
|
environment:
|
||||||
# environment:
|
|
||||||
# Uncomment this if you want to change the location of
|
# Uncomment this if you want to change the location of
|
||||||
# the SQLite DB file within the container
|
# the SQLite DB file within the container
|
||||||
# DB_SQLITE_FILE: "/data/database.sqlite"
|
# DB_SQLITE_FILE: "/data/database.sqlite"
|
||||||
@@ -55,7 +53,6 @@ are going to use.
|
|||||||
Here is an example of what your `docker-compose.yml` will look like when using a MariaDB container:
|
Here is an example of what your `docker-compose.yml` will look like when using a MariaDB container:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: '3.8'
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
@@ -101,6 +98,53 @@ Please note, that `DB_MYSQL_*` environment variables will take precedent over `D
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## Using Postgres database
|
||||||
|
|
||||||
|
Similar to the MySQL server setup:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
# These ports are in format <host-port>:<container-port>
|
||||||
|
- '80:80' # Public HTTP Port
|
||||||
|
- '443:443' # Public HTTPS Port
|
||||||
|
- '81:81' # Admin Web Port
|
||||||
|
# Add any other Stream port you want to expose
|
||||||
|
# - '21:21' # FTP
|
||||||
|
environment:
|
||||||
|
# Postgres parameters:
|
||||||
|
DB_POSTGRES_HOST: 'db'
|
||||||
|
DB_POSTGRES_PORT: '5432'
|
||||||
|
DB_POSTGRES_USER: 'npm'
|
||||||
|
DB_POSTGRES_PASSWORD: 'npmpass'
|
||||||
|
DB_POSTGRES_NAME: 'npm'
|
||||||
|
# Uncomment this if IPv6 is not enabled on your host
|
||||||
|
# DISABLE_IPV6: 'true'
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./letsencrypt:/etc/letsencrypt
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:latest
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: 'npm'
|
||||||
|
POSTGRES_PASSWORD: 'npmpass'
|
||||||
|
POSTGRES_DB: 'npm'
|
||||||
|
volumes:
|
||||||
|
- ./postgres:/var/lib/postgresql/data
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
|
||||||
|
Custom Postgres schema is not supported, as such `public` will be used.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Running on Raspberry PI / ARM devices
|
## Running on Raspberry PI / ARM devices
|
||||||
|
|
||||||
The docker images support the following architectures:
|
The docker images support the following architectures:
|
||||||
@@ -137,5 +181,13 @@ Email: admin@example.com
|
|||||||
Password: changeme
|
Password: changeme
|
||||||
```
|
```
|
||||||
|
|
||||||
Immediately after logging in with this default user you will be asked to modify your details and change your password.
|
Immediately after logging in with this default user you will be asked to modify your details and change your password. You can change defaults with:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
environment:
|
||||||
|
INITIAL_ADMIN_EMAIL: my@example.com
|
||||||
|
INITIAL_ADMIN_PASSWORD: mypassword1
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
1
docs/src/third-party/index.md
vendored
1
docs/src/third-party/index.md
vendored
@@ -12,6 +12,7 @@ Known integrations:
|
|||||||
- [HomeAssistant Hass.io plugin](https://github.com/hassio-addons/addon-nginx-proxy-manager)
|
- [HomeAssistant Hass.io plugin](https://github.com/hassio-addons/addon-nginx-proxy-manager)
|
||||||
- [UnRaid / Synology](https://github.com/jlesage/docker-nginx-proxy-manager)
|
- [UnRaid / Synology](https://github.com/jlesage/docker-nginx-proxy-manager)
|
||||||
- [Proxmox Scripts](https://github.com/ej52/proxmox-scripts/tree/main/apps/nginx-proxy-manager)
|
- [Proxmox Scripts](https://github.com/ej52/proxmox-scripts/tree/main/apps/nginx-proxy-manager)
|
||||||
|
- [Proxmox VE Helper-Scripts](https://community-scripts.github.io/ProxmoxVE/scripts?id=nginxproxymanager)
|
||||||
- [nginxproxymanagerGraf](https://github.com/ma-karai/nginxproxymanagerGraf)
|
- [nginxproxymanagerGraf](https://github.com/ma-karai/nginxproxymanagerGraf)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -873,9 +873,9 @@ mitt@^3.0.1:
|
|||||||
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
|
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
|
||||||
|
|
||||||
nanoid@^3.3.7:
|
nanoid@^3.3.7:
|
||||||
version "3.3.7"
|
version "3.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
|
||||||
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
|
||||||
|
|
||||||
oniguruma-to-js@0.4.3:
|
oniguruma-to-js@0.4.3:
|
||||||
version "0.4.3"
|
version "0.4.3"
|
||||||
@@ -1065,9 +1065,9 @@ vfile@^6.0.0:
|
|||||||
vfile-message "^4.0.0"
|
vfile-message "^4.0.0"
|
||||||
|
|
||||||
vite@^5.4.8:
|
vite@^5.4.8:
|
||||||
version "5.4.8"
|
version "5.4.14"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.8.tgz#af548ce1c211b2785478d3ba3e8da51e39a287e8"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.14.tgz#ff8255edb02134df180dcfca1916c37a6abe8408"
|
||||||
integrity sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==
|
integrity sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild "^0.21.3"
|
esbuild "^0.21.3"
|
||||||
postcss "^8.4.43"
|
postcss "^8.4.43"
|
||||||
|
@@ -4,444 +4,438 @@ const Tokens = require('./tokens');
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} route
|
* @param {String} route
|
||||||
* @param {Object} [options]
|
* @param {Object} [options]
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
navigate: function (route, options) {
|
navigate: function (route, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
Backbone.history.navigate(route.toString(), options);
|
Backbone.history.navigate(route.toString(), options);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login
|
* Login
|
||||||
*/
|
*/
|
||||||
showLogin: function () {
|
showLogin: function () {
|
||||||
window.location = '/login';
|
window.location = '/login';
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Users
|
* Users
|
||||||
*/
|
*/
|
||||||
showUsers: function () {
|
showUsers: function () {
|
||||||
let controller = this;
|
const controller = this;
|
||||||
if (Cache.User.isAdmin()) {
|
if (Cache.User.isAdmin()) {
|
||||||
require(['./main', './users/main'], (App, View) => {
|
require(['./main', './users/main'], (App, View) => {
|
||||||
controller.navigate('/users');
|
controller.navigate('/users');
|
||||||
App.UI.showAppContent(new View());
|
App.UI.showAppContent(new View());
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.showDashboard();
|
this.showDashboard();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Form
|
* User Form
|
||||||
*
|
*
|
||||||
* @param [model]
|
* @param [model]
|
||||||
*/
|
*/
|
||||||
showUserForm: function (model) {
|
showUserForm: function (model) {
|
||||||
if (Cache.User.isAdmin()) {
|
if (Cache.User.isAdmin()) {
|
||||||
require(['./main', './user/form'], function (App, View) {
|
require(['./main', './user/form'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Permissions Form
|
* User Permissions Form
|
||||||
*
|
*
|
||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
showUserPermissions: function (model) {
|
showUserPermissions: function (model) {
|
||||||
if (Cache.User.isAdmin()) {
|
if (Cache.User.isAdmin()) {
|
||||||
require(['./main', './user/permissions'], function (App, View) {
|
require(['./main', './user/permissions'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Password Form
|
* User Password Form
|
||||||
*
|
*
|
||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
showUserPasswordForm: function (model) {
|
showUserPasswordForm: function (model) {
|
||||||
if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) {
|
if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) {
|
||||||
require(['./main', './user/password'], function (App, View) {
|
require(['./main', './user/password'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Delete Confirm
|
* User Delete Confirm
|
||||||
*
|
*
|
||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
showUserDeleteConfirm: function (model) {
|
showUserDeleteConfirm: function (model) {
|
||||||
if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) {
|
if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) {
|
||||||
require(['./main', './user/delete'], function (App, View) {
|
require(['./main', './user/delete'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dashboard
|
* Dashboard
|
||||||
*/
|
*/
|
||||||
showDashboard: function () {
|
showDashboard: function () {
|
||||||
let controller = this;
|
const controller = this;
|
||||||
|
require(['./main', './dashboard/main'], (App, View) => {
|
||||||
|
controller.navigate('/');
|
||||||
|
App.UI.showAppContent(new View());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
require(['./main', './dashboard/main'], (App, View) => {
|
/**
|
||||||
controller.navigate('/');
|
* Nginx Proxy Hosts
|
||||||
App.UI.showAppContent(new View());
|
*/
|
||||||
});
|
showNginxProxy: function () {
|
||||||
},
|
if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) {
|
||||||
|
const controller = this;
|
||||||
|
|
||||||
/**
|
require(['./main', './nginx/proxy/main'], (App, View) => {
|
||||||
* Nginx Proxy Hosts
|
controller.navigate('/nginx/proxy');
|
||||||
*/
|
App.UI.showAppContent(new View());
|
||||||
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');
|
* Nginx Proxy Host Form
|
||||||
App.UI.showAppContent(new View());
|
*
|
||||||
});
|
* @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
|
* Proxy Host Delete Confirm
|
||||||
*
|
*
|
||||||
* @param [model]
|
* @param model
|
||||||
*/
|
*/
|
||||||
showNginxProxyForm: function (model) {
|
showNginxProxyDeleteConfirm: function (model) {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
|
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
|
||||||
require(['./main', './nginx/proxy/form'], function (App, View) {
|
require(['./main', './nginx/proxy/delete'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proxy Host Delete Confirm
|
* Nginx Redirection Hosts
|
||||||
*
|
*/
|
||||||
* @param model
|
showNginxRedirection: function () {
|
||||||
*/
|
if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) {
|
||||||
showNginxProxyDeleteConfirm: function (model) {
|
const controller = this;
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
|
require(['./main', './nginx/redirection/main'], (App, View) => {
|
||||||
require(['./main', './nginx/proxy/delete'], function (App, View) {
|
controller.navigate('/nginx/redirection');
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showAppContent(new View());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nginx Redirection Hosts
|
* Nginx Redirection Host Form
|
||||||
*/
|
*
|
||||||
showNginxRedirection: function () {
|
* @param [model]
|
||||||
if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) {
|
*/
|
||||||
let controller = this;
|
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');
|
* Proxy Redirection Delete Confirm
|
||||||
App.UI.showAppContent(new View());
|
*
|
||||||
});
|
* @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
|
* Nginx Stream Hosts
|
||||||
*
|
*/
|
||||||
* @param [model]
|
showNginxStream: function () {
|
||||||
*/
|
if (Cache.User.isAdmin() || Cache.User.canView('streams')) {
|
||||||
showNginxRedirectionForm: function (model) {
|
const controller = this;
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
|
require(['./main', './nginx/stream/main'], (App, View) => {
|
||||||
require(['./main', './nginx/redirection/form'], function (App, View) {
|
controller.navigate('/nginx/stream');
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showAppContent(new View());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proxy Redirection Delete Confirm
|
* Stream Form
|
||||||
*
|
*
|
||||||
* @param model
|
* @param [model]
|
||||||
*/
|
*/
|
||||||
showNginxRedirectionDeleteConfirm: function (model) {
|
showNginxStreamForm: function (model) {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
|
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
|
||||||
require(['./main', './nginx/redirection/delete'], function (App, View) {
|
require(['./main', './nginx/stream/form'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nginx Stream Hosts
|
* Stream Delete Confirm
|
||||||
*/
|
*
|
||||||
showNginxStream: function () {
|
* @param model
|
||||||
if (Cache.User.isAdmin() || Cache.User.canView('streams')) {
|
*/
|
||||||
let controller = this;
|
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');
|
* Nginx Dead Hosts
|
||||||
App.UI.showAppContent(new View());
|
*/
|
||||||
});
|
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
|
* Dead Host Form
|
||||||
*
|
*
|
||||||
* @param [model]
|
* @param [model]
|
||||||
*/
|
*/
|
||||||
showNginxStreamForm: function (model) {
|
showNginxDeadForm: function (model) {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
|
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
|
||||||
require(['./main', './nginx/stream/form'], function (App, View) {
|
require(['./main', './nginx/dead/form'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stream Delete Confirm
|
* Dead Host Delete Confirm
|
||||||
*
|
*
|
||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
showNginxStreamDeleteConfirm: function (model) {
|
showNginxDeadDeleteConfirm: function (model) {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
|
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
|
||||||
require(['./main', './nginx/stream/delete'], function (App, View) {
|
require(['./main', './nginx/dead/delete'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nginx Dead Hosts
|
* Help Dialog
|
||||||
*/
|
*
|
||||||
showNginxDead: function () {
|
* @param {String} title
|
||||||
if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) {
|
* @param {String} content
|
||||||
let controller = this;
|
*/
|
||||||
|
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');
|
* Nginx Access
|
||||||
App.UI.showAppContent(new View());
|
*/
|
||||||
});
|
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
|
* Nginx Access List Form
|
||||||
*
|
*
|
||||||
* @param [model]
|
* @param [model]
|
||||||
*/
|
*/
|
||||||
showNginxDeadForm: function (model) {
|
showNginxAccessListForm: function (model) {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
|
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
|
||||||
require(['./main', './nginx/dead/form'], function (App, View) {
|
require(['./main', './nginx/access/form'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dead Host Delete Confirm
|
* Access List Delete Confirm
|
||||||
*
|
*
|
||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
showNginxDeadDeleteConfirm: function (model) {
|
showNginxAccessListDeleteConfirm: function (model) {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
|
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
|
||||||
require(['./main', './nginx/dead/delete'], function (App, View) {
|
require(['./main', './nginx/access/delete'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Help Dialog
|
* Nginx Certificates
|
||||||
*
|
*/
|
||||||
* @param {String} title
|
showNginxCertificates: function () {
|
||||||
* @param {String} content
|
if (Cache.User.isAdmin() || Cache.User.canView('certificates')) {
|
||||||
*/
|
const controller = this;
|
||||||
showHelp: function (title, content) {
|
require(['./main', './nginx/certificates/main'], (App, View) => {
|
||||||
require(['./main', './help/main'], function (App, View) {
|
controller.navigate('/nginx/certificates');
|
||||||
App.UI.showModalDialog(new View({title: title, content: content}));
|
App.UI.showAppContent(new View());
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nginx Access
|
* Nginx Certificate Form
|
||||||
*/
|
*
|
||||||
showNginxAccess: function () {
|
* @param [model]
|
||||||
if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) {
|
*/
|
||||||
let controller = this;
|
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');
|
* Certificate Renew
|
||||||
App.UI.showAppContent(new View());
|
*
|
||||||
});
|
* @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
|
* Certificate Delete Confirm
|
||||||
*
|
*
|
||||||
* @param [model]
|
* @param model
|
||||||
*/
|
*/
|
||||||
showNginxAccessListForm: function (model) {
|
showNginxCertificateDeleteConfirm: function (model) {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
|
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
|
||||||
require(['./main', './nginx/access/form'], function (App, View) {
|
require(['./main', './nginx/certificates/delete'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Access List Delete Confirm
|
* Certificate Test Reachability
|
||||||
*
|
*
|
||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
showNginxAccessListDeleteConfirm: function (model) {
|
showNginxCertificateTestReachability: function (model) {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
|
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
|
||||||
require(['./main', './nginx/access/delete'], function (App, View) {
|
require(['./main', './nginx/certificates/test'], function (App, View) {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nginx Certificates
|
* Audit Log
|
||||||
*/
|
*/
|
||||||
showNginxCertificates: function () {
|
showAuditLog: function () {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canView('certificates')) {
|
const controller = this;
|
||||||
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
require(['./main', './nginx/certificates/main'], (App, View) => {
|
/**
|
||||||
controller.navigate('/nginx/certificates');
|
* Audit Log Metadata
|
||||||
App.UI.showAppContent(new View());
|
*
|
||||||
});
|
* @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
|
* Settings
|
||||||
*
|
*/
|
||||||
* @param [model]
|
showSettings: function () {
|
||||||
*/
|
const controller = this;
|
||||||
showNginxCertificateForm: function (model) {
|
if (Cache.User.isAdmin()) {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
|
require(['./main', './settings/main'], (App, View) => {
|
||||||
require(['./main', './nginx/certificates/form'], function (App, View) {
|
controller.navigate('/settings');
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
App.UI.showAppContent(new View());
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
},
|
this.showDashboard();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Certificate Renew
|
* Settings Item Form
|
||||||
*
|
*
|
||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
showNginxCertificateRenew: function (model) {
|
showSettingForm: function (model) {
|
||||||
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
|
if (Cache.User.isAdmin()) {
|
||||||
require(['./main', './nginx/certificates/renew'], function (App, View) {
|
if (model.get('id') === 'default-site') {
|
||||||
App.UI.showModalDialog(new View({model: model}));
|
require(['./main', './settings/default-site/main'], function (App, View) {
|
||||||
});
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
}
|
});
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Certificate Delete Confirm
|
* Logout
|
||||||
*
|
*/
|
||||||
* @param model
|
logout: function () {
|
||||||
*/
|
Tokens.dropTopToken();
|
||||||
showNginxCertificateDeleteConfirm: function (model) {
|
this.showLogin();
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@@ -6,87 +6,85 @@ const Helpers = require('../../lib/helpers');
|
|||||||
const template = require('./main.ejs');
|
const template = require('./main.ejs');
|
||||||
|
|
||||||
module.exports = Mn.View.extend({
|
module.exports = Mn.View.extend({
|
||||||
template: template,
|
template: template,
|
||||||
id: 'dashboard',
|
id: 'dashboard',
|
||||||
columns: 0,
|
columns: 0,
|
||||||
|
|
||||||
stats: {},
|
stats: {},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
links: 'a'
|
links: 'a'
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'click @ui.links': function (e) {
|
'click @ui.links': function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
Controller.navigate($(e.currentTarget).attr('href'), true);
|
Controller.navigate($(e.currentTarget).attr('href'), true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
templateContext: function () {
|
templateContext: function () {
|
||||||
let view = this;
|
const view = this;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getUserName: function () {
|
getUserName: function () {
|
||||||
return Cache.User.get('nickname') || Cache.User.get('name');
|
return Cache.User.get('nickname') || Cache.User.get('name');
|
||||||
},
|
},
|
||||||
|
|
||||||
getHostStat: function (type) {
|
getHostStat: function (type) {
|
||||||
if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') {
|
if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') {
|
||||||
return Helpers.niceNumber(view.stats.hosts[type]);
|
return Helpers.niceNumber(view.stats.hosts[type]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return '-';
|
return '-';
|
||||||
},
|
},
|
||||||
|
|
||||||
canShow: function (perm) {
|
canShow: function (perm) {
|
||||||
return Cache.User.isAdmin() || Cache.User.canView(perm);
|
return Cache.User.isAdmin() || Cache.User.canView(perm);
|
||||||
},
|
},
|
||||||
|
|
||||||
columns: view.columns
|
columns: view.columns
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
onRender: function () {
|
onRender: function () {
|
||||||
let view = this;
|
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()
|
* @param {Object} [model]
|
||||||
.then(response => {
|
*/
|
||||||
if (!view.isDestroyed()) {
|
preRender: function (model) {
|
||||||
view.stats.hosts = response;
|
this.columns = 0;
|
||||||
view.render();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
// calculate the available columns based on permissions for the objects
|
||||||
* @param {Object} [model]
|
// and store as a variable
|
||||||
*/
|
const perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts'];
|
||||||
preRender: function (model) {
|
|
||||||
this.columns = 0;
|
|
||||||
|
|
||||||
// calculate the available columns based on permissions for the objects
|
perms.map(perm => {
|
||||||
// and store as a variable
|
this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0;
|
||||||
//let view = this;
|
});
|
||||||
let perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts'];
|
|
||||||
|
|
||||||
perms.map(perm => {
|
// Prevent double rendering on initial calls
|
||||||
this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0;
|
if (typeof model !== 'undefined') {
|
||||||
});
|
this.render();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Prevent double rendering on initial calls
|
initialize: function () {
|
||||||
if (typeof model !== 'undefined') {
|
this.preRender();
|
||||||
this.render();
|
this.listenTo(Cache.User, 'change', this.preRender);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function () {
|
|
||||||
this.preRender();
|
|
||||||
this.listenTo(Cache.User, 'change', this.preRender);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
||||||
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
||||||
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -33,6 +33,13 @@
|
|||||||
<td class="<%- isExpired() ? 'text-danger' : '' %>">
|
<td class="<%- isExpired() ? 'text-danger' : '' %>">
|
||||||
<%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %>
|
<%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<% if (active_domain_names().length > 0) { %>
|
||||||
|
<span class="status-icon bg-success"></span> <%- i18n('certificates', 'in-use') %>
|
||||||
|
<% } else { %>
|
||||||
|
<span class="status-icon bg-danger"></span> <%- i18n('certificates', 'inactive') %>
|
||||||
|
<% } %>
|
||||||
|
</td>
|
||||||
<% if (canManage) { %>
|
<% if (canManage) { %>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div class="item-action dropdown">
|
<div class="item-action dropdown">
|
||||||
@@ -48,7 +55,14 @@
|
|||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
||||||
|
<% if (active_domain_names().length > 0) { %>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<span class="dropdown-header"><%- i18n('certificates', 'active-domain_names') %></span>
|
||||||
|
<% active_domain_names().forEach(function(host) { %>
|
||||||
|
<a href="https://<%- host %>" class="dropdown-item" target="_blank"><%- host %></a>
|
||||||
|
<% }); %>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<% } %>
|
<% } %>
|
@@ -44,14 +44,24 @@ module.exports = Mn.View.extend({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
templateContext: {
|
templateContext: function () {
|
||||||
canManage: App.Cache.User.canManage('certificates'),
|
return {
|
||||||
isExpired: function () {
|
canManage: App.Cache.User.canManage('certificates'),
|
||||||
return moment(this.expires_on).isBefore(moment());
|
isExpired: function () {
|
||||||
},
|
return moment(this.expires_on).isBefore(moment());
|
||||||
dns_providers: dns_providers
|
},
|
||||||
|
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 () {
|
initialize: function () {
|
||||||
this.listenTo(this.model, 'change', this.render);
|
this.listenTo(this.model, 'change', this.render);
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
<th><%- i18n('str', 'name') %></th>
|
<th><%- i18n('str', 'name') %></th>
|
||||||
<th><%- i18n('all-hosts', 'cert-provider') %></th>
|
<th><%- i18n('all-hosts', 'cert-provider') %></th>
|
||||||
<th><%- i18n('str', 'expires') %></th>
|
<th><%- i18n('str', 'expires') %></th>
|
||||||
|
<th><%- i18n('str', 'status') %></th>
|
||||||
<% if (canManage) { %>
|
<% if (canManage) { %>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@@ -74,7 +74,7 @@ module.exports = Mn.View.extend({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let query = this.ui.query.val();
|
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))
|
.then(response => this.showData(response))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.showError(err);
|
this.showError(err);
|
||||||
@@ -89,7 +89,7 @@ module.exports = Mn.View.extend({
|
|||||||
onRender: function () {
|
onRender: function () {
|
||||||
let view = this;
|
let view = this;
|
||||||
|
|
||||||
view.fetch(['owner'])
|
view.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'])
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!view.isDestroyed()) {
|
if (!view.isDestroyed()) {
|
||||||
if (response && response.length) {
|
if (response && response.length) {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
||||||
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
||||||
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
||||||
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@@ -3,48 +3,187 @@
|
|||||||
<h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5>
|
<h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5>
|
||||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body has-tabs">
|
||||||
|
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
|
||||||
<form>
|
<form>
|
||||||
<div class="row">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<div class="col-sm-12 col-md-12">
|
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
|
||||||
<div class="form-group">
|
<li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li>
|
||||||
<label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label>
|
</ul>
|
||||||
<input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" min="1" max="65535" value="<%- incoming_port %>" required>
|
<div class="tab-content">
|
||||||
|
<!-- Details -->
|
||||||
|
<div role="tabpanel" class="tab-pane active" id="details">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label>
|
||||||
|
<input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" min="1" max="65535" value="<%- incoming_port %>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8 col-md-8">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label>
|
||||||
|
<input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><%- i18n('streams', 'forwarding-port') %> <span class="form-required">*</span></label>
|
||||||
|
<input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" min="1" max="65535" value="<%- forwarding_port %>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="custom-switch">
|
||||||
|
<input type="checkbox" class="custom-switch-input" name="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>>
|
||||||
|
<span class="custom-switch-indicator"></span>
|
||||||
|
<span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="custom-switch">
|
||||||
|
<input type="checkbox" class="custom-switch-input" name="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>>
|
||||||
|
<span class="custom-switch-indicator"></span>
|
||||||
|
<span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-12">
|
||||||
|
<div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-8 col-md-8">
|
|
||||||
<div class="form-group">
|
<!-- SSL -->
|
||||||
<label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label>
|
<div role="tabpanel" class="tab-pane" id="ssl-options">
|
||||||
<input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required>
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><%- i18n('streams', 'ssl-certificate') %></label>
|
||||||
|
<select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>">
|
||||||
|
<option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option>
|
||||||
|
<option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DNS challenge -->
|
||||||
|
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label>
|
||||||
|
<input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="custom-switch">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="custom-switch-input"
|
||||||
|
name="meta[dns_challenge]"
|
||||||
|
value="1"
|
||||||
|
checked
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<span class="custom-switch-indicator"></span>
|
||||||
|
<span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||||
|
<fieldset class="form-fieldset dns-challenge">
|
||||||
|
<div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
|
||||||
|
|
||||||
|
<!-- Certbot DNS plugin selection -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
|
||||||
|
<select
|
||||||
|
name="meta[dns_provider]"
|
||||||
|
id="dns_provider"
|
||||||
|
class="form-control custom-select"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value=""
|
||||||
|
disabled
|
||||||
|
hidden
|
||||||
|
<%- getDnsProvider() === null ? 'selected' : '' %>
|
||||||
|
>Please Choose...</option>
|
||||||
|
<% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
|
||||||
|
<option
|
||||||
|
value="<%- plugin_name %>"
|
||||||
|
<%- getDnsProvider() === plugin_name ? 'selected' : '' %>
|
||||||
|
><%- plugin_info.name %></option>
|
||||||
|
<% }); %>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Certbot credentials file content -->
|
||||||
|
<div class="row credentials-file-content">
|
||||||
|
<div class="col-sm-12 col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
|
||||||
|
<textarea
|
||||||
|
name="meta[dns_provider_credentials]"
|
||||||
|
class="form-control text-monospace"
|
||||||
|
id="dns_provider_credentials"
|
||||||
|
><%- getDnsProviderCredentials() %></textarea>
|
||||||
|
<div class="text-secondary small">
|
||||||
|
<i class="fe fe-info"></i>
|
||||||
|
<%= i18n('ssl', 'credentials-file-content-info') %>
|
||||||
|
</div>
|
||||||
|
<div class="text-red small">
|
||||||
|
<i class="fe fe-alert-triangle"></i>
|
||||||
|
<%= i18n('ssl', 'stored-as-plaintext-info') %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DNS propagation delay -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-12">
|
||||||
|
<div class="form-group mb-0">
|
||||||
|
<label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
name="meta[propagation_seconds]"
|
||||||
|
class="form-control"
|
||||||
|
id="propagation_seconds"
|
||||||
|
value="<%- getPropagationSeconds() %>"
|
||||||
|
>
|
||||||
|
<div class="text-secondary small">
|
||||||
|
<i class="fe fe-info"></i>
|
||||||
|
<%= i18n('ssl', 'propagation-seconds-info') %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lets encrypt -->
|
||||||
|
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label>
|
||||||
|
<input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="custom-switch">
|
||||||
|
<input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled>
|
||||||
|
<span class="custom-switch-indicator"></span>
|
||||||
|
<span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-md-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label"><%- i18n('streams', 'forwarding-port') %> <span class="form-required">*</span></label>
|
|
||||||
<input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" min="1" max="65535" value="<%- forwarding_port %>" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="custom-switch">
|
|
||||||
<input type="checkbox" class="custom-switch-input" name="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>>
|
|
||||||
<span class="custom-switch-indicator"></span>
|
|
||||||
<span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="custom-switch">
|
|
||||||
<input type="checkbox" class="custom-switch-input" name="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>>
|
|
||||||
<span class="custom-switch-indicator"></span>
|
|
||||||
<span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,24 +1,38 @@
|
|||||||
const Mn = require('backbone.marionette');
|
const Mn = require('backbone.marionette');
|
||||||
const App = require('../../main');
|
const App = require('../../main');
|
||||||
const StreamModel = require('../../../models/stream');
|
const StreamModel = require('../../../models/stream');
|
||||||
const template = require('./form.ejs');
|
const template = require('./form.ejs');
|
||||||
|
const dns_providers = require('../../../../../global/certbot-dns-plugins');
|
||||||
|
|
||||||
require('jquery-serializejson');
|
require('jquery-serializejson');
|
||||||
require('jquery-mask-plugin');
|
require('jquery-mask-plugin');
|
||||||
require('selectize');
|
require('selectize');
|
||||||
|
const Helpers = require("../../../lib/helpers");
|
||||||
|
const certListItemTemplate = require("../certificates-list-item.ejs");
|
||||||
|
const i18n = require("../../i18n");
|
||||||
|
|
||||||
module.exports = Mn.View.extend({
|
module.exports = Mn.View.extend({
|
||||||
template: template,
|
template: template,
|
||||||
className: 'modal-dialog',
|
className: 'modal-dialog',
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
form: 'form',
|
form: 'form',
|
||||||
forwarding_host: 'input[name="forwarding_host"]',
|
forwarding_host: 'input[name="forwarding_host"]',
|
||||||
type_error: '.forward-type-error',
|
type_error: '.forward-type-error',
|
||||||
buttons: '.modal-footer button',
|
buttons: '.modal-footer button',
|
||||||
switches: '.custom-switch-input',
|
switches: '.custom-switch-input',
|
||||||
cancel: 'button.cancel',
|
cancel: 'button.cancel',
|
||||||
save: 'button.save'
|
save: 'button.save',
|
||||||
|
le_error_info: '#le-error-info',
|
||||||
|
certificate_select: 'select[name="certificate_id"]',
|
||||||
|
domain_names: 'input[name="domain_names"]',
|
||||||
|
dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
|
||||||
|
dns_challenge_content: '.dns-challenge',
|
||||||
|
dns_provider: 'select[name="meta[dns_provider]"]',
|
||||||
|
credentials_file_content: '.credentials-file-content',
|
||||||
|
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
|
||||||
|
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
|
||||||
|
letsencrypt: '.letsencrypt'
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
@@ -48,6 +62,35 @@ module.exports = Mn.View.extend({
|
|||||||
data.tcp_forwarding = !!data.tcp_forwarding;
|
data.tcp_forwarding = !!data.tcp_forwarding;
|
||||||
data.udp_forwarding = !!data.udp_forwarding;
|
data.udp_forwarding = !!data.udp_forwarding;
|
||||||
|
|
||||||
|
if (typeof data.meta === 'undefined') data.meta = {};
|
||||||
|
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
|
||||||
|
data.meta.dns_challenge = true;
|
||||||
|
|
||||||
|
if (data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
|
||||||
|
|
||||||
|
if (typeof data.domain_names === 'string' && data.domain_names) {
|
||||||
|
data.domain_names = data.domain_names.split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any domain names containing wildcards, which are not allowed with letsencrypt
|
||||||
|
if (data.certificate_id === 'new') {
|
||||||
|
let domain_err = false;
|
||||||
|
if (!data.meta.dns_challenge) {
|
||||||
|
data.domain_names.map(function (name) {
|
||||||
|
if (name.match(/\*/im)) {
|
||||||
|
domain_err = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (domain_err) {
|
||||||
|
alert(i18n('ssl', 'no-wildcard-without-dns'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.certificate_id = parseInt(data.certificate_id, 10);
|
||||||
|
}
|
||||||
|
|
||||||
let method = App.Api.Nginx.Streams.create;
|
let method = App.Api.Nginx.Streams.create;
|
||||||
let is_new = true;
|
let is_new = true;
|
||||||
|
|
||||||
@@ -70,10 +113,108 @@ module.exports = Mn.View.extend({
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
alert(err.message);
|
let more_info = '';
|
||||||
|
if (err.code === 500 && err.debug) {
|
||||||
|
try {
|
||||||
|
more_info = JSON.parse(err.debug).debug.stack.join("\n");
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>` : ''}`;
|
||||||
|
this.ui.le_error_info.show();
|
||||||
|
this.ui.le_error_info[0].scrollIntoView();
|
||||||
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
||||||
|
this.ui.save.removeClass('btn-loading');
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
'change @ui.certificate_select': function () {
|
||||||
|
let id = this.ui.certificate_select.val();
|
||||||
|
if (id === 'new') {
|
||||||
|
this.ui.letsencrypt.show().find('input').prop('disabled', false);
|
||||||
|
this.ui.domain_names.prop('required', 'required');
|
||||||
|
|
||||||
|
this.ui.dns_challenge_switch
|
||||||
|
.prop('disabled', true)
|
||||||
|
.parents('.form-group')
|
||||||
|
.css('opacity', 0.5);
|
||||||
|
|
||||||
|
this.ui.dns_provider.prop('required', 'required');
|
||||||
|
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
||||||
|
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
|
||||||
|
this.ui.dns_provider_credentials.prop('required', 'required');
|
||||||
|
}
|
||||||
|
this.ui.dns_challenge_content.show();
|
||||||
|
} else {
|
||||||
|
this.ui.letsencrypt.hide().find('input').prop('disabled', true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'change @ui.dns_provider': function () {
|
||||||
|
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
||||||
|
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
|
||||||
|
this.ui.dns_provider_credentials.prop('required', 'required');
|
||||||
|
this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
|
||||||
|
this.ui.credentials_file_content.show();
|
||||||
|
} else {
|
||||||
|
this.ui.dns_provider_credentials.prop('required', false);
|
||||||
|
this.ui.credentials_file_content.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
templateContext: {
|
||||||
|
getLetsencryptEmail: function () {
|
||||||
|
return App.Cache.User.get('email');
|
||||||
|
},
|
||||||
|
getDnsProvider: function () {
|
||||||
|
return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
|
||||||
|
},
|
||||||
|
getDnsProviderCredentials: function () {
|
||||||
|
return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
|
||||||
|
},
|
||||||
|
getPropagationSeconds: function () {
|
||||||
|
return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
|
||||||
|
},
|
||||||
|
dns_plugins: dns_providers,
|
||||||
|
},
|
||||||
|
|
||||||
|
onRender: function () {
|
||||||
|
let view = this;
|
||||||
|
|
||||||
|
// Certificates
|
||||||
|
this.ui.le_error_info.hide();
|
||||||
|
this.ui.dns_challenge_content.hide();
|
||||||
|
this.ui.credentials_file_content.hide();
|
||||||
|
this.ui.letsencrypt.hide();
|
||||||
|
this.ui.certificate_select.selectize({
|
||||||
|
valueField: 'id',
|
||||||
|
labelField: 'nice_name',
|
||||||
|
searchField: ['nice_name', 'domain_names'],
|
||||||
|
create: false,
|
||||||
|
preload: true,
|
||||||
|
allowEmptyOption: true,
|
||||||
|
render: {
|
||||||
|
option: function (item) {
|
||||||
|
item.i18n = App.i18n;
|
||||||
|
item.formatDbDate = Helpers.formatDbDate;
|
||||||
|
return certListItemTemplate(item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load: function (query, callback) {
|
||||||
|
App.Api.Nginx.Certificates.getAll()
|
||||||
|
.then(rows => {
|
||||||
|
callback(rows);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onLoad: function () {
|
||||||
|
view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id'));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
|
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
|
||||||
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -16,7 +16,10 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>
|
<div>
|
||||||
<% if (tcp_forwarding) { %>
|
<% if (certificate) { %>
|
||||||
|
<span class="tag"><%- i18n('streams', 'tcp+ssl') %></span>
|
||||||
|
<% }
|
||||||
|
else if (tcp_forwarding) { %>
|
||||||
<span class="tag"><%- i18n('streams', 'tcp') %></span>
|
<span class="tag"><%- i18n('streams', 'tcp') %></span>
|
||||||
<% }
|
<% }
|
||||||
if (udp_forwarding) { %>
|
if (udp_forwarding) { %>
|
||||||
@@ -24,6 +27,9 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('all-hosts', 'none') %></div>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<%
|
<%
|
||||||
var o = isOnline();
|
var o = isOnline();
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
<th><%- i18n('streams', 'incoming-port') %></th>
|
<th><%- i18n('streams', 'incoming-port') %></th>
|
||||||
<th><%- i18n('str', 'destination') %></th>
|
<th><%- i18n('str', 'destination') %></th>
|
||||||
<th><%- i18n('streams', 'protocol') %></th>
|
<th><%- i18n('streams', 'protocol') %></th>
|
||||||
|
<th><%- i18n('str', 'ssl') %></th>
|
||||||
<th><%- i18n('str', 'status') %></th>
|
<th><%- i18n('str', 'status') %></th>
|
||||||
<% if (canManage) { %>
|
<% if (canManage) { %>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
|
@@ -88,7 +88,7 @@ module.exports = Mn.View.extend({
|
|||||||
onRender: function () {
|
onRender: function () {
|
||||||
let view = this;
|
let view = this;
|
||||||
|
|
||||||
view.fetch(['owner'])
|
view.fetch(['owner', 'certificate'])
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!view.isDestroyed()) {
|
if (!view.isDestroyed()) {
|
||||||
if (response && response.length) {
|
if (response && response.length) {
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<form>
|
||||||
<h5 class="modal-title"><%- i18n('users', 'form-title', {id: id}) %></h5>
|
<div class="modal-header">
|
||||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
<h5 class="modal-title"><%- i18n('users', 'form-title', {id: id}) %></h5>
|
||||||
</div>
|
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||||
<div class="modal-body">
|
</div>
|
||||||
<form>
|
<div class="modal-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6 col-md-6">
|
<div class="col-sm-6 col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -49,10 +49,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
<div class="modal-footer">
|
||||||
<div class="modal-footer">
|
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
|
||||||
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
|
<button type="submit" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
|
||||||
<button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -19,7 +19,7 @@ module.exports = Mn.View.extend({
|
|||||||
|
|
||||||
events: {
|
events: {
|
||||||
|
|
||||||
'click @ui.save': function (e) {
|
'submit @ui.form': function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.ui.error.hide();
|
this.ui.error.hide();
|
||||||
let view = this;
|
let view = this;
|
||||||
|
@@ -179,7 +179,9 @@
|
|||||||
"delete-confirm": "Are you sure you want to delete this Stream?",
|
"delete-confirm": "Are you sure you want to delete this Stream?",
|
||||||
"help-title": "What is a Stream?",
|
"help-title": "What is a Stream?",
|
||||||
"help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.",
|
"help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.",
|
||||||
"search": "Search Incoming Port…"
|
"search": "Search Incoming Port…",
|
||||||
|
"ssl-certificate": "SSL Certificate for TCP Forwarding",
|
||||||
|
"tcp+ssl": "TCP+SSL"
|
||||||
},
|
},
|
||||||
"certificates": {
|
"certificates": {
|
||||||
"title": "SSL Certificates",
|
"title": "SSL Certificates",
|
||||||
@@ -206,7 +208,10 @@
|
|||||||
"reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
|
"reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"renew-title": "Renew Let's Encrypt Certificate",
|
"renew-title": "Renew Let's Encrypt Certificate",
|
||||||
"search": "Search Certificate…"
|
"search": "Search Certificate…",
|
||||||
|
"in-use" : "In use",
|
||||||
|
"inactive": "Inactive",
|
||||||
|
"active-domain_names": "Active domain names"
|
||||||
},
|
},
|
||||||
"access-lists": {
|
"access-lists": {
|
||||||
"title": "Access Lists",
|
"title": "Access Lists",
|
||||||
@@ -292,5 +297,297 @@
|
|||||||
"default-site-html": "Custom Page",
|
"default-site-html": "Custom Page",
|
||||||
"default-site-redirect": "Redirect"
|
"default-site-redirect": "Redirect"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"es": {
|
||||||
|
"str": {
|
||||||
|
"email-address": "Dirección de correo electrónico",
|
||||||
|
"username": "Nombre de usuario",
|
||||||
|
"password": "Contraseña",
|
||||||
|
"sign-in": "Registrarse",
|
||||||
|
"sign-out": "Cerrar sesión",
|
||||||
|
"try-again": "Intentar otra vez",
|
||||||
|
"name": "Nombre",
|
||||||
|
"email": "Correo electrónico",
|
||||||
|
"roles": "Roles",
|
||||||
|
"created-on": "Creado: {date}",
|
||||||
|
"save": "Guardar",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"close": "Cerrar",
|
||||||
|
"enable": "Habilitar",
|
||||||
|
"disable": "Inhabilitar",
|
||||||
|
"sure": "Si, estoy seguro",
|
||||||
|
"disabled": "Inhabilitado",
|
||||||
|
"choose-file": "Elija el archivo",
|
||||||
|
"source": "Fuente",
|
||||||
|
"destination": "Destino",
|
||||||
|
"ssl": "SSL",
|
||||||
|
"access": "Acceso",
|
||||||
|
"public": "Público",
|
||||||
|
"edit": "Editar",
|
||||||
|
"delete": "Borrar",
|
||||||
|
"logs": "Registros",
|
||||||
|
"status": "Estatus",
|
||||||
|
"online": "En línea",
|
||||||
|
"offline": "Desconectado",
|
||||||
|
"unknown": "Desconocido",
|
||||||
|
"expires": "Caduca",
|
||||||
|
"value": "Valor",
|
||||||
|
"please-wait": "Por favor, espere...",
|
||||||
|
"all": "Todo",
|
||||||
|
"any": "Ninguno"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"title": "Ingrese a su cuenta"
|
||||||
|
},
|
||||||
|
"main": {
|
||||||
|
"app": "Nginx Proxy Manager",
|
||||||
|
"version": "v{version}",
|
||||||
|
"welcome": "Bienvenidos a Nginx Proxy Manager",
|
||||||
|
"logged-in": "Has iniciado sesión como {name}",
|
||||||
|
"unknown-error": "Error al cargar cosas. Vuelva a cargar la aplicación.",
|
||||||
|
"unknown-user": "Usuario desconocido",
|
||||||
|
"sign-in-as": "Vuelve a iniciar sesión como {name}"
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"title": "Roles",
|
||||||
|
"admin": "Administrador",
|
||||||
|
"user": "Helicóptero Apache"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"dashboard": "Panel",
|
||||||
|
"hosts": "Servidores"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"fork-me": "Bifurcame en Github",
|
||||||
|
"copy": "© 2022 <a href=\"{url}\" target=\"_blank\">jc21.com</a>.",
|
||||||
|
"theme": "Tema por <a href=\"{url}\" target=\"_blank\">Tabler</a>"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Hola {name}"
|
||||||
|
},
|
||||||
|
"all-hosts": {
|
||||||
|
"empty-subtitle": "{manage, select, true{¿Por qué no creas uno?} other{Y no tienes permiso para crear uno.}}",
|
||||||
|
"details": "Detalles",
|
||||||
|
"enable-ssl": "Habilitar SSL",
|
||||||
|
"force-ssl": "Forzar SSL",
|
||||||
|
"http2-support": "Soporte HTTP/2",
|
||||||
|
"domain-names": "Nombres de Dominios",
|
||||||
|
"cert-provider": "Proveedor de certificados",
|
||||||
|
"block-exploits": "Bloquear exploits comunes",
|
||||||
|
"caching-enabled": "Caché de Activos",
|
||||||
|
"ssl-certificate": "Certificado SSL",
|
||||||
|
"none": "Ninguno",
|
||||||
|
"new-cert": "Solicitar un nuevo Certificado SSL",
|
||||||
|
"with-le": "con Let's Encrypt",
|
||||||
|
"no-ssl": "Este servidor no usará HTTPS",
|
||||||
|
"advanced": "Avanzado",
|
||||||
|
"advanced-warning": "¡Ingrese su configuración Nginx personalizada aquí bajo su propio riesgo!",
|
||||||
|
"advanced-config": "Configuración Nginx personalizada",
|
||||||
|
"advanced-config-var-headline": "Estos detalles del proxy están disponibles como variables Nginx:",
|
||||||
|
"advanced-config-header-info": "Tenga en cuenta que cualquier directiva add_header o set_header agregada aquí no será utilizada por Nginx. Deberá agregar una ubicación personalizada '/' y agregar el encabezado en la configuración personalizada allí.",
|
||||||
|
"hsts-enabled": "HSTS habilitado",
|
||||||
|
"hsts-subdomains": "Subdominios HSTS",
|
||||||
|
"locations": "Ubicaciones personalizadas"
|
||||||
|
},
|
||||||
|
"locations": {
|
||||||
|
"new_location": "Agregar ubicación",
|
||||||
|
"path": "/ruta",
|
||||||
|
"location_label": "Definir ubicación",
|
||||||
|
"delete": "Borrar"
|
||||||
|
},
|
||||||
|
"ssl": {
|
||||||
|
"letsencrypt": "Let's Encrypt",
|
||||||
|
"other": "Personalizada",
|
||||||
|
"none": "solo HTTP",
|
||||||
|
"letsencrypt-email": "Dirección de correo electrónico para Let's Encrypt",
|
||||||
|
"letsencrypt-agree": "Acepto los <a href=\"{url}\" target=\"_blank\">Términos de servicio de Let's Encrypt</a>",
|
||||||
|
"delete-ssl": "Los certificados SSL adjuntos NO se eliminarán, deberán eliminarse manualmente.",
|
||||||
|
"hosts-warning": "Estos dominios ya deben estar configurados para apuntar a esta instalación",
|
||||||
|
"no-wildcard-without-dns": "No se puede solicitar el certificado de Let's Encrypt para dominios comodín cuando no se usa el desafío de DNS",
|
||||||
|
"dns-challenge": "Utilice un desafío de DNS",
|
||||||
|
"certbot-warning": "Esta sección requiere algunos conocimientos sobre Certbot y sus complementos de DNS. Consulte la documentación de los complementos respectivos.",
|
||||||
|
"dns-provider": "Proveedor de DNS",
|
||||||
|
"please-choose": "Por favor, elige...",
|
||||||
|
"credentials-file-content": "Contenido del archivo de credenciales",
|
||||||
|
"credentials-file-content-info": "Este complemento requiere un archivo de configuración que contenga un token API u otras credenciales para su proveedor",
|
||||||
|
"stored-as-plaintext-info": "¡Estos datos se almacenarán como texto sin formato en la base de datos y en un archivo!",
|
||||||
|
"propagation-seconds": "Segundos de propagación",
|
||||||
|
"propagation-seconds-info": "Deje vacío para usar el valor predeterminado de los complementos. Número de segundos de espera para la propagación de DNS.",
|
||||||
|
"processing-info": "Procesando... Esto puede tardar unos minutos.",
|
||||||
|
"passphrase-protection-support-info": "Los archivos clave protegidos con una frase de contraseña no son compatibles."
|
||||||
|
},
|
||||||
|
"proxy-hosts": {
|
||||||
|
"title": "Servidores proxy",
|
||||||
|
"empty": "No hay Servidores proxy",
|
||||||
|
"add": "Agregar Servidor proxy",
|
||||||
|
"form-title": "{id, select, undefined{Nuevo} other{Editar}} Servidor proxy",
|
||||||
|
"forward-scheme": "Esquema",
|
||||||
|
"forward-host": "Reenviar nombre de servidor / IP",
|
||||||
|
"forward-port": "Reenviar Puerto",
|
||||||
|
"delete": "Borrar Servidor Proxy",
|
||||||
|
"delete-confirm": "¿Está seguro de que desea eliminar el Servidor Proxy para: <strong>{domains}</strong>?",
|
||||||
|
"help-title": "¿Qué es un Servidor proxy?",
|
||||||
|
"help-content": "Un servidor proxy es el punto final entrante para un servicio web que desea reenviar.\nProporciona una terminación SSL opcional para su servicio que podría no tener soporte SSL integrado.\nLos servidores proxy son el uso más común para Nginx Proxy Manager.",
|
||||||
|
"access-list": "Lista de acceso",
|
||||||
|
"allow-websocket-upgrade": "Soporte WebSockets",
|
||||||
|
"ignore-invalid-upstream-ssl": "Ignorar SSL no válido",
|
||||||
|
"custom-forward-host-help": "Agregue una ruta para el reenvío de subcarpetas.\nEjemplo: 203.0.113.25/ruta",
|
||||||
|
"search": "Buscar Servidor..."
|
||||||
|
},
|
||||||
|
"redirection-hosts": {
|
||||||
|
"title": "Servidores de redirección",
|
||||||
|
"empty": "No hay Servidores de redirección",
|
||||||
|
"add": "Agregar Servidor de redirección",
|
||||||
|
"form-title": "{id, select, undefined{Nuevo} other{Editar}} Servidor de redirección",
|
||||||
|
"forward-scheme": "Esquema",
|
||||||
|
"forward-http-status-code": "Código HTTP",
|
||||||
|
"forward-domain": "Reenviar dominio",
|
||||||
|
"preserve-path": "Conservar ruta",
|
||||||
|
"delete": "Borrar Servidor de redirección",
|
||||||
|
"delete-confirm": "¿Está seguro de que desea eliminar el servidor de redirección para: <strong>{domains}</strong>?",
|
||||||
|
"help-title": "¿Qué es un Servidor de redirección?",
|
||||||
|
"help-content": "Un servidor de redirección redirigirá las solicitudes del dominio entrante y empujará al espectador a otro dominio.\nLa razón más común para usar este tipo de servidor es cuando su sitio web cambia de dominio pero aún tiene un motor de búsqueda o enlaces de referencia que apuntan al dominio anterior.",
|
||||||
|
"search": "Buscar Servidor..."
|
||||||
|
},
|
||||||
|
"dead-hosts": {
|
||||||
|
"title": "",
|
||||||
|
"empty": "No hay Servidores 404",
|
||||||
|
"add": "Agregar Servidor 404",
|
||||||
|
"form-title": "{id, select, undefined{Nuevo} other{Editar}} Servidor 404",
|
||||||
|
"delete": "Borrar Servidor 404",
|
||||||
|
"delete-confirm": "¿Está seguro de que desea eliminar este Servidor 404?",
|
||||||
|
"help-title": "¿Qué es un Servidor 404?",
|
||||||
|
"help-content": "Un servidor 404 es simplemente una configuración de servidor que muestra una página 404.\nEsto puede ser útil cuando su dominio aparece en los motores de búsqueda y desea proporcionar una página de error más agradable o específicamente para decirles a los indexadores de búsqueda que las páginas del dominio ya no existen.\nOtro beneficio de tener este servidor es rastrear los registros de visitas y ver las referencias.",
|
||||||
|
"search": "Buscar Servidor..."
|
||||||
|
},
|
||||||
|
"streams": {
|
||||||
|
"title": "Streams",
|
||||||
|
"empty": "No hay Streams",
|
||||||
|
"add": "Agregar Stream",
|
||||||
|
"form-title": "{id, select, undefined{Nuevo} other{Editar}} Stream",
|
||||||
|
"incoming-port": "Puerto entrante",
|
||||||
|
"forwarding-host": "Servidor de reenvío",
|
||||||
|
"forwarding-port": "Puerto de reenvío",
|
||||||
|
"tcp-forwarding": "Reenvío TCP",
|
||||||
|
"udp-forwarding": "Reenvío UDP",
|
||||||
|
"forward-type-error": "Al menos un tipo de protocolo debe estar habilitado",
|
||||||
|
"protocol": "Protocol",
|
||||||
|
"tcp": "TCP",
|
||||||
|
"udp": "UDP",
|
||||||
|
"delete": "Borrar Stream",
|
||||||
|
"delete-confirm": "¿Está seguro de que desea eliminar esta Stream?",
|
||||||
|
"help-title": "¿Qué es un Stream?",
|
||||||
|
"help-content": "Una característica relativamente nueva para Nginx, un Stream servirá para reenviar el tráfico TCP/UDP directamente a otra computadora en la red.\nSi está ejecutando servidores de juegos, servidores FTP o SSH, esto puede ser útil.",
|
||||||
|
"search": "Buscar Puerto entrante..."
|
||||||
|
},
|
||||||
|
"certificates": {
|
||||||
|
"title": "Certificados SSL",
|
||||||
|
"empty": "No hay Certificados SSL",
|
||||||
|
"add": "Agregar Certificado SSL",
|
||||||
|
"form-title": "Agregar {provider, select, letsencrypt Certificado {Let's Encrypt} other{personalizado}}",
|
||||||
|
"delete": "Borrar Certificado SSL",
|
||||||
|
"delete-confirm": "¿Está seguro de que desea eliminar este certificado SSL? Cualquier servidor que lo use deberá actualizarse más tarde.",
|
||||||
|
"help-title": "Certificados SSL",
|
||||||
|
"help-content": "Los certificados SSL (correctamente conocidos como Certificados TLS) son una forma de clave de encriptación que permite que su sitio sea encriptado para el usuario final.\nNginx Proxy Manager usa un servicio llamado Let's Encrypt para emitir certificados SSL gratis.\nSi tiene algún tipo de información personal, contraseñas o datos confidenciales detrás de Nginx Proxy Manager, probablemente sea una buena idea usar un certificado.\nNginx Proxy Manager también es compatible con la autenticación DNS si no está ejecutando su sitio frente a Internet, o si solo desea un certificado comodín.",
|
||||||
|
"other-certificate": "Certificado",
|
||||||
|
"other-certificate-key": "Clave de Certificado",
|
||||||
|
"other-intermediate-certificate": "Certificado Intermedio",
|
||||||
|
"force-renew": "Renovar Ahora",
|
||||||
|
"test-reachability": "Accesibilidad del servidor de prueba",
|
||||||
|
"reachability-title": "Accesibilidad del servidor de prueba",
|
||||||
|
"reachability-info": "Pruebe si se puede acceder a los dominios desde la Internet pública utilizando Site24x7. Esto no es necesario cuando se utiliza el Desafío DNS.",
|
||||||
|
"reachability-failed-to-reach-api": "La comunicación con la API falló, ¿Nginx Proxy Manager se está ejecutando correctamente?",
|
||||||
|
"reachability-failed-to-check": "No se pudo verificar la accesibilidad debido a un error de comunicación con site24x7.com.",
|
||||||
|
"reachability-ok": "Su servidor es accesible y la creación de certificados debería ser posible.",
|
||||||
|
"reachability-404": "Hay un servidor encontrado en este dominio, pero no parece ser Nginx Proxy Manager. Asegúrese de que su dominio apunte a la IP donde se ejecuta su instancia de Nginx Proxy Manager.",
|
||||||
|
"reachability-not-resolved": "No hay servidor disponible en este dominio. Asegúrese de que su dominio exista y apunte a la IP donde se ejecuta su instancia de Nginx Proxy Manager y, si es necesario, se reenvía el puerto 80 en su enrutador.",
|
||||||
|
"reachability-wrong-data": "Se encontró un servidor en este dominio pero devolvió datos inesperados. ¿Es el servidor Nginx Proxy Manager? Asegúrese de que su dominio apunte a la IP donde se ejecuta su instancia de Nginx Proxy Manager.",
|
||||||
|
"reachability-other": "Se encontró un servidor en este dominio pero devolvió un código de estado inesperado {code}. ¿Es el servidor Nginx Proxy Manager? Asegúrese de que su dominio apunte a la IP donde se ejecuta su instancia de Nginx Proxy Manager.",
|
||||||
|
"download": "Descargar",
|
||||||
|
"renew-title": "Renovar Certificado Let's Encrypt",
|
||||||
|
"search": "Buscar Certificado..."
|
||||||
|
},
|
||||||
|
"access-lists": {
|
||||||
|
"title": "Listas de acceso",
|
||||||
|
"empty": "No hay Listas de acceso",
|
||||||
|
"add": "Agregar Listas de acceso",
|
||||||
|
"form-title": "{id, select, undefined{Nueva} other{Editar}} Lista de acceso",
|
||||||
|
"delete": "Borrar Lista de acceso",
|
||||||
|
"delete-confirm": "¿Está seguro de que desea eliminar esta lista de acceso?",
|
||||||
|
"public": "Públicamente Accesible",
|
||||||
|
"public-sub": "Sin restricciones de acceso",
|
||||||
|
"help-title": "¿Qué es una lista de acceso?",
|
||||||
|
"help-content": "Las listas de acceso proporcionan una lista negra o una lista blanca de direcciones IP de clientes específicos junto con la autenticación para los servidores proxy a través de la autenticación HTTP básica.\nPuede configurar varias reglas de cliente, nombres de usuario y contraseñas para una sola lista de acceso y luego aplicarla a un servidor proxy.\nEsto es más útil para los servicios web reenviados que no tienen mecanismos de autenticación integrados o que desea proteger del acceso de clientes desconocidos.",
|
||||||
|
"item-count": "{count} {count, select, 1{Usuario} other{Usuarios}}",
|
||||||
|
"client-count": "{count} {count, select, 1{Regla} other{Reglas}}",
|
||||||
|
"proxy-host-count": "{count} {count, select, 1{Servidor Proxy} other{Servidores Proxy}}",
|
||||||
|
"delete-has-hosts": "Esta lista de acceso está asociada con {count} Servidores Proxy. Estarán disponibles públicamente al eliminarlos.",
|
||||||
|
"details": "Detalles",
|
||||||
|
"authorization": "Autorización",
|
||||||
|
"access": "Acceso",
|
||||||
|
"satisfy": "Satisfacer",
|
||||||
|
"satisfy-any": "Satisfacer cualquiera",
|
||||||
|
"pass-auth": "Pasar autenticación al servidor",
|
||||||
|
"access-add": "Agregar",
|
||||||
|
"auth-add": "Agregar",
|
||||||
|
"search": "Buscar Acceso..."
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"title": "Usuarios",
|
||||||
|
"default_error": "Se debe cambiar la dirección de correo electrónico predeterminada",
|
||||||
|
"add": "Agregar Usuario",
|
||||||
|
"nickname": "Apodo",
|
||||||
|
"full-name": "Nombre completo",
|
||||||
|
"edit-details": "Editar Detalles",
|
||||||
|
"change-password": "Cambia contraseña",
|
||||||
|
"edit-permissions": "Editar Permisos",
|
||||||
|
"sign-in-as": "Iniciar sesión como usuario",
|
||||||
|
"form-title": "{id, select, undefined{Nuevo} other{Editar}} Usuario",
|
||||||
|
"delete": "Borrar {name, select, undefined{Usuario} other{{name}}}",
|
||||||
|
"delete-confirm": "¿Está seguro de que desea eliminar <strong>{name}</strong>?",
|
||||||
|
"password-title": "Cambia la contraseña{self, select, false{ for {name}} other{}}",
|
||||||
|
"current-password": "Contraseña actual",
|
||||||
|
"new-password": "Nueva contraseña",
|
||||||
|
"confirm-password": "Confirmar contraseña",
|
||||||
|
"permissions-title": "Permisos para {name}",
|
||||||
|
"admin-perms": "Este usuario es un administrador y algunos elementos no se pueden modificar",
|
||||||
|
"perms-visibility": "Visibilidad del elemento",
|
||||||
|
"perms-visibility-user": "Solo elementos creados",
|
||||||
|
"perms-visibility-all": "Todos los elementos",
|
||||||
|
"perm-manage": "Administrar",
|
||||||
|
"perm-view": "Sólo vista",
|
||||||
|
"perm-hidden": "Oculto",
|
||||||
|
"search": "Buscar Usuario..."
|
||||||
|
},
|
||||||
|
"audit-log": {
|
||||||
|
"title": "Registro de Auditoría",
|
||||||
|
"empty": "No hay Registros.",
|
||||||
|
"empty-subtitle": "Tan pronto como usted u otro usuario cambien algo, el historial de esos eventos aparecerá aquí.",
|
||||||
|
"proxy-host": "Servidor Proxy",
|
||||||
|
"redirection-host": "Servidor de redirección",
|
||||||
|
"dead-host": "Servidor 404",
|
||||||
|
"stream": "Stream",
|
||||||
|
"user": "Usuario",
|
||||||
|
"certificate": "Certificado",
|
||||||
|
"access-list": "Lista de acceso",
|
||||||
|
"created": "Creado {name}",
|
||||||
|
"updated": "Actualizado {name}",
|
||||||
|
"deleted": "Eliminado {name}",
|
||||||
|
"enabled": "Activado {name}",
|
||||||
|
"disabled": "Desactivado {name}",
|
||||||
|
"renewed": "Renovado {name}",
|
||||||
|
"meta-title": "Detalles del evento",
|
||||||
|
"view-meta": "Ver Detalles",
|
||||||
|
"date": "Fecha",
|
||||||
|
"search": "Buscar Registro..."
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Ajustes",
|
||||||
|
"default-site": "Sitio por defecto",
|
||||||
|
"default-site-congratulations": "Página de felicitaciones",
|
||||||
|
"default-site-404": "Página de 404",
|
||||||
|
"default-site-html": "Pagina personalizada",
|
||||||
|
"default-site-redirect": "Redirigir"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,8 +15,11 @@ const model = Backbone.Model.extend({
|
|||||||
udp_forwarding: false,
|
udp_forwarding: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
meta: {},
|
meta: {},
|
||||||
|
certificate_id: 0,
|
||||||
|
domain_names: [],
|
||||||
// The following are expansions:
|
// The following are expansions:
|
||||||
owner: null
|
owner: null,
|
||||||
|
certificate: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -2648,9 +2648,9 @@ electron-to-chromium@^1.3.47:
|
|||||||
integrity sha512-67V62Z4CFOiAtox+o+tosGfVk0QX4DJgH609tjT8QymbJZVAI/jWnAthnr8c5hnRNziIRwkc9EMQYejiVz3/9Q==
|
integrity sha512-67V62Z4CFOiAtox+o+tosGfVk0QX4DJgH609tjT8QymbJZVAI/jWnAthnr8c5hnRNziIRwkc9EMQYejiVz3/9Q==
|
||||||
|
|
||||||
elliptic@^6.5.3, elliptic@^6.5.4:
|
elliptic@^6.5.3, elliptic@^6.5.4:
|
||||||
version "6.5.7"
|
version "6.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b"
|
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.0.tgz#5919ec723286c1edf28685aa89261d4761afa210"
|
||||||
integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==
|
integrity sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==
|
||||||
dependencies:
|
dependencies:
|
||||||
bn.js "^4.11.9"
|
bn.js "^4.11.9"
|
||||||
brorand "^1.1.0"
|
brorand "^1.1.0"
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
"credentials": "dns_acmedns_api_url = http://acmedns-server/\ndns_acmedns_registration_file = /data/acme-registration.json",
|
"credentials": "dns_acmedns_api_url = http://acmedns-server/\ndns_acmedns_registration_file = /data/acme-registration.json",
|
||||||
"full_plugin_name": "dns-acmedns"
|
"full_plugin_name": "dns-acmedns"
|
||||||
},
|
},
|
||||||
"active24":{
|
"active24": {
|
||||||
"name": "Active24",
|
"name": "Active24",
|
||||||
"package_name": "certbot-dns-active24",
|
"package_name": "certbot-dns-active24",
|
||||||
"version": "~=1.5.1",
|
"version": "~=1.5.1",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"aliyun": {
|
"aliyun": {
|
||||||
"name": "Aliyun",
|
"name": "Aliyun",
|
||||||
"package_name": "certbot-dns-aliyun",
|
"package_name": "certbot-dns-aliyun",
|
||||||
"version": "~=0.38.1",
|
"version": "~=2.0.0",
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"credentials": "dns_aliyun_access_key = 12345678\ndns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef",
|
"credentials": "dns_aliyun_access_key = 12345678\ndns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef",
|
||||||
"full_plugin_name": "dns-aliyun"
|
"full_plugin_name": "dns-aliyun"
|
||||||
@@ -31,6 +31,14 @@
|
|||||||
"credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2",
|
"credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2",
|
||||||
"full_plugin_name": "dns-azure"
|
"full_plugin_name": "dns-azure"
|
||||||
},
|
},
|
||||||
|
"beget": {
|
||||||
|
"name":"Beget",
|
||||||
|
"package_name": "certbot-beget-plugin",
|
||||||
|
"version": "~=1.0.0.dev9",
|
||||||
|
"dependencies": "",
|
||||||
|
"credentials": "# Beget API credentials used by Certbot\nbeget_plugin_username = username\nbeget_plugin_password = password",
|
||||||
|
"full_plugin_name": "beget-plugin"
|
||||||
|
},
|
||||||
"bunny": {
|
"bunny": {
|
||||||
"name": "bunny.net",
|
"name": "bunny.net",
|
||||||
"package_name": "certbot-dns-bunny",
|
"package_name": "certbot-dns-bunny",
|
||||||
@@ -153,11 +161,11 @@
|
|||||||
},
|
},
|
||||||
"domainoffensive": {
|
"domainoffensive": {
|
||||||
"name": "DomainOffensive (do.de)",
|
"name": "DomainOffensive (do.de)",
|
||||||
"package_name": "certbot-dns-do",
|
"package_name": "certbot-dns-domainoffensive",
|
||||||
"version": "~=0.31.0",
|
"version": "~=2.0.0",
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"credentials": "dns_do_api_token = YOUR_DO_DE_AUTH_TOKEN",
|
"credentials": "dns_do_api_token = YOUR_DO_DE_AUTH_TOKEN",
|
||||||
"full_plugin_name": "dns-do"
|
"full_plugin_name": "dns-domainoffensive"
|
||||||
},
|
},
|
||||||
"domeneshop": {
|
"domeneshop": {
|
||||||
"name": "Domeneshop",
|
"name": "Domeneshop",
|
||||||
@@ -207,6 +215,14 @@
|
|||||||
"credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN",
|
"credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN",
|
||||||
"full_plugin_name": "dns-gandi"
|
"full_plugin_name": "dns-gandi"
|
||||||
},
|
},
|
||||||
|
"gcore": {
|
||||||
|
"name": "Gcore DNS",
|
||||||
|
"package_name": "certbot-dns-gcore",
|
||||||
|
"version": "~=0.1.8",
|
||||||
|
"dependencies": "",
|
||||||
|
"credentials": "dns_gcore_apitoken = 0123456789abcdef0123456789abcdef01234567",
|
||||||
|
"full_plugin_name": "dns-gcore"
|
||||||
|
},
|
||||||
"godaddy": {
|
"godaddy": {
|
||||||
"name": "GoDaddy",
|
"name": "GoDaddy",
|
||||||
"package_name": "certbot-dns-godaddy",
|
"package_name": "certbot-dns-godaddy",
|
||||||
@@ -247,6 +263,14 @@
|
|||||||
"credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef",
|
"credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef",
|
||||||
"full_plugin_name": "dns-hetzner"
|
"full_plugin_name": "dns-hetzner"
|
||||||
},
|
},
|
||||||
|
"hostingnl": {
|
||||||
|
"name": "Hosting.nl",
|
||||||
|
"package_name": "certbot-dns-hostingnl",
|
||||||
|
"version": "~=0.1.5",
|
||||||
|
"dependencies": "",
|
||||||
|
"credentials": "dns_hostingnl_api_key = 0123456789abcdef0123456789abcdef",
|
||||||
|
"full_plugin_name": "dns-hostingnl"
|
||||||
|
},
|
||||||
"hover": {
|
"hover": {
|
||||||
"name": "Hover",
|
"name": "Hover",
|
||||||
"package_name": "certbot-dns-hover",
|
"package_name": "certbot-dns-hover",
|
||||||
@@ -303,6 +327,14 @@
|
|||||||
"credentials": "dns_joker_username = <Dynamic DNS Authentication Username>\ndns_joker_password = <Dynamic DNS Authentication Password>\ndns_joker_domain = <Dynamic DNS Domain>",
|
"credentials": "dns_joker_username = <Dynamic DNS Authentication Username>\ndns_joker_password = <Dynamic DNS Authentication Password>\ndns_joker_domain = <Dynamic DNS Domain>",
|
||||||
"full_plugin_name": "dns-joker"
|
"full_plugin_name": "dns-joker"
|
||||||
},
|
},
|
||||||
|
"leaseweb": {
|
||||||
|
"name": "LeaseWeb",
|
||||||
|
"package_name": "certbot-dns-leaseweb",
|
||||||
|
"version": "~=1.0.1",
|
||||||
|
"dependencies": "",
|
||||||
|
"credentials": "dns_leaseweb_api_token = 01234556789",
|
||||||
|
"full_plugin_name": "dns-leaseweb"
|
||||||
|
},
|
||||||
"linode": {
|
"linode": {
|
||||||
"name": "Linode",
|
"name": "Linode",
|
||||||
"package_name": "certbot-dns-linode",
|
"package_name": "certbot-dns-linode",
|
||||||
@@ -332,7 +364,7 @@
|
|||||||
"package_name": "certbot-dns-mijn-host",
|
"package_name": "certbot-dns-mijn-host",
|
||||||
"version": "~=0.0.4",
|
"version": "~=0.0.4",
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"credentials": "dns-mijn-host-credentials = /etc/letsencrypt/mijnhost-credentials.ini",
|
"credentials": "dns_mijn_host_api_key=0123456789abcdef0123456789abcdef",
|
||||||
"full_plugin_name": "dns-mijn-host"
|
"full_plugin_name": "dns-mijn-host"
|
||||||
},
|
},
|
||||||
"namecheap": {
|
"namecheap": {
|
||||||
@@ -394,7 +426,7 @@
|
|||||||
"porkbun": {
|
"porkbun": {
|
||||||
"name": "Porkbun",
|
"name": "Porkbun",
|
||||||
"package_name": "certbot-dns-porkbun",
|
"package_name": "certbot-dns-porkbun",
|
||||||
"version": "~=0.2",
|
"version": "~=0.9",
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret",
|
"credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret",
|
||||||
"full_plugin_name": "dns-porkbun"
|
"full_plugin_name": "dns-porkbun"
|
||||||
@@ -424,13 +456,13 @@
|
|||||||
"full_plugin_name": "dns-rfc2136"
|
"full_plugin_name": "dns-rfc2136"
|
||||||
},
|
},
|
||||||
"rockenstein": {
|
"rockenstein": {
|
||||||
"name": "rockenstein AG",
|
"name": "rockenstein AG",
|
||||||
"package_name": "certbot-dns-rockenstein",
|
"package_name": "certbot-dns-rockenstein",
|
||||||
"version": "~=1.0.0",
|
"version": "~=1.0.0",
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"credentials": "dns_rockenstein_token=<token>",
|
"credentials": "dns_rockenstein_token=<token>",
|
||||||
"full_plugin_name": "dns-rockenstein"
|
"full_plugin_name": "dns-rockenstein"
|
||||||
},
|
},
|
||||||
"route53": {
|
"route53": {
|
||||||
"name": "Route 53 (Amazon)",
|
"name": "Route 53 (Amazon)",
|
||||||
"package_name": "certbot-dns-route53",
|
"package_name": "certbot-dns-route53",
|
||||||
@@ -487,7 +519,7 @@
|
|||||||
"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>",
|
"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>",
|
||||||
"full_plugin_name": "dns-websupport"
|
"full_plugin_name": "dns-websupport"
|
||||||
},
|
},
|
||||||
"wedos":{
|
"wedos": {
|
||||||
"name": "Wedos",
|
"name": "Wedos",
|
||||||
"package_name": "certbot-dns-wedos",
|
"package_name": "certbot-dns-wedos",
|
||||||
"version": "~=2.2",
|
"version": "~=2.2",
|
||||||
@@ -502,5 +534,13 @@
|
|||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf",
|
"credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf",
|
||||||
"full_plugin_name": "edgedns"
|
"full_plugin_name": "edgedns"
|
||||||
}
|
},
|
||||||
|
"zoneedit": {
|
||||||
|
"name": "ZoneEdit",
|
||||||
|
"package_name": "certbot-dns-zoneedit",
|
||||||
|
"version": "~=0.3.2",
|
||||||
|
"dependencies": "--no-deps dnspython",
|
||||||
|
"credentials": "dns_zoneedit_user = <login-user-id>\ndns_zoneedit_token = <dyn-authentication-token>",
|
||||||
|
"full_plugin_name": "dns-zoneedit"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ YELLOW='\E[1;33m'
|
|||||||
export BLUE CYAN GREEN RED RESET YELLOW
|
export BLUE CYAN GREEN RED RESET YELLOW
|
||||||
|
|
||||||
# Docker Compose
|
# Docker Compose
|
||||||
COMPOSE_PROJECT_NAME="npmdev"
|
COMPOSE_PROJECT_NAME="npm2dev"
|
||||||
COMPOSE_FILE="docker/docker-compose.dev.yml"
|
COMPOSE_FILE="docker/docker-compose.dev.yml"
|
||||||
|
|
||||||
export COMPOSE_FILE COMPOSE_PROJECT_NAME
|
export COMPOSE_FILE COMPOSE_PROJECT_NAME
|
||||||
|
@@ -67,6 +67,8 @@ printf "nameserver %s\noptions ndots:0" "${DNSROUTER_IP}" > "${LOCAL_RESOLVE}"
|
|||||||
# bring up all remaining containers, except cypress!
|
# bring up all remaining containers, except cypress!
|
||||||
docker-compose up -d --remove-orphans stepca squid
|
docker-compose up -d --remove-orphans stepca squid
|
||||||
docker-compose pull db-mysql || true # ok to fail
|
docker-compose pull db-mysql || true # ok to fail
|
||||||
|
docker-compose pull db-postgres || true # ok to fail
|
||||||
|
docker-compose pull authentik authentik-redis authentik-ldap || true # ok to fail
|
||||||
docker-compose up -d --remove-orphans --pull=never fullstack
|
docker-compose up -d --remove-orphans --pull=never fullstack
|
||||||
|
|
||||||
# wait for main container to be healthy
|
# wait for main container to be healthy
|
||||||
|
@@ -36,12 +36,11 @@ if hash docker-compose 2>/dev/null; then
|
|||||||
|
|
||||||
# bring up all remaining containers, except cypress!
|
# bring up all remaining containers, except cypress!
|
||||||
docker-compose up -d --remove-orphans stepca squid
|
docker-compose up -d --remove-orphans stepca squid
|
||||||
docker-compose pull db
|
docker-compose pull db db-postgres authentik-redis authentik authentik-worker authentik-ldap
|
||||||
docker-compose up -d --remove-orphans --pull=never fullstack
|
docker-compose build --pull --parallel fullstack
|
||||||
|
docker-compose up -d --remove-orphans fullstack
|
||||||
docker-compose up -d --remove-orphans swagger
|
docker-compose up -d --remove-orphans swagger
|
||||||
|
|
||||||
# docker-compose up -d --remove-orphans --force-recreate --build
|
|
||||||
|
|
||||||
# wait for main container to be healthy
|
# wait for main container to be healthy
|
||||||
bash "$DIR/wait-healthy" "$(docker-compose ps --all -q fullstack)" 120
|
bash "$DIR/wait-healthy" "$(docker-compose ps --all -q fullstack)" 120
|
||||||
|
|
||||||
@@ -53,10 +52,10 @@ if hash docker-compose 2>/dev/null; then
|
|||||||
|
|
||||||
if [ "$1" == "-f" ]; then
|
if [ "$1" == "-f" ]; then
|
||||||
echo -e "${BLUE}❯ ${YELLOW}Following Backend Container:${RESET}"
|
echo -e "${BLUE}❯ ${YELLOW}Following Backend Container:${RESET}"
|
||||||
docker logs -f npm_core
|
docker logs -f npm2dev.core
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Hint:${RESET} You can follow the output of some of the containers with:"
|
echo -e "${YELLOW}Hint:${RESET} You can follow the output of some of the containers with:"
|
||||||
echo " docker logs -f npm_core"
|
echo " docker logs -f npm2dev.core"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo -e "${RED}❯ docker-compose command is not available${RESET}"
|
echo -e "${RED}❯ docker-compose command is not available${RESET}"
|
||||||
|
@@ -1,11 +1,22 @@
|
|||||||
FROM cypress/included:13.9.0
|
FROM cypress/included:14.0.1
|
||||||
|
|
||||||
COPY --chown=1000 ./test /test
|
|
||||||
|
|
||||||
# Disable Cypress CLI colors
|
# Disable Cypress CLI colors
|
||||||
ENV FORCE_COLOR=0
|
ENV FORCE_COLOR=0
|
||||||
ENV NO_COLOR=1
|
ENV NO_COLOR=1
|
||||||
|
|
||||||
|
# testssl.sh and mkcert
|
||||||
|
RUN wget "https://github.com/testssl/testssl.sh/archive/refs/tags/v3.2rc4.tar.gz" -O /tmp/testssl.tgz -q \
|
||||||
|
&& tar -xzf /tmp/testssl.tgz -C /tmp \
|
||||||
|
&& mv /tmp/testssl.sh-3.2rc4 /testssl \
|
||||||
|
&& rm /tmp/testssl.tgz \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y bsdmainutils curl dnsutils \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& wget "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64" -O /bin/mkcert \
|
||||||
|
&& chmod +x /bin/mkcert
|
||||||
|
|
||||||
|
COPY --chown=1000 ./test /test
|
||||||
WORKDIR /test
|
WORKDIR /test
|
||||||
RUN yarn install && yarn cache clean
|
RUN yarn install && yarn cache clean
|
||||||
ENTRYPOINT []
|
ENTRYPOINT []
|
||||||
|
64
test/cypress/e2e/api/Ldap.cy.js
Normal file
64
test/cypress/e2e/api/Ldap.cy.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
describe('LDAP with Authentik', () => {
|
||||||
|
let token;
|
||||||
|
if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.getToken().then((tok) => {
|
||||||
|
token = tok;
|
||||||
|
|
||||||
|
// cy.task('backendApiPut', {
|
||||||
|
// token: token,
|
||||||
|
// path: '/api/settings/ldap-auth',
|
||||||
|
// data: {
|
||||||
|
// value: {
|
||||||
|
// host: 'authentik-ldap:3389',
|
||||||
|
// base_dn: 'ou=users,DC=ldap,DC=goauthentik,DC=io',
|
||||||
|
// user_dn: 'cn={{USERNAME}},ou=users,DC=ldap,DC=goauthentik,DC=io',
|
||||||
|
// email_property: 'mail',
|
||||||
|
// name_property: 'sn',
|
||||||
|
// self_filter: '(&(cn={{USERNAME}})(ak-active=TRUE))',
|
||||||
|
// auto_create_user: true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }).then((data) => {
|
||||||
|
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
|
||||||
|
// expect(data.result).to.have.property('id');
|
||||||
|
// expect(data.result.id).to.be.greaterThan(0);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// cy.task('backendApiPut', {
|
||||||
|
// token: token,
|
||||||
|
// path: '/api/settings/auth-methods',
|
||||||
|
// data: {
|
||||||
|
// value: [
|
||||||
|
// 'local',
|
||||||
|
// 'ldap'
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }).then((data) => {
|
||||||
|
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
|
||||||
|
// expect(data.result).to.have.property('id');
|
||||||
|
// expect(data.result.id).to.be.greaterThan(0);
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('Should log in with LDAP', function() {
|
||||||
|
// cy.task('backendApiPost', {
|
||||||
|
// token: token,
|
||||||
|
// path: '/api/auth',
|
||||||
|
// data: {
|
||||||
|
// // Authentik LDAP creds:
|
||||||
|
// type: 'ldap',
|
||||||
|
// identity: 'cypress',
|
||||||
|
// secret: 'fqXBfUYqHvYqiwBHWW7f'
|
||||||
|
// }
|
||||||
|
// }).then((data) => {
|
||||||
|
// cy.validateSwaggerSchema('post', 200, '/auth', data);
|
||||||
|
// expect(data.result).to.have.property('token');
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
97
test/cypress/e2e/api/OAuth.cy.js
Normal file
97
test/cypress/e2e/api/OAuth.cy.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
describe('OAuth with Authentik', () => {
|
||||||
|
let token;
|
||||||
|
if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.getToken().then((tok) => {
|
||||||
|
token = tok;
|
||||||
|
|
||||||
|
// cy.task('backendApiPut', {
|
||||||
|
// token: token,
|
||||||
|
// path: '/api/settings/oauth-auth',
|
||||||
|
// data: {
|
||||||
|
// value: {
|
||||||
|
// client_id: '7iO2AvuUp9JxiSVkCcjiIbQn4mHmUMBj7yU8EjqU',
|
||||||
|
// client_secret: 'VUMZzaGTrmXJ8PLksyqzyZ6lrtz04VvejFhPMBP9hGZNCMrn2LLBanySs4ta7XGrDr05xexPyZT1XThaf4ubg00WqvHRVvlu4Naa1aMootNmSRx3VAk6RSslUJmGyHzq',
|
||||||
|
// authorization_url: 'http://authentik:9000/application/o/authorize/',
|
||||||
|
// resource_url: 'http://authentik:9000/application/o/userinfo/',
|
||||||
|
// token_url: 'http://authentik:9000/application/o/token/',
|
||||||
|
// logout_url: 'http://authentik:9000/application/o/npm/end-session/',
|
||||||
|
// identifier: 'preferred_username',
|
||||||
|
// scopes: [],
|
||||||
|
// auto_create_user: true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }).then((data) => {
|
||||||
|
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
|
||||||
|
// expect(data.result).to.have.property('id');
|
||||||
|
// expect(data.result.id).to.be.greaterThan(0);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// cy.task('backendApiPut', {
|
||||||
|
// token: token,
|
||||||
|
// path: '/api/settings/auth-methods',
|
||||||
|
// data: {
|
||||||
|
// value: [
|
||||||
|
// 'local',
|
||||||
|
// 'oauth'
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }).then((data) => {
|
||||||
|
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
|
||||||
|
// expect(data.result).to.have.property('id');
|
||||||
|
// expect(data.result.id).to.be.greaterThan(0);
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('Should log in with OAuth', function() {
|
||||||
|
// cy.task('backendApiGet', {
|
||||||
|
// path: '/oauth/login?redirect_base=' + encodeURI(Cypress.config('baseUrl')),
|
||||||
|
// }).then((data) => {
|
||||||
|
// expect(data).to.have.property('result');
|
||||||
|
|
||||||
|
// cy.origin('http://authentik:9000', {args: data.result}, (url) => {
|
||||||
|
// cy.visit(url);
|
||||||
|
// cy.get('ak-flow-executor')
|
||||||
|
// .shadow()
|
||||||
|
// .find('ak-stage-identification')
|
||||||
|
// .shadow()
|
||||||
|
// .find('input[name="uidField"]', { visible: true })
|
||||||
|
// .type('cypress');
|
||||||
|
|
||||||
|
// cy.get('ak-flow-executor')
|
||||||
|
// .shadow()
|
||||||
|
// .find('ak-stage-identification')
|
||||||
|
// .shadow()
|
||||||
|
// .find('button[type="submit"]', { visible: true })
|
||||||
|
// .click();
|
||||||
|
|
||||||
|
// cy.get('ak-flow-executor')
|
||||||
|
// .shadow()
|
||||||
|
// .find('ak-stage-password')
|
||||||
|
// .shadow()
|
||||||
|
// .find('input[name="password"]', { visible: true })
|
||||||
|
// .type('fqXBfUYqHvYqiwBHWW7f');
|
||||||
|
|
||||||
|
// cy.get('ak-flow-executor')
|
||||||
|
// .shadow()
|
||||||
|
// .find('ak-stage-password')
|
||||||
|
// .shadow()
|
||||||
|
// .find('button[type="submit"]', { visible: true })
|
||||||
|
// .click();
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // we should be logged in
|
||||||
|
// cy.get('#root p.chakra-text')
|
||||||
|
// .first()
|
||||||
|
// .should('have.text', 'Nginx Proxy Manager');
|
||||||
|
|
||||||
|
// // logout:
|
||||||
|
// cy.clearLocalStorage();
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
213
test/cypress/e2e/api/Streams.cy.js
Normal file
213
test/cypress/e2e/api/Streams.cy.js
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
describe('Streams', () => {
|
||||||
|
let token;
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.getToken().then((tok) => {
|
||||||
|
token = tok;
|
||||||
|
// Set default site content
|
||||||
|
cy.task('backendApiPut', {
|
||||||
|
token: token,
|
||||||
|
path: '/api/settings/default-site',
|
||||||
|
data: {
|
||||||
|
value: 'html',
|
||||||
|
meta: {
|
||||||
|
html: '<p>yay it works</p>'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then((data) => {
|
||||||
|
cy.validateSwaggerSchema('put', 200, '/settings/{settingID}', data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a custom cert pair
|
||||||
|
cy.exec('mkcert -cert-file=/test/cypress/fixtures/website1.pem -key-file=/test/cypress/fixtures/website1.key.pem website1.example.com').then((result) => {
|
||||||
|
expect(result.code).to.eq(0);
|
||||||
|
// Install CA
|
||||||
|
cy.exec('mkcert -install').then((result) => {
|
||||||
|
expect(result.code).to.eq(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.exec('rm -f /test/results/testssl.json');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be able to create TCP Stream', function() {
|
||||||
|
cy.task('backendApiPost', {
|
||||||
|
token: token,
|
||||||
|
path: '/api/nginx/streams',
|
||||||
|
data: {
|
||||||
|
incoming_port: 1500,
|
||||||
|
forwarding_host: '127.0.0.1',
|
||||||
|
forwarding_port: 80,
|
||||||
|
certificate_id: 0,
|
||||||
|
meta: {
|
||||||
|
dns_provider_credentials: "",
|
||||||
|
letsencrypt_agree: false,
|
||||||
|
dns_challenge: true
|
||||||
|
},
|
||||||
|
tcp_forwarding: true,
|
||||||
|
udp_forwarding: false
|
||||||
|
}
|
||||||
|
}).then((data) => {
|
||||||
|
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
|
||||||
|
expect(data).to.have.property('id');
|
||||||
|
expect(data.id).to.be.greaterThan(0);
|
||||||
|
expect(data).to.have.property('enabled', true);
|
||||||
|
expect(data).to.have.property('tcp_forwarding', true);
|
||||||
|
expect(data).to.have.property('udp_forwarding', false);
|
||||||
|
|
||||||
|
cy.exec('curl --noproxy -- http://website1.example.com:1500').then((result) => {
|
||||||
|
expect(result.code).to.eq(0);
|
||||||
|
expect(result.stdout).to.contain('yay it works');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be able to create UDP Stream', function() {
|
||||||
|
cy.task('backendApiPost', {
|
||||||
|
token: token,
|
||||||
|
path: '/api/nginx/streams',
|
||||||
|
data: {
|
||||||
|
incoming_port: 1501,
|
||||||
|
forwarding_host: '127.0.0.1',
|
||||||
|
forwarding_port: 80,
|
||||||
|
certificate_id: 0,
|
||||||
|
meta: {
|
||||||
|
dns_provider_credentials: "",
|
||||||
|
letsencrypt_agree: false,
|
||||||
|
dns_challenge: true
|
||||||
|
},
|
||||||
|
tcp_forwarding: false,
|
||||||
|
udp_forwarding: true
|
||||||
|
}
|
||||||
|
}).then((data) => {
|
||||||
|
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
|
||||||
|
expect(data).to.have.property('id');
|
||||||
|
expect(data.id).to.be.greaterThan(0);
|
||||||
|
expect(data).to.have.property('enabled', true);
|
||||||
|
expect(data).to.have.property('tcp_forwarding', false);
|
||||||
|
expect(data).to.have.property('udp_forwarding', true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be able to create TCP/UDP Stream', function() {
|
||||||
|
cy.task('backendApiPost', {
|
||||||
|
token: token,
|
||||||
|
path: '/api/nginx/streams',
|
||||||
|
data: {
|
||||||
|
incoming_port: 1502,
|
||||||
|
forwarding_host: '127.0.0.1',
|
||||||
|
forwarding_port: 80,
|
||||||
|
certificate_id: 0,
|
||||||
|
meta: {
|
||||||
|
dns_provider_credentials: "",
|
||||||
|
letsencrypt_agree: false,
|
||||||
|
dns_challenge: true
|
||||||
|
},
|
||||||
|
tcp_forwarding: true,
|
||||||
|
udp_forwarding: true
|
||||||
|
}
|
||||||
|
}).then((data) => {
|
||||||
|
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
|
||||||
|
expect(data).to.have.property('id');
|
||||||
|
expect(data.id).to.be.greaterThan(0);
|
||||||
|
expect(data).to.have.property('enabled', true);
|
||||||
|
expect(data).to.have.property('tcp_forwarding', true);
|
||||||
|
expect(data).to.have.property('udp_forwarding', true);
|
||||||
|
|
||||||
|
cy.exec('curl --noproxy -- http://website1.example.com:1502').then((result) => {
|
||||||
|
expect(result.code).to.eq(0);
|
||||||
|
expect(result.stdout).to.contain('yay it works');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be able to create SSL TCP Stream', function() {
|
||||||
|
let certID = 0;
|
||||||
|
|
||||||
|
// Create custom cert
|
||||||
|
cy.task('backendApiPost', {
|
||||||
|
token: token,
|
||||||
|
path: '/api/nginx/certificates',
|
||||||
|
data: {
|
||||||
|
provider: "other",
|
||||||
|
nice_name: "Custom Certificate for SSL Stream",
|
||||||
|
},
|
||||||
|
}).then((data) => {
|
||||||
|
cy.validateSwaggerSchema('post', 201, '/nginx/certificates', data);
|
||||||
|
expect(data).to.have.property('id');
|
||||||
|
certID = data.id;
|
||||||
|
|
||||||
|
// Upload files
|
||||||
|
cy.task('backendApiPostFiles', {
|
||||||
|
token: token,
|
||||||
|
path: `/api/nginx/certificates/${certID}/upload`,
|
||||||
|
files: {
|
||||||
|
certificate: 'website1.pem',
|
||||||
|
certificate_key: 'website1.key.pem',
|
||||||
|
},
|
||||||
|
}).then((data) => {
|
||||||
|
cy.validateSwaggerSchema('post', 200, '/nginx/certificates/{certID}/upload', data);
|
||||||
|
expect(data).to.have.property('certificate');
|
||||||
|
expect(data).to.have.property('certificate_key');
|
||||||
|
|
||||||
|
// Create the stream
|
||||||
|
cy.task('backendApiPost', {
|
||||||
|
token: token,
|
||||||
|
path: '/api/nginx/streams',
|
||||||
|
data: {
|
||||||
|
incoming_port: 1503,
|
||||||
|
forwarding_host: '127.0.0.1',
|
||||||
|
forwarding_port: 80,
|
||||||
|
certificate_id: certID,
|
||||||
|
meta: {
|
||||||
|
dns_provider_credentials: "",
|
||||||
|
letsencrypt_agree: false,
|
||||||
|
dns_challenge: true
|
||||||
|
},
|
||||||
|
tcp_forwarding: true,
|
||||||
|
udp_forwarding: false
|
||||||
|
}
|
||||||
|
}).then((data) => {
|
||||||
|
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
|
||||||
|
expect(data).to.have.property('id');
|
||||||
|
expect(data.id).to.be.greaterThan(0);
|
||||||
|
expect(data).to.have.property("enabled", true);
|
||||||
|
expect(data).to.have.property('tcp_forwarding', true);
|
||||||
|
expect(data).to.have.property('udp_forwarding', false);
|
||||||
|
expect(data).to.have.property('certificate_id', certID);
|
||||||
|
|
||||||
|
// Check the ssl termination
|
||||||
|
cy.task('log', '[testssl.sh] Running ...');
|
||||||
|
cy.exec('/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503', {
|
||||||
|
timeout: 120000, // 2 minutes
|
||||||
|
}).then((result) => {
|
||||||
|
cy.task('log', '[testssl.sh] ' + result.stdout);
|
||||||
|
|
||||||
|
const allowedSeverities = ["INFO", "OK", "LOW", "MEDIUM"];
|
||||||
|
const ignoredIDs = [
|
||||||
|
'cert_chain_of_trust',
|
||||||
|
'cert_extlifeSpan',
|
||||||
|
'cert_revocation',
|
||||||
|
'overall_grade',
|
||||||
|
];
|
||||||
|
|
||||||
|
cy.readFile('/test/results/testssl.json').then((data) => {
|
||||||
|
// Parse each array item
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const item = data[i];
|
||||||
|
if (ignoredIDs.includes(item.id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
expect(item.severity).to.be.oneOf(allowedSeverities);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -4,18 +4,18 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jc21/cypress-swagger-validation": "^0.3.1",
|
"@jc21/cypress-swagger-validation": "^0.3.2",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.9",
|
||||||
"cypress": "^13.15.0",
|
"cypress": "^14.0.1",
|
||||||
"cypress-multi-reporters": "^1.6.4",
|
"cypress-multi-reporters": "^2.0.5",
|
||||||
"cypress-wait-until": "^3.0.2",
|
"cypress-wait-until": "^3.0.2",
|
||||||
"eslint": "^9.12.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-align-assignments": "^1.1.2",
|
"eslint-plugin-align-assignments": "^1.1.2",
|
||||||
"eslint-plugin-chai-friendly": "^1.0.1",
|
"eslint-plugin-chai-friendly": "^1.0.1",
|
||||||
"eslint-plugin-cypress": "^3.5.0",
|
"eslint-plugin-cypress": "^4.1.0",
|
||||||
"form-data": "^4.0.1",
|
"form-data": "^4.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mocha": "^10.7.3",
|
"mocha": "^11.1.0",
|
||||||
"mocha-junit-reporter": "^2.2.1"
|
"mocha-junit-reporter": "^2.2.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@@ -132,9 +132,9 @@
|
|||||||
integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==
|
integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==
|
||||||
|
|
||||||
"@eslint/plugin-kit@^0.2.0":
|
"@eslint/plugin-kit@^0.2.0":
|
||||||
version "0.2.0"
|
version "0.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz#8712dccae365d24e9eeecb7b346f85e750ba343d"
|
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz#812980a6a41ecf3a8341719f92a6d1e784a2e0e8"
|
||||||
integrity sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==
|
integrity sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==
|
||||||
dependencies:
|
dependencies:
|
||||||
levn "^0.4.1"
|
levn "^0.4.1"
|
||||||
|
|
||||||
@@ -628,9 +628,9 @@ core-util-is@1.0.2:
|
|||||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||||
|
|
||||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
||||||
version "7.0.3"
|
version "7.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key "^3.1.0"
|
path-key "^3.1.0"
|
||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
|
Reference in New Issue
Block a user