From 1a4382201ddc21fee5de4da2b9b8b7c9c941742d Mon Sep 17 00:00:00 2001 From: ochenstarik Date: Fri, 11 Sep 2026 09:36:04 +0700 Subject: [PATCH] feat(control-center): preserve concurrent AGI work on control center components and pages --- .../components/ControlCenterShell.tsx | 612 +++++++++++--- .../components/OperatorSessionSwitcher.tsx | 84 +- .../components/ServerMonitorOutageBanner.tsx | 16 +- .../control-center/components/StatusBadge.tsx | 118 +-- src/features/control-center/locales/ru.ts | 158 ++++ .../control-center/pages/BillingPage.tsx | 383 +++++---- .../pages/InfrastructurePage.tsx | 313 ++++--- .../pages/OrganizationsPage.tsx | 153 +++- .../control-center/pages/OverviewPage.tsx | 763 +++++++++++------- 9 files changed, 1765 insertions(+), 835 deletions(-) create mode 100644 src/features/control-center/locales/ru.ts diff --git a/src/features/control-center/components/ControlCenterShell.tsx b/src/features/control-center/components/ControlCenterShell.tsx index 3429e76..52e5e3a 100644 --- a/src/features/control-center/components/ControlCenterShell.tsx +++ b/src/features/control-center/components/ControlCenterShell.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState } from 'react'; -import { NavLink, Outlet } from 'react-router-dom'; +import React, { useEffect, useState, useMemo } from 'react'; +import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom'; import { LayoutDashboard, Building2, @@ -14,24 +14,48 @@ import { CheckCircle2, Radio, ExternalLink, + Menu, + X, + ShieldAlert, + Sun, + Moon, + Sparkles, + ShieldCheck, + ChevronRight, + SlidersHorizontal, + ChevronLeft, } from 'lucide-react'; -import { useKccStore } from '../store/kccStore'; +import { useKccStore, useOperatorRole } from '../store/kccStore'; import { OperatorSessionSwitcher } from './OperatorSessionSwitcher'; import { InspectorDrawer } from './InspectorDrawer'; import { ConfirmOperatorActionModal } from './ConfirmOperatorAction'; import { ServerMonitorOutageBanner } from './ServerMonitorOutageBanner'; +import { useThemeStore, ThemeMode } from '../../../stores/theme'; +import { RU_ROLES, RU_THEMES, RU_UI } from '../locales/ru'; export const ControlCenterShell: React.FC = () => { + const location = useLocation(); + const navigate = useNavigate(); + const isInitialized = useKccStore((state) => state.isInitialized); const initialize = useKccStore((state) => state.initialize); const resetToDefaults = useKccStore((state) => state.resetToDefaults); const serverMonitorOutage = useKccStore((state) => state.serverMonitorOutage); const toggleServerMonitorOutage = useKccStore((state) => state.toggleServerMonitorOutage); + const incidents = useKccStore((state) => state.incidents); + + const { role, operator } = useOperatorRole(); + const themeMode = useThemeStore((state) => state.themeMode); + const setTheme = useThemeStore((state) => state.setTheme); const [utcTime, setUtcTime] = useState(''); const [isResetting, setIsResetting] = useState(false); const [showSavedFeedback, setShowSavedFeedback] = useState(false); + // Mobile / Tablet Drawer states + const [isMobileDrawerOpen, setIsMobileDrawerOpen] = useState(false); + const [isTabletSidebarCollapsed, setIsTabletSidebarCollapsed] = useState(false); + useEffect(() => { if (!isInitialized) { initialize(); @@ -48,8 +72,13 @@ export const ControlCenterShell: React.FC = () => { return () => clearInterval(interval); }, []); + // Close mobile drawer on route change + useEffect(() => { + setIsMobileDrawerOpen(false); + }, [location.pathname]); + const handleReset = async () => { - if (confirm('Сбросить состояние демонстрационной базы данных к исходному состоянию?')) { + if (confirm('Сбросить демонстрационные данные платформы к исходному состоянию?')) { setIsResetting(true); await resetToDefaults(); setIsResetting(false); @@ -58,107 +87,260 @@ export const ControlCenterShell: React.FC = () => { } }; - const navItems = [ + // Critical events calculation + const criticalCount = useMemo(() => { + let count = 0; + if (serverMonitorOutage) count += 1; + count += incidents.filter( + (i) => i.status !== 'resolved' && (i.severity === 'P0_CRITICAL' || i.severity === 'P1_MAJOR') + ).length; + return count; + }, [serverMonitorOutage, incidents]); + + // Navigation Items + const primaryMobileNav = [ { to: '/control', label: 'Обзор', icon: LayoutDashboard, end: true }, { to: '/control/organizations', label: 'Организации', icon: Building2 }, - { to: '/control/billing', label: 'Подписки и биллинг', icon: CreditCard }, - { to: '/control/modules', label: 'Каталог модулей', icon: Package }, - { to: '/control/installations', label: 'Установки и клиенты', icon: Smartphone }, - { to: '/control/infrastructure', label: 'Инфраструктура', icon: Server }, - { to: '/control/announcements', label: 'Сообщения', icon: BellRing }, - { to: '/control/incidents', label: 'Инциденты', icon: AlertTriangle }, - { to: '/control/audit', label: 'Неизменяемый аудит', icon: FileCheck2 }, + { to: '/control/incidents', label: 'Инциденты', icon: AlertTriangle, badge: criticalCount > 0 ? criticalCount : undefined }, + { to: '/control/infrastructure', label: 'Серверы', icon: Server }, ]; + const allNavItems = [ + { to: '/control', label: 'Обзор', icon: LayoutDashboard, end: true }, + { to: '/control/organizations', label: 'Организации', icon: Building2 }, + { to: '/control/billing', label: 'Подписки и платежи', icon: CreditCard }, + { to: '/control/modules', label: 'Каталог модулей', icon: Package }, + { to: '/control/installations', label: 'Установки', icon: Smartphone }, + { to: '/control/infrastructure', label: 'Серверы', icon: Server }, + { to: '/control/announcements', label: 'Оповещения', icon: BellRing }, + { to: '/control/incidents', label: 'Инциденты', icon: AlertTriangle, badge: criticalCount > 0 ? criticalCount : undefined }, + { to: '/control/audit', label: 'Журнал аудита', icon: FileCheck2 }, + ]; + + const moreNavItems = [ + { to: '/control/billing', label: 'Подписки и платежи', icon: CreditCard }, + { to: '/control/modules', label: 'Каталог модулей', icon: Package }, + { to: '/control/installations', label: 'Установки', icon: Smartphone }, + { to: '/control/announcements', label: 'Оповещения', icon: BellRing }, + { to: '/control/audit', label: 'Журнал аудита', icon: FileCheck2 }, + ]; + + // Current section title + const currentTitle = useMemo(() => { + const current = allNavItems.find((item) => + item.end ? location.pathname === item.to : location.pathname.startsWith(item.to) + ); + return current?.label || 'Control Center'; + }, [location.pathname, allNavItems]); + + const currentRoleLabel = RU_ROLES[role]?.shortLabel || role; + return ( -
- {/* Top Console Bar */} -
-
- {/* Logo & Title */} -
-
+
+ {/* ========================================================================= */} + {/* 1. DESKTOP & TABLET HEADER (Hidden on mobile < md) */} + {/* ========================================================================= */} +
+
+ {/* Logo & Platform info */} +
+
K
-
+
Krona Control Center - - DEMO v1.0 + + PROD v1.0
-
+
Кабинет владельца платформы
-
- {utcTime} - • - - - IndexedDB Synced +
+ {utcTime} + • + + + {RU_UI.system.synced}
- {/* Right Header Actions */} -
- {/* Server Monitor Simulation Toggle */} + {/* Right Controls: Outage simulation, Theme Switcher, Operator Switcher, Reset */} +
+ {/* Circuit Breaker Outage toggle */} - {/* Reset Demo DB Button */} + {/* Theme Switcher in Desktop Header */} +
+ + + +
+ + {/* Reset Demo Data */} - {/* Operator RBAC Switcher */} + {/* Operator Role Switcher */} - - {/* Return to Client App Link */} - - В приложение - -
- {/* Main Console Layout */} -
- {/* Compact Operator Sidebar */} - - - {/* Content Workspace */} -
- - {showSavedFeedback && ( -
- - Демонстрационные данные успешно сброшены и сохранены в IndexedDB. + {/* Footer Info in Sidebar */} + {!isTabletSidebarCollapsed && ( +
+
+ {RU_UI.system.nodeLabel}: + srv-msk-prod-01 +
+
+ {RU_UI.scope}: + Боевой (Live) +
+ + + {RU_UI.system.exitToClient} +
)} - + + + {/* Main Content Area */} +
+
+ {/* Feedback Alert if Database Reset */} + {showSavedFeedback && ( +
+ + Демонстрационные данные успешно сброшены к эталонному состоянию. +
+ )} + + {/* Outage Banner */} + + + {/* Outlet for Page Routes */} + +
- {/* Contextual Inspector Drawer */} - + {/* ========================================================================= */} + {/* 4. MOBILE BOTTOM BAR (< md) */} + {/* ========================================================================= */} + + + {/* ========================================================================= */} + {/* 5. MOBILE DRAWER / BOTTOM SHEET */} + {/* ========================================================================= */} + {isMobileDrawerOpen && ( +
setIsMobileDrawerOpen(false)} + > +
e.stopPropagation()} + > + {/* Sheet Header */} +
+
+
+ K +
+ Панель оператора +
+ +
+ + {/* Scrollable Sheet Content */} +
+ {/* Operator Role Section */} +
+
+ Сессия оператора +
+
+
+
{operator.name}
+
{RU_ROLES[role]?.label}
+
+ +
+
+ + {/* Theme Switcher Section */} +
+
+ {RU_UI.system.themeLabel} +
+
+ {(['soft-light', 'light', 'dark'] as ThemeMode[]).map((t) => ( + + ))} +
+
+ + {/* More Navigation Links */} +
+
+ Все разделы +
+
+ {allNavItems.map((item) => { + const Icon = item.icon; + const isActive = item.end ? location.pathname === item.to : location.pathname.startsWith(item.to); + + return ( + +
+ + {item.label} +
+ {item.badge && item.badge > 0 && ( + + {item.badge} + + )} +
+ ); + })} +
+
+ + {/* System Actions */} +
+
+ Сервисные действия +
+ + + + + + + + Перейти в Krona Business OS + +
+ + {/* Node info */} +
+ srv-msk-prod-01 • Данные синхронизированы (IndexedDB) +
+
+
+
+ )} + + {/* Global Contextual Modals */} +
); diff --git a/src/features/control-center/components/OperatorSessionSwitcher.tsx b/src/features/control-center/components/OperatorSessionSwitcher.tsx index 8b88752..cb77483 100644 --- a/src/features/control-center/components/OperatorSessionSwitcher.tsx +++ b/src/features/control-center/components/OperatorSessionSwitcher.tsx @@ -1,13 +1,15 @@ import React, { useState, useRef, useEffect } from 'react'; import { ShieldCheck, ChevronDown, UserCircle } from 'lucide-react'; import { useKccStore, useOperatorRole } from '../store/kccStore'; -import { OPERATOR_ROLE_DEFINITIONS, OperatorRole } from '../contracts'; +import { OperatorRole } from '../contracts'; +import { RU_ROLES } from '../locales/ru'; interface OperatorSessionSwitcherProps { onRoleChange?: (role: OperatorRole) => void; + variant?: 'compact' | 'full'; } -export const OperatorSessionSwitcher: React.FC = ({ onRoleChange }) => { +export const OperatorSessionSwitcher: React.FC = ({ onRoleChange, variant = 'full' }) => { const { role, operator } = useOperatorRole(); const setActiveRole = useKccStore((state) => state.setActiveRole); const [isOpen, setIsOpen] = useState(false); @@ -32,73 +34,85 @@ export const OperatorSessionSwitcher: React.FC = ( 'release_manager', ]; + const handleSelectRole = (newRole: OperatorRole) => { + setActiveRole(newRole); + setIsOpen(false); + onRoleChange?.(newRole); + }; + + const currentRoleInfo = RU_ROLES[role] || { + label: role, + shortLabel: role, + description: '', + }; + return (
{isOpen && (
-
- Операторская сессия (RBAC Demo) -

+

+ Роль оператора (RBAC) +

Выберите роль для проверки разграничения прав доступа:

-
+
{rolesList.map((r) => { - const def = OPERATOR_ROLE_DEFINITIONS[r]; - const isSelected = r === role; + const info = RU_ROLES[r]; + const isCurrent = r === role; return ( ); diff --git a/src/features/control-center/components/ServerMonitorOutageBanner.tsx b/src/features/control-center/components/ServerMonitorOutageBanner.tsx index a2513a4..fa03fd5 100644 --- a/src/features/control-center/components/ServerMonitorOutageBanner.tsx +++ b/src/features/control-center/components/ServerMonitorOutageBanner.tsx @@ -10,19 +10,19 @@ export const ServerMonitorOutageBanner: React.FC = () => { return (
-
+
-
- Внимание: Сервис Server Monitor временно недоступен (Circuit Breaker Tripped) +
+ Авария: Сервис мониторинга недоступен (Circuit Breaker Tripped)
-
- Прямой mTLS канал с агентами узлов не отвечает. Разделы организаций, подписок и биллинга продолжают штатную +
+ Прямой mTLS-канал с агентами узлов не отвечает. Разделы организаций, подписок и биллинга продолжают штатную работу. Показаны последние закешированные данные телеметрии.
@@ -31,10 +31,10 @@ export const ServerMonitorOutageBanner: React.FC = () => {
); diff --git a/src/features/control-center/components/StatusBadge.tsx b/src/features/control-center/components/StatusBadge.tsx index 233cbec..f1c7b32 100644 --- a/src/features/control-center/components/StatusBadge.tsx +++ b/src/features/control-center/components/StatusBadge.tsx @@ -61,167 +61,171 @@ export const StatusBadge: React.FC = ({ status, customLabel, s // Platform & tenant active: { label: 'Активна', - className: 'bg-emerald-950/80 text-emerald-400 border-emerald-700/50', + className: 'bg-status-success-soft text-status-success-text border-status-success-border', icon: