Refactor static file serving in WebUI to allow CSS and JS access without authentication, and implement theme toggle functionality in login and main pages

This commit is contained in:
Andy Oknen 2025-07-30 13:21:30 +00:00
parent fc354865ea
commit 3187114780
7 changed files with 408 additions and 126 deletions

View file

@ -15,11 +15,20 @@ import (
// setupStaticHandler configures static file serving for development (files from disk) // setupStaticHandler configures static file serving for development (files from disk)
func setupStaticHandler(mux *http.ServeMux, server *WebUIServer) { func setupStaticHandler(mux *http.ServeMux, server *WebUIServer) {
// Serve static files from disk for development - with auth // Serve static files from disk for development
staticHandler := http.StripPrefix("/static/", http.FileServer(http.Dir("src/webui/static/"))) staticHandler := http.StripPrefix("/static/", http.FileServer(http.Dir("src/webui/static/")))
mux.HandleFunc("/static/", server.authMiddleware(func(rw http.ResponseWriter, r *http.Request) { mux.HandleFunc("/static/", func(rw http.ResponseWriter, r *http.Request) {
staticHandler.ServeHTTP(rw, r) // Allow access to CSS and JS files without auth (needed for login page)
})) path := strings.TrimPrefix(r.URL.Path, "/static/")
if strings.HasSuffix(path, ".css") || strings.HasSuffix(path, ".js") {
staticHandler.ServeHTTP(rw, r)
return
}
// For other static files, require auth
server.authMiddleware(func(rw http.ResponseWriter, r *http.Request) {
staticHandler.ServeHTTP(rw, r)
})(rw, r)
})
} }
// serveFile serves any file from disk or returns 404 if not found // serveFile serves any file from disk or returns 404 if not found

View file

@ -25,12 +25,22 @@ func setupStaticHandler(mux *http.ServeMux, server *WebUIServer) {
panic("failed to get embedded static files: " + err.Error()) panic("failed to get embedded static files: " + err.Error())
} }
// Serve static files from embedded FS - with auth // Serve static files from embedded FS
staticHandler := http.FileServer(http.FS(staticFS)) staticHandler := http.FileServer(http.FS(staticFS))
mux.HandleFunc("/static/", server.authMiddleware(func(rw http.ResponseWriter, r *http.Request) { mux.HandleFunc("/static/", func(rw http.ResponseWriter, r *http.Request) {
// Strip the /static/ prefix before serving // Allow access to CSS and JS files without auth (needed for login page)
http.StripPrefix("/static/", staticHandler).ServeHTTP(rw, r) path := strings.TrimPrefix(r.URL.Path, "/static/")
})) if strings.HasSuffix(path, ".css") || strings.HasSuffix(path, ".js") {
// Strip the /static/ prefix before serving
http.StripPrefix("/static/", staticHandler).ServeHTTP(rw, r)
return
}
// For other static files, require auth
server.authMiddleware(func(rw http.ResponseWriter, r *http.Request) {
// Strip the /static/ prefix before serving
http.StripPrefix("/static/", staticHandler).ServeHTTP(rw, r)
})(rw, r)
})
} }
// serveFile serves any file from embedded files or returns 404 if not found // serveFile serves any file from embedded files or returns 404 if not found

View file

@ -19,7 +19,10 @@
<p data-key="subtitle">Network mesh management dashboard</p> <p data-key="subtitle">Network mesh management dashboard</p>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<div class="language-switcher"> <div class="controls-group">
<button onclick="toggleTheme()" class="theme-btn" id="theme-btn">
<span class="theme-icon">🌙</span>
</button>
<button onclick="switchLanguage('ru')" class="lang-btn" id="lang-ru">RU</button> <button onclick="switchLanguage('ru')" class="lang-btn" id="lang-ru">RU</button>
<button onclick="switchLanguage('en')" class="lang-btn" id="lang-en">EN</button> <button onclick="switchLanguage('en')" class="lang-btn" id="lang-en">EN</button>
</div> </div>
@ -126,6 +129,7 @@
<script> <script>
let currentLanguage = localStorage.getItem('yggdrasil-language') || 'ru'; let currentLanguage = localStorage.getItem('yggdrasil-language') || 'ru';
let currentTheme = localStorage.getItem('yggdrasil-theme') || 'light';
function updateTexts() { function updateTexts() {
const elements = document.querySelectorAll('[data-key]'); const elements = document.querySelectorAll('[data-key]');
@ -137,6 +141,28 @@
}); });
} }
function toggleTheme() {
currentTheme = currentTheme === 'light' ? 'dark' : 'light';
localStorage.setItem('yggdrasil-theme', currentTheme);
applyTheme();
}
function applyTheme() {
const body = document.body;
const themeIcon = document.querySelector('.theme-icon');
const themeBtn = document.getElementById('theme-btn');
if (currentTheme === 'dark') {
body.setAttribute('data-theme', 'dark');
themeIcon.textContent = '☀️';
themeBtn.title = window.translations[currentLanguage]['theme_light'] || 'Light theme';
} else {
body.removeAttribute('data-theme');
themeIcon.textContent = '🌙';
themeBtn.title = window.translations[currentLanguage]['theme_dark'] || 'Dark theme';
}
}
function switchLanguage(lang) { function switchLanguage(lang) {
currentLanguage = lang; currentLanguage = lang;
localStorage.setItem('yggdrasil-language', lang); localStorage.setItem('yggdrasil-language', lang);
@ -146,6 +172,7 @@
document.getElementById('lang-' + lang).classList.add('active'); document.getElementById('lang-' + lang).classList.add('active');
updateTexts(); updateTexts();
applyTheme(); // Update theme button tooltip
} }
function logout() { function logout() {
@ -174,12 +201,14 @@
event.target.closest('.nav-item').classList.add('active'); event.target.closest('.nav-item').classList.add('active');
} }
// Initialize language on page load // Initialize language and theme on page load
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// Set active language button // Set active language button
document.getElementById('lang-' + currentLanguage).classList.add('active'); document.getElementById('lang-' + currentLanguage).classList.add('active');
// Update all texts // Update all texts
updateTexts(); updateTexts();
// Apply saved theme
applyTheme();
}); });
</script> </script>
</body> </body>

View file

@ -29,5 +29,12 @@ window.translations.en = {
'network_settings_description': 'Network interaction parameters', 'network_settings_description': 'Network interaction parameters',
'coming_soon': 'Coming soon...', 'coming_soon': 'Coming soon...',
'footer_text': 'Yggdrasil Network • Minimal WebUI v1.0', 'footer_text': 'Yggdrasil Network • Minimal WebUI v1.0',
'logout_confirm': 'Are you sure you want to logout?' 'logout_confirm': 'Are you sure you want to logout?',
'theme_light': 'Light theme',
'theme_dark': 'Dark theme',
'login_subtitle': 'Enter password to access the web interface',
'password_label': 'Password:',
'access_dashboard': 'Access Dashboard',
'error_invalid_password': 'Invalid password. Please try again.',
'error_too_many_attempts': 'Too many failed attempts. Please wait 1 minute before trying again.'
}; };

View file

@ -29,5 +29,12 @@ window.translations.ru = {
'network_settings_description': 'Параметры сетевого взаимодействия', 'network_settings_description': 'Параметры сетевого взаимодействия',
'coming_soon': 'Функция в разработке...', 'coming_soon': 'Функция в разработке...',
'footer_text': 'Yggdrasil Network • Minimal WebUI v1.0', 'footer_text': 'Yggdrasil Network • Minimal WebUI v1.0',
'logout_confirm': 'Вы уверены, что хотите выйти?' 'logout_confirm': 'Вы уверены, что хотите выйти?',
'theme_light': 'Светлая тема',
'theme_dark': 'Темная тема',
'login_subtitle': 'Введите пароль для доступа к веб-интерфейсу',
'password_label': 'Пароль:',
'access_dashboard': 'Войти в панель',
'error_invalid_password': 'Неверный пароль. Попробуйте снова.',
'error_too_many_attempts': 'Слишком много неудачных попыток. Подождите 1 минуту перед повторной попыткой.'
}; };

View file

@ -6,7 +6,18 @@
<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 - Login</title> <title>Yggdrasil Web Interface - Login</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>
<style> <style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
min-height: 100vh;
color: var(--text-primary);
margin: 0;
padding: 0;
}
.login-container { .login-container {
display: flex; display: flex;
align-items: center; align-items: center;
@ -16,24 +27,27 @@
} }
.login-form { .login-form {
background: white; background: var(--bg-info-card);
border-radius: 12px; border-radius: 4px;
padding: 40px; padding: 40px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px var(--shadow-dark);
border: 1px solid var(--border-card);
width: 100%; width: 100%;
max-width: 400px; max-width: 400px;
text-align: center; text-align: center;
} }
.login-form h1 { .login-form h1 {
color: #495057; color: var(--text-heading);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 2rem; font-size: 2rem;
font-weight: 700;
} }
.login-form p { .login-form p {
color: #6c757d; color: var(--text-body);
margin-bottom: 30px; margin-bottom: 30px;
font-weight: 500;
} }
.form-group { .form-group {
@ -44,95 +58,164 @@
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
color: #495057; color: var(--text-heading);
font-weight: 500; font-weight: 600;
} }
.form-group input { .form-group input {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
border: 1px solid #ced4da; border: 1px solid var(--border-card);
border-radius: 6px; border-radius: 2px;
font-size: 16px; font-size: 16px;
transition: border-color 0.2s ease; transition: border-color 0.2s ease;
background: var(--bg-status-card);
color: var(--text-body);
font-weight: 500;
} }
.form-group input:focus { .form-group input:focus {
outline: none; outline: none;
border-color: #667eea; border-color: var(--border-hover);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); box-shadow: 0 1px 4px var(--shadow-light);
} }
.form-group input:disabled { .form-group input:disabled {
background-color: #f8f9fa; background-color: var(--bg-nav-item);
border-color: #e9ecef; border-color: var(--border-nav-item);
color: #6c757d; color: var(--text-muted);
cursor: not-allowed; cursor: not-allowed;
} }
.login-button { .login-button {
width: 100%; width: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--bg-nav-active);
color: white; color: var(--text-white);
border: none; border: 1px solid var(--bg-nav-active-border);
padding: 12px; padding: 12px 16px;
border-radius: 6px; border-radius: 2px;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease; transition: background 0.2s ease, box-shadow 0.2s ease;
} }
.login-button:hover { .login-button:hover {
transform: translateY(-1px); background: var(--bg-nav-active-border);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); box-shadow: 0 2px 8px var(--shadow-medium);
}
.login-button:active {
transform: translateY(0);
} }
.error-message { .error-message {
background: #f8d7da; background: var(--bg-status-card);
color: #721c24; color: var(--bg-logout);
border: 1px solid #f1aeb5; border: 1px solid var(--border-logout);
border-radius: 6px; border-radius: 2px;
padding: 10px; padding: 12px;
margin-bottom: 20px; margin-bottom: 20px;
display: none; display: none;
font-weight: 500;
} }
.lock-icon { .lock-icon {
font-size: 3rem; font-size: 3rem;
margin-bottom: 20px; margin-bottom: 20px;
color: #667eea; color: var(--bg-nav-active);
}
.login-header {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
}
.login-container {
position: relative;
z-index: 1;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="login-header">
<div class="controls-group">
<button onclick="toggleTheme()" class="theme-btn" id="theme-btn">
<span class="theme-icon">🌙</span>
</button>
<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>
</div>
<div class="login-container"> <div class="login-container">
<form class="login-form" onsubmit="return handleLogin(event)"> <form class="login-form" onsubmit="return handleLogin(event)">
<div class="lock-icon">🔒</div> <div class="lock-icon">🔒</div>
<h1>🌳 Yggdrasil</h1> <h1 data-key="title">🌳 Yggdrasil</h1>
<p>Enter password to access the web interface</p> <p data-key="login_subtitle">Enter password to access the web interface</p>
<div class="error-message" id="errorMessage"> <div class="error-message" id="errorMessage">
Invalid password. Please try again. <span data-key="error_invalid_password">Invalid password. Please try again.</span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Password:</label> <label for="password" data-key="password_label">Password:</label>
<input type="password" id="password" name="password" required autofocus> <input type="password" id="password" name="password" required autofocus>
</div> </div>
<button type="submit" class="login-button"> <button type="submit" class="login-button" data-key="access_dashboard">
Access Dashboard Access Dashboard
</button> </button>
</form> </form>
</div> </div>
<script> <script>
let currentLanguage = localStorage.getItem('yggdrasil-language') || 'ru';
let currentTheme = localStorage.getItem('yggdrasil-theme') || 'light';
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 toggleTheme() {
currentTheme = currentTheme === 'light' ? 'dark' : 'light';
localStorage.setItem('yggdrasil-theme', currentTheme);
applyTheme();
}
function applyTheme() {
const body = document.body;
const themeIcon = document.querySelector('.theme-icon');
const themeBtn = document.getElementById('theme-btn');
if (currentTheme === 'dark') {
body.setAttribute('data-theme', 'dark');
themeIcon.textContent = '☀️';
themeBtn.title = window.translations[currentLanguage]['theme_light'] || 'Light theme';
} else {
body.removeAttribute('data-theme');
themeIcon.textContent = '🌙';
themeBtn.title = window.translations[currentLanguage]['theme_dark'] || 'Dark theme';
}
}
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();
applyTheme(); // Update theme button tooltip
}
async function handleLogin(event) { async function handleLogin(event) {
event.preventDefault(); event.preventDefault();
@ -153,7 +236,7 @@
window.location.href = '/'; window.location.href = '/';
} else if (response.status === 429) { } else if (response.status === 429) {
// Too many requests - IP blocked // Too many requests - IP blocked
errorMessage.textContent = 'Too many failed attempts. Please wait 1 minute before trying again.'; errorMessage.querySelector('span').textContent = window.translations[currentLanguage]['error_too_many_attempts'] || 'Too many failed attempts. Please wait 1 minute before trying again.';
errorMessage.style.display = 'block'; errorMessage.style.display = 'block';
document.getElementById('password').value = ''; document.getElementById('password').value = '';
document.getElementById('password').disabled = true; document.getElementById('password').disabled = true;
@ -166,7 +249,7 @@
}, 60000); }, 60000);
} else { } else {
// Invalid password // Invalid password
errorMessage.textContent = 'Invalid password. Please try again.'; errorMessage.querySelector('span').textContent = window.translations[currentLanguage]['error_invalid_password'] || 'Invalid password. Please try again.';
errorMessage.style.display = 'block'; errorMessage.style.display = 'block';
document.getElementById('password').value = ''; document.getElementById('password').value = '';
document.getElementById('password').focus(); document.getElementById('password').focus();
@ -183,6 +266,16 @@
document.getElementById('password').addEventListener('input', function () { document.getElementById('password').addEventListener('input', function () {
document.getElementById('errorMessage').style.display = 'none'; document.getElementById('errorMessage').style.display = 'none';
}); });
// Initialize language and theme on page load
document.addEventListener('DOMContentLoaded', function () {
// Set active language button
document.getElementById('lang-' + currentLanguage).classList.add('active');
// Update all texts
updateTexts();
// Apply saved theme
applyTheme();
});
</script> </script>
</body> </body>

View file

@ -1,3 +1,91 @@
/* Light theme (default) */
:root {
/* Background colors */
--bg-primary: #667eea;
--bg-secondary: #764ba2;
--bg-sidebar: #ffffff;
--bg-nav-item: #f8f9fa;
--bg-nav-hover: #e9ecef;
--bg-nav-active: #3498db;
--bg-nav-active-border: #2980b9;
--bg-main-content: #fafafa;
--bg-status-card: #f5f5f5;
--bg-info-card: #ffffff;
--bg-logout: #dc3545;
--bg-logout-hover: #c82333;
--bg-lang-switcher: rgba(255, 255, 255, 0.2);
--bg-lang-btn-hover: rgba(255, 255, 255, 0.3);
--bg-lang-btn-active: rgba(255, 255, 255, 0.4);
/* Border colors */
--border-sidebar: #e0e0e0;
--border-nav-item: #dee2e6;
--border-main: #e0e0e0;
--border-card: #e0e0e0;
--border-hover: #3498db;
--border-footer: #dee2e6;
--border-logout: #c82333;
--border-lang: rgba(255, 255, 255, 0.3);
/* Text colors */
--text-primary: #333;
--text-white: white;
--text-nav: #495057;
--text-heading: #343a40;
--text-body: #495057;
--text-muted: #6c757d;
/* Shadow colors */
--shadow-light: rgba(0, 0, 0, 0.1);
--shadow-medium: rgba(0, 0, 0, 0.15);
--shadow-dark: rgba(0, 0, 0, 0.2);
--shadow-heavy: rgba(0, 0, 0, 0.3);
}
/* Dark theme */
[data-theme="dark"] {
/* Background colors */
--bg-primary: #2c3e50;
--bg-secondary: #34495e;
--bg-sidebar: #37474f;
--bg-nav-item: #455a64;
--bg-nav-hover: #546e7a;
--bg-nav-active: #3498db;
--bg-nav-active-border: #2980b9;
--bg-main-content: #37474f;
--bg-status-card: #455a64;
--bg-info-card: #455a64;
--bg-logout: #dc3545;
--bg-logout-hover: #c82333;
--bg-lang-switcher: rgba(0, 0, 0, 0.3);
--bg-lang-btn-hover: rgba(255, 255, 255, 0.1);
--bg-lang-btn-active: rgba(255, 255, 255, 0.2);
/* Border colors */
--border-sidebar: #455a64;
--border-nav-item: #546e7a;
--border-main: #455a64;
--border-card: #546e7a;
--border-hover: #3498db;
--border-footer: #546e7a;
--border-logout: #c82333;
--border-lang: rgba(255, 255, 255, 0.3);
/* Text colors */
--text-primary: #333;
--text-white: white;
--text-nav: #eceff1;
--text-heading: #eceff1;
--text-body: #cfd8dc;
--text-muted: #b0bec5;
/* Shadow colors */
--shadow-light: rgba(0, 0, 0, 0.2);
--shadow-medium: rgba(0, 0, 0, 0.3);
--shadow-dark: rgba(0, 0, 0, 0.4);
--shadow-heavy: rgba(0, 0, 0, 0.5);
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -5,10 +93,10 @@
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
min-height: 100vh; min-height: 100vh;
color: #333; color: var(--text-primary);
} }
.container { .container {
@ -19,7 +107,7 @@ body {
header { header {
margin-bottom: 40px; margin-bottom: 40px;
color: white; color: var(--text-white);
} }
.header-content { .header-content {
@ -35,33 +123,58 @@ header {
gap: 15px; gap: 15px;
} }
.language-switcher { .controls-group {
display: flex; display: flex;
background: rgba(255, 255, 255, 0.15); background: var(--bg-lang-switcher);
border-radius: 8px; border-radius: 2px;
padding: 4px; padding: 2px;
border: 1px solid rgba(255, 255, 255, 0.2); border: 1px solid var(--border-lang);
gap: 2px;
}
.theme-btn {
background: transparent;
color: var(--text-white);
border: none;
padding: 6px 12px;
border-radius: 1px;
cursor: pointer;
font-size: 12px;
font-weight: 600;
transition: background 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.theme-btn:hover {
background: var(--bg-lang-btn-hover);
}
.theme-icon {
display: block;
transition: transform 0.3s ease;
font-size: 12px;
} }
.lang-btn { .lang-btn {
background: transparent; background: transparent;
color: white; color: var(--text-white);
border: none; border: none;
padding: 6px 12px; padding: 6px 12px;
border-radius: 6px; border-radius: 1px;
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 600;
transition: all 0.2s ease; transition: background 0.2s ease;
} }
.lang-btn:hover { .lang-btn:hover {
background: rgba(255, 255, 255, 0.2); background: var(--bg-lang-btn-hover);
} }
.lang-btn.active { .lang-btn.active {
background: rgba(255, 255, 255, 0.3); background: var(--bg-lang-btn-active);
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
} }
.header-content > div:first-child { .header-content > div:first-child {
@ -70,9 +183,11 @@ header {
} }
header h1 { header h1 {
font-size: 2.5rem; font-size: 2.2rem;
margin-bottom: 10px; margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3); text-shadow: 1px 1px 2px var(--shadow-heavy);
font-weight: 700;
letter-spacing: -0.5px;
} }
header p { header p {
@ -83,20 +198,20 @@ header p {
.sidebar-footer { .sidebar-footer {
margin-top: 20px; margin-top: 20px;
padding-top: 20px; padding-top: 20px;
border-top: 1px solid rgba(255, 255, 255, 0.2); border-top: 1px solid var(--border-footer);
} }
.logout-btn { .logout-btn {
width: 100%; width: 100%;
background: rgba(220, 53, 69, 0.2); background: var(--bg-logout);
color: white; color: var(--text-white);
border: 1px solid rgba(220, 53, 69, 0.3); border: 1px solid var(--border-logout);
padding: 12px 16px; padding: 10px 16px;
border-radius: 10px; border-radius: 2px;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 600;
transition: all 0.3s ease; transition: background 0.2s ease;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -104,9 +219,7 @@ header p {
} }
.logout-btn:hover { .logout-btn:hover {
background: rgba(220, 53, 69, 0.3); background: var(--bg-logout-hover);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(220, 53, 69, 0.2);
} }
.logout-btn:before { .logout-btn:before {
@ -122,12 +235,11 @@ header p {
.sidebar { .sidebar {
min-width: 250px; min-width: 250px;
background: rgba(255, 255, 255, 0.15); background: var(--bg-sidebar);
backdrop-filter: blur(10px); border-radius: 4px;
border-radius: 12px;
padding: 20px; padding: 20px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1); box-shadow: 0 2px 8px var(--shadow-heavy);
border: 1px solid rgba(255, 255, 255, 0.18); border: 1px solid var(--border-sidebar);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@ -143,25 +255,24 @@ header p {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
padding: 15px 18px; padding: 12px 16px;
background: rgba(255, 255, 255, 0.1); background: var(--bg-nav-item);
border-radius: 10px; border-radius: 2px;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: background 0.2s ease;
color: white; color: var(--text-nav);
border: 1px solid rgba(255, 255, 255, 0.2); border: 1px solid var(--border-nav-item);
font-weight: 500;
} }
.nav-item:hover { .nav-item:hover {
background: rgba(255, 255, 255, 0.2); background: var(--bg-nav-hover);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
} }
.nav-item.active { .nav-item.active {
background: rgba(255, 255, 255, 0.25); background: var(--bg-nav-active);
border-color: rgba(255, 255, 255, 0.4); border-color: var(--bg-nav-active-border);
box-shadow: 0 4px 15px rgba(0,0,0,0.2); color: var(--text-white);
} }
.nav-icon { .nav-icon {
@ -175,12 +286,11 @@ header p {
.main-content { .main-content {
flex: 1; flex: 1;
background: rgba(255, 255, 255, 0.95); background: var(--bg-main-content);
border-radius: 12px; border-radius: 4px;
padding: 30px; padding: 30px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1); box-shadow: 0 2px 8px var(--shadow-dark);
backdrop-filter: blur(10px); border: 1px solid var(--border-main);
border: 1px solid rgba(255, 255, 255, 0.18);
} }
.content-section { .content-section {
@ -192,13 +302,25 @@ header p {
} }
.status-card { .status-card {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); background: var(--bg-status-card);
border-radius: 15px; border-radius: 4px;
padding: 25px; padding: 20px;
margin-bottom: 30px; margin-bottom: 30px;
text-align: center; text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.08); box-shadow: 0 1px 4px var(--shadow-light);
border: 1px solid rgba(255, 255, 255, 0.5); border: 1px solid var(--border-card);
}
.status-card h2 {
color: var(--text-heading);
font-weight: 700;
margin-bottom: 15px;
font-size: 1.3rem;
}
.status-card p {
color: var(--text-body);
font-weight: 500;
} }
.status-indicator { .status-indicator {
@ -213,11 +335,11 @@ header p {
width: 12px; width: 12px;
height: 12px; height: 12px;
border-radius: 50%; border-radius: 50%;
background: #6c757d; background: var(--text-muted);
} }
.status-dot.active { .status-dot.active {
background: #28a745; background: var(--bg-nav-active);
animation: pulse 2s infinite; animation: pulse 2s infinite;
} }
@ -234,39 +356,44 @@ header p {
} }
.info-card { .info-card {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); background: var(--bg-info-card);
border: 1px solid rgba(233, 236, 239, 0.6); border: 1px solid var(--border-card);
border-radius: 15px; border-radius: 4px;
padding: 25px; padding: 20px;
text-align: center; text-align: center;
transition: all 0.3s ease; transition: box-shadow 0.2s ease;
box-shadow: 0 2px 10px rgba(0,0,0,0.05); box-shadow: 0 1px 4px var(--shadow-light);
} }
.info-card:hover { .info-card:hover {
transform: translateY(-5px); box-shadow: 0 2px 8px var(--shadow-medium);
box-shadow: 0 8px 25px rgba(0,0,0,0.15); border-color: var(--border-hover);
border-color: rgba(102, 126, 234, 0.3);
} }
.info-card h3 { .info-card h3 {
color: #495057; color: var(--text-heading);
margin-bottom: 10px; margin-bottom: 10px;
font-weight: 600;
font-size: 1.1rem;
} }
.info-card p { .info-card p {
color: #6c757d; color: var(--text-body);
margin-bottom: 10px; margin-bottom: 10px;
font-weight: 500;
} }
.info-card small { .info-card small {
color: #adb5bd; color: var(--text-muted);
font-style: italic; font-weight: 500;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.5px;
} }
footer { footer {
text-align: center; text-align: center;
color: white; color: var(--text-white);
opacity: 0.8; opacity: 0.8;
} }
@ -310,7 +437,7 @@ footer {
margin-left: 15px; margin-left: 15px;
padding-top: 0; padding-top: 0;
border-top: none; border-top: none;
border-left: 1px solid rgba(255, 255, 255, 0.2); border-left: 1px solid var(--border-footer);
padding-left: 15px; padding-left: 15px;
} }