From 6e0d97b65bd6115d51849e25f6e6572fb3d7c81a Mon Sep 17 00:00:00 2001 From: Dmitriy Petrov Date: Thu, 21 May 2026 20:28:12 +0300 Subject: [PATCH] feat: add windows client UI --- src/web/App.jsx | 22 +- src/web/api.js | 35 +++ src/web/components/Sidebar.jsx | 14 +- src/web/components/Topbar.jsx | 35 ++- src/web/components/WindowsOverviewPage.jsx | 261 +++++++++++++++++++ src/web/styles.css | 288 +++++++++++++++++++++ 6 files changed, 631 insertions(+), 24 deletions(-) create mode 100644 src/web/components/WindowsOverviewPage.jsx diff --git a/src/web/App.jsx b/src/web/App.jsx index 6092b32..67e62c5 100644 --- a/src/web/App.jsx +++ b/src/web/App.jsx @@ -7,6 +7,7 @@ import { Sidebar } from './components/Sidebar.jsx'; import { StatusPane } from './components/StatusPane.jsx'; import { OverviewPage } from './components/OverviewPage.jsx'; import { ClientOverviewPage } from './components/ClientOverviewPage.jsx'; +import { WindowsOverviewPage } from './components/WindowsOverviewPage.jsx'; import { ServersPage } from './components/ServersPage.jsx'; import { RoutingPage } from './components/RoutingPage.jsx'; import { LogsPage } from './components/LogsPage.jsx'; @@ -97,6 +98,9 @@ function App() { if (state?.mode === 'client' && page !== 'overview') { navigate('overview'); } + if (state?.mode === 'windows' && (page === 'servers' || page === 'routing')) { + navigate('overview'); + } }, [state?.mode, page]); useEffect(() => () => { @@ -381,6 +385,7 @@ function App() { [servers, state?.selectedTag], ); const isClientMode = state?.mode === 'client'; + const isWindowsMode = state?.mode === 'windows'; const dirtyRules = rulesSaveStatus === 'pending' || rulesSaveStatus === 'saving'; const dirtyDevices = Boolean( @@ -409,11 +414,14 @@ function App() { onTryApply={rollback} /> -
+
{!isClientMode && }
- {(page === 'overview' || isClientMode) && ( + {page === 'overview' && isWindowsMode && ( + + )} + {(page === 'overview' || isClientMode) && !isWindowsMode && ( isClientMode ? ( ) )} - {page === 'servers' && !isClientMode && ( + {page === 'servers' && !isClientMode && !isWindowsMode && ( )} - {page === 'routing' && !isClientMode && ( + {page === 'routing' && !isClientMode && !isWindowsMode && (
@@ -522,7 +530,7 @@ function App() {
)} - {(page === 'servers' && dirtyServer) && ( + {(page === 'servers' && dirtyServer && !isWindowsMode) && (
@@ -539,7 +547,7 @@ function App() { )}
- {!isClientMode && ( + {!isClientMode && !isWindowsMode && ( request("/api/windows/status"), + profiles: { + get: () => request("/api/windows/profiles"), + save: (profiles) => + request("/api/windows/profiles", { + method: "PUT", + body: JSON.stringify({ profiles }), + }), + scan: (profiles) => + request("/api/windows/profiles/scan", { + method: "POST", + body: JSON.stringify({ profiles }), + }), + apply: () => + request("/api/windows/profiles/apply", { + method: "POST", + }), + }, + targets: { + get: () => request("/api/windows/targets"), + save: (targets) => + request("/api/windows/targets", { + method: "PUT", + body: JSON.stringify({ targets }), + }), + }, + service: (service, action) => + request("/api/windows/service", { + method: "POST", + body: JSON.stringify({ service, action }), + }), + logs: () => request("/api/windows/logs"), + }, + configValidate: () => request("/api/config/validate", { method: "POST" }), }; diff --git a/src/web/components/Sidebar.jsx b/src/web/components/Sidebar.jsx index a22f8b6..79ea8ee 100644 --- a/src/web/components/Sidebar.jsx +++ b/src/web/components/Sidebar.jsx @@ -8,10 +8,18 @@ const NAV = [ { id: 'settings', label: 'Настройки', ico: '⚙' }, ]; +const WINDOWS_NAV = [ + { id: 'overview', label: 'Overview', ico: 'O' }, + { id: 'logs', label: 'Logs', ico: 'L' }, + { id: 'settings', label: 'Settings', ico: 'S' }, +]; + export function Sidebar({ active, onChange, badges = {}, mode = 'gateway' }) { - const items = mode === 'client' - ? NAV.filter((item) => item.id !== 'routing') - : NAV; + const items = mode === 'windows' + ? WINDOWS_NAV + : mode === 'client' + ? NAV.filter((item) => item.id !== 'routing') + : NAV; return (