diff --git a/Backend/WebServer/https_server.go b/Backend/WebServer/https_server.go index ec08378..f831e85 100644 --- a/Backend/WebServer/https_server.go +++ b/Backend/WebServer/https_server.go @@ -39,7 +39,6 @@ func StartHTTPS() { serverName := chi.ServerName if serverName == "" { - tools.Logs_file(1, "HTTPS", "⚠️ Подключение без SNI (возможно по IP)", "logs_https.log", false) } else if cert, ok := certMap[serverName]; ok { // Найден точный сертификат для домена @@ -59,7 +58,6 @@ func StartHTTPS() { } if fallbackCert != nil { - tools.Logs_file(1, "HTTPS", "⚠️ Используем fallback-сертификат", "logs_https.log", false) return fallbackCert, nil } diff --git a/Backend/WebServer/php_server.go b/Backend/WebServer/php_server.go index 7b01f48..2554906 100644 --- a/Backend/WebServer/php_server.go +++ b/Backend/WebServer/php_server.go @@ -377,7 +377,6 @@ func PHPHandler(w http.ResponseWriter, r *http.Request, host string, originalURI return } - tools.Logs_file(0, "PHP", fmt.Sprintf("✅ FastCGI обработал: %s (порт %d)", phpPath, port), "logs_php.log", false) } // Streaming чтение FastCGI ответа с поддержкой SSE и chunked transfer diff --git a/Backend/WebServer/proxy_server.go b/Backend/WebServer/proxy_server.go index 62eaa88..b09795f 100644 --- a/Backend/WebServer/proxy_server.go +++ b/Backend/WebServer/proxy_server.go @@ -28,16 +28,9 @@ func isWebSocketRequest(r *http.Request) bool { // Проксирует WebSocket соединение func handleWebSocketProxy(w http.ResponseWriter, r *http.Request, proxyConfig config.Proxy_Service) bool { - // Определяем протокол для локального соединения - protocol := "ws" networkProtocol := "tcp" - if proxyConfig.ServiceHTTPSuse { - protocol = "wss" - } - targetAddr := proxyConfig.LocalAddress + ":" + proxyConfig.LocalPort - tools.Logs_file(0, "WS-PROXY", "🔌 WebSocket: "+r.RemoteAddr+" → "+protocol+"://"+targetAddr+r.URL.Path, "logs_proxy.log", false) // Захватываем клиентское соединение через Hijacker hijacker, ok := w.(http.Hijacker) @@ -135,7 +128,6 @@ func handleWebSocketProxy(w http.ResponseWriter, r *http.Request, proxyConfig co return true } - tools.Logs_file(0, "WS-PROXY", "✅ WebSocket установлен: "+r.Host+r.URL.Path, "logs_proxy.log", false) // Двунаправленное проксирование данных done := make(chan struct{}, 2) @@ -171,7 +163,6 @@ func handleWebSocketProxy(w http.ResponseWriter, r *http.Request, proxyConfig co // Ждём завершения одного из направлений <-done - tools.Logs_file(0, "WS-PROXY", "🔌 WebSocket закрыт: "+r.Host+r.URL.Path, "logs_proxy.log", false) return true } diff --git a/Backend/WebServer/vAccess.go b/Backend/WebServer/vAccess.go index f27d414..420ce85 100644 --- a/Backend/WebServer/vAccess.go +++ b/Backend/WebServer/vAccess.go @@ -388,32 +388,32 @@ func checkRules(rules []VAccessRule, requestPath string, r *http.Request, checkF if errorPage == "" { errorPage = "404" } - tools.Logs_file(1, logPrefix, "🚫 Доступ запрещён для "+getClientIP(r)+" к "+requestPath, logFile, false) - return false, errorPage + tools.Logs_file(1, logPrefix, "🚫 Доступ запрещён для "+getClientIP(r)+" к "+r.Host+requestPath, logFile, false) + return false, errorPage + } + // Все условия Allow выполнены - разрешаем доступ + return true, "" + + case "Disable": + // Disable правило: запрещаем если ЛЮБОЕ условие выполнено + shouldBlock := true + + // Для расширений файлов (только если проверка включена) + if checkFileExtensions && len(rule.TypeFile) > 0 && !fileMatches { + shouldBlock = false + } + + // Для IP адресов + if len(rule.IPList) > 0 && !ipMatches { + shouldBlock = false + } + + if shouldBlock { + errorPage := rule.UrlError + if errorPage == "" { + errorPage = "404" } - // Все условия Allow выполнены - разрешаем доступ - return true, "" - - case "Disable": - // Disable правило: запрещаем если ЛЮБОЕ условие выполнено - shouldBlock := true - - // Для расширений файлов (только если проверка включена) - if checkFileExtensions && len(rule.TypeFile) > 0 && !fileMatches { - shouldBlock = false - } - - // Для IP адресов - if len(rule.IPList) > 0 && !ipMatches { - shouldBlock = false - } - - if shouldBlock { - errorPage := rule.UrlError - if errorPage == "" { - errorPage = "404" - } - tools.Logs_file(1, logPrefix, "🚫 Доступ запрещён для "+getClientIP(r)+" к "+requestPath, logFile, false) + tools.Logs_file(1, logPrefix, "🚫 Доступ запрещён для "+getClientIP(r)+" к "+r.Host+requestPath, logFile, false) return false, errorPage } diff --git a/Backend/admin/go/sites/methods.go b/Backend/admin/go/sites/methods.go index 5d99a05..8b4cba1 100644 --- a/Backend/admin/go/sites/methods.go +++ b/Backend/admin/go/sites/methods.go @@ -42,7 +42,7 @@ func CreateNewSite(siteData SiteInfo) error { return fmt.Errorf("ошибка добавления в конфиг: %w", err) } - tools.Logs_file(0, "SITES", fmt.Sprintf("✅ Новый сайт создан: %s (%s)", siteData.Name, siteData.Host), "logs_config.log", true) + tools.Logs_file(0, "SITES", fmt.Sprintf("✅ Новый сайт создан: %s (%s)", siteData.Name, siteData.Host), "logs_error.log", true) return nil } @@ -91,7 +91,7 @@ func CreateSiteFolder(host string) error { return fmt.Errorf("не удалось создать папку: %w", err) } - tools.Logs_file(0, "SITES", fmt.Sprintf("📁 Создана папка: %s", folderPath), "logs_config.log", false) + tools.Logs_file(0, "SITES", fmt.Sprintf("📁 Создана папка: %s", folderPath), "logs_error.log", false) return nil } @@ -113,7 +113,7 @@ func CreateStarterFile(host, rootFile string) error { return fmt.Errorf("не удалось создать файл: %w", err) } - tools.Logs_file(0, "SITES", fmt.Sprintf("📄 Создан стартовый файл: %s", rootFile), "logs_config.log", false) + tools.Logs_file(0, "SITES", fmt.Sprintf("📄 Создан стартовый файл: %s", rootFile), "logs_error.log", false) return nil } @@ -142,7 +142,7 @@ func CreateVAccessFile(host string) error { return fmt.Errorf("не удалось создать vAccess.conf: %w", err) } - tools.Logs_file(0, "SITES", "🔒 Создан vAccess.conf", "logs_config.log", false) + tools.Logs_file(0, "SITES", "🔒 Создан vAccess.conf", "logs_error.log", false) return nil } @@ -166,7 +166,7 @@ func AddSiteToConfig(siteData SiteInfo) error { return err } - tools.Logs_file(0, "SITES", "💾 Конфигурация обновлена", "logs_config.log", false) + tools.Logs_file(0, "SITES", "💾 Конфигурация обновлена", "logs_error.log", false) return nil } @@ -227,7 +227,7 @@ func UploadSiteCertificate(host, certType string, certData []byte) error { return fmt.Errorf("не удалось сохранить сертификат: %w", err) } - tools.Logs_file(0, "SITES", fmt.Sprintf("🔒 Загружен сертификат: %s для %s", fileName, host), "logs_config.log", true) + tools.Logs_file(0, "SITES", fmt.Sprintf("🔒 Загружен сертификат: %s для %s", fileName, host), "logs_error.log", true) return nil } @@ -251,7 +251,7 @@ func DeleteSiteCertificates(host string) error { return fmt.Errorf("не удалось удалить папку сертификатов: %w", err) } - tools.Logs_file(0, "SITES", fmt.Sprintf("🗑️ Удалены сертификаты для: %s", host), "logs_config.log", true) + tools.Logs_file(0, "SITES", fmt.Sprintf("🗑️ Удалены сертификаты для: %s", host), "logs_error.log", true) return nil } @@ -281,13 +281,13 @@ func DeleteSite(host string) error { if err := os.RemoveAll(absSiteDir); err != nil { return fmt.Errorf("не удалось удалить папку сайта: %w", err) } - tools.Logs_file(0, "SITES", fmt.Sprintf("🗑️ Удалена папка сайта: %s", siteDir), "logs_config.log", false) + tools.Logs_file(0, "SITES", fmt.Sprintf("🗑️ Удалена папка сайта: %s", siteDir), "logs_error.log", false) } // 3. Удаляем сертификаты if err := DeleteSiteCertificates(host); err != nil { // Логируем ошибку, но продолжаем удаление - tools.Logs_file(1, "SITES", fmt.Sprintf("Ошибка удаления сертификатов: %v", err), "logs_config.log", false) + tools.Logs_file(1, "SITES", fmt.Sprintf("Ошибка удаления сертификатов: %v", err), "logs_error.log", false) } // 4. Удаляем из конфига @@ -301,7 +301,7 @@ func DeleteSite(host string) error { return fmt.Errorf("ошибка сохранения конфигурации: %w", err) } - tools.Logs_file(0, "SITES", fmt.Sprintf("✅ Сайт '%s' полностью удалён", host), "logs_config.log", true) + tools.Logs_file(0, "SITES", fmt.Sprintf("✅ Сайт '%s' полностью удалён", host), "logs_error.log", true) return nil } diff --git a/Backend/config/config.go b/Backend/config/config.go index 1a48987..089c28c 100644 --- a/Backend/config/config.go +++ b/Backend/config/config.go @@ -49,16 +49,13 @@ func LoadConfig() { data, err := os.ReadFile(ConfigPath) if err != nil { - tools.Logs_file(0, "JSON", "Ошибка загрузки конфигурационного файла", "logs_config.log", true) - } else { - tools.Logs_file(0, "JSON", "config.json успешно загружен", "logs_config.log", true) + tools.Logs_file(1, "JSON", "Ошибка загрузки конфигурационного файла", "logs_error.log", true) + return } err = json.Unmarshal(data, &ConfigData) if err != nil { - tools.Logs_file(0, "JSON", "Ошибка парсинга конфигурационного файла", "logs_config.log", true) - } else { - tools.Logs_file(0, "JSON", "config.json успешно прочитан", "logs_config.log", true) + tools.Logs_file(1, "JSON", "Ошибка парсинга конфигурационного файла", "logs_error.log", true) } // Миграция: добавляем новые поля если их нет @@ -116,7 +113,7 @@ func migrateConfig(originalData []byte) { // Если нужно обновить - сохраняем конфиг с новыми полями if needsSave { - tools.Logs_file(0, "JSON", "🔄 Миграция конфига: добавляем новые поля", "logs_config.log", true) + tools.Logs_file(0, "JSON", "🔄 Миграция конфига: добавляем новые поля", "logs_error.log", true) saveConfig() } } @@ -125,15 +122,15 @@ func migrateConfig(originalData []byte) { func saveConfig() { formattedJSON, err := json.MarshalIndent(ConfigData, "", " ") if err != nil { - tools.Logs_file(1, "JSON", "Ошибка форматирования конфига: "+err.Error(), "logs_config.log", true) + tools.Logs_file(1, "JSON", "Ошибка форматирования конфига: "+err.Error(), "logs_error.log", true) return } err = os.WriteFile(ConfigPath, formattedJSON, 0644) if err != nil { - tools.Logs_file(1, "JSON", "Ошибка сохранения конфига: "+err.Error(), "logs_config.log", true) + tools.Logs_file(1, "JSON", "Ошибка сохранения конфига: "+err.Error(), "logs_error.log", true) return } - tools.Logs_file(0, "JSON", "✅ Конфиг обновлён с новыми полями", "logs_config.log", true) + tools.Logs_file(0, "JSON", "✅ Конфиг обновлён с новыми полями", "logs_error.log", true) } diff --git a/Backend/tools/message.go b/Backend/tools/message.go index 887f2c6..50502dc 100644 --- a/Backend/tools/message.go +++ b/Backend/tools/message.go @@ -82,12 +82,23 @@ func Logs_file(type_log int, service string, message string, log_file string, co } // Открываем файл для дозаписи, создаём если нет, права на запись. - file, err := os.OpenFile(logsDir+"/"+log_files, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + filePath := logsDir + "/" + log_files + isNew := false + if _, err := os.Stat(filePath); os.IsNotExist(err) { + isNew = true + } + + file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Fatal(err) } defer file.Close() + // UTF-8 BOM для новых файлов (чтобы Windows корректно читал) + if isNew { + file.Write([]byte{0xEF, 0xBB, 0xBF}) + } + // Пишем строку в файл if _, err := file.WriteString(text); err != nil { log.Fatal(err) diff --git a/README.md b/README.md index 63bdff0..7ba0f48 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # 🚀 vServer - Функциональный веб-сервер на Go **🇺🇸 [English version](README_EN.md)** -> Функциональный веб-сервер с поддержкой HTTP/HTTPS, MySQL, PHP и GUI-админкой +> Функциональный веб-сервер с поддержкой HTTP/HTTPS, MySQL, PHP, Let's Encrypt и GUI-админкой **👨‍💻 Автор:** Суманеев Роман -**🌐 Сайт:** [voxsel.ru](https://voxsel.ru) +**🌐 Сайт:** [vserf.ru](https://vserf.ru) **📞 Контакт:** [VK](https://vk.com/felias) ## 🎯 Возможности @@ -13,287 +13,119 @@ ### 🌐 Веб-сервер - ✅ **HTTP/HTTPS** сервер с поддержкой SSL сертификатов -- ✅ **Proxy сервер** для проксирования запросов -- ✅ **PHP сервер** со встроенной поддержкой PHP 8 -- ✅ **Статический контент** для размещения веб-сайтов -- ✅ **vAccess** - система контроля доступа для сайтов и прокси +- ✅ **Let's Encrypt** — автоматический выпуск и обновление SSL +- ✅ **Proxy сервер** — обратный прокси для локальных сервисов +- ✅ **PHP 8** — встроенная поддержка (FastCGI пул) +- ✅ **MySQL** — встроенный сервер базы данных +- ✅ **vAccess** — система контроля доступа +- ✅ **Wildcard** — поддержка wildcard-алиасов и сертификатов -### 🗄️ База данных -- ✅ **MySQL сервер** с полной поддержкой - -### 🔧 Администрирование -- ✅ **GUI Админка** - Wails desktop приложение с современным интерфейсом -- ✅ **Управление сервисами** - запуск/остановка HTTP, HTTPS, MySQL, PHP, Proxy -- ✅ **Редактор сайтов и прокси** - визуальное управление конфигурацией -- ✅ **vAccess редактор** - настройка правил доступа через интерфейс +### 🎛️ GUI Админка +- ✅ **Управление сервисами** — запуск/остановка HTTP, HTTPS, MySQL, PHP, Proxy +- ✅ **Управление сайтами** — создание, редактирование, удаление +- ✅ **Управление прокси** — визуальная настройка reverse proxy +- ✅ **SSL менеджер** — выпуск, перевыпуск, загрузка сертификатов +- ✅ **vAccess редактор** — настройка правил доступа с drag-and-drop +- ✅ **Настройки** — конфигурация портов MySQL, PHP, прокси, ACME +- ✅ **Тёмная/Светлая тема** и **RU/EN** локализация ## 🏗️ Архитектура ``` vServer/ -├── 🎯 main.go # Точка входа основного сервера +├── 🎯 main.go # Точка входа (Wails) │ ├── 🔧 Backend/ # Основная логика -│ │ -│ ├── admin/ # | 🎛️ GUI Админка (Wails) | -│ │ ├── go/ # | Go backend для админки | -│ │ └── frontend/ # | Современный UI | -│ │ -│ ├── config/ # | 🔧 Конфигурационные файлы Go | -│ ├── tools/ # | 🛠️ Утилиты и хелперы | -│ └── WebServer/ # | 🌐 Модули веб-сервера | +│ ├── admin/ +│ │ ├── go/ # Go backend админки +│ │ └── API.md # Документация API +│ ├── config/ # Конфигурация Go +│ ├── tools/ # Утилиты и хелперы +│ └── WebServer/ # Модули веб-сервера +│ └── acme/ # Let's Encrypt │ -├── 🌐 WebServer/ # Веб-контент и конфигурация -│ │ -│ ├── cert/ # | 🔐 SSL сертификаты | -│ ├── soft/ # | 📦 MySQL и PHP | -│ ├── tools/ # | 📊 Логи и инструменты | -│ └── www/ # | 🌍 Веб-контент | +├── 🖥️ front_vue/ # Vue 3 фронтенд админки +│ └── src/ +│ ├── Core/ # API, stores, i18n, router +│ └── Design/ # Компоненты, views, стили │ -├── 📄 go.mod # Go модули -├── 🔨 build_admin.ps1 # Сборка GUI админки -└── 🚀 vSerf.exe # GUI админка (после сборки) +├── 🌐 WebServer/ # Рабочие файлы сервера +│ ├── config.json # Конфигурация +│ ├── cert/ # SSL сертификаты +│ ├── soft/ # MySQL и PHP +│ ├── tools/ # Логи, error page, vAccess +│ └── www/ # Веб-контент (сайты) +│ +├── 🔨 build_admin.ps1 # Скрипт сборки +└── 🚀 vSerf.exe # Готовое приложение ``` ## 🚀 Установка и запуск -### 🔨 Сборка основного сервера +### Для пользователей + +1. Скачайте последний [релиз](https://github.com/AiVoxel/vServer/releases) +2. Распакуйте архив `WebServer/soft/soft.rar` в папку `WebServer/soft/` +3. Запустите `vSerf.exe` — откроется GUI админка +4. Сервер стартует автоматически, управляйте через интерфейс + +> 🔑 **Пароль MySQL по умолчанию:** `root` + +### Для разработчиков + ```powershell +# Сборка (проверит/установит Go, Node.js, Wails) ./build_admin.ps1 ``` Скрипт автоматически: -- Проверит/создаст `go.mod` -- Установит зависимости (`go mod tidy`) -- Проверит/установит Wails CLI -- Соберёт приложение → `vSerf.exe` +- Проверит зависимости (Go, Node.js, npm) — предложит установить через `winget` +- Установит Go модули и Wails CLI +- Соберёт Vue фронтенд +- Скомпилирует → `vSerf.exe` -### 📦 Подготовка компонентов -1. Распакуйте архив `WebServer/soft/soft.rar` в папку `WebServer/soft/` -2. Запустите `vServer.exe` - основной сервер -3. Запустите `vSerf.exe` - GUI админка для управления +## 🔒 vAccess — Контроль доступа -> 🔑 **Важно:** Пароль MySQL по умолчанию - `root` +Гибкая система правил для сайтов и прокси. Настраивается через GUI админку (раздел vAccess). -### 📦 Готовый проект для пользователя -Для работы необходимы: -- 📄 `vSerf.exe` - GUI админка (опционально) -- 📁 `WebServer/` - конфигурация и ресурсы - -> 💡 Папка `Backend/` и файлы `go.mod`, `main.go` нужны только для разработки - -## ⚙️ Конфигурация - -Настройка через `WebServer/config.json`: - -```json -{ - "Site_www": [ - { - "name": "Локальный сайт", - "host": "127.0.0.1", - "alias": ["localhost"], - "status": "active", - "root_file": "index.html", - "root_file_routing": true - } - ], - "Proxy_Service": [ - { - "Enable": true, - "ExternalDomain": "git.example.ru", - "LocalAddress": "127.0.0.1", - "LocalPort": "3333", - "ServiceHTTPSuse": false, - "AutoHTTPS": true - } - ], - "Soft_Settings": { - "mysql_port": 3306, "mysql_host": "127.0.0.1", - "php_port": 8000, "php_host": "localhost", - "proxy_enabled": true - } -} -``` - -**Основные параметры:** -- `Site_www` - настройки веб-сайтов -- `Proxy_Service` - конфигурация прокси-сервисов -- `Soft_Settings` - порты и хосты сервисов (MySQL, PHP, proxy_enabled) - -### 🌐 Alias с поддержкой Wildcard - -Для сайтов поддерживается wildcard (`*`) в алиасах: - -```json -{ - "alias": [ - "*.test.ru", // Все поддомены voxsel.ru - "*.test.com", // Все поддомены voxsel.com - "test.com", // Точное совпадение - "api.*" // api с любой зоной - ], - "host": "test.ru" -} -``` - -**Примеры работы wildcard:** -- `*.example.com` → `api.example.com`, `admin.example.com`, `test.example.com` ✅ -- `example.*` → `example.com`, `example.ru`, `example.org` ✅ -- `*example.com` → `test-example.com`, `my-example.com` ✅ -- `*` → любой домен ✅ (осторожно!) -- `example.com` → только `example.com` ✅ (без wildcard) - -### 🔄 Прокси-сервер - -Прокси-сервер позволяет перенаправлять внешние запросы на локальные сервисы. - -**Параметры Proxy_Service:** -- `Enable` - включить/отключить прокси (true/false) -- `ExternalDomain` - внешний домен для перехвата запросов -- `LocalAddress` - локальный адрес сервиса -- `LocalPort` - порт локального сервиса -- `ServiceHTTPSuse` - использовать HTTPS для подключения к локальному сервису (true/false) -- `AutoHTTPS` - автоматически перенаправлять HTTP → HTTPS (true/false) - -**Пример множественных прокси:** -```json -"Proxy_Service": [ - { - "Enable": true, - "ExternalDomain": "git.example.com", - "LocalAddress": "127.0.0.1", - "LocalPort": "3000", - "ServiceHTTPSuse": false, - "AutoHTTPS": true - }, - { - "Enable": false, - "ExternalDomain": "api.example.com", - "LocalAddress": "127.0.0.1", - "LocalPort": "8080", - "ServiceHTTPSuse": false, - "AutoHTTPS": false - } -] -``` - -#### 📖 Подробное описание параметров: - -**`ServiceHTTPSuse`** - протокол подключения к локальному сервису: -- `false` - vServer подключается к локальному сервису по HTTP (по умолчанию) -- `true` - vServer подключается к локальному сервису по HTTPS - -**`AutoHTTPS`** - автоматический редирект на HTTPS: -- `true` - все HTTP запросы автоматически перенаправляются на HTTPS (рекомендуется) -- `false` - разрешены как HTTP, так и HTTPS запросы - -**Схема работы:** -``` -Клиент (HTTP/HTTPS) → vServer (проверка AutoHTTPS) → Локальный сервис (ServiceHTTPSuse) -``` - -**Применение изменений:** -- Введите команду `config_reload` в консоли для перезагрузки конфигурации -- Изменения применятся к новым запросам без перезапуска сервера - -## 🔒 vAccess - Система контроля доступа - -vServer включает гибкую систему контроля доступа **vAccess** для сайтов и прокси-сервисов. - -### 📁 Расположение конфигураций - -**Для сайтов:** -``` -WebServer/www/{host}/vAccess.conf -``` - -**Для прокси:** -``` -WebServer/tools/Proxy_vAccess/{domain}_vAccess.conf -``` - -### ⚙️ Основные возможности - -- ✅ **IP-фильтрация** - разрешение/блокировка по IP адресам -- ✅ **Контроль путей** - ограничение доступа к определённым директориям -- ✅ **Фильтрация файлов** - блокировка по расширениям (*.php, *.exe) -- ✅ **Исключения** - гибкие правила с exceptions_dir -- ✅ **Кастомные ошибки** - редиректы или страницы ошибок - -### 📝 Пример конфигурации - -```conf -# Разрешаем админку только с локальных IP -type: Allow -path_access: /admin/*, /api/admin/* -ip_list: 127.0.0.1, 192.168.1.100 -url_error: 404 - -# Блокируем опасные файлы в uploads -type: Disable -type_file: *.php, *.exe, *.sh -path_access: /uploads/* -url_error: 404 -``` - -### 📚 Документация - -Подробная документация по vAccess: -- **Для сайтов:** см. `WebServer/www/{host}/vAccess.conf` (примеры в файле) -- **Для прокси:** см. `WebServer/tools/Proxy_vAccess/README.md` - -## 📝 Логирование - -Все логи сохраняются в `WebServer/tools/logs/`: - -- 🌐 `logs_http.log` - HTTP запросы (включая прокси P-HTTP) -- 🔒 `logs_https.log` - HTTPS запросы (включая прокси P-HTTPS) -- 🔄 `logs_proxy.log` - Ошибки прокси-сервера -- 🗄️ `logs_mysql.log` - MySQL операции -- 🐘 `logs_php.log` - PHP ошибки -- ⚙️ `logs_config.log` - Конфигурация -- 🔐 `logs_vaccess.log` - Контроль доступа для сайтов -- 🔐 `logs_vaccess_proxy.log` - Контроль доступа для прокси +**Возможности:** +- IP-фильтрация — разрешение/блокировка по IP +- Контроль путей — ограничение доступа к директориям +- Фильтрация файлов — блокировка по расширениям +- Исключения — пути, к которым правило не применяется +- Кастомные ошибки — редиректы или страницы ошибок ## 🔐 SSL Сертификаты -### Установка сертификата - -1. Откройте каталог `WebServer/` -2. Создайте папку `cert/` (если её нет) -3. Создайте папку с именем вашего домена или IP-адреса -4. Поместите в неё файлы сертификатов с **точными** именами: - ``` - certificate.crt - private.key - ca_bundle.crt - ``` -5. Сертификат будет автоматически загружен при запуске сервера - -### 📁 Структура сертификатов +### Автоматически (Let's Encrypt) +Включите ACME в настройках → сертификаты выпускаются и обновляются автоматически. +### Вручную +Загрузите через GUI админку (раздел SSL менеджер) или поместите файлы: ``` -WebServer/ -└── cert/ - ├── example.com/ # Основной домен - │ ├── certificate.crt - │ ├── private.key - │ └── ca_bundle.crt - │ - └── sub.example.com/ # Поддомен (опционально) - ├── certificate.crt - ├── private.key - └── ca_bundle.crt +WebServer/cert/{domain}/ +├── certificate.crt +├── private.key +└── ca_bundle.crt ``` -### 🎯 Работа с поддоменами +> 💡 **Wildcard:** один сертификат в папке основного домена покрывает все поддомены. -**Важно:** Если для поддомена не создана отдельная папка в `cert/`, то автоматически будет использоваться сертификат родительского домена. +## 📝 Логирование -**Примеры:** -- ✅ Запрос к `example.com` → использует сертификат из `cert/example.com/` -- ✅ Запрос к `sub.example.com` (папка существует) → использует `cert/sub.example.com/` -- ✅ Запрос к `sub.example.com` (папка НЕ существует) → использует `cert/example.com/` +Логи в `WebServer/tools/logs/`: -**Это удобно для wildcard-сертификатов:** достаточно одного сертификата в папке основного домена для всех поддоменов! 🌟 \ No newline at end of file +| Файл | Содержимое | +|------|------------| +| `logs_http.log` | HTTP запросы | +| `logs_https.log` | HTTPS запросы | +| `logs_proxy.log` | Ошибки прокси | +| `logs_mysql.log` | MySQL операции | +| `logs_php.log` | PHP ошибки | +| `logs_config.log` | Конфигурация | +| `logs_vaccess.log` | Контроль доступа | +| `logs_acme.log` | Let's Encrypt | + +## 📡 API + +Документация всех методов API админки: [`Backend/admin/API.md`](Backend/admin/API.md) diff --git a/README_EN.md b/README_EN.md index 120e224..043979f 100644 --- a/README_EN.md +++ b/README_EN.md @@ -1,9 +1,9 @@ # 🚀 vServer - Functional Web Server on Go **🇷🇺 [Русская версия](README.md)** -> Full-featured web server with HTTP/HTTPS, MySQL, PHP support and GUI admin panel +> Full-featured web server with HTTP/HTTPS, MySQL, PHP, Let's Encrypt and GUI admin panel **👨‍💻 Author:** Roman Sumaneev -**🌐 Website:** [voxsel.ru](https://voxsel.ru) +**🌐 Website:** [vserf.ru](https://vserf.ru) **📞 Contact:** [VK](https://vk.com/felias) ## 🎯 Features @@ -13,288 +13,119 @@ ### 🌐 Web Server - ✅ **HTTP/HTTPS** server with SSL certificate support -- ✅ **Proxy server** for request proxying -- ✅ **PHP server** with built-in PHP 8 support -- ✅ **Static content** for hosting websites -- ✅ **vAccess** - access control system for sites and proxies +- ✅ **Let's Encrypt** — automatic SSL issuance and renewal +- ✅ **Proxy server** — reverse proxy for local services +- ✅ **PHP 8** — built-in support (FastCGI pool) +- ✅ **MySQL** — built-in database server +- ✅ **vAccess** — access control system +- ✅ **Wildcard** — wildcard aliases and certificates support -### 🗄️ Database -- ✅ **MySQL server** with full support - -### 🔧 Administration -- ✅ **GUI Admin Panel** - Wails desktop application with modern interface -- ✅ **Service Management** - start/stop HTTP, HTTPS, MySQL, PHP, Proxy -- ✅ **Site and Proxy Editor** - visual configuration management -- ✅ **vAccess Editor** - access rules configuration through interface +### 🎛️ GUI Admin Panel +- ✅ **Service management** — start/stop HTTP, HTTPS, MySQL, PHP, Proxy +- ✅ **Site management** — create, edit, delete +- ✅ **Proxy management** — visual reverse proxy configuration +- ✅ **SSL manager** — issue, renew, upload certificates +- ✅ **vAccess editor** — access rules with drag-and-drop +- ✅ **Settings** — MySQL, PHP, proxy, ACME configuration +- ✅ **Dark/Light theme** and **RU/EN** localization ## 🏗️ Architecture ``` vServer/ -├── 🎯 main.go # Main server entry point +├── 🎯 main.go # Entry point (Wails) │ ├── 🔧 Backend/ # Core logic -│ │ -│ ├── admin/ # | 🎛️ GUI Admin Panel (Wails) | -│ │ ├── go/ # | Go backend for admin panel | -│ │ └── frontend/ # | Modern UI | -│ │ -│ ├── config/ # | 🔧 Go configuration files | -│ ├── tools/ # | 🛠️ Utilities and helpers | -│ └── WebServer/ # | 🌐 Web server modules | +│ ├── admin/ +│ │ ├── go/ # Go admin backend +│ │ └── API.md # API documentation +│ ├── config/ # Go configuration +│ ├── tools/ # Utilities and helpers +│ └── WebServer/ # Web server modules +│ └── acme/ # Let's Encrypt │ -├── 🌐 WebServer/ # Web content and configuration -│ │ -│ ├── cert/ # | 🔐 SSL certificates | -│ ├── soft/ # | 📦 MySQL and PHP | -│ ├── tools/ # | 📊 Logs and tools | -│ └── www/ # | 🌍 Web content | +├── 🖥️ front_vue/ # Vue 3 admin frontend +│ └── src/ +│ ├── Core/ # API, stores, i18n, router +│ └── Design/ # Components, views, styles │ -├── 📄 go.mod # Go modules -├── 🔨 build_admin.ps1 # Build GUI admin panel -└── 🚀 vSerf.exe # GUI admin panel (after build) +├── 🌐 WebServer/ # Server working files +│ ├── config.json # Configuration +│ ├── cert/ # SSL certificates +│ ├── soft/ # MySQL and PHP +│ ├── tools/ # Logs, error page, vAccess +│ └── www/ # Web content (sites) +│ +├── 🔨 build_admin.ps1 # Build script +└── 🚀 vSerf.exe # Built application ``` ## 🚀 Installation and Launch -### 🔨 Building the Main Server +### For Users + +1. Download the latest [release](https://github.com/AiVoxel/vServer/releases) +2. Extract `WebServer/soft/soft.rar` to `WebServer/soft/` +3. Run `vSerf.exe` — the GUI admin panel will open +4. Server starts automatically, manage everything through the interface + +> 🔑 **Default MySQL password:** `root` + +### For Developers + ```powershell +# Build (checks/installs Go, Node.js, Wails) ./build_admin.ps1 ``` -The script will automatically: -- Check/create `go.mod` -- Install dependencies (`go mod tidy`) -- Check/install Wails CLI -- Build the application → `vSerf.exe` +The script automatically: +- Checks dependencies (Go, Node.js, npm) — offers to install via `winget` +- Installs Go modules and Wails CLI +- Builds Vue frontend +- Compiles → `vSerf.exe` -### 📦 Component Preparation -1. Extract `WebServer/soft/soft.rar` archive to `WebServer/soft/` folder -2. Run `vServer.exe` - main server -3. Run `vSerf.exe` - GUI admin panel for management +## 🔒 vAccess — Access Control -> 🔑 **Important:** Default MySQL password is `root` +Flexible rules system for sites and proxies. Configured through GUI admin panel (vAccess section). -### 📦 Ready Project for Users -Required for operation: -- 📄 `vSerf.exe` - GUI admin panel (optional) -- 📁 `WebServer/` - configuration and resources - -> 💡 The `Backend/` folder and `go.mod`, `main.go` files are only needed for development - -## ⚙️ Configuration - -Configuration via `WebServer/config.json`: - -```json -{ - "Site_www": [ - { - "name": "Local Site", - "host": "127.0.0.1", - "alias": ["localhost"], - "status": "active", - "root_file": "index.html", - "root_file_routing": true - } - ], - "Proxy_Service": [ - { - "Enable": true, - "ExternalDomain": "git.example.com", - "LocalAddress": "127.0.0.1", - "LocalPort": "3333", - "ServiceHTTPSuse": false, - "AutoHTTPS": true - } - ], - "Soft_Settings": { - "mysql_port": 3306, "mysql_host": "127.0.0.1", - "php_port": 8000, "php_host": "localhost", - "proxy_enabled": true - } -} -``` - -**Main Parameters:** -- `Site_www` - website settings -- `Proxy_Service` - proxy service configuration -- `Soft_Settings` - service ports and hosts (MySQL, PHP, proxy_enabled) - -### 🌐 Alias with Wildcard Support - -Wildcard (`*`) support in aliases for sites: - -```json -{ - "alias": [ - "*.test.com", // All subdomains of test.com - "*.test.ru", // All subdomains of test.ru - "test.com", // Exact match - "api.*" // api with any zone - ], - "host": "test.com" -} -``` - -**Wildcard Examples:** -- `*.example.com` → `api.example.com`, `admin.example.com`, `test.example.com` ✅ -- `example.*` → `example.com`, `example.ru`, `example.org` ✅ -- `*example.com` → `test-example.com`, `my-example.com` ✅ -- `*` → any domain ✅ (use carefully!) -- `example.com` → only `example.com` ✅ (without wildcard) - -### 🔄 Proxy Server - -The proxy server allows redirecting external requests to local services. - -**Proxy_Service Parameters:** -- `Enable` - enable/disable proxy (true/false) -- `ExternalDomain` - external domain for request interception -- `LocalAddress` - local service address -- `LocalPort` - local service port -- `ServiceHTTPSuse` - use HTTPS for connecting to local service (true/false) -- `AutoHTTPS` - automatically redirect HTTP → HTTPS (true/false) - -**Multiple Proxy Example:** -```json -"Proxy_Service": [ - { - "Enable": true, - "ExternalDomain": "git.example.com", - "LocalAddress": "127.0.0.1", - "LocalPort": "3000", - "ServiceHTTPSuse": false, - "AutoHTTPS": true - }, - { - "Enable": false, - "ExternalDomain": "api.example.com", - "LocalAddress": "127.0.0.1", - "LocalPort": "8080", - "ServiceHTTPSuse": false, - "AutoHTTPS": false - } -] -``` - -#### 📖 Detailed Parameter Description: - -**`ServiceHTTPSuse`** - protocol for connecting to local service: -- `false` - vServer connects to local service via HTTP (default) -- `true` - vServer connects to local service via HTTPS - -**`AutoHTTPS`** - automatic HTTPS redirect: -- `true` - all HTTP requests are automatically redirected to HTTPS (recommended) -- `false` - both HTTP and HTTPS requests are allowed - -**How it Works:** -``` -Client (HTTP/HTTPS) → vServer (AutoHTTPS check) → Local Service (ServiceHTTPSuse) -``` - -**Applying Changes:** -- Enter `config_reload` command in console to reload configuration -- Changes will apply to new requests without server restart - -## 🔒 vAccess - Access Control System - -vServer includes a flexible access control system **vAccess** for sites and proxy services. - -### 📁 Configuration Locations - -**For Sites:** -``` -WebServer/www/{host}/vAccess.conf -``` - -**For Proxy:** -``` -WebServer/tools/Proxy_vAccess/{domain}_vAccess.conf -``` - -### ⚙️ Main Features - -- ✅ **IP Filtering** - allow/block by IP addresses -- ✅ **Path Control** - restrict access to specific directories -- ✅ **File Filtering** - block by extensions (*.php, *.exe) -- ✅ **Exceptions** - flexible rules with exceptions_dir -- ✅ **Custom Errors** - redirects or error pages - -### 📝 Configuration Example - -```conf -# Allow admin panel only from local IPs -type: Allow -path_access: /admin/*, /api/admin/* -ip_list: 127.0.0.1, 192.168.1.100 -url_error: 404 - -# Block dangerous files in uploads -type: Disable -type_file: *.php, *.exe, *.sh -path_access: /uploads/* -url_error: 404 -``` - -### 📚 Documentation - -Detailed vAccess documentation: -- **For Sites:** see `WebServer/www/{host}/vAccess.conf` (examples in file) -- **For Proxy:** see `WebServer/tools/Proxy_vAccess/README.md` - -## 📝 Logging - -All logs are saved in `WebServer/tools/logs/`: - -- 🌐 `logs_http.log` - HTTP requests (including proxy P-HTTP) -- 🔒 `logs_https.log` - HTTPS requests (including proxy P-HTTPS) -- 🔄 `logs_proxy.log` - Proxy server errors -- 🗄️ `logs_mysql.log` - MySQL operations -- 🐘 `logs_php.log` - PHP errors -- ⚙️ `logs_config.log` - Configuration -- 🔐 `logs_vaccess.log` - Access control for sites -- 🔐 `logs_vaccess_proxy.log` - Access control for proxy +**Features:** +- IP filtering — allow/block by IP addresses +- Path control — restrict access to directories +- File filtering — block by extensions +- Exceptions — paths excluded from rules +- Custom errors — redirects or error pages ## 🔐 SSL Certificates -### Certificate Installation - -1. Open `WebServer/` directory -2. Create `cert/` folder (if it doesn't exist) -3. Create a folder with your domain name or IP address -4. Place certificate files with **exact** names: - ``` - certificate.crt - private.key - ca_bundle.crt - ``` -5. Certificate will be automatically loaded at server startup - -### 📁 Certificate Structure +### Automatic (Let's Encrypt) +Enable ACME in settings → certificates are issued and renewed automatically. +### Manual +Upload through GUI admin panel (SSL Manager section) or place files: ``` -WebServer/ -└── cert/ - ├── example.com/ # Main domain - │ ├── certificate.crt - │ ├── private.key - │ └── ca_bundle.crt - │ - └── sub.example.com/ # Subdomain (optional) - ├── certificate.crt - ├── private.key - └── ca_bundle.crt +WebServer/cert/{domain}/ +├── certificate.crt +├── private.key +└── ca_bundle.crt ``` -### 🎯 Working with Subdomains +> 💡 **Wildcard:** one certificate in the main domain folder covers all subdomains. -**Important:** If no separate folder is created in `cert/` for a subdomain, the parent domain's certificate will be used automatically. +## 📝 Logging -**Examples:** -- ✅ Request to `example.com` → uses certificate from `cert/example.com/` -- ✅ Request to `sub.example.com` (folder exists) → uses `cert/sub.example.com/` -- ✅ Request to `sub.example.com` (folder does NOT exist) → uses `cert/example.com/` +Logs in `WebServer/tools/logs/`: -**This is convenient for wildcard certificates:** one certificate in the main domain folder is enough for all subdomains! 🌟 +| File | Contents | +|------|----------| +| `logs_http.log` | HTTP requests | +| `logs_https.log` | HTTPS requests | +| `logs_proxy.log` | Proxy errors | +| `logs_mysql.log` | MySQL operations | +| `logs_php.log` | PHP errors | +| `logs_config.log` | Configuration | +| `logs_vaccess.log` | Access control | +| `logs_acme.log` | Let's Encrypt | +## 📡 API + +Full admin API documentation: [`Backend/admin/API.md`](Backend/admin/API.md) diff --git a/front_vue/src/Design/assets/css/base.css b/front_vue/src/Design/assets/css/base.css index e6d55c6..2ce1699 100644 --- a/front_vue/src/Design/assets/css/base.css +++ b/front_vue/src/Design/assets/css/base.css @@ -44,7 +44,7 @@ code { font-family: var(--font-mono); background: rgba(var(--accent-rgb), 0.1); padding: 3px 8px; - border-radius: var(--radius-sm); + border-radius: var(--radius); font-size: var(--text-sm); color: var(--accent-purple-light); border: 1px solid var(--glass-border); diff --git a/front_vue/src/Design/assets/css/forms.css b/front_vue/src/Design/assets/css/forms.css index bced6da..6efa8c8 100644 --- a/front_vue/src/Design/assets/css/forms.css +++ b/front_vue/src/Design/assets/css/forms.css @@ -9,7 +9,7 @@ .form-section { padding: var(--space-xl); background: rgba(var(--accent-rgb), 0.02); - border-radius: var(--radius-xl); + border-radius: var(--radius); border: 1px solid var(--glass-border); transition: all var(--transition-base); } @@ -83,7 +83,7 @@ padding: 10px var(--space-md); background: rgba(var(--muted-rgb), 0.1); border: 1px solid rgba(var(--muted-rgb), 0.3); - border-radius: var(--radius-md); + border-radius: var(--radius); color: var(--text-muted); font-size: var(--text-base); font-weight: var(--font-semibold); diff --git a/front_vue/src/Design/assets/css/tables.css b/front_vue/src/Design/assets/css/tables.css index 16da6d2..3c0967d 100644 --- a/front_vue/src/Design/assets/css/tables.css +++ b/front_vue/src/Design/assets/css/tables.css @@ -34,7 +34,7 @@ background: var(--glass-bg-light); backdrop-filter: var(--backdrop-blur); border: 1px solid var(--glass-border); - border-radius: var(--radius-xl); + border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-md); } @@ -121,7 +121,7 @@ width: 22px; height: 22px; margin-right: 8px; - border-radius: var(--radius-sm); + border-radius: var(--radius); font-size: 12px; } @@ -154,7 +154,7 @@ justify-content: center; background: var(--btn-icon-bg); border: 1px solid var(--btn-icon-border); - border-radius: var(--radius-md); + border-radius: var(--radius); color: var(--btn-icon-color); font-size: var(--text-md); cursor: pointer; diff --git a/front_vue/src/Design/assets/css/themes/dark.css b/front_vue/src/Design/assets/css/themes/dark.css index 84ec903..a74c850 100644 --- a/front_vue/src/Design/assets/css/themes/dark.css +++ b/front_vue/src/Design/assets/css/themes/dark.css @@ -58,9 +58,9 @@ --table-border: rgba(255, 255, 255, 0.05); /* Компоненты */ - --card-hover-shadow: 0 8px 32px rgba(139, 92, 246, 0.3); - --card-border-hover: rgba(139, 92, 246, 0.3); - --service-card-gradient: linear-gradient(90deg, #8b5cf6, #a78bfa, #06b6d4); + --card-hover-shadow: 0 4px 16px rgba(139, 92, 246, 0.12); + --card-border-hover: rgba(139, 92, 246, 0.2); + --service-card-gradient: linear-gradient(90deg, rgba(139, 92, 246, 0.6), rgba(167, 139, 250, 0.6), rgba(6, 182, 212, 0.6)); --btn-icon-bg: rgba(139, 92, 246, 0.1); --btn-icon-border: rgba(139, 92, 246, 0.3); --btn-icon-color: #a78bfa; diff --git a/front_vue/src/Design/assets/css/variables.css b/front_vue/src/Design/assets/css/variables.css index 884db87..edd189d 100644 --- a/front_vue/src/Design/assets/css/variables.css +++ b/front_vue/src/Design/assets/css/variables.css @@ -13,10 +13,7 @@ --space-3xl: 60px; /* Border Radius */ - --radius-sm: 6px; - --radius-md: 8px; - --radius-lg: 12px; - --radius-xl: 16px; + --radius: 8px; --radius-full: 50%; /* Transitions */ diff --git a/front_vue/src/Design/components/layout/Breadcrumbs.vue b/front_vue/src/Design/components/layout/Breadcrumbs.vue index 5e5fd4d..9e1f696 100644 --- a/front_vue/src/Design/components/layout/Breadcrumbs.vue +++ b/front_vue/src/Design/components/layout/Breadcrumbs.vue @@ -39,7 +39,7 @@ const goBack = () => { margin-bottom: var(--space-md); padding: var(--space-md) 20px; background: rgba(var(--accent-rgb), 0.05); - border-radius: var(--radius-lg); + border-radius: var(--radius); border: 1px solid var(--glass-border); } @@ -61,7 +61,7 @@ const goBack = () => { background: none; border: none; padding: var(--space-sm) var(--space-lg); - border-radius: var(--radius-md); + border-radius: var(--radius); cursor: pointer; transition: all var(--transition-base); display: flex; diff --git a/front_vue/src/Design/components/layout/PageHeader.vue b/front_vue/src/Design/components/layout/PageHeader.vue index 2208e68..f48b66a 100644 --- a/front_vue/src/Design/components/layout/PageHeader.vue +++ b/front_vue/src/Design/components/layout/PageHeader.vue @@ -29,7 +29,7 @@ defineProps({ margin-bottom: var(--space-md); padding: var(--space-lg); background: rgba(var(--accent-rgb), 0.03); - border-radius: var(--radius-xl); + border-radius: var(--radius); border: 1px solid var(--glass-border); } diff --git a/front_vue/src/Design/components/layout/Sidebar.vue b/front_vue/src/Design/components/layout/Sidebar.vue index 62bf678..e7ea541 100644 --- a/front_vue/src/Design/components/layout/Sidebar.vue +++ b/front_vue/src/Design/components/layout/Sidebar.vue @@ -60,7 +60,7 @@ const navigate = (item) => { justify-content: center; background: transparent; border: none; - border-radius: var(--radius-lg); + border-radius: var(--radius); color: var(--nav-color); font-size: 20px; cursor: pointer; diff --git a/front_vue/src/Design/components/layout/TitleBar.vue b/front_vue/src/Design/components/layout/TitleBar.vue index c5f6e25..1da07f3 100644 --- a/front_vue/src/Design/components/layout/TitleBar.vue +++ b/front_vue/src/Design/components/layout/TitleBar.vue @@ -131,7 +131,7 @@ const windowClose = () => { if (isWails) window.runtime.Quit() } align-items: center; gap: var(--space-sm); padding: var(--space-xs) var(--space-md); - border-radius: var(--radius-lg); + border-radius: var(--radius); background: var(--glass-bg); } @@ -173,7 +173,7 @@ const windowClose = () => { if (isWails) window.runtime.Quit() } gap: var(--space-sm); padding: var(--space-xs) var(--space-md); border: 1px solid var(--glass-border); - border-radius: var(--radius-md); + border-radius: var(--radius); background: var(--glass-bg); color: var(--text-primary); cursor: pointer; @@ -194,7 +194,7 @@ const windowClose = () => { if (isWails) window.runtime.Quit() } width: 32px; height: 32px; border: none; - border-radius: var(--radius-md); + border-radius: var(--radius); background: transparent; color: var(--text-secondary); cursor: pointer; @@ -208,7 +208,7 @@ const windowClose = () => { if (isWails) window.runtime.Quit() } width: 32px; height: 32px; border: none; - border-radius: var(--radius-md); + border-radius: var(--radius); background: transparent; color: var(--text-secondary); cursor: pointer; diff --git a/front_vue/src/Design/components/proxies/ProxiesTable.vue b/front_vue/src/Design/components/proxies/ProxiesTable.vue index 7d43501..33360e9 100644 --- a/front_vue/src/Design/components/proxies/ProxiesTable.vue +++ b/front_vue/src/Design/components/proxies/ProxiesTable.vue @@ -82,7 +82,7 @@ const findCertForDomain = (domain) => { - @@ -235,7 +229,7 @@ const formatList = (arr) => { padding: 10px 18px; background: transparent; border: none; - border-radius: var(--radius-md); + border-radius: var(--radius); color: var(--text-muted); font-size: var(--text-base); font-weight: var(--font-medium); @@ -302,6 +296,12 @@ const formatList = (arr) => { border-bottom: 1px solid rgba(var(--accent-rgb), 0.05); } +.th-with-info { + display: inline-flex; + align-items: center; + gap: 6px; +} + .col-drag { width: 3%; min-width: 40px; text-align: center; } .col-type { width: 8%; min-width: 80px; } .col-files { width: 15%; min-width: 120px; } @@ -345,7 +345,7 @@ const formatList = (arr) => { .mini-tags code { padding: 2px 6px; background: rgba(var(--accent-rgb), 0.15); - border-radius: var(--radius-sm); + border-radius: var(--radius); font-size: var(--text-sm); color: var(--accent-purple-light); } @@ -356,6 +356,36 @@ const formatList = (arr) => { font-style: italic; } +.type-toggle { + cursor: pointer; + transition: all var(--transition-fast); +} + +.type-toggle:hover { + opacity: 0.7; +} + +.inline-input { + padding: 4px 8px; + background: var(--glass-bg-dark); + border: 1px solid var(--glass-border); + border-radius: var(--radius); + color: var(--text-primary); + font-size: 12px; + font-family: var(--font-mono); + outline: none; + width: 80px; +} + +.inline-input:focus { + border-color: rgba(var(--accent-rgb), 0.5); +} + +.inline-input::placeholder { + color: var(--text-muted); + opacity: 0.4; +} + .col-actions-cell { text-align: center; } @@ -368,7 +398,7 @@ const formatList = (arr) => { justify-content: center; background: rgba(var(--danger-rgb), 0.1); border: 1px solid rgba(var(--danger-rgb), 0.3); - border-radius: var(--radius-sm); + border-radius: var(--radius); color: var(--accent-red); font-size: 12px; cursor: pointer; @@ -413,7 +443,7 @@ const formatList = (arr) => { .help-card { background: var(--subtle-overlay); - border-radius: var(--radius-xl); + border-radius: var(--radius); padding: var(--space-xl); border: 1px solid var(--glass-border); transition: all var(--transition-slow); @@ -459,7 +489,7 @@ const formatList = (arr) => { .help-param { padding: 20px; background: rgba(var(--accent-rgb), 0.03); - border-radius: var(--radius-lg); + border-radius: var(--radius); border-left: 3px solid var(--accent-purple); } @@ -479,7 +509,7 @@ const formatList = (arr) => { .help-param code { padding: 3px 8px; background: rgba(var(--accent-rgb), 0.15); - border-radius: var(--radius-sm); + border-radius: var(--radius); font-size: var(--text-base); color: var(--accent-purple-light); }