www/caddy: widgets, hide unused certs, improve error handling (#4372)

* www/caddy: CaddyCertificate widget, compare certs on disk with configured hostnames and only display relevant ones

* www/caddy: Caddy Domain widget improve error handling
This commit is contained in:
Monviech
2024-12-02 14:40:35 +01:00
committed by GitHub
parent 76bb1b96c3
commit 3913456755
4 changed files with 58 additions and 40 deletions
+2
View File
@@ -17,6 +17,8 @@ Plugin Changelog
* Add: Layer4 TLS Termination
* Add: h2c protocol to HTTP Handler
* Cleanup: Caddy Certificate widget hides unused automatic certificates
* Cleanup: Widgets error handling improved
* Cleanup: Refactor caddy_certs.php to Trust model
1.7.4
@@ -38,8 +38,8 @@ export default class CaddyCertificate extends BaseTableWidget {
}
getMarkup() {
let $container = $('<div></div>');
let $caddyCertificateTable = this.createTable('caddyCertificateTable', {
const $container = $('<div></div>');
const $caddyCertificateTable = this.createTable('caddyCertificateTable', {
headerPosition: 'none'
});
@@ -48,49 +48,54 @@ export default class CaddyCertificate extends BaseTableWidget {
}
async onWidgetTick() {
// Check if Caddy is enabled
const caddyStatus = await this.ajaxCall('/api/caddy/reverse_proxy/get');
if (!caddyStatus.caddy.general || caddyStatus.caddy.general.enabled === "0") {
const proxyData = await this.ajaxCall('/api/caddy/reverse_proxy/get');
if (!proxyData.caddy.general || proxyData.caddy.general.enabled === "0") {
this.displayError(`${this.translations.unconfigured}`);
return;
}
// Fetch the certificate details
const response = await this.ajaxCall('/api/caddy/diagnostics/certificate');
if (response.status !== "success") {
const domains = Object.values(proxyData.caddy.reverseproxy?.reverse || [])
.map(proxy => proxy.FromDomain)
.filter(Boolean);
const certificates = (await this.ajaxCall('/api/caddy/diagnostics/certificate')).content || [];
// Display certificate if hostname in config and CN of stored cert on disk match
const matchingCertificates = certificates.filter(cert => domains.includes(cert.hostname));
if (matchingCertificates.length === 0) {
this.displayError(`${this.translations.nocerts}`);
return;
}
// Process certificates if the response is successful
this.processCertificates(response.content);
this.clearError();
this.processCertificates(matchingCertificates);
}
// Utility function to display errors within the widget
displayError(message) {
const $error = $(`<div class="error-message"><a href="/ui/caddy/general">${message}</a></div>`);
$('#caddyCertificateTable').empty().append($error);
}
processCertificates(certificates) {
if (!this.dataChanged('certificates', certificates)) {
return;
}
clearError() {
$('#caddyCertificateTable .error-message').remove();
}
processCertificates(certificates) {
$('.caddy-certificate-tooltip').tooltip('hide');
let rows = certificates.map(certificate => {
let colorClass = 'text-success';
if (certificate.remaining_days === 0) {
colorClass = 'text-danger';
} else if (certificate.remaining_days < 14) {
colorClass = 'text-warning';
}
const rows = certificates.map(certificate => {
const colorClass = certificate.remaining_days === 0
? 'text-danger'
: certificate.remaining_days < 14
? 'text-warning'
: 'text-success';
let statusText = certificate.remaining_days === 0 ? this.translations.expired :
this.translations.valid;
const statusText = certificate.remaining_days === 0
? this.translations.expired
: this.translations.valid;
let row = `
const row = `
<div>
<i class="fa fa-lock ${colorClass} caddy-certificate-tooltip" style="cursor: pointer;"
data-tooltip="caddy-certificate-${certificate.hostname}" title="${statusText}">
@@ -98,19 +103,19 @@ export default class CaddyCertificate extends BaseTableWidget {
&nbsp;
<span><b>${certificate.hostname}</b></span>
<br/>
<div style="margin-top: 5px; margin-bottom: 5px;"><i>${this.translations.expires}</i> ${certificate.remaining_days} ${this.translations.days}, ${new Date(certificate.expiration_date).toLocaleString()}</div>
<div style="margin-top: 5px; margin-bottom: 5px;">
<i>${this.translations.expires}</i> ${certificate.remaining_days} ${this.translations.days},
${new Date(certificate.expiration_date).toLocaleString()}
</div>
</div>`;
return { html: row, expirationDate: new Date(certificate.expiration_date) };
});
// Sort rows by expiration date from lowest to highest
rows.sort((a, b) => a.expirationDate - b.expirationDate);
// Extract sorted HTML rows and update table
let sortedRows = rows.map(row => [row.html]);
const sortedRows = rows.map(row => [row.html]);
super.updateTable('caddyCertificateTable', sortedRows);
// Initialize tooltips for new elements
$('.caddy-certificate-tooltip').tooltip({container: 'body'});
$('.caddy-certificate-tooltip').tooltip({ container: 'body' });
}
}
@@ -37,8 +37,8 @@ export default class CaddyDomain extends BaseTableWidget {
}
getMarkup() {
let $container = $('<div></div>');
let $caddyDomainTable = this.createTable('caddyDomainTable', {
const $container = $('<div></div>');
const $caddyDomainTable = this.createTable('caddyDomainTable', {
headerPosition: 'none'
});
@@ -55,7 +55,14 @@ export default class CaddyDomain extends BaseTableWidget {
}
// Process domains if caddy is enabled
let domains = { ...data.caddy.reverseproxy.reverse, ...data.caddy.reverseproxy.subdomain };
const domains = { ...data.caddy.reverseproxy.reverse, ...data.caddy.reverseproxy.subdomain };
if (Object.keys(domains).length === 0) {
this.displayError(`${this.translations.nodomains}`);
return;
}
this.clearError();
this.processDomains(domains);
}
@@ -65,6 +72,10 @@ export default class CaddyDomain extends BaseTableWidget {
$('#caddyDomainTable').empty().append($error);
}
clearError() {
$('#caddyDomainTable .error-message').remove();
}
processDomains(domains) {
if (!this.dataChanged('domains', domains)) {
return;
@@ -72,19 +83,18 @@ export default class CaddyDomain extends BaseTableWidget {
$('.caddy-domain-tooltip').tooltip('hide');
let rows = [];
// Assuming domains is a combination of both reverse and subdomains
const rows = [];
for (const key in domains) {
const domain = domains[key];
let colorClass = domain.enabled === "1" ? 'text-success' : 'text-danger';
let tooltipText = domain.enabled === "1" ? this.translations.enabled : this.translations.disabled;
const colorClass = domain.enabled === "1" ? 'text-success' : 'text-danger';
const tooltipText = domain.enabled === "1" ? this.translations.enabled : this.translations.disabled;
let domainPort = domain.FromDomain;
if (domain.FromPort) {
domainPort += `:${domain.FromPort}`;
}
let row = $(`
const row = $(`
<div class="caddy-info">
<div class="caddy-enabled">
<i class="fa fa-globe ${colorClass} caddy-domain-tooltip" style="cursor: pointer;"
@@ -108,6 +118,6 @@ export default class CaddyDomain extends BaseTableWidget {
super.updateTable('caddyDomainTable', rows.map(row => [row.html]));
// Initialize tooltips for interactivity
$('.caddy-domain-tooltip').tooltip({container: 'body'});
$('.caddy-domain-tooltip').tooltip({ container: 'body' });
}
}
@@ -10,6 +10,7 @@
<enabled>Enabled</enabled>
<disabled>Disabled</disabled>
<unconfigured>Caddy is disabled or not configured.</unconfigured>
<nodomains>Caddy does not manage any domains.</nodomains>
</translations>
</caddydomain>
<caddycertificate>