mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-08-24 16:05:07 +03:00
Enhance WebUI with multilingual support
This commit is contained in:
parent
a984fba30d
commit
008ac3d864
4 changed files with 362 additions and 45 deletions
|
@ -6,6 +6,8 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Yggdrasil Web Interface</title>
|
||||
<link rel="stylesheet" href="static/style.css">
|
||||
<script src="static/lang/ru.js"></script>
|
||||
<script src="static/lang/en.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -13,57 +15,169 @@
|
|||
<header>
|
||||
<div class="header-content">
|
||||
<div>
|
||||
<h1>🌳 Yggdrasil Web Interface</h1>
|
||||
<p>Network mesh management dashboard</p>
|
||||
<h1 data-key="title">🌳 Yggdrasil Web Interface</h1>
|
||||
<p data-key="subtitle">Network mesh management dashboard</p>
|
||||
</div>
|
||||
<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>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="status-card">
|
||||
<h2>Node Status</h2>
|
||||
<div class="status-indicator">
|
||||
<span class="status-dot active"></span>
|
||||
<span>Active</span>
|
||||
</div>
|
||||
<p>WebUI is running and accessible</p>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="info-card">
|
||||
<h3>Configuration</h3>
|
||||
<p>Manage node settings and peers</p>
|
||||
<small>Coming soon...</small>
|
||||
<main class="main-content">
|
||||
<div id="status-section" class="content-section active">
|
||||
<div class="status-card">
|
||||
<h2 data-key="status_title">Состояние узла</h2>
|
||||
<div class="status-indicator">
|
||||
<span class="status-dot active"></span>
|
||||
<span data-key="status_active">Активен</span>
|
||||
</div>
|
||||
<p data-key="status_description">WebUI запущен и доступен</p>
|
||||
</div>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="info-card">
|
||||
<h3 data-key="network_info">Сетевая информация</h3>
|
||||
<p><span data-key="address">Адрес</span>: 200:1234:5678:9abc::1</p>
|
||||
<p><span data-key="subnet">Подсеть</span>: 300:1234:5678:9abc::/64</p>
|
||||
</div>
|
||||
|
||||
<div class="info-card">
|
||||
<h3 data-key="statistics">Статистика</h3>
|
||||
<p><span data-key="uptime">Время работы</span>: 2д 15ч 42м</p>
|
||||
<p><span data-key="connections">Активных соединений</span>: 3</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card">
|
||||
<h3>Peers</h3>
|
||||
<p>View and manage peer connections</p>
|
||||
<small>Coming soon...</small>
|
||||
<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 class="info-card">
|
||||
<h3 data-key="add_peer">Добавить пир</h3>
|
||||
<p data-key="add_peer_description">Подключение к новому узлу</p>
|
||||
<small data-key="coming_soon">Функция в разработке...</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card">
|
||||
<h3>Network</h3>
|
||||
<p>Network topology and routing</p>
|
||||
<small>Coming soon...</small>
|
||||
<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>
|
||||
</main>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>Yggdrasil Network • Minimal WebUI v1.0</p>
|
||||
<p data-key="footer_text">Yggdrasil Network • Minimal WebUI v1.0</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<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() {
|
||||
if (confirm('Are you sure you want to logout?')) {
|
||||
const confirmText = window.translations[currentLanguage]['logout_confirm'];
|
||||
if (confirm(confirmText)) {
|
||||
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>
|
||||
</body>
|
||||
|
||||
|
|
33
src/webui/static/lang/en.js
Normal file
33
src/webui/static/lang/en.js
Normal 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?'
|
||||
};
|
33
src/webui/static/lang/ru.js
Normal file
33
src/webui/static/lang/ru.js
Normal 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': 'Вы уверены, что хотите выйти?'
|
||||
};
|
|
@ -29,6 +29,41 @@ header {
|
|||
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 {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
|
@ -60,20 +95,88 @@ header p {
|
|||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
main {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
||||
.layout {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
.sidebar {
|
||||
min-width: 250px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 12px;
|
||||
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;
|
||||
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 {
|
||||
|
@ -109,17 +212,19 @@ main {
|
|||
}
|
||||
|
||||
.info-card {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
border: 1px solid rgba(233, 236, 239, 0.6);
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
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 {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
||||
border-color: rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.info-card h3 {
|
||||
|
@ -153,6 +258,11 @@ footer {
|
|||
gap: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header-content > div:first-child {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -161,8 +271,35 @@ footer {
|
|||
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;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue