Enhance WebUI with multilingual support

This commit is contained in:
Andy Oknen 2025-07-30 09:19:25 +00:00
parent a984fba30d
commit 008ac3d864
4 changed files with 362 additions and 45 deletions

View file

@ -6,6 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Yggdrasil Web Interface</title> <title>Yggdrasil Web Interface</title>
<link rel="stylesheet" href="static/style.css"> <link rel="stylesheet" href="static/style.css">
<script src="static/lang/ru.js"></script>
<script src="static/lang/en.js"></script>
</head> </head>
<body> <body>
@ -13,57 +15,169 @@
<header> <header>
<div class="header-content"> <div class="header-content">
<div> <div>
<h1>🌳 Yggdrasil Web Interface</h1> <h1 data-key="title">🌳 Yggdrasil Web Interface</h1>
<p>Network mesh management dashboard</p> <p data-key="subtitle">Network mesh management dashboard</p>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<button onclick="logout()" class="logout-btn">Logout</button> <div class="language-switcher">
<button onclick="switchLanguage('ru')" class="lang-btn" id="lang-ru">RU</button>
<button onclick="switchLanguage('en')" class="lang-btn" id="lang-en">EN</button>
</div>
<button onclick="logout()" class="logout-btn" data-key="logout">Logout</button>
</div> </div>
</div> </div>
</header> </header>
<main> <div class="layout">
<aside class="sidebar">
<nav class="nav-menu">
<div class="nav-item active" onclick="showSection('status')">
<span class="nav-icon">📊</span>
<span class="nav-text" data-key="nav_status">Состояние</span>
</div>
<div class="nav-item" onclick="showSection('peers')">
<span class="nav-icon">🌐</span>
<span class="nav-text" data-key="nav_peers">Пиры</span>
</div>
<div class="nav-item" onclick="showSection('config')">
<span class="nav-icon">⚙️</span>
<span class="nav-text" data-key="nav_config">Конфигурация</span>
</div>
</nav>
</aside>
<main class="main-content">
<div id="status-section" class="content-section active">
<div class="status-card"> <div class="status-card">
<h2>Node Status</h2> <h2 data-key="status_title">Состояние узла</h2>
<div class="status-indicator"> <div class="status-indicator">
<span class="status-dot active"></span> <span class="status-dot active"></span>
<span>Active</span> <span data-key="status_active">Активен</span>
</div> </div>
<p>WebUI is running and accessible</p> <p data-key="status_description">WebUI запущен и доступен</p>
</div> </div>
<div class="info-grid"> <div class="info-grid">
<div class="info-card"> <div class="info-card">
<h3>Configuration</h3> <h3 data-key="network_info">Сетевая информация</h3>
<p>Manage node settings and peers</p> <p><span data-key="address">Адрес</span>: 200:1234:5678:9abc::1</p>
<small>Coming soon...</small> <p><span data-key="subnet">Подсеть</span>: 300:1234:5678:9abc::/64</p>
</div> </div>
<div class="info-card"> <div class="info-card">
<h3>Peers</h3> <h3 data-key="statistics">Статистика</h3>
<p>View and manage peer connections</p> <p><span data-key="uptime">Время работы</span>: 2д 15ч 42м</p>
<small>Coming soon...</small> <p><span data-key="connections">Активных соединений</span>: 3</p>
</div>
</div>
</div>
<div id="peers-section" class="content-section">
<div class="status-card">
<h2 data-key="peers_title">Управление пирами</h2>
<p data-key="peers_description">Просмотр и управление соединениями с пирами</p>
</div>
<div class="info-grid">
<div class="info-card">
<h3 data-key="active_peers">Активные пиры</h3>
<p data-key="active_connections">Количество активных соединений</p>
<small data-key="coming_soon">Функция в разработке...</small>
</div> </div>
<div class="info-card"> <div class="info-card">
<h3>Network</h3> <h3 data-key="add_peer">Добавить пир</h3>
<p>Network topology and routing</p> <p data-key="add_peer_description">Подключение к новому узлу</p>
<small>Coming soon...</small> <small data-key="coming_soon">Функция в разработке...</small>
</div>
</div>
</div>
<div id="config-section" class="content-section">
<div class="status-card">
<h2 data-key="config_title">Конфигурация</h2>
<p data-key="config_description">Настройки узла и параметры сети</p>
</div>
<div class="info-grid">
<div class="info-card">
<h3 data-key="basic_settings">Основные настройки</h3>
<p data-key="basic_settings_description">Базовая конфигурация узла</p>
<small data-key="coming_soon">Функция в разработке...</small>
</div>
<div class="info-card">
<h3 data-key="network_settings">Сетевые настройки</h3>
<p data-key="network_settings_description">Параметры сетевого взаимодействия</p>
<small data-key="coming_soon">Функция в разработке...</small>
</div>
</div> </div>
</div> </div>
</main> </main>
</div>
<footer> <footer>
<p>Yggdrasil Network • Minimal WebUI v1.0</p> <p data-key="footer_text">Yggdrasil Network • Minimal WebUI v1.0</p>
</footer> </footer>
</div> </div>
<script> <script>
let currentLanguage = localStorage.getItem('yggdrasil-language') || 'ru';
function updateTexts() {
const elements = document.querySelectorAll('[data-key]');
elements.forEach(element => {
const key = element.getAttribute('data-key');
if (window.translations && window.translations[currentLanguage] && window.translations[currentLanguage][key]) {
element.textContent = window.translations[currentLanguage][key];
}
});
}
function switchLanguage(lang) {
currentLanguage = lang;
localStorage.setItem('yggdrasil-language', lang);
// Update active button
document.querySelectorAll('.lang-btn').forEach(btn => btn.classList.remove('active'));
document.getElementById('lang-' + lang).classList.add('active');
updateTexts();
}
function logout() { function logout() {
if (confirm('Are you sure you want to logout?')) { const confirmText = window.translations[currentLanguage]['logout_confirm'];
if (confirm(confirmText)) {
window.location.href = '/auth/logout'; window.location.href = '/auth/logout';
} }
} }
function showSection(sectionName) {
// Hide all sections
const sections = document.querySelectorAll('.content-section');
sections.forEach(section => section.classList.remove('active'));
// Remove active class from all nav items
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => item.classList.remove('active'));
// Show selected section
const targetSection = document.getElementById(sectionName + '-section');
if (targetSection) {
targetSection.classList.add('active');
}
// Add active class to clicked nav item
event.target.closest('.nav-item').classList.add('active');
}
// Initialize language on page load
document.addEventListener('DOMContentLoaded', function () {
// Set active language button
document.getElementById('lang-' + currentLanguage).classList.add('active');
// Update all texts
updateTexts();
});
</script> </script>
</body> </body>

View file

@ -0,0 +1,33 @@
window.translations = window.translations || {};
window.translations.en = {
'title': '🌳 Yggdrasil Web Interface',
'subtitle': 'Network mesh management dashboard',
'logout': 'Logout',
'nav_status': 'Status',
'nav_peers': 'Peers',
'nav_config': 'Configuration',
'status_title': 'Node Status',
'status_active': 'Active',
'status_description': 'WebUI is running and accessible',
'network_info': 'Network Information',
'address': 'Address',
'subnet': 'Subnet',
'statistics': 'Statistics',
'uptime': 'Uptime',
'connections': 'Active connections',
'peers_title': 'Peer Management',
'peers_description': 'View and manage peer connections',
'active_peers': 'Active Peers',
'active_connections': 'Number of active connections',
'add_peer': 'Add Peer',
'add_peer_description': 'Connect to a new node',
'config_title': 'Configuration',
'config_description': 'Node settings and network parameters',
'basic_settings': 'Basic Settings',
'basic_settings_description': 'Basic node configuration',
'network_settings': 'Network Settings',
'network_settings_description': 'Network interaction parameters',
'coming_soon': 'Coming soon...',
'footer_text': 'Yggdrasil Network • Minimal WebUI v1.0',
'logout_confirm': 'Are you sure you want to logout?'
};

View file

@ -0,0 +1,33 @@
window.translations = window.translations || {};
window.translations.ru = {
'title': '🌳 Yggdrasil Web Interface',
'subtitle': 'Панель управления mesh-сетью',
'logout': 'Выход',
'nav_status': 'Состояние',
'nav_peers': 'Пиры',
'nav_config': 'Конфигурация',
'status_title': 'Состояние узла',
'status_active': 'Активен',
'status_description': 'WebUI запущен и доступен',
'network_info': 'Сетевая информация',
'address': 'Адрес',
'subnet': 'Подсеть',
'statistics': 'Статистика',
'uptime': 'Время работы',
'connections': 'Активных соединений',
'peers_title': 'Управление пирами',
'peers_description': 'Просмотр и управление соединениями с пирами',
'active_peers': 'Активные пиры',
'active_connections': 'Количество активных соединений',
'add_peer': 'Добавить пир',
'add_peer_description': 'Подключение к новому узлу',
'config_title': 'Конфигурация',
'config_description': 'Настройки узла и параметры сети',
'basic_settings': 'Основные настройки',
'basic_settings_description': 'Базовая конфигурация узла',
'network_settings': 'Сетевые настройки',
'network_settings_description': 'Параметры сетевого взаимодействия',
'coming_soon': 'Функция в разработке...',
'footer_text': 'Yggdrasil Network • Minimal WebUI v1.0',
'logout_confirm': 'Вы уверены, что хотите выйти?'
};

View file

@ -29,6 +29,41 @@ header {
text-align: left; text-align: left;
} }
.header-actions {
display: flex;
align-items: center;
gap: 15px;
}
.language-switcher {
display: flex;
background: rgba(255, 255, 255, 0.15);
border-radius: 8px;
padding: 4px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.lang-btn {
background: transparent;
color: white;
border: none;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
transition: all 0.2s ease;
}
.lang-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.lang-btn.active {
background: rgba(255, 255, 255, 0.3);
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.header-content > div:first-child { .header-content > div:first-child {
text-align: center; text-align: center;
flex: 1; flex: 1;
@ -60,20 +95,88 @@ header p {
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
} }
main { .layout {
background: white; display: flex;
border-radius: 12px; gap: 20px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
margin-bottom: 20px; margin-bottom: 20px;
} }
.status-card { .sidebar {
background: #f8f9fa; min-width: 250px;
border-radius: 8px; background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px; padding: 20px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.nav-menu {
display: flex;
flex-direction: column;
gap: 10px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 15px 18px;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.nav-item:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.nav-item.active {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.4);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.nav-icon {
font-size: 20px;
}
.nav-text {
font-weight: 500;
font-size: 16px;
}
.main-content {
flex: 1;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.content-section {
display: none;
}
.content-section.active {
display: block;
}
.status-card {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 15px;
padding: 25px;
margin-bottom: 30px; margin-bottom: 30px;
text-align: center; text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid rgba(255, 255, 255, 0.5);
} }
.status-indicator { .status-indicator {
@ -109,17 +212,19 @@ main {
} }
.info-card { .info-card {
background: #ffffff; background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border: 1px solid #e9ecef; border: 1px solid rgba(233, 236, 239, 0.6);
border-radius: 8px; border-radius: 15px;
padding: 20px; padding: 25px;
text-align: center; text-align: center;
transition: transform 0.2s ease, box-shadow 0.2s ease; transition: all 0.3s ease;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
} }
.info-card:hover { .info-card:hover {
transform: translateY(-2px); transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1); box-shadow: 0 8px 25px rgba(0,0,0,0.15);
border-color: rgba(102, 126, 234, 0.3);
} }
.info-card h3 { .info-card h3 {
@ -153,6 +258,11 @@ footer {
gap: 20px; gap: 20px;
} }
.header-actions {
flex-direction: column;
gap: 10px;
}
.header-content > div:first-child { .header-content > div:first-child {
text-align: center; text-align: center;
} }
@ -161,8 +271,35 @@ footer {
font-size: 2rem; font-size: 2rem;
} }
main { .layout {
flex-direction: column;
gap: 15px;
}
.sidebar {
min-width: auto;
order: 2;
}
.nav-menu {
flex-direction: row;
overflow-x: auto;
gap: 8px;
}
.nav-item {
min-width: 120px;
justify-content: center;
padding: 12px 15px;
}
.nav-text {
font-size: 14px;
}
.main-content {
padding: 20px; padding: 20px;
order: 1;
} }
.info-grid { .info-grid {