Skip to content

Commit d3d38c6

Browse files
committed
Merge branch 'dev'
2 parents 1257e8d + 05d3568 commit d3d38c6

17 files changed

Lines changed: 339 additions & 225 deletions

public/css/style.css

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,11 @@ html {
416416
gap: 8px;
417417
}
418418

419+
.card-header h2 i {
420+
font-size: 18px;
421+
opacity: 0.9;
422+
}
423+
419424
.card-body {
420425
padding: 24px;
421426
}
@@ -2225,4 +2230,96 @@ html {
22252230
.form-group .datetime-input::-webkit-calendar-picker-indicator:focus-visible {
22262231
outline: 2px solid var(--accent);
22272232
outline-offset: 1px;
2233+
}
2234+
2235+
/* ── Tabler Icons ── */
2236+
.ti {
2237+
font-size: 1em;
2238+
line-height: 1;
2239+
vertical-align: -0.125em;
2240+
}
2241+
2242+
/* Spinner animation for loader icons */
2243+
@keyframes ti-spin {
2244+
from { transform: rotate(0deg); }
2245+
to { transform: rotate(360deg); }
2246+
}
2247+
.ti-loader-2 {
2248+
display: inline-block;
2249+
animation: ti-spin 1s linear infinite;
2250+
}
2251+
2252+
/* ─── Page enter animations ───────────────────────────────────── */
2253+
@keyframes fadeUp {
2254+
from { opacity: 0; transform: translateY(12px); }
2255+
to { opacity: 1; transform: translateY(0); }
2256+
}
2257+
2258+
@keyframes fadeIn {
2259+
from { opacity: 0; }
2260+
to { opacity: 1; }
2261+
}
2262+
2263+
/* Main content area fades in on navigation */
2264+
.main-content {
2265+
animation: fadeIn 0.2s ease-out;
2266+
}
2267+
2268+
/* Stats cards stagger */
2269+
.stat-card {
2270+
animation: fadeUp 0.3s ease-out both;
2271+
}
2272+
.stat-card:nth-child(1) { animation-delay: 0.03s; }
2273+
.stat-card:nth-child(2) { animation-delay: 0.07s; }
2274+
.stat-card:nth-child(3) { animation-delay: 0.11s; }
2275+
.stat-card:nth-child(4) { animation-delay: 0.15s; }
2276+
2277+
/* Cards */
2278+
.card {
2279+
animation: fadeUp 0.25s ease-out both;
2280+
}
2281+
2282+
/* Dashboard grid children stagger */
2283+
.dashboard-main > .card:nth-child(1) { animation-delay: 0.05s; }
2284+
.dashboard-main > .card:nth-child(2) { animation-delay: 0.1s; }
2285+
.dashboard-sidebar > .card:nth-child(1) { animation-delay: 0.08s; }
2286+
.dashboard-sidebar > .card:nth-child(2) { animation-delay: 0.13s; }
2287+
2288+
/* Table rows */
2289+
.table tbody tr {
2290+
animation: fadeIn 0.2s ease-out both;
2291+
}
2292+
.table tbody tr:nth-child(1) { animation-delay: 0.04s; }
2293+
.table tbody tr:nth-child(2) { animation-delay: 0.07s; }
2294+
.table tbody tr:nth-child(3) { animation-delay: 0.10s; }
2295+
.table tbody tr:nth-child(4) { animation-delay: 0.13s; }
2296+
.table tbody tr:nth-child(5) { animation-delay: 0.16s; }
2297+
.table tbody tr:nth-child(n+6) { animation-delay: 0.18s; }
2298+
2299+
/* Топбар */
2300+
.topbar {
2301+
animation: fadeIn 0.15s ease-out;
2302+
}
2303+
2304+
/* Page header */
2305+
.page-header {
2306+
animation: fadeUp 0.2s ease-out both;
2307+
}
2308+
2309+
/* Quick action items */
2310+
.quick-action {
2311+
animation: fadeUp 0.25s ease-out both;
2312+
}
2313+
.quick-actions .quick-action:nth-child(1) { animation-delay: 0.05s; }
2314+
.quick-actions .quick-action:nth-child(2) { animation-delay: 0.08s; }
2315+
.quick-actions .quick-action:nth-child(3) { animation-delay: 0.11s; }
2316+
.quick-actions .quick-action:nth-child(4) { animation-delay: 0.14s; }
2317+
.quick-actions .quick-action:nth-child(5) { animation-delay: 0.17s; }
2318+
2319+
/* Уважаем prefer-reduced-motion */
2320+
@media (prefers-reduced-motion: reduce) {
2321+
.main-content, .stat-card, .card, .table tbody tr,
2322+
.nav-menu li, .topbar, .page-header, .quick-action {
2323+
animation: none;
2324+
}
22282325
}

src/locales/en.json

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"add": "Add",
88
"create": "Create",
99
"back": "Back",
10-
"backToList": "Back to list",
10+
"backToList": "Back to list",
1111
"loading": "Loading...",
1212
"error": "Error",
1313
"success": "Success",
@@ -282,7 +282,7 @@
282282
"txRx": "TX / RX",
283283
"groupsCount": "groups",
284284
"subscription": "Subscription",
285-
"details": "Details",
285+
"details": "Details",
286286
"turnedOn": "✓ Enabled",
287287
"turnedOff": "○ Disabled"
288288
},
@@ -323,17 +323,17 @@
323323
"port": "Port",
324324
"mongodb": "MongoDB",
325325
"systemSettings": "⚙️ System Settings",
326-
"loadBalancing": "⚖️ Load Balancing",
326+
"loadBalancing": "Load Balancing",
327327
"loadBalancingEnabled": "Sort by load",
328328
"loadBalancingHint": "Less loaded nodes will be first in subscription",
329329
"hideOverloaded": "Hide overloaded",
330330
"hideOverloadedHint": "Nodes with online ≥ limit won't be included in subscription",
331-
"deviceLimit": "📱 Device Limit",
331+
"deviceLimit": "Device Limit",
332332
"gracePeriod": "Grace Period (min)",
333333
"gracePeriodHint": "How long to remember IP after disconnection. Protects from false triggers when switching WiFi↔LTE",
334334
"howItWorks": "ℹ️ How it works:",
335335
"howItWorksDesc": "Devices are counted by unique IP addresses. If user connects from new IP and old one was active less than {minutes} minutes ago — both IPs count toward the limit.",
336-
"caching": "🚀 Caching (Redis)",
336+
"caching": "Caching (Redis)",
337337
"subscriptionsTTL": "Subscriptions (sec)",
338338
"subscriptionsTTLHint": "Ready configs. Reset on node changes",
339339
"usersTTL": "Users (sec)",
@@ -342,10 +342,10 @@
342342
"onlineSessionsTTLHint": "For device limit. Less = more accurate",
343343
"activeNodesTTL": "Active Nodes (sec)",
344344
"activeNodesTTLHint": "Faster reaction to node changes",
345-
"rateLimiting": "🛡️ Rate Limiting",
345+
"rateLimiting": "Rate Limiting",
346346
"subscriptionsPerMinute": "Subscriptions/min per IP",
347347
"subscriptionsPerMinuteHint": "Protection from token brute force. Norm: 60-100",
348-
"sshPool": "🔗 SSH Connection Pool",
348+
"sshPool": "SSH Connection Pool",
349349
"sshPoolEnabled": "Enable SSH connection pool",
350350
"sshPoolHint": "Reuses SSH connections for faster operations. Saves ~200-500ms per operation.",
351351
"sshPoolIdleTime": "Idle timeout (sec)",
@@ -359,8 +359,8 @@
359359
"nodeAuth": "Node Auth API",
360360
"nodeAuthInsecure": "Allow self-signed certificates",
361361
"nodeAuthInsecureHint": "Enable if panel uses HTTP or self-signed SSL. Nodes will accept any certificate when connecting to auth API. Disable for production with valid SSL.",
362-
"saveAllSettings": "💾 Save All Settings",
363-
"security": "🔐 Security",
362+
"saveAllSettings": "Save All Settings",
363+
"security": "Security",
364364
"administrator": "Administrator",
365365
"lastLogin": "Last Login",
366366
"never": "Never",
@@ -377,20 +377,20 @@
377377
"httpModeHint": "To enable HTTPS set variables:",
378378
"letsEncryptAuto": "Let's Encrypt auto-renewal",
379379
"systemStatus": "System Status",
380-
"dangerZone": "⚠️ Danger Zone",
380+
"dangerZone": "Danger Zone",
381381
"resetStats": "Reset Statistics",
382382
"resetStatsDesc": "Delete all historical statistics data (online, traffic, nodes charts). This action is irreversible!",
383-
"resetStatsBtn": "🗑️ Clear Statistics",
383+
"resetStatsBtn": "Clear Statistics",
384384
"resetStatsConfirm": "⚠️ Delete all statistics history?\n\nThis will clear all charts and tables on the Statistics page.\n\nContinue?",
385385
"statsReset": "✓ Statistics cleared ({count} records)",
386386
"resetTrafficCounter": "Reset Traffic Counter",
387387
"resetTrafficDesc": "Resets used traffic counter (tx/rx) for all users. This action is irreversible!",
388-
"resetAllTraffic": "🗑️ Reset All Traffic",
388+
"resetAllTraffic": "Reset All Traffic",
389389
"resetConfirm": "⚠️ WARNING!\n\nAre you sure you want to reset traffic counter for ALL users?\n\nThis action is irreversible!",
390390
"resetConfirmFinal": "Final confirmation.\n\nCounters tx/rx will be reset for all users.\n\nContinue?",
391391
"resetting": "⏳ Resetting...",
392392
"trafficReset": "✓ Traffic reset for {count} users",
393-
"backup": "💾 Backup",
393+
"backup": "Backup",
394394
"autoBackup": "Automatic Backup",
395395
"autoBackupHint": "Automatically create database backups on schedule",
396396
"backupInterval": "Interval (hours)",
@@ -399,7 +399,7 @@
399399
"keepLastHint": "How many local backups to keep (old ones are deleted automatically)",
400400
"lastBackup": "Last Backup",
401401
"never": "Never",
402-
"s3Settings": "☁️ Upload to S3 (optional)",
402+
"s3Settings": "Upload to S3 (optional)",
403403
"s3Enabled": "Upload to S3",
404404
"s3EnabledHint": "Additionally send backups to S3-compatible storage",
405405
"s3Endpoint": "Endpoint (for MinIO)",
@@ -412,11 +412,11 @@
412412
"s3SecretKey": "Secret Access Key",
413413
"s3KeepLast": "Keep in S3",
414414
"s3KeepLastHint": "How many backups to keep in S3",
415-
"testS3": "🔗 Test Connection",
415+
"testS3": "Test Connection",
416416
"testingS3": "Testing...",
417417
"s3TestSuccess": "✓ S3 connection successful",
418418
"s3TestError": "✗ Connection error: {error}",
419-
"createBackupNow": "📦 Create Backup Now",
419+
"createBackupNow": "Create Backup Now",
420420
"creatingBackup": "Creating...",
421421
"backupCreated": "✓ Backup created: {filename} ({size} MB)",
422422
"backupError": "Backup creation error",
@@ -432,10 +432,10 @@
432432
"saved": "Settings saved",
433433
"hintLoadBalancing": "Sort nodes by current load in subscriptions",
434434
"hintHideOverloaded": "Don't show nodes that reached max online users",
435-
"cacheManagement": "🗄️ Cache Management",
435+
"cacheManagement": "Cache Management",
436436
"flushCache": "Flush Cache",
437437
"flushCacheDesc": "Clears all cached data in Redis (subscriptions, users, sessions). May temporarily increase server load.",
438-
"flushCacheBtn": "🧹 Flush Cache",
438+
"flushCacheBtn": "Flush Cache",
439439
"flushCacheConfirm": "Clear all cached data?\n\nThis will temporarily increase server load as cache rebuilds.",
440440
"flushing": "⏳ Flushing...",
441441
"cacheFlushed": "✓ Cache cleared successfully"
@@ -535,7 +535,7 @@
535535
"applyError": "Apply error",
536536
"replaceRules": "Replace current rules?",
537537
"noRulesDefault": "(no rules — using default config)",
538-
"backToNode": "Back to node"
538+
"backToNode": "Back to node"
539539
},
540540
"stats": {
541541
"title": "Statistics",

src/locales/ru.json

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"add": "Добавить",
88
"create": "Создать",
99
"back": "Назад",
10-
"backToList": "Назад к списку",
10+
"backToList": "Назад к списку",
1111
"loading": "Загрузка...",
1212
"error": "Ошибка",
1313
"success": "Успешно",
@@ -282,7 +282,7 @@
282282
"txRx": "TX / RX",
283283
"groupsCount": "групп",
284284
"subscription": "Подписка",
285-
"details": "Подробнее",
285+
"details": "Подробнее",
286286
"turnedOn": "✓ Включён",
287287
"turnedOff": "○ Отключён"
288288
},
@@ -323,17 +323,17 @@
323323
"port": "Порт",
324324
"mongodb": "MongoDB",
325325
"systemSettings": "⚙️ Настройки системы",
326-
"loadBalancing": "⚖️ Балансировка нагрузки",
326+
"loadBalancing": "Балансировка нагрузки",
327327
"loadBalancingEnabled": "Сортировать по загрузке",
328328
"loadBalancingHint": "Менее загруженные ноды будут первыми в подписке",
329329
"hideOverloaded": "Скрывать перегруженные",
330330
"hideOverloadedHint": "Ноды с онлайн ≥ лимиту не будут включены в подписку",
331-
"deviceLimit": "📱 Лимит устройств",
331+
"deviceLimit": "Лимит устройств",
332332
"gracePeriod": "Grace Period (мин)",
333333
"gracePeriodHint": "Как долго помнить IP после отключения. Защищает от ложных срабатываний при смене WiFi↔LTE",
334334
"howItWorks": "ℹ️ Как работает:",
335335
"howItWorksDesc": "Устройства считаются по уникальным IP-адресам. Если пользователь подключается с нового IP, а старый был активен менее {minutes} минут назад — оба IP учитываются в лимите.",
336-
"caching": "🚀 Кэширование (Redis)",
336+
"caching": "Кэширование (Redis)",
337337
"subscriptionsTTL": "Подписки (сек)",
338338
"subscriptionsTTLHint": "Готовые конфиги. Сбрасывается при изменении нод",
339339
"usersTTL": "Пользователи (сек)",
@@ -342,10 +342,10 @@
342342
"onlineSessionsTTLHint": "Для лимита устройств. Меньше = точнее",
343343
"activeNodesTTL": "Активные ноды (сек)",
344344
"activeNodesTTLHint": "Быстрее реакция на изменения нод",
345-
"rateLimiting": "🛡️ Rate Limiting",
345+
"rateLimiting": "Rate Limiting",
346346
"subscriptionsPerMinute": "Подписки/мин на IP",
347347
"subscriptionsPerMinuteHint": "Защита от перебора токенов. Норма: 60-100",
348-
"sshPool": "🔗 Пул SSH соединений",
348+
"sshPool": "Пул SSH соединений",
349349
"sshPoolEnabled": "Включить пул SSH соединений",
350350
"sshPoolHint": "Переиспользует SSH соединения для ускорения операций. Экономит ~200-500мс на операцию.",
351351
"sshPoolIdleTime": "Таймаут простоя (сек)",
@@ -359,8 +359,8 @@
359359
"nodeAuth": "Node Auth API",
360360
"nodeAuthInsecure": "Разрешить self-signed сертификаты",
361361
"nodeAuthInsecureHint": "Включите, если панель использует HTTP или self-signed SSL. Ноды будут принимать любой сертификат при подключении к auth API. Отключите для production с валидным SSL.",
362-
"saveAllSettings": "💾 Сохранить все настройки",
363-
"security": "🔐 Безопасность",
362+
"saveAllSettings": "Сохранить все настройки",
363+
"security": "Безопасность",
364364
"administrator": "Администратор",
365365
"lastLogin": "Последний вход",
366366
"never": "Никогда",
@@ -377,20 +377,20 @@
377377
"httpModeHint": "Для включения HTTPS задайте переменные:",
378378
"letsEncryptAuto": "Let's Encrypt автообновление",
379379
"systemStatus": "Статус системы",
380-
"dangerZone": "⚠️ Опасная зона",
380+
"dangerZone": "Опасная зона",
381381
"resetStats": "Сброс статистики",
382382
"resetStatsDesc": "Удалить все исторические данные статистики (графики онлайна, трафика, нод). Действие необратимо!",
383-
"resetStatsBtn": "🗑️ Очистить статистику",
383+
"resetStatsBtn": "Очистить статистику",
384384
"resetStatsConfirm": "⚠️ Удалить всю историю статистики?\n\nЭто очистит все графики и таблицы на странице Статистика.\n\nПродолжить?",
385385
"statsReset": "✓ Статистика очищена ({count} записей)",
386386
"resetTrafficCounter": "Сброс счетчика трафика",
387387
"resetTrafficDesc": "Обнуляет счетчик использованного трафика (tx/rx) для всех пользователей. Действие необратимо!",
388-
"resetAllTraffic": "🗑️ Сбросить весь трафик",
388+
"resetAllTraffic": "Сбросить весь трафик",
389389
"resetConfirm": "⚠️ ВНИМАНИЕ!\n\nВы уверены что хотите сбросить счетчик трафика для ВСЕХ пользователей?\n\nЭто действие необратимо!",
390390
"resetConfirmFinal": "Последнее подтверждение.\n\nСчетчики tx/rx будут обнулены у всех пользователей.\n\nПродолжить?",
391391
"resetting": "⏳ Сброс...",
392392
"trafficReset": "✓ Трафик сброшен у {count} пользователей",
393-
"backup": "💾 Резервное копирование",
393+
"backup": "Резервное копирование",
394394
"autoBackup": "Автоматический бэкап",
395395
"autoBackupHint": "Автоматически создавать бэкапы базы данных по расписанию",
396396
"backupInterval": "Интервал (часы)",
@@ -399,7 +399,7 @@
399399
"keepLastHint": "Сколько локальных бэкапов хранить (старые удаляются автоматически)",
400400
"lastBackup": "Последний бэкап",
401401
"never": "Никогда",
402-
"s3Settings": "☁️ Загрузка в S3 (опционально)",
402+
"s3Settings": "Загрузка в S3 (опционально)",
403403
"s3Enabled": "Загружать в S3",
404404
"s3EnabledHint": "Дополнительно отправлять бэкапы в S3-совместимое хранилище",
405405
"s3Endpoint": "Endpoint (для MinIO)",
@@ -412,11 +412,11 @@
412412
"s3SecretKey": "Secret Access Key",
413413
"s3KeepLast": "Хранить в S3",
414414
"s3KeepLastHint": "Сколько бэкапов хранить в S3",
415-
"testS3": "🔗 Проверить подключение",
415+
"testS3": "Проверить подключение",
416416
"testingS3": "Проверка...",
417417
"s3TestSuccess": "✓ Подключение к S3 успешно",
418418
"s3TestError": "✗ Ошибка подключения: {error}",
419-
"createBackupNow": "📦 Создать бэкап сейчас",
419+
"createBackupNow": "Создать бэкап сейчас",
420420
"creatingBackup": "Создание...",
421421
"backupCreated": "✓ Бэкап создан: {filename} ({size} МБ)",
422422
"backupError": "Ошибка создания бэкапа",
@@ -432,10 +432,10 @@
432432
"saved": "Настройки сохранены",
433433
"hintLoadBalancing": "Сортировать ноды по текущей загрузке в подписках",
434434
"hintHideOverloaded": "Не показывать ноды, достигшие лимита онлайн",
435-
"cacheManagement": "🗄️ Управление кэшем",
435+
"cacheManagement": "Управление кэшем",
436436
"flushCache": "Очистить кэш",
437437
"flushCacheDesc": "Очищает все кэшированные данные в Redis (подписки, пользователи, сессии). Может временно увеличить нагрузку на сервер.",
438-
"flushCacheBtn": "🧹 Очистить кэш",
438+
"flushCacheBtn": "Очистить кэш",
439439
"flushCacheConfirm": "Очистить все кэшированные данные?\n\nЭто временно увеличит нагрузку на сервер пока кэш восстановится.",
440440
"flushing": "⏳ Очистка...",
441441
"cacheFlushed": "✓ Кэш успешно очищен"
@@ -535,7 +535,7 @@
535535
"applyError": "Ошибка применения",
536536
"replaceRules": "Заменить текущие правила?",
537537
"noRulesDefault": "(правил нет — используется конфиг по умолчанию)",
538-
"backToNode": "Назад к ноде"
538+
"backToNode": "Назад к ноде"
539539
},
540540
"stats": {
541541
"title": "Статистика",

0 commit comments

Comments
 (0)