Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f0b282b679 | |||
| 4f1fce367e | |||
| 9eb3dfa2e6 | |||
| a40da55993 | |||
| e5df0d3370 | |||
| fcd73169e0 |
+22
-2
@@ -6,7 +6,24 @@ correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [1.1.9] - 2026-07-17
|
## [1.1.12] - 2026-07-17
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- The host edit form's "Parent Wildcard" option stayed greyed out even when a valid wildcard actually existed for that host, so an already-created host could never be switched onto one from the edit modal (only brand-new hosts, via the field's `keyup` handler, ever saw it become available). The underlying `/host/lookup/:item` check also had the same self-match issue as the recently-fixed backend bug: it resolved an already-existing host to its own record instead of a sibling wildcard. Added a dedicated `/host/wildcard-parent/:item` endpoint that checks both directions, and the edit form now actually runs the check when it opens.
|
||||||
|
- Fixed an nginx startup warning: `the "listen ... http2" directive is deprecated, use the "http2" directive instead`. Migrated to the standalone `http2 on;` directive (nginx 1.25.1+).
|
||||||
|
|
||||||
|
Bumps to v1.1.12.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Moved the help (❓) link out of the global header and onto each relevant card individually (Proxy List, Add/Edit host, Add DNS Provider, Dynamic A Records, Add New User, User List, Add Permission, Permissions, Add Group) — each now deep-links straight to the doc that actually covers it, instead of one generic header icon.
|
||||||
|
|
||||||
|
Bumps to v1.1.11.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- A help icon (❓) in the top-right header now deep-links to the doc most relevant to the current page (falls back to the docs index elsewhere).
|
||||||
|
- The in-app docs viewer (`/docs`) is now searchable — a simple line-substring search over the same local doc set, no new dependency, still works with no internet access.
|
||||||
|
|
||||||
|
Bumps to v1.1.10.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- The host list now shows who created each host, and when.
|
- The host list now shows who created each host, and when.
|
||||||
@@ -71,7 +88,10 @@ First tagged release. Establishes the `vX.Y.Z` tag convention that the in-app up
|
|||||||
- Standalone backup script (`ops/backup.sh`) for deployments not using theta-env's orchestrator — snapshots Redis and `./config`, with retention.
|
- Standalone backup script (`ops/backup.sh`) for deployments not using theta-env's orchestrator — snapshots Redis and `./config`, with retention.
|
||||||
- Admin-only in-app banner that checks GitHub releases every 24h and surfaces available updates.
|
- Admin-only in-app banner that checks GitHub releases every 24h and surfaces available updates.
|
||||||
|
|
||||||
[Unreleased]: https://github.com/theta42/proxy/compare/v1.1.9...HEAD
|
[Unreleased]: https://github.com/theta42/proxy/compare/v1.1.12...HEAD
|
||||||
|
[1.1.12]: https://github.com/theta42/proxy/compare/v1.1.11...v1.1.12
|
||||||
|
[1.1.11]: https://github.com/theta42/proxy/compare/v1.1.10...v1.1.11
|
||||||
|
[1.1.10]: https://github.com/theta42/proxy/compare/v1.1.9...v1.1.10
|
||||||
[1.1.9]: https://github.com/theta42/proxy/compare/v1.1.8...v1.1.9
|
[1.1.9]: https://github.com/theta42/proxy/compare/v1.1.8...v1.1.9
|
||||||
[1.1.8]: https://github.com/theta42/proxy/compare/v1.1.7...v1.1.8
|
[1.1.8]: https://github.com/theta42/proxy/compare/v1.1.7...v1.1.8
|
||||||
[1.1.7]: https://github.com/theta42/proxy/compare/v1.1.6...v1.1.7
|
[1.1.7]: https://github.com/theta42/proxy/compare/v1.1.6...v1.1.7
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "proxy-api",
|
"name": "proxy-api",
|
||||||
"version": "1.1.9",
|
"version": "1.1.12",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "proxy-api",
|
"name": "proxy-api",
|
||||||
"version": "1.1.9",
|
"version": "1.1.12",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^7.3.0",
|
"@fortawesome/fontawesome-free": "^7.3.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "proxy-api",
|
"name": "proxy-api",
|
||||||
"version": "1.1.9",
|
"version": "1.1.12",
|
||||||
"private": true,
|
"private": true,
|
||||||
"author": [
|
"author": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,6 +60,30 @@ router.get('/', function(req, res) {
|
|||||||
res.render('docs_index', {...values, docs: docList});
|
res.render('docs_index', {...values, docs: docList});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Plain, dependency-free line-substring search over the same allowlisted
|
||||||
|
// doc set -- no separate index to build/maintain, no new dependency, and it
|
||||||
|
// keeps working with no internet access (same reasoning as the rest of this
|
||||||
|
// route). Must be registered before the /:slug catch-all below, or "search"
|
||||||
|
// would be treated as a (nonexistent) doc slug and 404.
|
||||||
|
router.get('/search', function(req, res) {
|
||||||
|
const q = (req.query.q || '').trim();
|
||||||
|
if (!q) return res.json({results: []});
|
||||||
|
const qLower = q.toLowerCase();
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
for (const [slug, doc] of Object.entries(DOCS)) {
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(doc.file, 'utf8');
|
||||||
|
const matchLine = content.split('\n').find(line => line.toLowerCase().includes(qLower));
|
||||||
|
if (matchLine) {
|
||||||
|
results.push({slug, title: doc.title, snippet: matchLine.trim().slice(0, 200)});
|
||||||
|
}
|
||||||
|
} catch (error) { /* unreadable doc file -- skip it */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({results});
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/:slug', function(req, res, next) {
|
router.get('/:slug', function(req, res, next) {
|
||||||
const doc = DOCS[req.params.slug];
|
const doc = DOCS[req.params.slug];
|
||||||
if (!doc) return next({status: 404, message: 'Doc not found'});
|
if (!doc) return next({status: 404, message: 'Doc not found'});
|
||||||
|
|||||||
@@ -128,6 +128,29 @@ router.get('/lookup/:item', authz.requireDomainRole('viewer', authz.resolve.host
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Is there a wildcard host that could serve as :item's parent (i.e. an
|
||||||
|
// already-issued cert :item could reuse instead of getting its own)? Two
|
||||||
|
// cases, covered by two different lookups: a brand-new subdomain that has
|
||||||
|
// never been created (lookUp()'s normal wildcard fallback finds it, since
|
||||||
|
// the name has no leaf of its own yet), and an ALREADY-EXISTING host or the
|
||||||
|
// wildcard's own base domain (lookUp() would just resolve to that host's
|
||||||
|
// own leaf -- lookUpWildcardParent() checks the sibling "*" slot instead;
|
||||||
|
// see its comment in models/host.js). Used by the host create/edit form to
|
||||||
|
// decide whether to offer "Parent Wildcard" as a challenge type.
|
||||||
|
router.get('/wildcard-parent/:item', authz.requireDomainRole('viewer', authz.resolve.hostParam), async function(req, res, next){
|
||||||
|
try{
|
||||||
|
let match = Model.lookUp(req.params.item);
|
||||||
|
if(!match || !match.is_wildcard){
|
||||||
|
match = Model.lookUpWildcardParent(req.params.item);
|
||||||
|
}
|
||||||
|
return res.json({
|
||||||
|
results: (match && match.is_wildcard) ? match : null,
|
||||||
|
});
|
||||||
|
}catch(error){
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// The full lookup tree exposes every host, so restrict it to admins.
|
// The full lookup tree exposes every host, so restrict it to admins.
|
||||||
router.get('/lookupobj', authz.requireAdmin, async function(req, res, next){
|
router.get('/lookupobj', authz.requireAdmin, async function(req, res, next){
|
||||||
try{
|
try{
|
||||||
|
|||||||
@@ -187,6 +187,52 @@ describe('Host wildcard base-domain lookup', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for the exact fallback combination used by
|
||||||
|
* routes/host.js's GET /wildcard-parent/:item (and, via hostMatchWildcard(),
|
||||||
|
* the host create/edit form's "Parent Wildcard" option) -- lookUp() first
|
||||||
|
* (handles a brand-new subdomain that has no leaf of its own yet), falling
|
||||||
|
* back to lookUpWildcardParent() only when lookUp() didn't resolve to a
|
||||||
|
* wildcard (handles an ALREADY-EXISTING host, which lookUp() would resolve
|
||||||
|
* to its own record). Regression coverage for the edit-form bug where the
|
||||||
|
* "Parent Wildcard" option stayed permanently greyed out for an existing
|
||||||
|
* host, because the route only ever tried lookUp().
|
||||||
|
*/
|
||||||
|
describe('Host wildcard-parent route fallback (lookUp then lookUpWildcardParent)', () => {
|
||||||
|
|
||||||
|
let Host;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
Host = createMockHostClassWithWildcardParentFix();
|
||||||
|
});
|
||||||
|
|
||||||
|
function findWildcardParent(host){
|
||||||
|
let match = Host.lookUp(host);
|
||||||
|
if(!match || !match.is_wildcard) match = Host.lookUpWildcardParent(host);
|
||||||
|
return (match && match.is_wildcard) ? match : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('finds the wildcard for a brand-new subdomain that was never created', async () => {
|
||||||
|
await populateTree(Host, ['*.cool.mysite.com']);
|
||||||
|
const result = findWildcardParent('newthing.cool.mysite.com');
|
||||||
|
assert.ok(result);
|
||||||
|
assert.strictEqual(result.host, '*.cool.mysite.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('finds the wildcard for the wildcard\'s own base domain, whether or not it is already a plain host', async () => {
|
||||||
|
await populateTree(Host, ['*.cool.mysite.com']);
|
||||||
|
assert.strictEqual(findWildcardParent('cool.mysite.com').host, '*.cool.mysite.com');
|
||||||
|
|
||||||
|
await populateTree(Host, ['*.cool.mysite.com', 'cool.mysite.com']);
|
||||||
|
assert.strictEqual(findWildcardParent('cool.mysite.com').host, '*.cool.mysite.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null when the host has no wildcard sibling at all', async () => {
|
||||||
|
await populateTree(Host, ['cool.mysite.com']);
|
||||||
|
assert.strictEqual(findWildcardParent('cool.mysite.com'), null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same mock shape as createMockHostClass() above, plus the parent-record
|
* Same mock shape as createMockHostClass() above, plus the parent-record
|
||||||
* stamp in the tree-population loop and the lookUpWildcardParent() method --
|
* stamp in the tree-population loop and the lookUpWildcardParent() method --
|
||||||
@@ -243,7 +289,11 @@ async function populateTree(Host, hosts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(fragments.length === 0){
|
if(fragments.length === 0){
|
||||||
pointer[fragment]['#record'] = {host};
|
// is_wildcard mirrors the real Host model's own field (set
|
||||||
|
// whenever a host is DNS-01 wildcard-issued, i.e. starts with
|
||||||
|
// "*."), needed by tests that check it the same way the real
|
||||||
|
// /wildcard-parent/:item route does.
|
||||||
|
pointer[fragment]['#record'] = {host, is_wildcard: host.startsWith('*.')};
|
||||||
|
|
||||||
if(fragment === '*' && !pointer['#record']){
|
if(fragment === '*' && !pointer['#record']){
|
||||||
pointer['#record'] = pointer[fragment]['#record'];
|
pointer['#record'] = pointer[fragment]['#record'];
|
||||||
|
|||||||
@@ -125,6 +125,7 @@
|
|||||||
Add DNS Provider
|
Add DNS Provider
|
||||||
</span>
|
</span>
|
||||||
<span class="float-end">
|
<span class="float-end">
|
||||||
|
<a href="/docs/installation" class="text-reset me-2" title="Help"><i class="fa-solid fa-circle-question"></i></a>
|
||||||
<i class="fa-solid fa-circle-minus"></i>
|
<i class="fa-solid fa-circle-minus"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -226,6 +227,7 @@
|
|||||||
<div class="card-header d-flex align-items-center">
|
<div class="card-header d-flex align-items-center">
|
||||||
<span class="card-icon me-2"><i class="fa-solid fa-tower-broadcast"></i></span>
|
<span class="card-icon me-2"><i class="fa-solid fa-tower-broadcast"></i></span>
|
||||||
<span class="card-title">Dynamic A Records</span>
|
<span class="card-title">Dynamic A Records</span>
|
||||||
|
<a href="/docs/installation" class="text-reset ms-2" title="Help"><i class="fa-solid fa-circle-question"></i></a>
|
||||||
<span class="ms-auto text-muted small">
|
<span class="ms-auto text-muted small">
|
||||||
This server's public IP:
|
This server's public IP:
|
||||||
<span class="badge text-bg-primary fs-6"><i class="fa-solid fa-globe me-1"></i><span id="ddns-current-ip">…</span></span>
|
<span class="badge text-bg-primary fs-6"><i class="fa-solid fa-globe me-1"></i><span id="ddns-current-ip">…</span></span>
|
||||||
|
|||||||
@@ -11,7 +11,12 @@
|
|||||||
A local copy of this project's documentation, readable from the
|
A local copy of this project's documentation, readable from the
|
||||||
running app -- no internet access required.
|
running app -- no internet access required.
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-group">
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text"><i class="fa-solid fa-magnifying-glass"></i></span>
|
||||||
|
<input type="search" id="docs-search-input" class="form-control" placeholder="Search the docs…" oninput="docsSearch(this.value)">
|
||||||
|
</div>
|
||||||
|
<div id="docs-search-results" style="display:none"></div>
|
||||||
|
<ul id="docs-list" class="list-group">
|
||||||
<% docs.forEach(function(doc){ %>
|
<% docs.forEach(function(doc){ %>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<a href="/docs/<%= doc.slug %>"><%= doc.title %></a>
|
<a href="/docs/<%= doc.slug %>"><%= doc.title %></a>
|
||||||
@@ -22,5 +27,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var docsSearchTimer;
|
||||||
|
function docsSearch(q){
|
||||||
|
clearTimeout(docsSearchTimer);
|
||||||
|
docsSearchTimer = setTimeout(function(){ docsSearchRun(q); }, 200);
|
||||||
|
}
|
||||||
|
function docsSearchRun(q){
|
||||||
|
q = (q || '').trim();
|
||||||
|
var $results = $('#docs-search-results');
|
||||||
|
var $list = $('#docs-list');
|
||||||
|
if(!q){
|
||||||
|
$results.hide().empty();
|
||||||
|
$list.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Not app.api.get() -- routes/docs.js is mounted at /docs directly,
|
||||||
|
// not under /api, unlike the rest of this app's endpoints.
|
||||||
|
$.getJSON('/docs/search', {q: q}, function(data){
|
||||||
|
$list.hide();
|
||||||
|
$results.empty().show();
|
||||||
|
var hits = (data && data.results) || [];
|
||||||
|
if(!hits.length){
|
||||||
|
$results.append($('<p class="text-muted"></p>').text('No results for "' + q + '".'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var $ul = $('<ul class="list-group"></ul>');
|
||||||
|
hits.forEach(function(hit){
|
||||||
|
var $li = $('<li class="list-group-item"></li>');
|
||||||
|
$('<a></a>').attr('href', '/docs/' + hit.slug).text(hit.title).appendTo($li);
|
||||||
|
$('<div class="text-muted small"></div>').text(hit.snippet).appendTo($li);
|
||||||
|
$ul.append($li);
|
||||||
|
});
|
||||||
|
$results.append($ul);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<%- include('bottom') %>
|
<%- include('bottom') %>
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
<div class="card-header text-center">
|
<div class="card-header text-center">
|
||||||
<span class="card-icon float-start"><i class="fa-solid fa-users-gear"></i></span>
|
<span class="card-icon float-start"><i class="fa-solid fa-users-gear"></i></span>
|
||||||
<span class="card-title">Add Group</span>
|
<span class="card-title">Add Group</span>
|
||||||
|
<a href="/docs/architecture" class="text-reset float-end" title="Help"><i class="fa-solid fa-circle-question"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-header actionMessage" style="display:none"></div>
|
<div class="card-header actionMessage" style="display:none"></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
+32
-5
@@ -212,7 +212,7 @@
|
|||||||
hostModal().show();
|
hostModal().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hostEditOpen(host){
|
async function hostEditOpen(host){
|
||||||
hostFormReset();
|
hostFormReset();
|
||||||
let h = $.scope.hosts.getByKey(host);
|
let h = $.scope.hosts.getByKey(host);
|
||||||
let $f = $('#hostForm');
|
let $f = $('#hostForm');
|
||||||
@@ -258,8 +258,31 @@
|
|||||||
let hostRenameable = !h.is_wildcard && !h.wildcard_parent && !h.is_cache;
|
let hostRenameable = !h.is_wildcard && !h.wildcard_parent && !h.is_cache;
|
||||||
$f.find('[name=host]').prop('disabled', !hostRenameable);
|
$f.find('[name=host]').prop('disabled', !hostRenameable);
|
||||||
$('#host-rename-help').toggle(!hostRenameable);
|
$('#host-rename-help').toggle(!hostRenameable);
|
||||||
|
|
||||||
|
// Reflect + enable the challenge-type options actually available for
|
||||||
|
// this host. Setting the host field's .val() above does not fire a
|
||||||
|
// 'keyup' event, so without this the "Parent Wildcard" option stayed
|
||||||
|
// permanently greyed out on edit even when a valid parent wildcard
|
||||||
|
// existed -- it only ever got un-greyed by the user re-typing the
|
||||||
|
// hostname (the keyup handler further down).
|
||||||
|
$('#challengeType-child-container, #challengeType-DNS-01-wildcard-container, #wildcard_matchAny-container')
|
||||||
|
.addClass('challengeType-container');
|
||||||
|
|
||||||
if(h.is_wildcard){
|
if(h.is_wildcard){
|
||||||
|
$('#challengeType-DNS-01-wildcard-container').removeClass('challengeType-container');
|
||||||
|
$('#challengeType-DNS-01-wildcard').prop('checked', true);
|
||||||
$('#wildcard_matchAny-container').removeClass('challengeType-container');
|
$('#wildcard_matchAny-container').removeClass('challengeType-container');
|
||||||
|
}else{
|
||||||
|
let wildcardParent = await hostMatchWildcard(h.host);
|
||||||
|
if(wildcardParent){
|
||||||
|
$('#challengeType-child-container').removeClass('challengeType-container');
|
||||||
|
$('#challengeType-child-relatedHost').text(wildcardParent.host);
|
||||||
|
}
|
||||||
|
if(h.wildcard_parent){
|
||||||
|
$('#challengeType-wildcardChild').prop('checked', true);
|
||||||
|
}else{
|
||||||
|
$('#challengeType-HTTP-01').prop('checked', true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hostModal().show();
|
hostModal().show();
|
||||||
@@ -306,10 +329,12 @@
|
|||||||
|
|
||||||
async function hostMatchWildcard(host){
|
async function hostMatchWildcard(host){
|
||||||
try{
|
try{
|
||||||
let res = await app.api.get(`host/lookup/${host}`);
|
// Not /host/lookup/ -- that resolves an ALREADY-EXISTING host to its
|
||||||
if(res.results && res.results.is_wildcard){
|
// own record, not a sibling wildcard (see the route's comment). This
|
||||||
return res.results;
|
// dedicated endpoint correctly finds a usable wildcard parent whether
|
||||||
}
|
// @host is brand new or already exists as its own host.
|
||||||
|
let res = await app.api.get(`host/wildcard-parent/${host}`);
|
||||||
|
return res.results || false;
|
||||||
}catch(error){
|
}catch(error){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -394,6 +419,7 @@
|
|||||||
<span class="card-icon me-2"><i class="fa-solid fa-network-wired"></i></span>
|
<span class="card-icon me-2"><i class="fa-solid fa-network-wired"></i></span>
|
||||||
<span class="card-title fw-bold">Proxy List</span>
|
<span class="card-title fw-bold">Proxy List</span>
|
||||||
<span class="ms-auto">
|
<span class="ms-auto">
|
||||||
|
<a href="/docs/installation" class="text-reset me-2" title="Help"><i class="fa-solid fa-circle-question"></i></a>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="hostClearCache(this)" title="Clear cached wildcard subdomain lookups">
|
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="hostClearCache(this)" title="Clear cached wildcard subdomain lookups">
|
||||||
<i class="fa-solid fa-broom"></i>
|
<i class="fa-solid fa-broom"></i>
|
||||||
Clear cache
|
Clear cache
|
||||||
@@ -527,6 +553,7 @@
|
|||||||
<div class="modal-content card border-0">
|
<div class="modal-content card border-0">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="hostModalTitle">Add host</h5>
|
<h5 class="modal-title" id="hostModalTitle">Add host</h5>
|
||||||
|
<a href="/docs/installation" class="text-reset me-2" title="Help"><i class="fa-solid fa-circle-question"></i></a>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
<i class="fa-solid fa-user-shield"></i>
|
<i class="fa-solid fa-user-shield"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="card-title">Add Permission</span>
|
<span class="card-title">Add Permission</span>
|
||||||
|
<a href="/docs/architecture" class="text-reset float-end" title="Help"><i class="fa-solid fa-circle-question"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-header actionMessage" style="display:none"></div>
|
<div class="card-header actionMessage" style="display:none"></div>
|
||||||
@@ -140,6 +141,7 @@
|
|||||||
<i class="fa-solid fa-list-check"></i>
|
<i class="fa-solid fa-list-check"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="card-title">Permissions</span>
|
<span class="card-title">Permissions</span>
|
||||||
|
<a href="/docs/architecture" class="text-reset float-end" title="Help"><i class="fa-solid fa-circle-question"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-header actionMessage" style="display:none"></div>
|
<div class="card-header actionMessage" style="display:none"></div>
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
Add New User
|
Add New User
|
||||||
</span>
|
</span>
|
||||||
<span class="float-end">
|
<span class="float-end">
|
||||||
|
<a href="/docs/architecture" class="text-reset me-2" title="Help"><i class="fa-solid fa-circle-question"></i></a>
|
||||||
<i class="fa-solid fa-circle-minus"></i>
|
<i class="fa-solid fa-circle-minus"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,6 +108,7 @@
|
|||||||
User List
|
User List
|
||||||
</span>
|
</span>
|
||||||
<span class="float-end">
|
<span class="float-end">
|
||||||
|
<a href="/docs/architecture" class="text-reset me-2" title="Help"><i class="fa-solid fa-circle-question"></i></a>
|
||||||
<i class="fa-solid fa-circle-minus"></i>
|
<i class="fa-solid fa-circle-minus"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
listen 443 ssl http2;
|
listen 443 ssl;
|
||||||
listen 4443 ssl;
|
listen 4443 ssl;
|
||||||
|
# The "http2" listen parameter is deprecated since nginx 1.25.1 in favor of
|
||||||
|
# this standalone directive, which applies to every "listen ... ssl" in the
|
||||||
|
# server block (both 443 and 4443 here).
|
||||||
|
http2 on;
|
||||||
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
|
|||||||
Reference in New Issue
Block a user