feature: ssl status in list websites

This commit is contained in:
usmannasir
2025-08-22 21:00:03 +05:00
parent f2f7bb12b2
commit f2352bd516
4 changed files with 264 additions and 4 deletions

4
.idea/workspace.xml generated
View File

@@ -4,9 +4,7 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="5251c5c9-f2a1-41f2-bc76-10b517091df1" name="Changes" comment=""> <list default="true" id="5251c5c9-f2a1-41f2-bc76-10b517091df1" name="Changes" comment="" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />

View File

@@ -2768,6 +2768,38 @@ app.controller('listWebsites', function ($scope, $http, $window) {
return site.version !== undefined; return site.version !== undefined;
}; };
// Function to get SSL tooltip text
$scope.getSslTooltip = function(web) {
if (!web.ssl) return '';
var tooltip = '';
if (web.ssl.issuer && web.ssl.issuer !== '') {
tooltip += 'Issuer: ' + web.ssl.issuer;
}
if (web.ssl.days !== undefined) {
if (tooltip) tooltip += ' | ';
if (web.ssl.days < 0) {
tooltip += 'Expired ' + Math.abs(web.ssl.days) + ' days ago';
} else {
tooltip += 'Valid for ' + web.ssl.days + ' days';
}
}
if (web.ssl.is_wildcard) {
if (tooltip) tooltip += ' | ';
tooltip += 'Wildcard Certificate';
}
if (web.ssl.status === 'none') {
tooltip = 'No SSL certificate installed. Click "Issue SSL" to secure this site.';
} else if (web.ssl.status === 'self-signed') {
tooltip = 'Self-signed certificate detected. Not trusted by browsers.';
}
return tooltip;
};
// Initial fetch of websites // Initial fetch of websites
$scope.getFurtherWebsitesFromDB = function () { $scope.getFurtherWebsitesFromDB = function () {
$scope.loading = true; // Set loading to true when starting fetch $scope.loading = true; // Set loading to true when starting fetch
@@ -6057,6 +6089,38 @@ app.controller('listWebsites', function ($scope, $http, $window) {
return site.version !== undefined; return site.version !== undefined;
}; };
// Function to get SSL tooltip text
$scope.getSslTooltip = function(web) {
if (!web.ssl) return '';
var tooltip = '';
if (web.ssl.issuer && web.ssl.issuer !== '') {
tooltip += 'Issuer: ' + web.ssl.issuer;
}
if (web.ssl.days !== undefined) {
if (tooltip) tooltip += ' | ';
if (web.ssl.days < 0) {
tooltip += 'Expired ' + Math.abs(web.ssl.days) + ' days ago';
} else {
tooltip += 'Valid for ' + web.ssl.days + ' days';
}
}
if (web.ssl.is_wildcard) {
if (tooltip) tooltip += ' | ';
tooltip += 'Wildcard Certificate';
}
if (web.ssl.status === 'none') {
tooltip = 'No SSL certificate installed. Click "Issue SSL" to secure this site.';
} else if (web.ssl.status === 'self-signed') {
tooltip = 'Self-signed certificate detected. Not trusted by browsers.';
}
return tooltip;
};
// Initial fetch of websites // Initial fetch of websites
$scope.getFurtherWebsitesFromDB = function () { $scope.getFurtherWebsitesFromDB = function () {
$scope.loading = true; // Set loading to true when starting fetch $scope.loading = true; // Set loading to true when starting fetch
@@ -9690,6 +9754,38 @@ app.controller('listWebsites', function ($scope, $http, $window) {
return site.version !== undefined; return site.version !== undefined;
}; };
// Function to get SSL tooltip text
$scope.getSslTooltip = function(web) {
if (!web.ssl) return '';
var tooltip = '';
if (web.ssl.issuer && web.ssl.issuer !== '') {
tooltip += 'Issuer: ' + web.ssl.issuer;
}
if (web.ssl.days !== undefined) {
if (tooltip) tooltip += ' | ';
if (web.ssl.days < 0) {
tooltip += 'Expired ' + Math.abs(web.ssl.days) + ' days ago';
} else {
tooltip += 'Valid for ' + web.ssl.days + ' days';
}
}
if (web.ssl.is_wildcard) {
if (tooltip) tooltip += ' | ';
tooltip += 'Wildcard Certificate';
}
if (web.ssl.status === 'none') {
tooltip = 'No SSL certificate installed. Click "Issue SSL" to secure this site.';
} else if (web.ssl.status === 'self-signed') {
tooltip = 'Self-signed certificate detected. Not trusted by browsers.';
}
return tooltip;
};
// Initial fetch of websites // Initial fetch of websites
$scope.getFurtherWebsitesFromDB = function () { $scope.getFurtherWebsitesFromDB = function () {
$scope.loading = true; // Set loading to true when starting fetch $scope.loading = true; // Set loading to true when starting fetch

View File

@@ -240,6 +240,53 @@
white-space: nowrap; white-space: nowrap;
} }
/* SSL Status Badge */
.ssl-badge {
display: inline-flex;
align-items: center;
padding: 4px 10px;
font-size: 11px;
font-weight: 600;
border-radius: 20px;
gap: 5px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.ssl-badge.valid {
background: #f0fdf4;
color: #10b981;
}
.ssl-badge.warning {
background: #fef3c7;
color: #f59e0b;
}
.ssl-badge.expiring,
.ssl-badge.expired {
background: #fee2e2;
color: #ef4444;
}
.ssl-badge.self-signed {
background: #fef3c7;
color: #f59e0b;
}
.ssl-badge.none {
background: #f3f4f6;
color: #9ca3af;
}
.ssl-badge .wildcard-indicator {
background: rgba(255, 255, 255, 0.3);
padding: 1px 4px;
border-radius: 8px;
font-size: 10px;
margin-left: 2px;
}
.loading-indicator { .loading-indicator {
color: #5b5fcf; color: #5b5fcf;
margin-left: 8px; margin-left: 8px;
@@ -667,6 +714,25 @@
<span ng-if="web.loading" class="loading-indicator"> <span ng-if="web.loading" class="loading-indicator">
<i class="fa fa-spinner fa-spin"></i> <i class="fa fa-spinner fa-spin"></i>
</span> </span>
<!-- SSL Status Badge -->
<span ng-if="web.ssl" class="ssl-badge" ng-class="web.ssl.status"
data-toggle="tooltip"
data-placement="top"
title="{$ getSslTooltip(web) $}">
<i class="fas" ng-class="{
'fa-lock': web.ssl.status === 'valid',
'fa-exclamation-triangle': web.ssl.status === 'warning' || web.ssl.status === 'self-signed',
'fa-exclamation-circle': web.ssl.status === 'expiring' || web.ssl.status === 'expired',
'fa-unlock': web.ssl.status === 'none'
}"></i>
<span ng-if="web.ssl.status === 'valid'">Secure</span>
<span ng-if="web.ssl.status === 'warning'">SSL {$ web.ssl.days $}d</span>
<span ng-if="web.ssl.status === 'expiring'">Expiring {$ web.ssl.days $}d</span>
<span ng-if="web.ssl.status === 'expired'">Expired</span>
<span ng-if="web.ssl.status === 'self-signed'">Self-Signed</span>
<span ng-if="web.ssl.status === 'none'">No SSL</span>
<span ng-if="web.ssl.is_wildcard" class="wildcard-indicator" title="Wildcard SSL Certificate">*</span>
</span>
</div> </div>
<div class="row-actions"> <div class="row-actions">
<a href="/websites/{$ web.domain $}" class="btn btn-primary btn-sm" title="{% trans 'Manage' %}"> <a href="/websites/{$ web.domain $}" class="btn btn-primary btn-sm" title="{% trans 'Manage' %}">

View File

@@ -2534,6 +2534,9 @@ Require valid-user
# Convert numeric state to text # Convert numeric state to text
state = "Active" if website.state == 1 else "Suspended" state = "Active" if website.state == 1 else "Suspended"
# Get SSL status
ssl_status = self.getSSLStatus(website.domain)
json_data.append({ json_data.append({
'domain': website.domain, 'domain': website.domain,
'adminEmail': website.adminEmail, 'adminEmail': website.adminEmail,
@@ -2543,10 +2546,107 @@ Require valid-user
'package': website.package.packageName, 'package': website.package.packageName,
'admin': website.admin.userName, 'admin': website.admin.userName,
'wp_sites': wp_sites, 'wp_sites': wp_sites,
'diskUsed': diskUsed 'diskUsed': diskUsed,
'ssl': ssl_status
}) })
return json.dumps(json_data) return json.dumps(json_data)
def getSSLStatus(self, domain):
"""Get SSL status for a domain"""
try:
import OpenSSL
from datetime import datetime
# Check main domain certificate
filePath = '/etc/letsencrypt/live/%s/fullchain.pem' % domain
if not os.path.exists(filePath):
# Check for wildcard certificate in parent domain
parts = domain.split('.')
if len(parts) > 2: # Subdomain like mail.example.com or ftp.example.com
parent_domain = '.'.join(parts[-2:])
wildcard_path = '/etc/letsencrypt/live/%s/fullchain.pem' % parent_domain
if os.path.exists(wildcard_path):
# Check if it's actually a wildcard cert
try:
x509 = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM,
open(wildcard_path, 'r').read()
)
cn = None
for component in x509.get_subject().get_components():
if component[0].decode('utf-8') == 'CN':
cn = component[1].decode('utf-8')
break
if cn and cn.startswith('*.'):
filePath = wildcard_path
is_wildcard = True
else:
return {'status': 'none', 'days': 0, 'issuer': '', 'is_wildcard': False}
except:
return {'status': 'none', 'days': 0, 'issuer': '', 'is_wildcard': False}
else:
return {'status': 'none', 'days': 0, 'issuer': '', 'is_wildcard': False}
else:
return {'status': 'none', 'days': 0, 'issuer': '', 'is_wildcard': False}
else:
is_wildcard = False
# Load and analyze certificate
x509 = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM,
open(filePath, 'r').read()
)
# Get expiration date
expireData = x509.get_notAfter().decode('ascii')
finalDate = datetime.strptime(expireData, '%Y%m%d%H%M%SZ')
now = datetime.now()
diff = finalDate - now
days = diff.days
# Get issuer
issuer_org = None
for component in x509.get_issuer().get_components():
if component[0].decode('utf-8') == 'O':
issuer_org = component[1].decode('utf-8')
break
if not issuer_org:
issuer_org = 'Unknown'
# Check if it's a wildcard certificate
if not is_wildcard:
cn = None
for component in x509.get_subject().get_components():
if component[0].decode('utf-8') == 'CN':
cn = component[1].decode('utf-8')
break
if cn and cn.startswith('*.'):
is_wildcard = True
# Determine status
if issuer_org == 'Denial':
status = 'self-signed'
elif days < 0:
status = 'expired'
elif days <= 7:
status = 'expiring'
elif days <= 30:
status = 'warning'
else:
status = 'valid'
return {
'status': status,
'days': days,
'issuer': issuer_org,
'is_wildcard': is_wildcard
}
except Exception as e:
return {'status': 'none', 'days': 0, 'issuer': '', 'is_wildcard': False}
def findDockersitesListJson(self, Dockersite): def findDockersitesListJson(self, Dockersite):