Оптимизация

- Оптимизация JS файлов
- FIX: Исправил Crash, если не было папки logs
- Удалил скомпилированный EXE файл с репозитория исходников.
This commit is contained in:
2025-11-15 23:33:57 +07:00
parent 7c77afabed
commit c1a781a0f5
20 changed files with 411 additions and 736 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
# Исключения
myapp.exe
vSerf.exe
.vscode/
WebServer/cert/*
!WebServer/cert/no_cert/

View File

@@ -3,123 +3,94 @@
Работа с конфигурацией
============================================ */
import { isWailsAvailable, log } from '../utils/helpers.js';
import { isWailsAvailable } from '../utils/helpers.js';
/**
* Класс для работы с конфигурацией
*/
// Класс для работы с конфигурацией
class ConfigAPI {
constructor() {
this.available = isWailsAvailable();
}
/**
* Получить конфигурацию
*/
// Получить конфигурацию
async getConfig() {
if (!this.available) return null;
try {
return await window.go.admin.App.GetConfig();
} catch (error) {
log(`Ошибка получения конфигурации: ${error.message}`, 'error');
return null;
}
}
/**
* Сохранить конфигурацию
*/
// Сохранить конфигурацию
async saveConfig(configJSON) {
if (!this.available) return 'Error: API недоступен';
try {
return await window.go.admin.App.SaveConfig(configJSON);
} catch (error) {
log(`Ошибка сохранения конфигурации: ${error.message}`, 'error');
return `Error: ${error.message}`;
}
}
/**
* Включить Proxy Service
*/
// Включить Proxy Service
async enableProxyService() {
if (!this.available) return;
try {
await window.go.admin.App.EnableProxyService();
} catch (error) {
log(`Ошибка включения Proxy: ${error.message}`, 'error');
}
}
/**
* Отключить Proxy Service
*/
// Отключить Proxy Service
async disableProxyService() {
if (!this.available) return;
try {
await window.go.admin.App.DisableProxyService();
} catch (error) {
log(`Ошибка отключения Proxy: ${error.message}`, 'error');
}
}
/**
* Перезапустить все сервисы
*/
// Перезапустить все сервисы
async restartAllServices() {
if (!this.available) return;
try {
await window.go.admin.App.RestartAllServices();
} catch (error) {
log(`Ошибка перезапуска сервисов: ${error.message}`, 'error');
}
}
/**
* Запустить HTTP Service
*/
// Запустить HTTP Service
async startHTTPService() {
if (!this.available) return;
try {
await window.go.admin.App.StartHTTPService();
} catch (error) {
log(`Ошибка запуска HTTP: ${error.message}`, 'error');
}
}
/**
* Остановить HTTP Service
*/
// Остановить HTTP Service
async stopHTTPService() {
if (!this.available) return;
try {
await window.go.admin.App.StopHTTPService();
} catch (error) {
log(`Ошибка остановки HTTP: ${error.message}`, 'error');
}
}
/**
* Запустить HTTPS Service
*/
// Запустить HTTPS Service
async startHTTPSService() {
if (!this.available) return;
try {
await window.go.admin.App.StartHTTPSService();
} catch (error) {
log(`Ошибка запуска HTTPS: ${error.message}`, 'error');
}
}
/**
* Остановить HTTPS Service
*/
// Остановить HTTPS Service
async stopHTTPSService() {
if (!this.available) return;
try {
await window.go.admin.App.StopHTTPSService();
} catch (error) {
log(`Ошибка остановки HTTPS: ${error.message}`, 'error');
}
}
}

View File

@@ -3,119 +3,91 @@
Обёртка над Wails API
============================================ */
import { isWailsAvailable, log } from '../utils/helpers.js';
import { isWailsAvailable } from '../utils/helpers.js';
/**
* Базовый класс для работы с Wails API
*/
// Базовый класс для работы с Wails API
class WailsAPI {
constructor() {
this.available = isWailsAvailable();
}
/**
* Проверка доступности API
*/
// Проверка доступности API
checkAvailability() {
if (!this.available) {
log('Wails API недоступен', 'warn');
return false;
}
return true;
}
/**
* Получить статус всех сервисов
*/
// Получить статус всех сервисов
async getAllServicesStatus() {
if (!this.checkAvailability()) return null;
try {
return await window.go.admin.App.GetAllServicesStatus();
} catch (error) {
log(`Ошибка получения статуса сервисов: ${error.message}`, 'error');
return null;
}
}
/**
* Получить список сайтов
*/
// Получить список сайтов
async getSitesList() {
if (!this.checkAvailability()) return [];
try {
return await window.go.admin.App.GetSitesList();
} catch (error) {
log(`Ошибка получения списка сайтов: ${error.message}`, 'error');
return [];
}
}
/**
* Получить список прокси
*/
// Получить список прокси
async getProxyList() {
if (!this.checkAvailability()) return [];
try {
return await window.go.admin.App.GetProxyList();
} catch (error) {
log(`Ошибка получения списка прокси: ${error.message}`, 'error');
return [];
}
}
/**
* Получить правила vAccess
*/
// Получить правила vAccess
async getVAccessRules(host, isProxy) {
if (!this.checkAvailability()) return { rules: [] };
try {
return await window.go.admin.App.GetVAccessRules(host, isProxy);
} catch (error) {
log(`Ошибка получения правил vAccess: ${error.message}`, 'error');
return { rules: [] };
}
}
/**
* Сохранить правила vAccess
*/
// Сохранить правила vAccess
async saveVAccessRules(host, isProxy, configJSON) {
if (!this.checkAvailability()) return 'Error: API недоступен';
try {
return await window.go.admin.App.SaveVAccessRules(host, isProxy, configJSON);
} catch (error) {
log(`Ошибка сохранения правил vAccess: ${error.message}`, 'error');
return `Error: ${error.message}`;
}
}
/**
* Запустить сервер
*/
// Запустить сервер
async startServer() {
if (!this.checkAvailability()) return;
try {
await window.go.admin.App.StartServer();
} catch (error) {
log(`Ошибка запуска сервера: ${error.message}`, 'error');
}
}
/**
* Остановить сервер
*/
// Остановить сервер
async stopServer() {
if (!this.checkAvailability()) return;
try {
await window.go.admin.App.StopServer();
} catch (error) {
log(`Ошибка остановки сервера: ${error.message}`, 'error');
}
}
/**
* Проверить готовность сервисов
*/
// Проверить готовность сервисов
async checkServicesReady() {
if (!this.checkAvailability()) return false;
try {
@@ -125,66 +97,51 @@ class WailsAPI {
}
}
/**
* Открыть папку сайта
*/
// Открыть папку сайта
async openSiteFolder(host) {
if (!this.checkAvailability()) return;
try {
await window.go.admin.App.OpenSiteFolder(host);
} catch (error) {
log(`Ошибка открытия папки: ${error.message}`, 'error');
}
}
/**
* Создать новый сайт
*/
// Создать новый сайт
async createNewSite(siteJSON) {
if (!this.checkAvailability()) return 'Error: API недоступен';
try {
return await window.go.admin.App.CreateNewSite(siteJSON);
} catch (error) {
log(`Ошибка создания сайта: ${error.message}`, 'error');
return `Error: ${error.message}`;
}
}
/**
* Загрузить сертификат для сайта
*/
// Загрузить сертификат для сайта
async uploadCertificate(host, certType, certDataBase64) {
if (!this.checkAvailability()) return 'Error: API недоступен';
try {
return await window.go.admin.App.UploadCertificate(host, certType, certDataBase64);
} catch (error) {
log(`Ошибка загрузки сертификата: ${error.message}`, 'error');
return `Error: ${error.message}`;
}
}
/**
* Перезагрузить SSL сертификаты
*/
// Перезагрузить SSL сертификаты
async reloadSSLCertificates() {
if (!this.checkAvailability()) return 'Error: API недоступен';
try {
return await window.go.admin.App.ReloadSSLCertificates();
} catch (error) {
log(`Ошибка перезагрузки сертификатов: ${error.message}`, 'error');
return `Error: ${error.message}`;
}
}
/**
* Удалить сайт
*/
// Удалить сайт
async deleteSite(host) {
if (!this.checkAvailability()) return 'Error: API недоступен';
try {
return await window.go.admin.App.DeleteSite(host);
} catch (error) {
log(`Ошибка удаления сайта: ${error.message}`, 'error');
return `Error: ${error.message}`;
}
}

View File

@@ -7,9 +7,7 @@ import { api } from '../api/wails.js';
import { isWailsAvailable } from '../utils/helpers.js';
import { $ } from '../utils/dom.js';
/**
* Класс для управления прокси
*/
// Класс для управления прокси
export class ProxyManager {
constructor() {
this.proxiesData = [];
@@ -44,9 +42,7 @@ export class ProxyManager {
];
}
/**
* Загрузить список прокси
*/
// Загрузить список прокси
async load() {
if (isWailsAvailable()) {
this.proxiesData = await api.getProxyList();
@@ -57,9 +53,7 @@ export class ProxyManager {
this.render();
}
/**
* Отрисовать список прокси
*/
// Отрисовать список прокси
render() {
const tbody = $('proxyTable')?.querySelector('tbody');
if (!tbody) return;
@@ -92,9 +86,7 @@ export class ProxyManager {
this.attachEventListeners();
}
/**
* Добавить обработчики событий
*/
// Добавить обработчики событий
attachEventListeners() {
// Кликабельные ссылки
const links = document.querySelectorAll('.clickable-link[data-url]');
@@ -115,9 +107,7 @@ export class ProxyManager {
});
}
/**
* Обработчик действий
*/
// Обработчик действий
handleAction(action, btn) {
const host = btn.getAttribute('data-host');
const index = parseInt(btn.getAttribute('data-index'));
@@ -137,9 +127,7 @@ export class ProxyManager {
}
}
/**
* Открыть ссылку
*/
// Открыть ссылку
openLink(url) {
if (window.runtime?.BrowserOpenURL) {
window.runtime.BrowserOpenURL(url);

View File

@@ -8,9 +8,7 @@ import { $, $$, addClass, removeClass } from '../utils/dom.js';
import { notification } from '../ui/notification.js';
import { sleep, isWailsAvailable } from '../utils/helpers.js';
/**
* Класс для управления сервисами
*/
// Класс для управления сервисами
export class ServicesManager {
constructor() {
this.serverRunning = true;
@@ -40,9 +38,7 @@ export class ServicesManager {
}
}
/**
* Переключить состояние сервера
*/
// Переключить состояние сервера
async toggleServer() {
if (this.serverRunning) {
await this.stopServer();
@@ -51,9 +47,7 @@ export class ServicesManager {
}
}
/**
* Запустить сервер
*/
// Запустить сервер
async startServer() {
this.isOperating = true;
this.controlBtn.disabled = true;
@@ -80,9 +74,7 @@ export class ServicesManager {
this.controlBtn.disabled = false;
}
/**
* Остановить сервер
*/
// Остановить сервер
async stopServer() {
this.isOperating = true;
this.controlBtn.disabled = true;
@@ -100,9 +92,7 @@ export class ServicesManager {
this.controlBtn.disabled = false;
}
/**
* Установить статус сервера
*/
// Установить статус сервера
setServerStatus(isOnline, text) {
this.serverRunning = isOnline;
@@ -117,9 +107,7 @@ export class ServicesManager {
this.statusText.textContent = text;
}
/**
* Установить всем сервисам статус pending
*/
// Установить всем сервисам статус pending
setAllServicesPending(text) {
const badges = $$('.service-card .badge');
badges.forEach(badge => {
@@ -128,9 +116,7 @@ export class ServicesManager {
});
}
/**
* Отрисовать статусы сервисов
*/
// Отрисовать статусы сервисов
renderServices(data) {
const services = [data.http, data.https, data.mysql, data.php, data.proxy];
const cards = $$('.service-card');
@@ -166,9 +152,7 @@ export class ServicesManager {
});
}
/**
* Загрузить статусы сервисов
*/
// Загрузить статусы сервисов
async loadStatus() {
if (isWailsAvailable()) {
const data = await api.getAllServicesStatus();

View File

@@ -10,9 +10,7 @@ import { notification } from '../ui/notification.js';
import { isWailsAvailable } from '../utils/helpers.js';
import { initCustomSelects } from '../ui/custom-select.js';
/**
* Класс для создания новых сайтов
*/
// Класс для создания новых сайтов
export class SiteCreator {
constructor() {
this.aliases = [];
@@ -23,9 +21,7 @@ export class SiteCreator {
};
}
/**
* Открыть страницу создания сайта
*/
// Открыть страницу создания сайта
open() {
// Скрываем все секции
this.hideAllSections();
@@ -43,9 +39,7 @@ export class SiteCreator {
setTimeout(() => initCustomSelects(), 100);
}
/**
* Скрыть все секции
*/
// Скрыть все секции
hideAllSections() {
hide($('sectionServices'));
hide($('sectionSites'));
@@ -55,9 +49,7 @@ export class SiteCreator {
hide($('sectionAddSite'));
}
/**
* Вернуться на главную
*/
// Вернуться на главную
backToMain() {
this.hideAllSections();
show($('sectionServices'));
@@ -65,9 +57,7 @@ export class SiteCreator {
show($('sectionProxy'));
}
/**
* Очистить форму
*/
// Очистить форму
resetForm() {
$('newSiteName').value = '';
$('newSiteHost').value = '';
@@ -107,9 +97,7 @@ export class SiteCreator {
labels.forEach(label => label.classList.remove('file-uploaded'));
}
/**
* Привязать обработчики событий
*/
// Привязать обработчики событий
attachEventListeners() {
const createBtn = $('createSiteBtn');
if (createBtn) {
@@ -120,9 +108,7 @@ export class SiteCreator {
this.setupDragAndDrop();
}
/**
* Настроить Drag & Drop для файлов
*/
// Настроить Drag & Drop для файлов
setupDragAndDrop() {
const fileWrappers = [
{ wrapper: document.querySelector('label[for="certFile"]')?.parentElement, input: $('certFile'), type: 'certificate' },
@@ -171,9 +157,7 @@ export class SiteCreator {
});
}
/**
* Парсить aliases из строки (через запятую)
*/
// Парсить aliases из строки (через запятую)
parseAliases() {
const input = $('newSiteAliasInput');
const value = input?.value.trim();
@@ -190,9 +174,7 @@ export class SiteCreator {
.filter(alias => alias.length > 0);
}
/**
* Переключить видимость блока загрузки сертификатов
*/
// Переключить видимость блока загрузки сертификатов
toggleCertUpload() {
const mode = $('certMode')?.value;
const block = $('certUploadBlock');
@@ -204,9 +186,7 @@ export class SiteCreator {
}
}
/**
* Обработать выбор файла сертификата
*/
// Обработать выбор файла сертификата
handleCertFile(input, certType) {
const file = input.files[0];
const statusId = certType === 'certificate' ? 'certFileStatus' :
@@ -257,9 +237,7 @@ export class SiteCreator {
reader.readAsText(file);
}
/**
* Валидация формы
*/
// Валидация формы
validateForm() {
const name = $('newSiteName')?.value.trim();
const host = $('newSiteHost')?.value.trim();
@@ -296,9 +274,7 @@ export class SiteCreator {
return true;
}
/**
* Создать сайт
*/
// Создать сайт
async createSite() {
if (!this.validateForm()) {
return;

View File

@@ -7,9 +7,7 @@ import { api } from '../api/wails.js';
import { isWailsAvailable } from '../utils/helpers.js';
import { $ } from '../utils/dom.js';
/**
* Класс для управления сайтами
*/
// Класс для управления сайтами
export class SitesManager {
constructor() {
this.sitesData = [];
@@ -41,9 +39,7 @@ export class SitesManager {
];
}
/**
* Загрузить список сайтов
*/
// Загрузить список сайтов
async load() {
if (isWailsAvailable()) {
this.sitesData = await api.getSitesList();
@@ -54,9 +50,7 @@ export class SitesManager {
this.render();
}
/**
* Отрисовать список сайтов
*/
// Отрисовать список сайтов
render() {
const tbody = $('sitesTable')?.querySelector('tbody');
if (!tbody) return;
@@ -88,9 +82,7 @@ export class SitesManager {
this.attachEventListeners();
}
/**
* Добавить обработчики событий
*/
// Добавить обработчики событий
attachEventListeners() {
// Кликабельные ссылки
const links = document.querySelectorAll('.clickable-link[data-url]');
@@ -111,9 +103,7 @@ export class SitesManager {
});
}
/**
* Обработчик действий
*/
// Обработчик действий
async handleAction(action, btn) {
const host = btn.getAttribute('data-host');
const index = parseInt(btn.getAttribute('data-index'));
@@ -136,9 +126,7 @@ export class SitesManager {
}
}
/**
* Открыть ссылку
*/
// Открыть ссылку
openLink(url) {
if (window.runtime?.BrowserOpenURL) {
window.runtime.BrowserOpenURL(url);

View File

@@ -9,9 +9,7 @@ import { notification } from '../ui/notification.js';
import { modal } from '../ui/modal.js';
import { isWailsAvailable } from '../utils/helpers.js';
/**
* Класс для управления vAccess правилами
*/
// Класс для управления vAccess правилами
export class VAccessManager {
constructor() {
this.vAccessHost = '';
@@ -22,9 +20,7 @@ export class VAccessManager {
this.editingField = null;
}
/**
* Открыть редактор vAccess
*/
// Открыть редактор vAccess
async open(host, isProxy) {
this.vAccessHost = host;
this.vAccessIsProxy = isProxy;
@@ -77,9 +73,7 @@ export class VAccessManager {
}
}
/**
* Скрыть все секции
*/
// Скрыть все секции
hideAllSections() {
hide($('sectionServices'));
hide($('sectionSites'));
@@ -88,9 +82,7 @@ export class VAccessManager {
hide($('sectionVAccessEditor'));
}
/**
* Вернуться на главную
*/
// Вернуться на главную
backToMain() {
this.hideAllSections();
show($('sectionServices'));
@@ -98,9 +90,7 @@ export class VAccessManager {
show($('sectionProxy'));
}
/**
* Переключить вкладку
*/
// Переключить вкладку
switchTab(tab) {
const tabs = document.querySelectorAll('.vaccess-tab[data-tab]');
tabs.forEach(t => {
@@ -120,9 +110,7 @@ export class VAccessManager {
}
}
/**
* Сохранить изменения
*/
// Сохранить изменения
async save() {
if (isWailsAvailable()) {
const config = { rules: this.vAccessRules };
@@ -140,9 +128,7 @@ export class VAccessManager {
}
}
/**
* Отрисовать список правил
*/
// Отрисовать список правил
renderRulesList() {
const tbody = $('vAccessTableBody');
const emptyState = $('vAccessEmpty');
@@ -191,9 +177,7 @@ export class VAccessManager {
this.attachRulesEventListeners();
}
/**
* Добавить обработчики событий для правил
*/
// Добавить обработчики событий для правил
attachRulesEventListeners() {
// Drag & Drop
const rows = document.querySelectorAll('#vAccessTableBody tr[draggable]');
@@ -223,9 +207,7 @@ export class VAccessManager {
});
}
/**
* Добавить новое правило
*/
// Добавить новое правило
addRule() {
this.vAccessRules.push({
type: 'Disable',
@@ -240,17 +222,13 @@ export class VAccessManager {
this.renderRulesList();
}
/**
* Удалить правило
*/
// Удалить правило
removeRule(index) {
this.vAccessRules.splice(index, 1);
this.renderRulesList();
}
/**
* Редактировать поле правила
*/
// Редактировать поле правила
editRuleField(index, field) {
const rule = this.vAccessRules[index];
@@ -271,9 +249,7 @@ export class VAccessManager {
}
}
/**
* Показать редактор поля
*/
// Показать редактор поля
showFieldEditor(index, field) {
const rule = this.vAccessRules[index];
const fieldNames = {
@@ -328,9 +304,7 @@ export class VAccessManager {
}, 100);
}
/**
* Добавить значение в поле
*/
// Добавить значение в поле
addFieldValue() {
const input = $('fieldInput');
const value = input?.value.trim();
@@ -346,9 +320,7 @@ export class VAccessManager {
}
}
/**
* Удалить значение из поля
*/
// Удалить значение из поля
removeFieldValue(value) {
if (this.editingField) {
const { index, field } = this.editingField;
@@ -361,9 +333,7 @@ export class VAccessManager {
}
}
/**
* Закрыть редактор поля
*/
// Закрыть редактор поля
closeFieldEditor() {
modal.closeFieldEditor();
this.renderRulesList();

View File

@@ -3,7 +3,7 @@
Точка входа приложения
============================================ */
import { log, isWailsAvailable, sleep } from './utils/helpers.js';
import { isWailsAvailable, sleep } from './utils/helpers.js';
import { WindowControls } from './ui/window.js';
import { Navigation } from './ui/navigation.js';
import { notification } from './ui/notification.js';
@@ -18,9 +18,7 @@ import { configAPI } from './api/config.js';
import { initCustomSelects } from './ui/custom-select.js';
import { $ } from './utils/dom.js';
/**
* Главный класс приложения
*/
// Главный класс приложения
class App {
constructor() {
this.windowControls = new WindowControls();
@@ -32,15 +30,29 @@ class App {
this.siteCreator = new SiteCreator();
this.isWails = isWailsAvailable();
log('Приложение инициализировано');
}
/**
* Запустить приложение
*/
// Загрузить шаблоны из templates.html
async loadTemplates() {
try {
const response = await fetch('templates.html');
const html = await response.text();
document.getElementById('templates-container').innerHTML = html;
} catch (error) {
// Игнорируем ошибку
}
}
// Получить шаблон по ID
getTemplate(templateId) {
const template = document.getElementById(templateId);
return template ? template.content.cloneNode(true) : null;
}
// Запустить приложение
async start() {
log('Запуск приложения...');
// Загружаем шаблоны
await this.loadTemplates();
// Скрываем loader если не в Wails
if (!this.isWails) {
@@ -50,12 +62,6 @@ class App {
// Ждём немного перед загрузкой данных
await sleep(1000);
if (this.isWails) {
log('Wails API доступен', 'info');
} else {
log('Wails API недоступен (браузерный режим)', 'warn');
}
// Загружаем начальные данные
await this.loadInitialData();
@@ -75,13 +81,9 @@ class App {
// Инициализируем кастомные select'ы
initCustomSelects();
log('Приложение запущено');
}
/**
* Загрузить начальные данные
*/
// Загрузить начальные данные
async loadInitialData() {
await Promise.all([
this.servicesManager.loadStatus(),
@@ -90,18 +92,14 @@ class App {
]);
}
/**
* Запустить автообновление
*/
// Запустить автообновление
startAutoRefresh() {
setInterval(async () => {
await this.loadInitialData();
}, 5000);
}
/**
* Привязать кнопки
*/
// Привязать кнопки
setupButtons() {
// Кнопка добавления сайта
const addSiteBtn = $('addSiteBtn');
@@ -139,156 +137,70 @@ class App {
}
}
/**
* Настроить глобальные обработчики
*/
// Настроить глобальные обработчики
setupGlobalHandlers() {
// Глобальная ссылка на sitesManager
window.sitesManager = this.sitesManager;
window.siteCreator = this.siteCreator;
Object.assign(window, {
// Ссылки на менеджеры
sitesManager: this.sitesManager,
siteCreator: this.siteCreator,
// Для SiteCreator
window.backToMainFromAddSite = () => {
this.siteCreator.backToMain();
};
// SiteCreator
backToMainFromAddSite: () => this.siteCreator.backToMain(),
toggleCertUpload: () => this.siteCreator.toggleCertUpload(),
handleCertFileSelect: (input, certType) => this.siteCreator.handleCertFile(input, certType),
window.toggleCertUpload = () => {
this.siteCreator.toggleCertUpload();
};
// vAccess
editVAccess: (host, isProxy) => this.vAccessManager.open(host, isProxy),
backToMain: () => this.vAccessManager.backToMain(),
switchVAccessTab: (tab) => this.vAccessManager.switchTab(tab),
saveVAccessChanges: async () => await this.vAccessManager.save(),
addVAccessRule: () => this.vAccessManager.addRule(),
dragStart: (e) => this.vAccessManager.onDragStart(e),
dragOver: (e) => this.vAccessManager.onDragOver(e),
drop: (e) => this.vAccessManager.onDrop(e),
editRuleField: (i, f) => this.vAccessManager.editRuleField(i, f),
removeVAccessRule: (i) => this.vAccessManager.removeRule(i),
closeFieldEditor: () => this.vAccessManager.closeFieldEditor(),
addFieldValue: () => this.vAccessManager.addFieldValue(),
removeFieldValue: (v) => this.vAccessManager.removeFieldValue(v),
window.handleCertFileSelect = (input, certType) => {
this.siteCreator.handleCertFile(input, certType);
};
// Settings
loadConfig: async () => await this.loadConfigSettings(),
saveSettings: async () => await this.saveConfigSettings(),
// Для vAccess
window.editVAccess = (host, isProxy) => {
this.vAccessManager.open(host, isProxy);
};
// Модальные окна
editSite: (i) => this.editSite(i),
editProxy: (i) => this.editProxy(i),
setStatus: (s) => this.setModalStatus(s),
setProxyStatus: (s) => this.setModalStatus(s),
addAliasTag: () => this.addAliasTag(),
removeAliasTag: (btn) => btn.parentElement.remove(),
saveModalData: async () => await this.saveModalData(),
deleteSiteConfirm: async () => await this.deleteSiteConfirm(),
window.backToMain = () => {
this.vAccessManager.backToMain();
};
window.switchVAccessTab = (tab) => {
this.vAccessManager.switchTab(tab);
};
window.saveVAccessChanges = async () => {
await this.vAccessManager.save();
};
window.addVAccessRule = () => {
this.vAccessManager.addRule();
};
// Для Settings
window.loadConfig = async () => {
await this.loadConfigSettings();
};
window.saveSettings = async () => {
await this.saveConfigSettings();
};
// Для модальных окон
window.editSite = (index) => {
this.editSite(index);
};
window.editProxy = (index) => {
this.editProxy(index);
};
window.setStatus = (status) => {
this.setModalStatus(status);
};
window.setProxyStatus = (status) => {
this.setModalStatus(status);
};
window.addAliasTag = () => {
this.addAliasTag();
};
window.removeAliasTag = (btn) => {
btn.parentElement.remove();
};
window.saveModalData = async () => {
await this.saveModalData();
};
// Drag & Drop для vAccess
window.dragStart = (event, index) => {
this.vAccessManager.onDragStart(event);
};
window.dragOver = (event) => {
this.vAccessManager.onDragOver(event);
};
window.drop = (event, index) => {
this.vAccessManager.onDrop(event);
};
window.editRuleField = (index, field) => {
this.vAccessManager.editRuleField(index, field);
};
window.removeVAccessRule = (index) => {
this.vAccessManager.removeRule(index);
};
window.closeFieldEditor = () => {
this.vAccessManager.closeFieldEditor();
};
window.addFieldValue = () => {
this.vAccessManager.addFieldValue();
};
window.removeFieldValue = (value) => {
this.vAccessManager.removeFieldValue(value);
};
// Тестовые функции (для браузерного режима)
window.editTestSite = (index) => {
const testSites = [
// Тестовые функции
editTestSite: (i) => {
this.sitesManager.sitesData = [
{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}
];
this.sitesManager.sitesData = testSites;
this.editSite(index);
};
window.editTestProxy = (index) => {
const testProxies = [
this.editSite(i);
},
editTestProxy: (i) => {
this.proxyManager.proxiesData = [
{enable: true, external_domain: 'git.example.ru', local_address: '127.0.0.1', local_port: '3333', service_https_use: false, auto_https: true},
{enable: true, external_domain: 'api.example.com', local_address: '127.0.0.1', local_port: '8080', service_https_use: true, auto_https: false},
{enable: false, external_domain: 'test.example.net', local_address: '127.0.0.1', local_port: '5000', service_https_use: false, auto_https: false}
];
this.proxyManager.proxiesData = testProxies;
this.editProxy(index);
};
window.openTestLink = (url) => {
this.sitesManager.openLink(url);
};
window.openSiteFolder = async (host) => {
await this.sitesManager.handleAction('open-folder', { getAttribute: () => host });
};
window.deleteSiteConfirm = async () => {
await this.deleteSiteConfirm();
};
this.editProxy(i);
},
openTestLink: (url) => this.sitesManager.openLink(url),
openSiteFolder: async (host) => await this.sitesManager.handleAction('open-folder', { getAttribute: () => host })
});
}
/**
* Загрузить настройки конфигурации
*/
// Загрузить настройки конфигурации
async loadConfigSettings() {
if (!isWailsAvailable()) {
// Тестовые данные для браузерного режима
@@ -310,9 +222,7 @@ class App {
$('proxyEnabled').checked = config.Soft_Settings?.proxy_enabled !== false;
}
/**
* Сохранить настройки конфигурации
*/
// Сохранить настройки конфигурации
async saveConfigSettings() {
const saveBtn = $('saveSettingsBtn');
const originalText = saveBtn.querySelector('span').textContent;
@@ -353,146 +263,101 @@ class App {
}
}
/**
* Редактировать сайт
*/
// Редактировать сайт
editSite(index) {
const site = this.sitesManager.sitesData[index];
if (!site) return;
const content = `
<div class="settings-form">
<div class="form-group">
<label class="form-label">Статус сайта:</label>
<div class="status-toggle">
<button class="status-btn ${site.status === 'active' ? 'active' : ''}" onclick="setStatus('active')" data-value="active">
<i class="fas fa-check-circle"></i> Active
</button>
<button class="status-btn ${site.status === 'inactive' ? 'active' : ''}" onclick="setStatus('inactive')" data-value="inactive">
<i class="fas fa-times-circle"></i> Inactive
</button>
</div>
</div>
<div class="form-group">
<label class="form-label">Название сайта:</label>
<input type="text" class="form-input" id="editName" value="${site.name}">
</div>
<div class="form-group">
<label class="form-label">Host:</label>
<input type="text" class="form-input" id="editHost" value="${site.host}">
</div>
<div class="form-group">
<label class="form-label">Alias:</label>
<div class="tag-input-wrapper">
<input type="text" class="form-input" id="editAliasInput" placeholder="Введите alias и нажмите Добавить...">
<button class="action-btn" onclick="addAliasTag()"><i class="fas fa-plus"></i> Добавить</button>
</div>
<div class="tags-container" id="aliasTagsContainer">
${site.alias.map(alias => `
<span class="tag">
${alias}
<button class="tag-remove" onclick="removeAliasTag(this)"><i class="fas fa-times"></i></button>
</span>
`).join('')}
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Root файл:</label>
<input type="text" class="form-input" id="editRootFile" value="${site.root_file}">
</div>
<div class="form-group">
<label class="form-label">Роутинг:</label>
<div class="toggle-wrapper">
<label class="toggle-switch">
<input type="checkbox" id="editRouting" ${site.root_file_routing ? 'checked' : ''}>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Включён</span>
</div>
</div>
</div>
</div>
`;
const template = this.getTemplate('edit-site-template');
if (!template) return;
modal.open('Редактировать сайт', content);
const container = document.createElement('div');
container.appendChild(template);
// Открываем модальное окно с шаблоном
modal.open('Редактировать сайт', container.innerHTML);
window.currentEditType = 'site';
window.currentEditIndex = index;
// Добавляем кнопку удаления в футер модального окна
// Заполняем данные ПОСЛЕ открытия модального окна
setTimeout(() => {
const statusBtn = document.querySelector(`[data-status="${site.status}"]`);
if (statusBtn) statusBtn.classList.add('active');
const editName = $('editName');
const editHost = $('editHost');
const editRootFile = $('editRootFile');
const editRouting = $('editRouting');
if (editName) editName.value = site.name;
if (editHost) editHost.value = site.host;
if (editRootFile) editRootFile.value = site.root_file;
if (editRouting) editRouting.checked = site.root_file_routing;
// Добавляем alias теги
const aliasContainer = $('aliasTagsContainer');
if (aliasContainer) {
site.alias.forEach(alias => {
const tag = document.createElement('span');
tag.className = 'tag';
tag.innerHTML = `${alias}<button class="tag-remove" onclick="removeAliasTag(this)"><i class="fas fa-times"></i></button>`;
aliasContainer.appendChild(tag);
});
}
// Привязываем обработчик кнопок статуса
document.querySelectorAll('.status-btn').forEach(btn => {
btn.onclick = () => this.setModalStatus(btn.dataset.value);
});
}, 50);
this.addDeleteButtonToModal();
}
/**
* Редактировать прокси
*/
// Редактировать прокси
editProxy(index) {
const proxy = this.proxyManager.proxiesData[index];
if (!proxy) return;
const content = `
<div class="settings-form">
<div class="form-group">
<label class="form-label">Статус прокси:</label>
<div class="status-toggle">
<button class="status-btn ${proxy.enable ? 'active' : ''}" onclick="setProxyStatus('enable')" data-value="enable">
<i class="fas fa-check-circle"></i> Включён
</button>
<button class="status-btn ${!proxy.enable ? 'active' : ''}" onclick="setProxyStatus('disable')" data-value="disable">
<i class="fas fa-times-circle"></i> Отключён
</button>
</div>
</div>
<div class="form-group">
<label class="form-label">Внешний домен:</label>
<input type="text" class="form-input" id="editDomain" value="${proxy.external_domain}">
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Локальный адрес:</label>
<input type="text" class="form-input" id="editLocalAddr" value="${proxy.local_address}">
</div>
<div class="form-group">
<label class="form-label">Локальный порт:</label>
<input type="text" class="form-input" id="editLocalPort" value="${proxy.local_port}">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">HTTPS к сервису:</label>
<div class="toggle-wrapper">
<label class="toggle-switch">
<input type="checkbox" id="editServiceHTTPS" ${proxy.service_https_use ? 'checked' : ''}>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Включён</span>
</div>
</div>
<div class="form-group">
<label class="form-label">Авто HTTPS:</label>
<div class="toggle-wrapper">
<label class="toggle-switch">
<input type="checkbox" id="editAutoHTTPS" ${proxy.auto_https ? 'checked' : ''}>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Включён</span>
</div>
</div>
</div>
</div>
`;
const template = this.getTemplate('edit-proxy-template');
if (!template) return;
modal.open('Редактировать прокси', content);
const container = document.createElement('div');
container.appendChild(template);
// Открываем модальное окно с шаблоном
modal.open('Редактировать прокси', container.innerHTML);
window.currentEditType = 'proxy';
window.currentEditIndex = index;
// Убираем кнопку удаления (для прокси не нужна)
// Заполняем данные ПОСЛЕ открытия модального окна
setTimeout(() => {
const status = proxy.enable ? 'enable' : 'disable';
const statusBtn = document.querySelector(`[data-status="${status}"]`);
if (statusBtn) statusBtn.classList.add('active');
const editDomain = $('editDomain');
const editLocalAddr = $('editLocalAddr');
const editLocalPort = $('editLocalPort');
const editServiceHTTPS = $('editServiceHTTPS');
const editAutoHTTPS = $('editAutoHTTPS');
if (editDomain) editDomain.value = proxy.external_domain;
if (editLocalAddr) editLocalAddr.value = proxy.local_address;
if (editLocalPort) editLocalPort.value = proxy.local_port;
if (editServiceHTTPS) editServiceHTTPS.checked = proxy.service_https_use;
if (editAutoHTTPS) editAutoHTTPS.checked = proxy.auto_https;
// Привязываем обработчик кнопок статуса
document.querySelectorAll('.status-btn').forEach(btn => {
btn.onclick = () => this.setModalStatus(btn.dataset.value);
});
}, 50);
this.removeDeleteButtonFromModal();
}
/**
* Установить статус в модальном окне
*/
// Установить статус в модальном окне
setModalStatus(status) {
const buttons = document.querySelectorAll('.status-btn');
buttons.forEach(btn => {
@@ -503,9 +368,7 @@ class App {
});
}
/**
* Добавить alias tag
*/
// Добавить alias tag
addAliasTag() {
const input = $('editAliasInput');
const value = input?.value.trim();
@@ -523,9 +386,7 @@ class App {
}
}
/**
* Сохранить данные модального окна
*/
// Сохранить данные модального окна
async saveModalData() {
if (!isWailsAvailable()) {
notification.success('Данные сохранены (тестовый режим)', 1000);
@@ -540,9 +401,17 @@ class App {
}
}
/**
* Сохранить данные сайта
*/
// Перезапустить HTTP/HTTPS сервисы
async restartHttpServices() {
notification.show('Перезапуск HTTP/HTTPS...', 'success', 800);
await configAPI.stopHTTPService();
await configAPI.stopHTTPSService();
await sleep(500);
await configAPI.startHTTPService();
await configAPI.startHTTPSService();
}
// Сохранить данные сайта
async saveSiteData() {
const index = window.currentEditIndex;
const tags = document.querySelectorAll('#aliasTagsContainer .tag');
@@ -559,29 +428,19 @@ class App {
root_file_routing: $('editRouting').checked
};
const configJSON = JSON.stringify(config, null, 4);
const result = await configAPI.saveConfig(configJSON);
const result = await configAPI.saveConfig(JSON.stringify(config, null, 4));
if (result.startsWith('Error')) {
notification.error(result);
} else {
notification.show('Перезапуск HTTP/HTTPS...', 'success', 800);
await configAPI.stopHTTPService();
await configAPI.stopHTTPSService();
await sleep(500);
await configAPI.startHTTPService();
await configAPI.startHTTPSService();
return;
}
await this.restartHttpServices();
notification.success('Изменения сохранены и применены!', 1000);
await this.sitesManager.load();
modal.close();
}
}
/**
* Сохранить данные прокси
*/
// Сохранить данные прокси
async saveProxyData() {
const index = window.currentEditIndex;
const statusBtn = document.querySelector('.status-btn.active');
@@ -597,29 +456,19 @@ class App {
AutoHTTPS: $('editAutoHTTPS').checked
};
const configJSON = JSON.stringify(config, null, 4);
const result = await configAPI.saveConfig(configJSON);
const result = await configAPI.saveConfig(JSON.stringify(config, null, 4));
if (result.startsWith('Error')) {
notification.error(result);
} else {
notification.show('Перезапуск HTTP/HTTPS...', 'success', 800);
await configAPI.stopHTTPService();
await configAPI.stopHTTPSService();
await sleep(500);
await configAPI.startHTTPService();
await configAPI.startHTTPSService();
return;
}
await this.restartHttpServices();
notification.success('Изменения сохранены и применены!', 1000);
await this.proxyManager.load();
modal.close();
}
}
/**
* Добавить кнопку удаления в модальное окно
*/
// Добавить кнопку удаления в модальное окно
addDeleteButtonToModal() {
const footer = document.querySelector('.modal-footer');
if (!footer) return;
@@ -645,17 +494,13 @@ class App {
}
}
/**
* Удалить кнопку удаления из модального окна
*/
// Удалить кнопку удаления из модального окна
removeDeleteButtonFromModal() {
const deleteBtn = document.querySelector('#modalDeleteBtn');
if (deleteBtn) deleteBtn.remove();
}
/**
* Подтверждение удаления сайта
*/
// Подтверждение удаления сайта
async deleteSiteConfirm() {
const index = window.currentEditIndex;
const site = this.sitesManager.sitesData[index];
@@ -685,15 +530,7 @@ class App {
}
notification.success('✅ Сайт успешно удалён!', 1500);
// Перезапускаем HTTP/HTTPS
notification.show('Перезапуск серверов...', 'success', 800);
await configAPI.stopHTTPService();
await configAPI.stopHTTPSService();
await sleep(500);
await configAPI.startHTTPService();
await configAPI.startHTTPSService();
await this.restartHttpServices();
notification.success('🚀 Серверы перезапущены!', 1000);
// Закрываем модальное окно и обновляем список
@@ -712,5 +549,3 @@ document.addEventListener('DOMContentLoaded', () => {
app.start();
});
log('vServer Admin Panel загружен');

View File

@@ -5,9 +5,7 @@
import { $ } from '../utils/dom.js';
/**
* Инициализация всех кастомных select'ов на странице
*/
// Инициализация всех кастомных select'ов на странице
export function initCustomSelects() {
const selects = document.querySelectorAll('select.form-input');
selects.forEach(select => {
@@ -17,9 +15,7 @@ export function initCustomSelects() {
});
}
/**
* Создать кастомный select из нативного
*/
// Создать кастомный select из нативного
function createCustomSelect(selectElement) {
// Помечаем как обработанный
selectElement.dataset.customized = 'true';
@@ -88,9 +84,7 @@ function createCustomSelect(selectElement) {
});
}
/**
* Открыть/закрыть dropdown
*/
// Открыть/закрыть dropdown
function toggleDropdown(wrapper) {
const isOpen = wrapper.classList.contains('open');
@@ -104,16 +98,12 @@ function toggleDropdown(wrapper) {
}
}
/**
* Закрыть dropdown
*/
// Закрыть dropdown
function closeDropdown(wrapper) {
wrapper.classList.remove('open');
}
/**
* Выбрать опцию
*/
// Выбрать опцию
function selectOption(selectElement, wrapper, optionElement, index) {
// Обновляем оригинальный select
selectElement.selectedIndex = index;

View File

@@ -5,9 +5,7 @@
import { $, addClass, removeClass } from '../utils/dom.js';
/**
* Класс для управления модальными окнами
*/
// Класс для управления модальными окнами
export class Modal {
constructor() {
this.overlay = $('modalOverlay');
@@ -46,39 +44,26 @@ export class Modal {
}
}
/**
* Открыть модальное окно
* @param {string} title - Заголовок
* @param {string} htmlContent - HTML контент
*/
// Открыть модальное окно
open(title, htmlContent) {
if (this.title) this.title.textContent = title;
if (this.content) this.content.innerHTML = htmlContent;
if (this.overlay) addClass(this.overlay, 'show');
}
/**
* Закрыть модальное окно
*/
// Закрыть модальное окно
close() {
if (this.overlay) removeClass(this.overlay, 'show');
}
/**
* Установить обработчик сохранения
* @param {Function} callback - Функция обратного вызова
*/
// Установить обработчик сохранения
onSave(callback) {
if (this.saveBtn) {
this.saveBtn.onclick = callback;
}
}
/**
* Открыть редактор поля
* @param {string} title - Заголовок
* @param {string} htmlContent - HTML контент
*/
// Открыть редактор поля
openFieldEditor(title, htmlContent) {
const fieldTitle = $('fieldEditorTitle');
const fieldContent = $('fieldEditorContent');
@@ -88,9 +73,7 @@ export class Modal {
if (this.fieldEditorOverlay) addClass(this.fieldEditorOverlay, 'show');
}
/**
* Закрыть редактор поля
*/
// Закрыть редактор поля
closeFieldEditor() {
if (this.fieldEditorOverlay) removeClass(this.fieldEditorOverlay, 'show');
}

View File

@@ -5,9 +5,7 @@
import { $, $$, hide, show, removeClass, addClass } from '../utils/dom.js';
/**
* Класс для управления навигацией
*/
// Класс для управления навигацией
export class Navigation {
constructor() {
this.navItems = $$('.nav-item');

View File

@@ -5,21 +5,14 @@
import { $, addClass, removeClass } from '../utils/dom.js';
/**
* Класс для управления уведомлениями
*/
// Класс для управления уведомлениями
export class NotificationManager {
constructor() {
this.container = $('notification');
this.loader = $('appLoader');
}
/**
* Показать уведомление
* @param {string} message - Текст сообщения
* @param {string} type - Тип (success, error)
* @param {number} duration - Длительность показа (мс)
*/
// Показать уведомление
show(message, type = 'success', duration = 1000) {
if (!this.container) return;
@@ -41,27 +34,17 @@ export class NotificationManager {
}, duration);
}
/**
* Показать успешное уведомление
* @param {string} message - Текст сообщения
* @param {number} duration - Длительность
*/
// Показать успешное уведомление
success(message, duration = 1000) {
this.show(message, 'success', duration);
}
/**
* Показать уведомление об ошибке
* @param {string} message - Текст сообщения
* @param {number} duration - Длительность
*/
// Показать уведомление об ошибке
error(message, duration = 2000) {
this.show(message, 'error', duration);
}
/**
* Скрыть загрузчик приложения
*/
// Скрыть загрузчик приложения
hideLoader() {
if (!this.loader) return;

View File

@@ -5,9 +5,7 @@
import { $, addClass } from '../utils/dom.js';
/**
* Класс для управления окном
*/
// Класс для управления окном
export class WindowControls {
constructor() {
this.minimizeBtn = $('minimizeBtn');

View File

@@ -3,47 +3,29 @@
Утилиты для работы с DOM
============================================ */
/**
* Получить элемент по ID
* @param {string} id - ID элемента
* @returns {HTMLElement|null}
*/
// Получить элемент по ID
export function $(id) {
return document.getElementById(id);
}
/**
* Получить все элементы по селектору
* @param {string} selector - CSS селектор
* @param {HTMLElement} parent - Родительский элемент
* @returns {NodeList}
*/
// Получить все элементы по селектору
export function $$(selector, parent = document) {
return parent.querySelectorAll(selector);
}
/**
* Показать элемент
* @param {HTMLElement|string} element - Элемент или ID
*/
// Показать элемент
export function show(element) {
const el = typeof element === 'string' ? $(element) : element;
if (el) el.style.display = 'block';
}
/**
* Скрыть элемент
* @param {HTMLElement|string} element - Элемент или ID
*/
// Скрыть элемент
export function hide(element) {
const el = typeof element === 'string' ? $(element) : element;
if (el) el.style.display = 'none';
}
/**
* Переключить видимость элемента
* @param {HTMLElement|string} element - Элемент или ID
*/
// Переключить видимость элемента
export function toggle(element) {
const el = typeof element === 'string' ? $(element) : element;
if (el) {
@@ -51,31 +33,19 @@ export function toggle(element) {
}
}
/**
* Добавить класс
* @param {HTMLElement|string} element - Элемент или ID
* @param {string} className - Имя класса
*/
// Добавить класс
export function addClass(element, className) {
const el = typeof element === 'string' ? $(element) : element;
if (el) el.classList.add(className);
}
/**
* Удалить класс
* @param {HTMLElement|string} element - Элемент или ID
* @param {string} className - Имя класса
*/
// Удалить класс
export function removeClass(element, className) {
const el = typeof element === 'string' ? $(element) : element;
if (el) el.classList.remove(className);
}
/**
* Переключить класс
* @param {HTMLElement|string} element - Элемент или ID
* @param {string} className - Имя класса
*/
// Переключить класс
export function toggleClass(element, className) {
const el = typeof element === 'string' ? $(element) : element;
if (el) el.classList.toggle(className);

View File

@@ -3,21 +3,12 @@
Вспомогательные функции
============================================ */
/**
* Ждёт указанное время
* @param {number} ms - Миллисекунды
* @returns {Promise}
*/
// Ждёт указанное время
export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Debounce функция
* @param {Function} func - Функция для debounce
* @param {number} wait - Время задержки
* @returns {Function}
*/
// Debounce функция
export function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
@@ -30,28 +21,9 @@ export function debounce(func, wait) {
};
}
/**
* Проверяет доступность Wails API
* @returns {boolean}
*/
// Проверяет доступность Wails API
export function isWailsAvailable() {
return typeof window.go !== 'undefined' &&
window.go?.admin?.App !== undefined;
}
/**
* Логирование с префиксом
* @param {string} message - Сообщение
* @param {string} type - Тип (log, error, warn, info)
*/
export function log(message, type = 'log') {
const prefix = '🚀 vServer:';
const styles = {
log: '✅',
error: '❌',
warn: '⚠️',
info: ''
};
console[type](`${prefix} ${styles[type]} ${message}`);
}

View File

@@ -692,6 +692,8 @@
</div>
</div>
<div id="templates-container"></div>
<script src="wailsjs/runtime/runtime.js"></script>
<script src="wailsjs/go/admin/App.js"></script>
<script type="module" src="assets/js/main.js"></script>

View File

@@ -0,0 +1,102 @@
<!-- Шаблон редактирования сайта -->
<template id="edit-site-template">
<div class="settings-form">
<div class="form-group">
<label class="form-label">Статус сайта:</label>
<div class="status-toggle">
<button class="status-btn" data-status="active" data-value="active">
<i class="fas fa-check-circle"></i> Active
</button>
<button class="status-btn" data-status="inactive" data-value="inactive">
<i class="fas fa-times-circle"></i> Inactive
</button>
</div>
</div>
<div class="form-group">
<label class="form-label">Название сайта:</label>
<input type="text" class="form-input" id="editName">
</div>
<div class="form-group">
<label class="form-label">Host:</label>
<input type="text" class="form-input" id="editHost">
</div>
<div class="form-group">
<label class="form-label">Alias:</label>
<div class="tag-input-wrapper">
<input type="text" class="form-input" id="editAliasInput" placeholder="Введите alias и нажмите Добавить...">
<button class="action-btn" onclick="addAliasTag()"><i class="fas fa-plus"></i> Добавить</button>
</div>
<div class="tags-container" id="aliasTagsContainer"></div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Root файл:</label>
<input type="text" class="form-input" id="editRootFile">
</div>
<div class="form-group">
<label class="form-label">Роутинг:</label>
<div class="toggle-wrapper">
<label class="toggle-switch">
<input type="checkbox" id="editRouting">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Включён</span>
</div>
</div>
</div>
</div>
</template>
<!-- Шаблон редактирования прокси -->
<template id="edit-proxy-template">
<div class="settings-form">
<div class="form-group">
<label class="form-label">Статус прокси:</label>
<div class="status-toggle">
<button class="status-btn" data-status="enable" data-value="enable">
<i class="fas fa-check-circle"></i> Включён
</button>
<button class="status-btn" data-status="disable" data-value="disable">
<i class="fas fa-times-circle"></i> Отключён
</button>
</div>
</div>
<div class="form-group">
<label class="form-label">Внешний домен:</label>
<input type="text" class="form-input" id="editDomain">
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Локальный адрес:</label>
<input type="text" class="form-input" id="editLocalAddr">
</div>
<div class="form-group">
<label class="form-label">Локальный порт:</label>
<input type="text" class="form-input" id="editLocalPort">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">HTTPS к сервису:</label>
<div class="toggle-wrapper">
<label class="toggle-switch">
<input type="checkbox" id="editServiceHTTPS">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Включён</span>
</div>
</div>
<div class="form-group">
<label class="form-label">Авто HTTPS:</label>
<div class="toggle-wrapper">
<label class="toggle-switch">
<input type="checkbox" id="editAutoHTTPS">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Включён</span>
</div>
</div>
</div>
</div>
</template>

View File

@@ -73,8 +73,16 @@ func Logs_file(type_log int, service string, message string, log_file string, co
colored_text := time.Now().Format("2006-01-02 15:04:05") + type_log_str + service_str + message
text := RemoveAnsiCodes(colored_text) + "\n"
// Создаём папку logs если её нет
logsDir := "WebServer/tools/logs"
if _, err := os.Stat(logsDir); os.IsNotExist(err) {
if err := os.MkdirAll(logsDir, 0755); err != nil {
log.Fatal(err)
}
}
// Открываем файл для дозаписи, создаём если нет, права на запись.
file, err := os.OpenFile("WebServer/tools/logs/"+log_files, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
file, err := os.OpenFile(logsDir+"/"+log_files, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}

BIN
vSerf.exe

Binary file not shown.