Улучшен фронт

1. Добавлен функционал в интерфейс по управлению сертификатами и службой редактирования сертификатов.

2. Добавлена кнопка для добавления прокси и экран редактирования прокси.
This commit is contained in:
2026-01-17 11:57:57 +07:00
parent 9a788800b5
commit 05ddb1e796
22 changed files with 1641 additions and 77 deletions

View File

@@ -11,45 +11,120 @@ import { $ } from '../utils/dom.js';
export class SitesManager {
constructor() {
this.sitesData = [];
this.certsCache = {};
this.mockData = [
{
name: 'Home Voxsel',
host: 'home.voxsel.ru',
alias: ['home.voxsel.com'],
status: 'active',
root_file: 'index.html',
root_file_routing: true,
auto_create_ssl: false
},
{
name: 'Finance',
host: 'finance.voxsel.ru',
alias: [],
status: 'active',
root_file: 'index.php',
root_file_routing: false,
auto_create_ssl: true
},
{
name: 'Локальный сайт',
host: '127.0.0.1',
alias: ['localhost'],
status: 'active',
root_file: 'index.html',
root_file_routing: true
},
{
name: 'Тестовый проект',
host: 'test.local',
alias: ['*.test.local', 'test.com'],
status: 'active',
root_file: 'index.php',
root_file_routing: false
},
{
name: 'API сервис',
host: 'api.example.com',
alias: ['*.api.example.com'],
status: 'inactive',
root_file: 'index.php',
root_file_routing: true
root_file_routing: true,
auto_create_ssl: false
}
];
this.mockCerts = {
'voxsel.ru': { has_cert: true, is_expired: false, days_left: 79, dns_names: ['*.voxsel.com', '*.voxsel.ru', 'voxsel.com', 'voxsel.ru'] },
'finance.voxsel.ru': { has_cert: true, is_expired: false, days_left: 89, dns_names: ['finance.voxsel.ru'] }
};
}
// Загрузить список сайтов
async load() {
if (isWailsAvailable()) {
this.sitesData = await api.getSitesList();
await this.loadCertsInfo();
} else {
// Используем тестовые данные если Wails недоступен
this.sitesData = this.mockData;
this.certsCache = this.mockCerts;
}
this.render();
}
// Загрузить информацию о сертификатах
async loadCertsInfo() {
const allCerts = await api.getAllCertsInfo();
this.certsCache = {};
for (const cert of allCerts) {
this.certsCache[cert.domain] = cert;
}
}
// Проверить соответствие домена wildcard паттерну
matchesWildcard(domain, pattern) {
if (pattern.startsWith('*.')) {
const wildcardBase = pattern.slice(2);
const domainParts = domain.split('.');
if (domainParts.length >= 2) {
const domainBase = domainParts.slice(1).join('.');
return domainBase === wildcardBase;
}
}
return domain === pattern;
}
// Найти сертификат для домена (включая wildcard)
findCertForDomain(domain) {
if (this.certsCache[domain]?.has_cert) {
return this.certsCache[domain];
}
const domainParts = domain.split('.');
if (domainParts.length >= 2) {
const wildcardDomain = '*.' + domainParts.slice(1).join('.');
if (this.certsCache[wildcardDomain]?.has_cert) {
return this.certsCache[wildcardDomain];
}
}
for (const [certDomain, cert] of Object.entries(this.certsCache)) {
if (cert.has_cert && cert.dns_names) {
for (const dnsName of cert.dns_names) {
if (this.matchesWildcard(domain, dnsName)) {
return cert;
}
}
}
}
return null;
}
// Получить иконку сертификата для домена
getCertIcon(host, aliases = []) {
const allDomains = [host, ...aliases.filter(a => !a.includes('*'))];
for (const domain of allDomains) {
const cert = this.findCertForDomain(domain);
if (cert) {
if (cert.is_expired) {
return `<span class="cert-icon cert-expired" title="SSL сертификат истёк"><i class="fas fa-shield-alt"></i></span>`;
} else {
return `<span class="cert-icon cert-valid" title="SSL активен (${cert.days_left} дн.)"><i class="fas fa-shield-alt"></i></span>`;
}
}
}
return '';
}
// Отрисовать список сайтов
render() {
const tbody = $('sitesTable')?.querySelector('tbody');
@@ -61,16 +136,18 @@ export class SitesManager {
const row = document.createElement('tr');
const statusBadge = site.status === 'active' ? 'badge-online' : 'badge-offline';
const aliases = site.alias.join(', ');
const certIcon = this.getCertIcon(site.host, site.alias);
row.innerHTML = `
<td>${site.name}</td>
<td>${certIcon}${site.name}</td>
<td><code class="clickable-link" data-url="http://${site.host}">${site.host} <i class="fas fa-external-link-alt"></i></code></td>
<td><code>${aliases}</code></td>
<td><span class="badge ${statusBadge}">${site.status}</span></td>
<td><code>${site.root_file}</code></td>
<td>
<button class="icon-btn" data-action="open-folder" data-host="${site.host}" title="Открыть папку"><i class="fas fa-folder-open"></i></button>
<button class="icon-btn" data-action="edit-vaccess" data-host="${site.host}" data-is-proxy="false" title="vAccess"><i class="fas fa-shield-alt"></i></button>
<button class="icon-btn" data-action="edit-vaccess" data-host="${site.host}" data-is-proxy="false" title="vAccess"><i class="fas fa-user-lock"></i></button>
<button class="icon-btn" data-action="open-certs" data-host="${site.host}" data-aliases="${site.alias.join(',')}" data-is-proxy="false" title="SSL сертификаты"><i class="fas fa-shield-alt"></i></button>
<button class="icon-btn" data-action="edit-site" data-index="${index}" title="Редактировать"><i class="fas fa-edit"></i></button>
</td>
`;
@@ -118,6 +195,13 @@ export class SitesManager {
window.editVAccess(host, isProxy);
}
break;
case 'open-certs':
if (window.openCertManager) {
const aliasesStr = btn.getAttribute('data-aliases') || '';
const aliases = aliasesStr ? aliasesStr.split(',').filter(a => a) : [];
window.openCertManager(host, isProxy, aliases);
}
break;
case 'edit-site':
if (window.editSite) {
window.editSite(index);