fix: frontend-backend integration — 14 issues resolved

- nginx: add proxy routes for /settings/, /templates/, /ipam/, /logs/, /admin/, /health
- dashboard: fix data format mismatch (servers[] not nodes[]/vms[])
- dashboard: add WebSocket subscription to dashboard_metrics for real-time updates
- dashboard: connect activity feed to /logs/api/logs endpoint
- dashboard: reduce polling to 15s with WS as primary update source
- graphs: fix rrddata response unwrap (backend returns {data:[],timeframe:''})
- graphs: add i18n translations for chart titles
- console: add xterm.js CSS import for proper terminal rendering
- console: add i18n translations (connecting, reconnect)
- workspace: add switcher dropdown in TopBar
- workspace: add edit dialog in WorkspacesPage
- workspace: create Zustand store with localStorage persistence
- workspace: add X-Active-Workspace header to API client
- sidebar: replace generic icon with actual favicon.svg logo
- instances: translate RowActionMenu and InstanceDetailPage strings
- locales: add graphs, console, workspace, instance tab keys to en/ru
This commit is contained in:
markmorado
2026-04-07 00:45:43 +02:00
parent 4546a798d0
commit 1d08c815a7
138 changed files with 17826 additions and 32024 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,157 +0,0 @@
/**
* Client-side translations
* Synchronized with backend i18n.py
*/
const translations = {
ru: {
// Common
loading: "Загрузка...",
save: "Сохранить",
cancel: "Отмена",
delete: "Удалить",
edit: "Редактировать",
add: "Добавить",
search: "Поиск",
filter: "Фильтр",
actions: "Действия",
status: "Статус",
name: "Название",
description: "Описание",
success: "Успешно",
error: "Ошибка",
warning: "Предупреждение",
confirm: "Подтвердить",
yes: "Да",
no: "Нет",
// Messages
profile_updated: "Профиль обновлён",
password_changed: "Пароль успешно изменён",
settings_saved: "Настройки сохранены",
language_changed: "Язык изменён. Страница будет перезагружена...",
error_occurred: "Произошла ошибка",
confirm_delete: "Вы уверены, что хотите удалить?",
// Updates
update_check_disabled: "Проверка обновлений отключена",
checking_updates: "Проверка обновлений...",
check_updates: "Проверить обновления",
update_available: "Доступно обновление",
no_updates: "Установлена последняя версия",
update_warning: "Панель будет недоступна во время обновления",
updating_system: "Обновление системы...",
// SSH Keys
ssh_key_saved: "SSH ключ успешно сохранён",
error_saving_ssh_key: "Ошибка сохранения SSH ключа",
invalid_ssh_key_format: "Неверный формат SSH ключа",
// Dashboard
nodes_label: "Ноды",
clusters_label: "Кластеры",
alerts_label: "Оповещения",
vms: "ВМ",
running: "Запущено",
online: "Онлайн",
paused: "Пауза",
stopped: "Остановлен",
loading_dots: "Загрузка…",
no_vms_found: "Нет виртуальных машин",
no_nodes: "Нет доступных нод",
all_healthy: "Все работают",
no_alerts: "Нет оповещений",
},
en: {
// Common
loading: "Loading...",
save: "Save",
cancel: "Cancel",
delete: "Delete",
edit: "Edit",
add: "Add",
search: "Search",
filter: "Filter",
actions: "Actions",
status: "Status",
name: "Name",
description: "Description",
success: "Success",
error: "Error",
warning: "Warning",
confirm: "Confirm",
yes: "Yes",
no: "No",
// Messages
profile_updated: "Profile updated",
password_changed: "Password changed successfully",
settings_saved: "Settings saved",
language_changed: "Language changed. Page will reload...",
error_occurred: "An error occurred",
confirm_delete: "Are you sure you want to delete?",
// Updates
update_check_disabled: "Update check is disabled",
checking_updates: "Checking for updates...",
check_updates: "Check for Updates",
update_available: "Update available",
no_updates: "You have the latest version",
update_warning: "Panel will be unavailable during update",
updating_system: "Updating system...",
// SSH Keys
ssh_key_saved: "SSH key saved successfully",
error_saving_ssh_key: "Error saving SSH key",
invalid_ssh_key_format: "Invalid SSH key format",
// Dashboard
nodes_label: "Nodes",
clusters_label: "Clusters",
alerts_label: "Alerts",
vms: "VMs",
running: "Running",
online: "Online",
paused: "Paused",
stopped: "Stopped",
loading_dots: "Loading…",
no_vms_found: "No VMs found",
no_nodes: "No nodes available",
all_healthy: "All healthy",
no_alerts: "No alerts",
}
};
// Get current language from localStorage or default
function getCurrentLanguage() {
return localStorage.getItem('panel_language') || 'ru';
}
// Set language
function setLanguage(lang) {
localStorage.setItem('panel_language', lang);
}
// Get translation
function t(key, lang = null) {
if (!lang) {
lang = getCurrentLanguage();
}
if (translations[lang] && translations[lang][key]) {
return translations[lang][key];
}
// Fallback to Russian
if (translations.ru[key]) {
return translations.ru[key];
}
return key;
}
// Export for use in other scripts
if (typeof module !== 'undefined' && module.exports) {
module.exports = { translations, t, getCurrentLanguage, setLanguage };
}

View File

@@ -1,144 +0,0 @@
/**
* Theme Manager - Handles light/dark mode switching and sidebar toggle
*/
(function() {
'use strict';
const THEME_KEY = 'panel-theme';
const SIDEBAR_KEY = 'panel-sidebar';
const DARK_THEME = 'dark';
const LIGHT_THEME = 'light';
// Get saved theme or default to light
function getPreferredTheme() {
const savedTheme = localStorage.getItem(THEME_KEY);
if (savedTheme) {
return savedTheme;
}
// Default to light theme (cloud panel style)
return LIGHT_THEME;
}
// Apply theme to document
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem(THEME_KEY, theme);
// Update toggle button icon
updateToggleIcon(theme);
}
// Update toggle button icon
function updateToggleIcon(theme) {
const toggle = document.getElementById('themeToggle');
if (!toggle) return;
const sunIcon = toggle.querySelector('.icon-sun');
const moonIcon = toggle.querySelector('.icon-moon');
if (sunIcon && moonIcon) {
if (theme === DARK_THEME) {
sunIcon.style.display = 'block';
moonIcon.style.display = 'none';
} else {
sunIcon.style.display = 'none';
moonIcon.style.display = 'block';
}
}
}
// Toggle theme
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme') || DARK_THEME;
const newTheme = currentTheme === DARK_THEME ? LIGHT_THEME : DARK_THEME;
applyTheme(newTheme);
}
// Toggle sidebar
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
if (sidebar) {
sidebar.classList.toggle('open');
if (overlay) {
overlay.classList.toggle('active', sidebar.classList.contains('open'));
}
}
}
// Close sidebar on escape key
function handleKeydown(e) {
if (e.key === 'Escape') {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
if (sidebar && sidebar.classList.contains('open')) {
sidebar.classList.remove('open');
if (overlay) {
overlay.classList.remove('active');
}
}
}
}
// Initialize on DOM load
function init() {
// Apply saved or preferred theme immediately
const theme = getPreferredTheme();
applyTheme(theme);
// Set up toggle button
const toggle = document.getElementById('themeToggle');
if (toggle) {
toggle.addEventListener('click', toggleTheme);
}
// Listen for escape key
document.addEventListener('keydown', handleKeydown);
// Listen for system theme changes
if (window.matchMedia) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
// Only auto-switch if user hasn't manually selected a theme
if (!localStorage.getItem(THEME_KEY)) {
applyTheme(e.matches ? DARK_THEME : LIGHT_THEME);
}
});
}
// Close sidebar when clicking on a link (mobile)
const sidebarLinks = document.querySelectorAll('.sidebar-nav a');
sidebarLinks.forEach(link => {
link.addEventListener('click', () => {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
if (window.innerWidth <= 768 && sidebar) {
sidebar.classList.remove('open');
if (overlay) {
overlay.classList.remove('active');
}
}
});
});
}
// Apply theme before DOM is ready to prevent flash
const savedTheme = getPreferredTheme();
document.documentElement.setAttribute('data-theme', savedTheme);
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Expose functions globally
window.toggleTheme = toggleTheme;
window.toggleSidebar = toggleSidebar;
})();