From 5d8aeb8b41b2e568a89ce5c358888b02ae9020b5 Mon Sep 17 00:00:00 2001
From: Giacomo Sanchietti
Date: Wed, 4 Feb 2026 16:45:42 +0100
Subject: [PATCH] feat(ovpntunnel): show all cert expirations
Display the expiration of all involved certificates:
- client
- server
- CA
---
.../openvpn_tunnel/TunnelInfoModal.vue | 120 +++++++++++++-----
.../openvpn_tunnel/TunnelManager.vue | 11 +-
.../standalone/openvpn_tunnel/TunnelTable.vue | 69 +++++-----
src/i18n/en.json | 15 +--
src/i18n/it.json | 15 +--
5 files changed, 142 insertions(+), 88 deletions(-)
diff --git a/src/components/standalone/openvpn_tunnel/TunnelInfoModal.vue b/src/components/standalone/openvpn_tunnel/TunnelInfoModal.vue
index 5942ed2be..ff0bc8abe 100644
--- a/src/components/standalone/openvpn_tunnel/TunnelInfoModal.vue
+++ b/src/components/standalone/openvpn_tunnel/TunnelInfoModal.vue
@@ -6,7 +6,7 @@
import { NeModal } from '@nethesis/vue-components'
import { useI18n } from 'vue-i18n'
import type { ServerTunnel, ClientTunnel } from './TunnelManager.vue'
-import { watch, ref, computed } from 'vue'
+import { watch, ref } from 'vue'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { getCertificateStatus } from './TunnelTable.vue'
@@ -33,14 +33,10 @@ function isClientTunnel(item: ServerTunnel | ClientTunnel): item is ClientTunnel
return 'remote_host' in item
}
-const certificateStatus = computed(() => {
- if (!_itemToShow.value?.cert_expiry_ts) return { show: false }
- return getCertificateStatus(
- _itemToShow.value.cert_expiry_ts,
- isClientTunnel(_itemToShow.value),
- true
- )
-})
+function getCertStatus(expirationTimestamp: number) {
+ if (!_itemToShow.value?.certificates) return { show: false }
+ return getCertificateStatus({ cert: expirationTimestamp })
+}
@@ -224,33 +220,91 @@ const certificateStatus = computed(() => {
-
-
- {{
- isClientTunnel(_itemToShow!)
- ? t('standalone.openvpn_tunnel.client_cert_expiry')
- : t('standalone.openvpn_tunnel.cert_expiry')
- }}
-
-
-
- {{
- _itemToShow.cert_expiry_ts
- ? new Date(_itemToShow.cert_expiry_ts * 1000).toLocaleString(locale)
- : ''
- }}
+
+
+
+ {{ t('standalone.openvpn_tunnel.server_cert_expiration') }}
+
+
+
+ {{ new Date(_itemToShow.certificates.server * 1000).toLocaleString(locale) }}
+
+
+
+
+ {{
+ t(
+ getCertStatus(_itemToShow.certificates.server).messageKey!,
+ getCertStatus(_itemToShow.certificates.server).messageParams!
+ )
+ }}
+
+
+
+
+
+
+ {{ t('standalone.openvpn_tunnel.client_cert_expiration') }}
-
-
+
- {{ t(certificateStatus.messageKey!, certificateStatus.messageParams!) }}
+ {{ new Date(_itemToShow.certificates.client * 1000).toLocaleString(locale) }}
-
-
+
+
+
+ {{
+ t(
+ getCertStatus(_itemToShow.certificates.client).messageKey!,
+ getCertStatus(_itemToShow.certificates.client).messageParams!
+ )
+ }}
+
+
+
+
+
+
+ {{ t('standalone.openvpn_tunnel.ca_cert_expiration') }}
+
+
+
+ {{ new Date(_itemToShow.certificates.CA * 1000).toLocaleString(locale) }}
+
+
+
+
+ {{
+ t(
+ getCertStatus(_itemToShow.certificates.CA).messageKey!,
+ getCertStatus(_itemToShow.certificates.CA).messageParams!
+ )
+ }}
+
+
+
+
diff --git a/src/components/standalone/openvpn_tunnel/TunnelManager.vue b/src/components/standalone/openvpn_tunnel/TunnelManager.vue
index 24400badc..6a2bcc4fb 100644
--- a/src/components/standalone/openvpn_tunnel/TunnelManager.vue
+++ b/src/components/standalone/openvpn_tunnel/TunnelManager.vue
@@ -27,7 +27,11 @@ import { getProductName } from '@/lib/config'
export type ServerTunnel = {
/* always available */
- cert_expiry_ts: number
+ certificates: {
+ server: number
+ client: number
+ CA: number
+ }
connected: boolean
enabled: boolean
id: string
@@ -47,7 +51,10 @@ export type ServerTunnel = {
export type ClientTunnel = {
/* always available */
- cert_expiry_ts: number
+ certificates: {
+ client: number
+ CA: number
+ }
connected: boolean
enabled: boolean
id: string
diff --git a/src/components/standalone/openvpn_tunnel/TunnelTable.vue b/src/components/standalone/openvpn_tunnel/TunnelTable.vue
index 79fc67026..68847ea44 100644
--- a/src/components/standalone/openvpn_tunnel/TunnelTable.vue
+++ b/src/components/standalone/openvpn_tunnel/TunnelTable.vue
@@ -28,40 +28,49 @@ export function isCertificatesExpired(expiryTimestamp: number): boolean {
return expiryTimestamp <= Date.now() / 1000
}
-export function getCertificateStatus(
- expiryTimestamp: number,
- isClientTunnel: boolean = false,
- tunnelDetailModal: boolean = false
-): CertificateStatusResult {
- if (isCertificatesExpired(expiryTimestamp)) {
+export function getCertificateStatus(certificates: {
+ [key: string]: number
+}): CertificateStatusResult {
+ // Determine which certificate to check (client for client tunnels, server for server tunnels)
+
+ // Check if any certificate is expired
+ let isExpired = false
+ for (const cert of Object.values(certificates)) {
+ if (isCertificatesExpired(cert)) {
+ isExpired = true
+ break
+ }
+ }
+
+ if (isExpired) {
return {
show: true,
icon: faCircleExclamation,
colorClass: 'text-red-700 dark:text-red-500',
- messageKey: tunnelDetailModal
- ? isClientTunnel
- ? 'standalone.openvpn_tunnel.client_cert_expired_complete_message'
- : 'standalone.openvpn_tunnel.cert_expired_complete_message'
- : isClientTunnel
- ? 'standalone.openvpn_tunnel.client_cert_expired_message'
- : 'standalone.openvpn_tunnel.cert_expired_message'
+ messageKey: 'standalone.openvpn_tunnel.cert_expired_message'
+ }
+ }
+
+ let isExpiring = false
+ for (const cert of Object.values(certificates)) {
+ if (shouldShowCertExpiryBadge(cert)) {
+ isExpiring = true
+ break
}
}
- if (shouldShowCertExpiryBadge(expiryTimestamp)) {
+ if (isExpiring) {
+ // Get the minimum days until expiry among all certificates
+ const daysUntilExpiry = Math.min(
+ ...Object.values(certificates).map((cert) => getDaysUntilExpiry(cert))
+ )
return {
show: true,
icon: faTriangleExclamation,
colorClass: 'text-amber-700 dark:text-amber-500',
- messageKey: tunnelDetailModal
- ? isClientTunnel
- ? 'standalone.openvpn_tunnel.client_cert_expiring_complete_message'
- : 'standalone.openvpn_tunnel.cert_expiring_complete_message'
- : isClientTunnel
- ? 'standalone.openvpn_tunnel.client_cert_expiring_message'
- : 'standalone.openvpn_tunnel.cert_expiring_message',
+ messageKey: 'standalone.openvpn_tunnel.cert_expiring_message',
messageParams: {
- days: getDaysUntilExpiry(expiryTimestamp)
+ days: daysUntilExpiry
}
}
}
@@ -219,19 +228,13 @@ function checkIsClientTunnel(item: ServerTunnel | ClientTunnel): item is ClientT
{{ item.ns_name }}
@@ -239,8 +242,8 @@ function checkIsClientTunnel(item: ServerTunnel | ClientTunnel): item is ClientT
{{
t(
- getCertificateStatus(item.cert_expiry_ts, checkIsClientTunnel(item))
- .messageKey!
+ getCertificateStatus(item.certificates).messageKey!,
+ getCertificateStatus(item.certificates).messageParams!
)
}}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 3d66c6f2a..6eea54fd6 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -2086,18 +2086,13 @@
"regenerate_cert_button": "Regenerate",
"tunnel_name_max_length": "Maximum 10 characters allowed",
"tunnel_details": "Tunnel details",
- "cert_expiring_message": "Certificates expiring soon",
- "client_cert_expiring_message": "Certificate expiring soon",
- "cert_expiring_complete_message": "The certificates will expire in {days} days",
- "client_cert_expiring_complete_message": "The client certificate will expire in {days} days",
- "cert_expired_message": "Certificates are expired",
- "client_cert_expired_message": "Certificate is expired",
- "cert_expired_complete_message": "The certificates are expired",
- "client_cert_expired_complete_message": "The client certificate is expired",
+ "cert_expiring_message": "The certificate will expire in {days} days",
+ "cert_expired_message": "The certificate is expired",
"bytes_received": "Bytes received",
"bytes_sent": "Bytes sent",
- "client_cert_expiry": "Client certificate expire",
- "cert_expiry": "Certificates expire",
+ "server_cert_expiration": "Server certificate expiration",
+ "client_cert_expiration": "Client certificate expiration",
+ "ca_cert_expiration": "CA certificate expiration",
"tunnel_id": "Tunnel ID",
"real_address": "Real address",
"since": "Connection start",
diff --git a/src/i18n/it.json b/src/i18n/it.json
index ecf97f19b..c729e2cf3 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -1477,18 +1477,13 @@
"regenerate_cert_button": "Rigenera",
"tunnel_name_max_length": "Massimo 10 caratteri",
"tunnel_details": "Dettagli tunnel",
- "cert_expiring_message": "Certificati in scadenza",
- "client_cert_expiring_message": "Certificato in scadenza",
- "cert_expiring_complete_message": "I certificati scadranno tra {days} giorni",
- "client_cert_expiring_complete_message": "Il certificato del client scadrà tra {days} giorni",
- "cert_expired_message": "Certificati scaduti",
- "client_cert_expired_message": "Certificato scaduto",
- "cert_expired_complete_message": "I certificati sono scaduti",
- "client_cert_expired_complete_message": "Il certificato del client è scaduto",
+ "cert_expiring_message": "Il certificato scadrà tra {days} giorni",
+ "cert_expired_message": "Il certificato è scaduto",
"bytes_received": "Bytes ricevuti",
"bytes_sent": "Bytes inviati",
- "client_cert_expiry": "Scadenza certificato client",
- "cert_expiry": "Scadenza certificati",
+ "server_cert_expiration": "Scadenza certificato server",
+ "client_cert_expiration": "Scadenza certificato client",
+ "ca_cert_expiration": "Scadenza certificato CA",
"tunnel_id": "ID Tunnel",
"real_address": "Indirizzo reale",
"since": "Inizio connessione",