From 35f55ea73e37479aba9d5a7011a53dd62046a250 Mon Sep 17 00:00:00 2001 From: DipokalLab Date: Tue, 28 Apr 2026 16:52:01 +0900 Subject: [PATCH 01/10] feat: update server url --- .../src/features/account-switcher/index.tsx | 4 ++- apps/client/src/features/sidebar/index.tsx | 12 ++++---- .../src/widgets/auth/AuthenticatedLayout.tsx | 29 ++++++++++++------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/apps/client/src/features/account-switcher/index.tsx b/apps/client/src/features/account-switcher/index.tsx index 4dcbc4c..3ad0cbf 100644 --- a/apps/client/src/features/account-switcher/index.tsx +++ b/apps/client/src/features/account-switcher/index.tsx @@ -37,7 +37,9 @@ export function AccountSwitcher({
Server - @{selectedVersion} + + {selectedVersion} +
diff --git a/apps/client/src/features/sidebar/index.tsx b/apps/client/src/features/sidebar/index.tsx index 2cf1be2..192a9de 100644 --- a/apps/client/src/features/sidebar/index.tsx +++ b/apps/client/src/features/sidebar/index.tsx @@ -30,6 +30,7 @@ import { } from "lucide-react"; import { NavFooter } from "./footer"; import { isElectron } from "@/lib/electron"; +import { storage } from "@/lib/storage"; import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; import { useIntegrationStore } from "@/entities/integrations/store"; import { useConfigStore } from "@/entities/configurations/store"; @@ -47,7 +48,6 @@ const CONTROLS_OPEN_KEY = "controls-menu-open"; const SIDEBAR_SCROLL_KEY = "sidebar-scroll-top"; const data = { - versions: ["main"], navMain: [ { title: "Dashboard", @@ -132,6 +132,11 @@ export function AppSidebar({ ...props }: ComponentProps) { useIntegrationStore(); const { configurations, fetchConfigs } = useConfigStore(); + const versions = useMemo(() => { + const serverUrl = storage.getServerUrl(); + return [serverUrl ?? "main"]; + }, []); + const scrollRef = useRef(null); const navMain = useMemo(() => { @@ -225,10 +230,7 @@ export function AppSidebar({ ...props }: ComponentProps) { }} > - + {navMain.map((group) => ( diff --git a/apps/client/src/widgets/auth/AuthenticatedLayout.tsx b/apps/client/src/widgets/auth/AuthenticatedLayout.tsx index 72f2c02..15d8548 100644 --- a/apps/client/src/widgets/auth/AuthenticatedLayout.tsx +++ b/apps/client/src/widgets/auth/AuthenticatedLayout.tsx @@ -5,7 +5,11 @@ import { FlowUiEventBridge } from "@/features/ws/FlowUiEventBridge"; import { TopBarWrapper } from "./TopBarWrapper"; import { isDemoMode } from "@/shared/demo"; import { storage } from "@/lib/storage"; -import { ChatPanelContainer, useChatStore, PANEL_WIDTH } from "@/features/llm-chat"; +import { + ChatPanelContainer, + useChatStore, + PANEL_WIDTH, +} from "@/features/llm-chat"; import { useIsMobile } from "@/hooks/use-mobile"; import { useConfigStore } from "@/entities/configurations/store"; import { isTauri, type SidecarStatus } from "@/shared/desktop"; @@ -49,14 +53,17 @@ export function AuthenticatedLayout() { (async () => { try { const { listen } = await import("@tauri-apps/api/event"); - const dispose = await listen("sidecar-restarted", (event) => { - const next = event.payload?.base_url; - if (next) { - storage.setServerUrl(next); - } - setWsUrl(null); - setReloadKey((k) => k + 1); - }); + const dispose = await listen( + "sidecar-restarted", + (event) => { + const next = event.payload?.base_url; + if (next) { + storage.setServerUrl(next); + } + setWsUrl(null); + setReloadKey((k) => k + 1); + }, + ); if (cancelled) { dispose(); } else { @@ -82,7 +89,7 @@ export function AuthenticatedLayout() { const isMobile = useIsMobile(); if (!wsUrl) { - return
Loading...
; + return
; } // Disable content push on mobile @@ -97,7 +104,7 @@ export function AuthenticatedLayout() { "--chat-panel-width": `${chatPanelWidth}px`, } as React.CSSProperties } - className="transition-[padding] duration-300 pr-[var(--chat-panel-width)]" + className='transition-[padding] duration-300 pr-[var(--chat-panel-width)]' > From c1b1f88fccd8028d6e41b00ff13e621d22360a80 Mon Sep 17 00:00:00 2001 From: DipokalLab Date: Tue, 28 Apr 2026 17:15:54 +0900 Subject: [PATCH 02/10] feat: add multiple server --- .../src/features/account-switcher/index.tsx | 129 ++++++++++-- apps/client/src/features/auth/hook.ts | 12 +- apps/client/src/features/auth/index.tsx | 55 ++--- apps/client/src/features/error/index.tsx | 4 +- apps/client/src/features/sidebar/index.tsx | 8 +- apps/client/src/lib/resetStores.ts | 42 ++++ apps/client/src/lib/storage.ts | 194 ++++++++++++++++-- apps/client/tsconfig.app.tsbuildinfo | 2 +- 8 files changed, 376 insertions(+), 70 deletions(-) create mode 100644 apps/client/src/lib/resetStores.ts diff --git a/apps/client/src/features/account-switcher/index.tsx b/apps/client/src/features/account-switcher/index.tsx index 3ad0cbf..7ed91bc 100644 --- a/apps/client/src/features/account-switcher/index.tsx +++ b/apps/client/src/features/account-switcher/index.tsx @@ -1,10 +1,12 @@ import * as React from "react"; -import { Check, ChevronsUpDown } from "lucide-react"; +import { Check, ChevronsUpDown, Plus, X } from "lucide-react"; +import { useNavigate } from "react-router"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { @@ -13,15 +15,62 @@ import { SidebarMenuItem, } from "@/components/ui/sidebar"; import { VesselLogo } from "@/components/icon/Logo"; +import { storage, type ServerConnection } from "@/lib/storage"; +import { hardNavigateToDashboard } from "@/lib/resetStores"; +import { isTauri } from "@/shared/desktop"; -export function AccountSwitcher({ - versions, - defaultVersion, -}: { - versions: string[]; - defaultVersion: string; -}) { - const [selectedVersion, setSelectedVersion] = React.useState(defaultVersion); +function displayName(server: ServerConnection): string { + if (server.name) return server.name; + try { + return new URL(server.url).host; + } catch { + return server.url; + } +} + +function useServers() { + const subscribe = React.useCallback( + (cb: () => void) => storage.subscribeServers(cb), + [], + ); + const servers = React.useSyncExternalStore( + subscribe, + () => storage.getServers(), + () => [], + ); + const active = React.useSyncExternalStore( + subscribe, + () => storage.getActiveServer(), + () => null, + ); + return { servers, active }; +} + +export function AccountSwitcher() { + const { servers, active } = useServers(); + const navigate = useNavigate(); + const desktop = isTauri(); + + const headerLabel = active ? displayName(active) : "main"; + + const switchTo = (id: string) => { + if (active?.id === id) return; + storage.setActiveServer(id); + hardNavigateToDashboard(); + }; + + const remove = (id: string) => { + const wasActive = active?.id === id; + storage.removeServer(id); + if (wasActive) { + const remaining = storage.getServers(); + if (remaining.length > 0) { + hardNavigateToDashboard(); + } else { + navigate("/auth", { replace: true }); + } + } + }; return ( @@ -35,10 +84,10 @@ export function AccountSwitcher({
-
+
Server - - {selectedVersion} + + {headerLabel}
@@ -48,15 +97,53 @@ export function AccountSwitcher({ className='w-(--radix-dropdown-menu-trigger-width)' align='start' > - {versions.map((version) => ( - setSelectedVersion(version)} - > - @{version}{" "} - {version === selectedVersion && } - - ))} + {servers.length === 0 && ( + No servers + )} + {servers.map((server) => { + const isActive = active?.id === server.id; + return ( + { + e.preventDefault(); + switchTo(server.id); + }} + className='flex items-center gap-2' + > + + @{displayName(server)} + + {isActive && } + {!desktop && ( + + )} + + ); + })} + {!desktop && ( + <> + + navigate("/auth?add=1")} + className='flex items-center gap-2' + > + + Add server + + + )} diff --git a/apps/client/src/features/auth/hook.ts b/apps/client/src/features/auth/hook.ts index 4602f8d..1b6df44 100644 --- a/apps/client/src/features/auth/hook.ts +++ b/apps/client/src/features/auth/hook.ts @@ -1,13 +1,21 @@ import { useCallback } from "react"; import { useNavigate } from "react-router"; import { storage } from "@/lib/storage"; +import { hardNavigateToDashboard } from "@/lib/resetStores"; export const useLogout = () => { const navigate = useNavigate(); const logout = useCallback(() => { - storage.removeToken(); - storage.removeServerUrl(); + const active = storage.getActiveServer(); + if (active) { + storage.removeServer(active.id); + } + const remaining = storage.getServers(); + if (remaining.length > 0) { + hardNavigateToDashboard(); + return; + } navigate("/auth", { replace: true }); }, [navigate]); diff --git a/apps/client/src/features/auth/index.tsx b/apps/client/src/features/auth/index.tsx index f7c523a..d7741f3 100644 --- a/apps/client/src/features/auth/index.tsx +++ b/apps/client/src/features/auth/index.tsx @@ -5,7 +5,7 @@ import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { useNavigate } from "react-router"; +import { useNavigate, useSearchParams } from "react-router"; import { ArrowRight, Loader2, Trash2 } from "lucide-react"; import { DEMO_SERVER_URL, DEMO_TOKEN, isDemoMode } from "@/shared/demo"; import { DefaultAdminPasswordDialog } from "./DefaultAdminPasswordDialog"; @@ -53,11 +53,12 @@ export function LoginForm({ ); const [desktopError, setDesktopError] = useState(null); const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const isAddMode = searchParams.get("add") === "1"; const connectToServer = async (targetUrl: string) => { if (isDemoMode) { - storage.setServerUrl(DEMO_SERVER_URL); - storage.setToken(DEMO_TOKEN); + storage.addServer({ url: DEMO_SERVER_URL, token: DEMO_TOKEN }); toast.success("Demo mode enabled. Loading demo dashboard..."); navigate("/dashboard"); return; @@ -98,7 +99,6 @@ export function LoginForm({ setRecentUrls(updatedUrls); storage.setRecentUrls(updatedUrls); - storage.setServerUrl(processedUrl); setServerUrlForDialog(processedUrl); setShowAuthFields(true); @@ -148,8 +148,7 @@ export function LoginForm({ password, }); - storage.setToken(token); - storage.setServerUrl(processedUrl); + storage.addServer({ url: processedUrl, token }); setServerUrlForDialog(processedUrl); @@ -193,7 +192,13 @@ export function LoginForm({ ); return; } - storage.setServerUrl(baseUrl); + // Tauri: ensure a server entry exists for the local sidecar; token added on auth success. + const existing = storage.getServers().find((s) => s.url === baseUrl); + if (!existing) { + storage.addServer({ url: baseUrl, token: "" }); + } else { + storage.setActiveServer(existing.id); + } setUrl(baseUrl); setShowAuthFields(true); } finally { @@ -203,8 +208,7 @@ export function LoginForm({ useEffect(() => { if (isDemoMode) { - storage.setServerUrl(DEMO_SERVER_URL); - storage.setToken(DEMO_TOKEN); + storage.addServer({ url: DEMO_SERVER_URL, token: DEMO_TOKEN }); toast.message("Demo mode active", { description: "Using mock data without a backend.", }); @@ -212,21 +216,23 @@ export function LoginForm({ return; } - const token = storage.getToken(); - const serverUrl = storage.getServerUrl(); + if (!isAddMode) { + const token = storage.getToken(); + const serverUrl = storage.getServerUrl(); - if (token && serverUrl) { - const parsed = parseJwt(token); - if (!parsed?.exp) { - storage.removeToken(); - } else { - const now = new Date(); - const exp = new Date(parsed.exp * 1000); - if (now.getTime() >= exp.getTime()) { + if (token && serverUrl) { + const parsed = parseJwt(token); + if (!parsed?.exp) { storage.removeToken(); } else { - navigate("/dashboard"); - return; + const now = new Date(); + const exp = new Date(parsed.exp * 1000); + if (now.getTime() >= exp.getTime()) { + storage.removeToken(); + } else { + navigate("/dashboard"); + return; + } } } } @@ -240,7 +246,7 @@ export function LoginForm({ if (storedUrls.length > 0) { setRecentUrls(storedUrls); } - }, [navigate, isTauriClient]); + }, [navigate, isTauriClient, isAddMode]); return (
@@ -399,9 +405,10 @@ export function LoginForm({ open={isPasswordDialogOpen} serverUrl={serverUrlForDialog} onSuccess={(refreshedToken) => { - storage.setToken(refreshedToken); if (serverUrlForDialog) { - storage.setServerUrl(serverUrlForDialog); + storage.addServer({ url: serverUrlForDialog, token: refreshedToken }); + } else { + storage.setToken(refreshedToken); } setIsPasswordDialogOpen(false); toast.success( diff --git a/apps/client/src/features/error/index.tsx b/apps/client/src/features/error/index.tsx index 49a0ce5..0ee0025 100644 --- a/apps/client/src/features/error/index.tsx +++ b/apps/client/src/features/error/index.tsx @@ -2,9 +2,9 @@ import { Button } from "@/components/ui/button"; export function ErrorRender() { return ( -
+
-

+

ERR

diff --git a/apps/client/src/features/sidebar/index.tsx b/apps/client/src/features/sidebar/index.tsx index 192a9de..a4da1aa 100644 --- a/apps/client/src/features/sidebar/index.tsx +++ b/apps/client/src/features/sidebar/index.tsx @@ -30,7 +30,6 @@ import { } from "lucide-react"; import { NavFooter } from "./footer"; import { isElectron } from "@/lib/electron"; -import { storage } from "@/lib/storage"; import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; import { useIntegrationStore } from "@/entities/integrations/store"; import { useConfigStore } from "@/entities/configurations/store"; @@ -132,11 +131,6 @@ export function AppSidebar({ ...props }: ComponentProps) { useIntegrationStore(); const { configurations, fetchConfigs } = useConfigStore(); - const versions = useMemo(() => { - const serverUrl = storage.getServerUrl(); - return [serverUrl ?? "main"]; - }, []); - const scrollRef = useRef(null); const navMain = useMemo(() => { @@ -230,7 +224,7 @@ export function AppSidebar({ ...props }: ComponentProps) { }} > - + {navMain.map((group) => ( diff --git a/apps/client/src/lib/resetStores.ts b/apps/client/src/lib/resetStores.ts new file mode 100644 index 0000000..a2855bf --- /dev/null +++ b/apps/client/src/lib/resetStores.ts @@ -0,0 +1,42 @@ +import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; +import { useIntegrationStore } from "@/entities/integrations/store"; +import { useConfigStore } from "@/entities/configurations/store"; + +/** + * Wipe in-memory zustand caches that are scoped to the active server. + * Belt-and-braces guard alongside the hard reload — covers the brief window + * before the browser unloads the document. + */ +export function resetServerScopedStores(): void { + useDynamicDashboardStore.setState({ + dashboards: [], + activeDashboardId: undefined, + isLoading: false, + hasLoaded: false, + error: null, + }); + + useIntegrationStore.setState({ + isHaConnected: false, + isRos2Connected: false, + isSdrConnected: false, + isLoading: false, + error: null, + }); + + useConfigStore.setState({ + configurations: [], + isLoading: false, + error: null, + }); +} + +/** + * Replace current location with `/dashboard` and force a fresh document load. + * The cache-buster query defeats Chrome's same-URL fast path that can keep + * the document alive when assigning the same path the user is already on. + */ +export function hardNavigateToDashboard(): void { + resetServerScopedStores(); + window.location.replace(`/dashboard?_=${Date.now()}`); +} diff --git a/apps/client/src/lib/storage.ts b/apps/client/src/lib/storage.ts index 3be584e..8672bbe 100644 --- a/apps/client/src/lib/storage.ts +++ b/apps/client/src/lib/storage.ts @@ -1,33 +1,198 @@ -const TOKEN_KEY = "vessel_token"; -const SERVER_URL_KEY = "vessel_server_url"; +const LEGACY_TOKEN_KEY = "vessel_token"; +const LEGACY_SERVER_URL_KEY = "vessel_server_url"; +const SERVERS_KEY = "vessel_servers"; const RECENT_URLS_KEY = "vessel_recent_server_urls"; const CAPSULE_URL_KEY = "vessel_capsule_url"; +export type ServerConnection = { + id: string; + url: string; + token: string; + name?: string; +}; + +type ServersBlob = { + servers: ServerConnection[]; + activeId: string | null; +}; + +type ServersListener = () => void; +const listeners = new Set(); + +function emit() { + listeners.forEach((l) => l()); +} + +function generateId(): string { + if (typeof crypto !== "undefined" && "randomUUID" in crypto) { + return crypto.randomUUID(); + } + return `srv_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`; +} + +function parseBlob(raw: string | null): ServersBlob { + if (!raw) return { servers: [], activeId: null }; + try { + const parsed = JSON.parse(raw); + if ( + parsed && + Array.isArray(parsed.servers) && + (typeof parsed.activeId === "string" || parsed.activeId === null) + ) { + return parsed as ServersBlob; + } + } catch { + // fall through + } + return { servers: [], activeId: null }; +} + +let cachedBlob: ServersBlob = { servers: [], activeId: null }; + +function readBlob(): ServersBlob { + return cachedBlob; +} + +function writeBlob(blob: ServersBlob): void { + cachedBlob = blob; + localStorage.setItem(SERVERS_KEY, JSON.stringify(blob)); + emit(); +} + +function getActive(blob: ServersBlob): ServerConnection | null { + if (!blob.activeId) return null; + return blob.servers.find((s) => s.id === blob.activeId) ?? null; +} + +function migrateLegacyIfNeeded(): void { + if (localStorage.getItem(SERVERS_KEY)) return; + const legacyToken = localStorage.getItem(LEGACY_TOKEN_KEY); + const legacyUrl = localStorage.getItem(LEGACY_SERVER_URL_KEY); + if (legacyToken && legacyUrl) { + const id = generateId(); + const blob: ServersBlob = { + servers: [{ id, url: legacyUrl, token: legacyToken }], + activeId: id, + }; + localStorage.setItem(SERVERS_KEY, JSON.stringify(blob)); + } + localStorage.removeItem(LEGACY_TOKEN_KEY); + localStorage.removeItem(LEGACY_SERVER_URL_KEY); +} + +if (typeof window !== "undefined") { + try { + migrateLegacyIfNeeded(); + cachedBlob = parseBlob(localStorage.getItem(SERVERS_KEY)); + window.addEventListener("storage", (e) => { + if (e.key !== SERVERS_KEY) return; + cachedBlob = parseBlob(e.newValue); + emit(); + }); + } catch { + // best effort + } +} + export const storage = { - getToken: (): string | null => { - return localStorage.getItem(TOKEN_KEY); + // ---------- multi-server API ---------- + getServers: (): ServerConnection[] => readBlob().servers, + + getActiveServer: (): ServerConnection | null => getActive(readBlob()), + + setActiveServer: (id: string): void => { + const blob = readBlob(); + if (!blob.servers.some((s) => s.id === id)) return; + writeBlob({ ...blob, activeId: id }); + }, + + addServer: (input: { url: string; token: string; name?: string }): string => { + const blob = readBlob(); + const existing = blob.servers.find((s) => s.url === input.url); + if (existing) { + const updated = { ...existing, token: input.token, name: input.name ?? existing.name }; + const servers = blob.servers.map((s) => (s.id === existing.id ? updated : s)); + writeBlob({ servers, activeId: existing.id }); + return existing.id; + } + const id = generateId(); + const next: ServerConnection = { id, url: input.url, token: input.token, name: input.name }; + writeBlob({ servers: [...blob.servers, next], activeId: id }); + return id; + }, + + removeServer: (id: string): void => { + const blob = readBlob(); + const servers = blob.servers.filter((s) => s.id !== id); + let activeId = blob.activeId; + if (activeId === id) { + activeId = servers[0]?.id ?? null; + } + writeBlob({ servers, activeId }); + }, + + updateServerToken: (id: string, token: string): void => { + const blob = readBlob(); + const servers = blob.servers.map((s) => (s.id === id ? { ...s, token } : s)); + writeBlob({ ...blob, servers }); + }, + + updateServerName: (id: string, name: string): void => { + const blob = readBlob(); + const servers = blob.servers.map((s) => (s.id === id ? { ...s, name } : s)); + writeBlob({ ...blob, servers }); + }, + + subscribeServers: (listener: ServersListener): (() => void) => { + listeners.add(listener); + return () => { + listeners.delete(listener); + }; }, + // ---------- legacy single-server API (routes through active server) ---------- + getToken: (): string | null => getActive(readBlob())?.token ?? null, + setToken: (token: string): void => { - localStorage.setItem(TOKEN_KEY, token); + const blob = readBlob(); + const active = getActive(blob); + if (!active) return; + const servers = blob.servers.map((s) => (s.id === active.id ? { ...s, token } : s)); + writeBlob({ ...blob, servers }); }, removeToken: (): void => { - localStorage.removeItem(TOKEN_KEY); + const blob = readBlob(); + const active = getActive(blob); + if (!active) return; + const servers = blob.servers.map((s) => (s.id === active.id ? { ...s, token: "" } : s)); + writeBlob({ ...blob, servers }); }, - getServerUrl: (): string | null => { - return localStorage.getItem(SERVER_URL_KEY); - }, + getServerUrl: (): string | null => getActive(readBlob())?.url ?? null, setServerUrl: (url: string): void => { - localStorage.setItem(SERVER_URL_KEY, url); + const blob = readBlob(); + const active = getActive(blob); + if (active) { + const servers = blob.servers.map((s) => (s.id === active.id ? { ...s, url } : s)); + writeBlob({ ...blob, servers }); + return; + } + const id = generateId(); + writeBlob({ + servers: [...blob.servers, { id, url, token: "" }], + activeId: id, + }); }, removeServerUrl: (): void => { - localStorage.removeItem(SERVER_URL_KEY); + const blob = readBlob(); + if (!blob.activeId) return; + storage.removeServer(blob.activeId); }, + // ---------- recent URLs (login history; unchanged) ---------- getRecentUrls: (): string[] => { const stored = localStorage.getItem(RECENT_URLS_KEY); if (!stored) return []; @@ -47,6 +212,7 @@ export const storage = { localStorage.removeItem(RECENT_URLS_KEY); }, + // ---------- capsule URL (LLM chat; unrelated to server URL) ---------- getCapsuleUrl: (): string | null => { return localStorage.getItem(CAPSULE_URL_KEY); }, @@ -60,8 +226,10 @@ export const storage = { }, clearAll: (): void => { - localStorage.removeItem(TOKEN_KEY); - localStorage.removeItem(SERVER_URL_KEY); + localStorage.removeItem(SERVERS_KEY); + localStorage.removeItem(LEGACY_TOKEN_KEY); + localStorage.removeItem(LEGACY_SERVER_URL_KEY); localStorage.removeItem(CAPSULE_URL_KEY); + emit(); }, }; diff --git a/apps/client/tsconfig.app.tsbuildinfo b/apps/client/tsconfig.app.tsbuildinfo index 39de0cc..21f0ed5 100644 --- a/apps/client/tsconfig.app.tsbuildinfo +++ b/apps/client/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/app/pagewrapper/page-wrapper.tsx","./src/app/providers/theme-provider.tsx","./src/components/icon/logo.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/command.tsx","./src/components/ui/context-menu.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/navigation-menu.tsx","./src/components/ui/popover.tsx","./src/components/ui/resizable.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/tooltip.tsx","./src/contexts/supabaseauthcontext.tsx","./src/entities/configurations/api.ts","./src/entities/configurations/codeservice.ts","./src/entities/configurations/store.ts","./src/entities/configurations/types.ts","./src/entities/custom-nodes/api.ts","./src/entities/custom-nodes/presets.ts","./src/entities/custom-nodes/store.ts","./src/entities/custom-nodes/types.ts","./src/entities/device/api.ts","./src/entities/device/store.ts","./src/entities/device/types.ts","./src/entities/device-token/api.ts","./src/entities/device-token/store.ts","./src/entities/device-token/types.ts","./src/entities/dynamic-dashboard/api.ts","./src/entities/dynamic-dashboard/interaction.ts","./src/entities/dynamic-dashboard/layoutresolve.ts","./src/entities/dynamic-dashboard/store.ts","./src/entities/entity/api.ts","./src/entities/entity/store.ts","./src/entities/entity/types.ts","./src/entities/file/api.ts","./src/entities/file/store.ts","./src/entities/file/types.ts","./src/entities/flow/api.ts","./src/entities/flow/store.ts","./src/entities/flow/types.ts","./src/entities/ha/api.ts","./src/entities/ha/store.ts","./src/entities/ha/types.ts","./src/entities/integrations/api.ts","./src/entities/integrations/store.ts","./src/entities/integrations/types.ts","./src/entities/log/api.ts","./src/entities/log/types.ts","./src/entities/map/api.ts","./src/entities/map/store.ts","./src/entities/map/types.ts","./src/entities/permission/api.ts","./src/entities/permission/store.ts","./src/entities/permission/types.ts","./src/entities/recording/api.ts","./src/entities/recording/index.ts","./src/entities/recording/store.ts","./src/entities/recording/types.ts","./src/entities/role/api.ts","./src/entities/role/store.ts","./src/entities/role/types.ts","./src/entities/stat/api.ts","./src/entities/stat/store.ts","./src/entities/stat/types.ts","./src/entities/tunnel/api.ts","./src/entities/tunnel/store.ts","./src/entities/tunnel/types.ts","./src/entities/user/api.ts","./src/entities/user/store.ts","./src/entities/user/types.ts","./src/features/account-switcher/index.tsx","./src/features/auth/authinterceptor.tsx","./src/features/auth/defaultadminpassworddialog.tsx","./src/features/auth/api.ts","./src/features/auth/hook.ts","./src/features/auth/index.tsx","./src/features/code/createitemdialog.tsx","./src/features/code/fileeditor.tsx","./src/features/code/filetree.tsx","./src/features/configurations/configurationactionbutton.tsx","./src/features/configurations/configurationcreate.tsx","./src/features/configurations/configurationcreatebutton.tsx","./src/features/darkmode/mode-toggle.tsx","./src/features/dashboard-swipe/dashboardswipeheader.tsx","./src/features/dashboard-swipe/dashboardswipelayout.tsx","./src/features/device/devicecreatebutton.tsx","./src/features/device/devicedeletebutton.tsx","./src/features/device/devicekeybutton.tsx","./src/features/device/deviceupdatebutton.tsx","./src/features/device-token/devicetokenmanager.tsx","./src/features/dynamic-dashboard/groupcanvas.tsx","./src/features/dynamic-dashboard/events/dispatcher.test.ts","./src/features/dynamic-dashboard/events/dispatcher.ts","./src/features/dynamic-dashboard/panels/buttonpanel.tsx","./src/features/dynamic-dashboard/panels/flowpanel.tsx","./src/features/dynamic-dashboard/panels/mappanel.tsx","./src/features/entity/allentities.tsx","./src/features/entity/analyzemenuitem.tsx","./src/features/entity/card.tsx","./src/features/entity/entitycreatebutton.tsx","./src/features/entity/entitydeletebutton.tsx","./src/features/entity/entityupdatebutton.tsx","./src/features/entity/selectplatforms.tsx","./src/features/entity/selecttypes.tsx","./src/features/entity/statehistorysheet.tsx","./src/features/entity/useentitiesdata.ts","./src/features/error/index.tsx","./src/features/flow/addcustomnode.tsx","./src/features/flow/flow.tsx","./src/features/flow/graph.tsx","./src/features/flow/options.tsx","./src/features/flow/optionsvariation.tsx","./src/features/flow/runflow.tsx","./src/features/flow/selecteditemactions.tsx","./src/features/flow/flownode.ts","./src/features/flow/flowtypes.ts","./src/features/flow/flowutils.ts","./src/features/flow/flow-chat/buildsystemprompt.ts","./src/features/flow/flow-chat/executetoolcalls.ts","./src/features/flow/flow-chat/flowtools.ts","./src/features/flow/flow-chat/index.ts","./src/features/flow/nodes/buttonnode.tsx","./src/features/flow/nodes/calcnode.tsx","./src/features/flow/nodes/httpnode.tsx","./src/features/flow/nodes/intervalnode.tsx","./src/features/flow/nodes/logicnode.tsx","./src/features/flow/nodes/loopnode.tsx","./src/features/flow/nodes/mqttnode.tsx","./src/features/flow/nodes/numbernode.tsx","./src/features/flow/nodes/processingnode.tsx","./src/features/flow/nodes/titlenode.tsx","./src/features/flow/nodes/varnode.tsx","./src/features/flow-log/flowlog.tsx","./src/features/footer/index.tsx","./src/features/gps/parsegps.ts","./src/features/ha/haentitiestable.tsx","./src/features/ha/hastatblock.tsx","./src/features/ha/index.tsx","./src/features/integration/ha.tsx","./src/features/integration/integration.tsx","./src/features/integration/ros.tsx","./src/features/integration/sdr.tsx","./src/features/integration/constants.ts","./src/features/integration/types.ts","./src/features/json/jsoneditor.tsx","./src/features/llm-chat/chatinput.tsx","./src/features/llm-chat/chatmessage.tsx","./src/features/llm-chat/chatmessages.tsx","./src/features/llm-chat/chatpanel.tsx","./src/features/llm-chat/chatpanelcontainer.tsx","./src/features/llm-chat/chatpanelmobile.tsx","./src/features/llm-chat/index.tsx","./src/features/llm-chat/store.ts","./src/features/llm-chat/types.ts","./src/features/llm-chat/usechatkeyboard.ts","./src/features/log/index.tsx","./src/features/map/currentlocationmarker.tsx","./src/features/map/mapviewpersistence.tsx","./src/features/map/index.tsx","./src/features/map-draw/featuredetailspanel.tsx","./src/features/map-draw/featuredrawingpreview.tsx","./src/features/map-draw/featureeditor.tsx","./src/features/map-draw/featurerenderer.tsx","./src/features/map-draw/layerdialog.tsx","./src/features/map-draw/layersidebar.tsx","./src/features/map-draw/mapevents.tsx","./src/features/map-draw/maptoolbar.tsx","./src/features/map-entity/entitydetailspanel.tsx","./src/features/map-entity/render.tsx","./src/features/map-entity/store.ts","./src/features/recording/recordingbutton.tsx","./src/features/recording/recordingslist.tsx","./src/features/recording/videoplaybackdialog.tsx","./src/features/recording/index.ts","./src/features/recording/components/audiowaveformplayer.tsx","./src/features/recording/components/frametimeline.tsx","./src/features/recording/components/playbackcontrols.tsx","./src/features/recording/components/timeruler.tsx","./src/features/recording/components/videocontrolbar.tsx","./src/features/recording/components/waveformcanvas.tsx","./src/features/recording/hooks/useaudiowaveform.ts","./src/features/recording/hooks/usemediaplayback.ts","./src/features/recording/hooks/usevideoframes.ts","./src/features/role/roledialogs.tsx","./src/features/role/roleform.tsx","./src/features/ros2/ros2dashboard.tsx","./src/features/rtc/audiolevelbar.tsx","./src/features/rtc/streamreceiver.tsx","./src/features/rtc/webrtcprovider.tsx","./src/features/rtc/captureframe.ts","./src/features/rtc/rtc.ts","./src/features/rtc/turnservice.ts","./src/features/sdr/sdraudioplayer.tsx","./src/features/sdr/sdrdashboard.tsx","./src/features/sdr/api.ts","./src/features/search/search-form.tsx","./src/features/server-resource/resourceusage.tsx","./src/features/setup/index.tsx","./src/features/sidebar/footer.tsx","./src/features/sidebar/index.tsx","./src/features/stat/index.tsx","./src/features/topbar/index.tsx","./src/features/user/userroleassigner.tsx","./src/features/user/useradd.tsx","./src/features/user/userdelete.tsx","./src/features/user/useredit.tsx","./src/features/user/userform.tsx","./src/features/ws/flowuieventbridge.tsx","./src/features/ws/isconnected.tsx","./src/features/ws/websocketprovider.tsx","./src/features/ws/flowuieventrouter.test.ts","./src/features/ws/flowuieventrouter.ts","./src/features/ws/ws.ts","./src/features/ws/wsmock.ts","./src/features/ws/flowuiadapters/toastflowuiadapter.ts","./src/hooks/use-mobile.ts","./src/hooks/usedesktopsidecar.ts","./src/hooks/usepreventbacknavigation.ts","./src/lib/electron.ts","./src/lib/geometry-precision.ts","./src/lib/geometry.ts","./src/lib/jwt.ts","./src/lib/storage.ts","./src/lib/string.ts","./src/lib/supabase.ts","./src/lib/time.ts","./src/lib/utils.ts","./src/pages/auth/index.tsx","./src/pages/code/index.tsx","./src/pages/dashboard/dashboardmainpanel.tsx","./src/pages/dashboard/index.tsx","./src/pages/desktop-settings/index.tsx","./src/pages/devices/index.tsx","./src/pages/dynamic-dashboard/dynamicdashboardmainpanel.tsx","./src/pages/dynamic-dashboard/newdynamicdashboardpanel.tsx","./src/pages/dynamic-dashboard/index.tsx","./src/pages/flow/index.tsx","./src/pages/landing/index.tsx","./src/pages/map/index.tsx","./src/pages/notfound/index.tsx","./src/pages/recordings/index.tsx","./src/pages/settings/account.tsx","./src/pages/settings/config.tsx","./src/pages/settings/index.tsx","./src/pages/settings/integration.tsx","./src/pages/settings/log.tsx","./src/pages/settings/networks.tsx","./src/pages/settings/services.tsx","./src/pages/settings/users.tsx","./src/pages/setup/index.tsx","./src/shared/demo.ts","./src/shared/desktop.ts","./src/shared/api/index.ts","./src/shared/mock/mockadapter.ts","./src/shared/mock/mockdata.ts","./src/widgets/auth/authenticatedlayout.tsx","./src/widgets/auth/topbarwrapper.tsx","./src/widgets/device-list/devicelist.tsx","./src/widgets/entity-list/entitylist.tsx","./src/widgets/role-table/rolelist.tsx","./src/widgets/user-table/userlist.tsx"],"errors":true,"version":"5.8.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/app/pagewrapper/page-wrapper.tsx","./src/app/providers/theme-provider.tsx","./src/components/icon/logo.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/command.tsx","./src/components/ui/context-menu.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/navigation-menu.tsx","./src/components/ui/popover.tsx","./src/components/ui/resizable.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/tooltip.tsx","./src/contexts/supabaseauthcontext.tsx","./src/entities/configurations/api.ts","./src/entities/configurations/codeservice.ts","./src/entities/configurations/store.ts","./src/entities/configurations/types.ts","./src/entities/custom-nodes/api.ts","./src/entities/custom-nodes/presets.ts","./src/entities/custom-nodes/store.ts","./src/entities/custom-nodes/types.ts","./src/entities/device/api.ts","./src/entities/device/store.ts","./src/entities/device/types.ts","./src/entities/device-token/api.ts","./src/entities/device-token/store.ts","./src/entities/device-token/types.ts","./src/entities/dynamic-dashboard/api.ts","./src/entities/dynamic-dashboard/interaction.ts","./src/entities/dynamic-dashboard/layoutresolve.ts","./src/entities/dynamic-dashboard/store.ts","./src/entities/entity/api.ts","./src/entities/entity/store.ts","./src/entities/entity/types.ts","./src/entities/file/api.ts","./src/entities/file/store.ts","./src/entities/file/types.ts","./src/entities/flow/api.ts","./src/entities/flow/store.ts","./src/entities/flow/types.ts","./src/entities/ha/api.ts","./src/entities/ha/store.ts","./src/entities/ha/types.ts","./src/entities/integrations/api.ts","./src/entities/integrations/store.ts","./src/entities/integrations/types.ts","./src/entities/log/api.ts","./src/entities/log/types.ts","./src/entities/map/api.ts","./src/entities/map/store.ts","./src/entities/map/types.ts","./src/entities/permission/api.ts","./src/entities/permission/store.ts","./src/entities/permission/types.ts","./src/entities/recording/api.ts","./src/entities/recording/index.ts","./src/entities/recording/store.ts","./src/entities/recording/types.ts","./src/entities/role/api.ts","./src/entities/role/store.ts","./src/entities/role/types.ts","./src/entities/stat/api.ts","./src/entities/stat/store.ts","./src/entities/stat/types.ts","./src/entities/tunnel/api.ts","./src/entities/tunnel/store.ts","./src/entities/tunnel/types.ts","./src/entities/user/api.ts","./src/entities/user/store.ts","./src/entities/user/types.ts","./src/features/account-switcher/index.tsx","./src/features/auth/authinterceptor.tsx","./src/features/auth/defaultadminpassworddialog.tsx","./src/features/auth/api.ts","./src/features/auth/hook.ts","./src/features/auth/index.tsx","./src/features/code/createitemdialog.tsx","./src/features/code/fileeditor.tsx","./src/features/code/filetree.tsx","./src/features/configurations/configurationactionbutton.tsx","./src/features/configurations/configurationcreate.tsx","./src/features/configurations/configurationcreatebutton.tsx","./src/features/darkmode/mode-toggle.tsx","./src/features/dashboard-swipe/dashboardswipeheader.tsx","./src/features/dashboard-swipe/dashboardswipelayout.tsx","./src/features/device/devicecreatebutton.tsx","./src/features/device/devicedeletebutton.tsx","./src/features/device/devicekeybutton.tsx","./src/features/device/deviceupdatebutton.tsx","./src/features/device-token/devicetokenmanager.tsx","./src/features/dynamic-dashboard/groupcanvas.tsx","./src/features/dynamic-dashboard/events/dispatcher.test.ts","./src/features/dynamic-dashboard/events/dispatcher.ts","./src/features/dynamic-dashboard/panels/buttonpanel.tsx","./src/features/dynamic-dashboard/panels/flowpanel.tsx","./src/features/dynamic-dashboard/panels/mappanel.tsx","./src/features/entity/allentities.tsx","./src/features/entity/analyzemenuitem.tsx","./src/features/entity/card.tsx","./src/features/entity/entitycreatebutton.tsx","./src/features/entity/entitydeletebutton.tsx","./src/features/entity/entityupdatebutton.tsx","./src/features/entity/selectplatforms.tsx","./src/features/entity/selecttypes.tsx","./src/features/entity/statehistorysheet.tsx","./src/features/entity/useentitiesdata.ts","./src/features/error/index.tsx","./src/features/flow/addcustomnode.tsx","./src/features/flow/flow.tsx","./src/features/flow/graph.tsx","./src/features/flow/options.tsx","./src/features/flow/optionsvariation.tsx","./src/features/flow/runflow.tsx","./src/features/flow/selecteditemactions.tsx","./src/features/flow/flownode.ts","./src/features/flow/flowtypes.ts","./src/features/flow/flowutils.ts","./src/features/flow/flow-chat/buildsystemprompt.ts","./src/features/flow/flow-chat/executetoolcalls.ts","./src/features/flow/flow-chat/flowtools.ts","./src/features/flow/flow-chat/index.ts","./src/features/flow/nodes/buttonnode.tsx","./src/features/flow/nodes/calcnode.tsx","./src/features/flow/nodes/httpnode.tsx","./src/features/flow/nodes/intervalnode.tsx","./src/features/flow/nodes/logicnode.tsx","./src/features/flow/nodes/loopnode.tsx","./src/features/flow/nodes/mqttnode.tsx","./src/features/flow/nodes/numbernode.tsx","./src/features/flow/nodes/processingnode.tsx","./src/features/flow/nodes/titlenode.tsx","./src/features/flow/nodes/varnode.tsx","./src/features/flow-log/flowlog.tsx","./src/features/footer/index.tsx","./src/features/gps/parsegps.ts","./src/features/ha/haentitiestable.tsx","./src/features/ha/hastatblock.tsx","./src/features/ha/index.tsx","./src/features/integration/ha.tsx","./src/features/integration/integration.tsx","./src/features/integration/ros.tsx","./src/features/integration/sdr.tsx","./src/features/integration/constants.ts","./src/features/integration/types.ts","./src/features/json/jsoneditor.tsx","./src/features/llm-chat/chatinput.tsx","./src/features/llm-chat/chatmessage.tsx","./src/features/llm-chat/chatmessages.tsx","./src/features/llm-chat/chatpanel.tsx","./src/features/llm-chat/chatpanelcontainer.tsx","./src/features/llm-chat/chatpanelmobile.tsx","./src/features/llm-chat/index.tsx","./src/features/llm-chat/store.ts","./src/features/llm-chat/types.ts","./src/features/llm-chat/usechatkeyboard.ts","./src/features/log/index.tsx","./src/features/map/currentlocationmarker.tsx","./src/features/map/mapviewpersistence.tsx","./src/features/map/index.tsx","./src/features/map-draw/featuredetailspanel.tsx","./src/features/map-draw/featuredrawingpreview.tsx","./src/features/map-draw/featureeditor.tsx","./src/features/map-draw/featurerenderer.tsx","./src/features/map-draw/layerdialog.tsx","./src/features/map-draw/layersidebar.tsx","./src/features/map-draw/mapevents.tsx","./src/features/map-draw/maptoolbar.tsx","./src/features/map-entity/entitydetailspanel.tsx","./src/features/map-entity/render.tsx","./src/features/map-entity/store.ts","./src/features/recording/recordingbutton.tsx","./src/features/recording/recordingslist.tsx","./src/features/recording/videoplaybackdialog.tsx","./src/features/recording/index.ts","./src/features/recording/components/audiowaveformplayer.tsx","./src/features/recording/components/frametimeline.tsx","./src/features/recording/components/playbackcontrols.tsx","./src/features/recording/components/timeruler.tsx","./src/features/recording/components/videocontrolbar.tsx","./src/features/recording/components/waveformcanvas.tsx","./src/features/recording/hooks/useaudiowaveform.ts","./src/features/recording/hooks/usemediaplayback.ts","./src/features/recording/hooks/usevideoframes.ts","./src/features/role/roledialogs.tsx","./src/features/role/roleform.tsx","./src/features/ros2/ros2dashboard.tsx","./src/features/rtc/audiolevelbar.tsx","./src/features/rtc/streamreceiver.tsx","./src/features/rtc/webrtcprovider.tsx","./src/features/rtc/captureframe.ts","./src/features/rtc/rtc.ts","./src/features/rtc/turnservice.ts","./src/features/sdr/sdraudioplayer.tsx","./src/features/sdr/sdrdashboard.tsx","./src/features/sdr/api.ts","./src/features/search/search-form.tsx","./src/features/server-resource/resourceusage.tsx","./src/features/setup/index.tsx","./src/features/sidebar/footer.tsx","./src/features/sidebar/index.tsx","./src/features/stat/index.tsx","./src/features/topbar/index.tsx","./src/features/user/userroleassigner.tsx","./src/features/user/useradd.tsx","./src/features/user/userdelete.tsx","./src/features/user/useredit.tsx","./src/features/user/userform.tsx","./src/features/ws/flowuieventbridge.tsx","./src/features/ws/isconnected.tsx","./src/features/ws/websocketprovider.tsx","./src/features/ws/flowuieventrouter.test.ts","./src/features/ws/flowuieventrouter.ts","./src/features/ws/ws.ts","./src/features/ws/wsmock.ts","./src/features/ws/flowuiadapters/toastflowuiadapter.ts","./src/hooks/use-mobile.ts","./src/hooks/usedesktopsidecar.ts","./src/hooks/usepreventbacknavigation.ts","./src/lib/electron.ts","./src/lib/geometry-precision.ts","./src/lib/geometry.ts","./src/lib/jwt.ts","./src/lib/resetstores.ts","./src/lib/storage.ts","./src/lib/string.ts","./src/lib/supabase.ts","./src/lib/time.ts","./src/lib/utils.ts","./src/pages/auth/index.tsx","./src/pages/code/index.tsx","./src/pages/dashboard/dashboardmainpanel.tsx","./src/pages/dashboard/index.tsx","./src/pages/desktop-settings/index.tsx","./src/pages/devices/index.tsx","./src/pages/dynamic-dashboard/dynamicdashboardmainpanel.tsx","./src/pages/dynamic-dashboard/newdynamicdashboardpanel.tsx","./src/pages/dynamic-dashboard/index.tsx","./src/pages/flow/index.tsx","./src/pages/landing/index.tsx","./src/pages/map/index.tsx","./src/pages/notfound/index.tsx","./src/pages/recordings/index.tsx","./src/pages/settings/account.tsx","./src/pages/settings/config.tsx","./src/pages/settings/index.tsx","./src/pages/settings/integration.tsx","./src/pages/settings/log.tsx","./src/pages/settings/networks.tsx","./src/pages/settings/services.tsx","./src/pages/settings/users.tsx","./src/pages/setup/index.tsx","./src/shared/demo.ts","./src/shared/desktop.ts","./src/shared/api/index.ts","./src/shared/mock/mockadapter.ts","./src/shared/mock/mockdata.ts","./src/widgets/auth/authenticatedlayout.tsx","./src/widgets/auth/topbarwrapper.tsx","./src/widgets/device-list/devicelist.tsx","./src/widgets/entity-list/entitylist.tsx","./src/widgets/role-table/rolelist.tsx","./src/widgets/user-table/userlist.tsx"],"errors":true,"version":"5.8.3"} \ No newline at end of file From 5632ca0f6fdced811f3d2c8d05d7316750e99eb9 Mon Sep 17 00:00:00 2001 From: DipokalLab Date: Tue, 28 Apr 2026 17:28:38 +0900 Subject: [PATCH 03/10] feat: add e2e test code --- jest.config.js | 6 +- tests/api.test.js | 1135 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 1020 insertions(+), 121 deletions(-) diff --git a/jest.config.js b/jest.config.js index f053ebf..11fa62a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1 +1,5 @@ -module.exports = {}; +module.exports = { + rootDir: __dirname, + roots: ["/tests"], + testMatch: ["/tests/**/*.test.js"], +}; diff --git a/tests/api.test.js b/tests/api.test.js index afcd172..a695de9 100644 --- a/tests/api.test.js +++ b/tests/api.test.js @@ -1,18 +1,16 @@ const request = require("supertest"); const API_BASE_URL = "http://localhost:6174"; +const RUN_ID = Date.now(); +const uniq = (s) => `${s}_${RUN_ID}`; describe("Vessel API E2E Test", () => { let authToken; - let newDeviceId; - let newEntityId; - let newLayerId; - let newFeatureId; beforeAll(async () => { const response = await request(API_BASE_URL).post("/api/auth").send({ id: "admin", - password: "admin", + password: "admin1", }); expect(response.status).toBe(200); @@ -20,165 +18,1062 @@ describe("Vessel API E2E Test", () => { authToken = response.body.token; }); - describe("Devices API", () => { - it("should create a new device successfully", async () => { - const response = await request(API_BASE_URL) - .post("/api/devices") - .set("Authorization", `Bearer ${authToken}`) - .send({ - device_id: "jest_test_device_01", - name: "Jest Test Device", - manufacturer: "Jest", - model: "Supertest v1", - }); + const auth = (req) => req.set("Authorization", `Bearer ${authToken}`); + + describe("Server Info", () => { + it("GET /info returns server info without auth", async () => { + const res = await request(API_BASE_URL).get("/info"); + expect(res.status).toBe(200); + expect(res.body).toMatchObject({ id: "vessel-server", code: 200 }); + }); + }); + + describe("Auth API", () => { + it("POST /api/auth rejects wrong credentials with 401", async () => { + const res = await request(API_BASE_URL).post("/api/auth").send({ + id: "admin", + password: "definitely-wrong-password", + }); + expect(res.status).toBe(401); + }); - expect(response.status).toBe(200); - expect(response.body).toHaveProperty("id"); - expect(response.body.name).toBe("Jest Test Device"); + it("POST /api/auth returns 401 when user does not exist", async () => { + const res = await request(API_BASE_URL).post("/api/auth").send({ + id: uniq("ghost"), + password: "any", + }); + expect(res.status).toBe(401); + }); - newDeviceId = response.body.id; + it("protected endpoint without token returns 400", async () => { + const res = await request(API_BASE_URL).get("/api/devices"); + expect(res.status).toBe(400); }); - it("should get a list of all devices", async () => { - const response = await request(API_BASE_URL) + it("protected endpoint with bad token returns 401", async () => { + const res = await request(API_BASE_URL) .get("/api/devices") - .set("Authorization", `Bearer ${authToken}`); + .set("Authorization", "Bearer not-a-real-token"); + expect(res.status).toBe(401); + }); + }); - expect(response.status).toBe(200); - expect(Array.isArray(response.body)).toBe(true); - expect(response.body.length).toBeGreaterThan(0); + describe("Permissions API", () => { + let permissionId; + + it("POST /api/permissions creates permission", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/permissions") + ).send({ + name: uniq("perm.test"), + description: "Permission created by jest", + }); + expect(res.status).toBe(201); + expect(res.body).toHaveProperty("id"); + expect(res.body.name).toBe(uniq("perm.test")); + permissionId = res.body.id; }); - it("should update an existing device", async () => { - const response = await request(API_BASE_URL) - .put(`/api/devices/${newDeviceId}`) - .set("Authorization", `Bearer ${authToken}`) - .send({ - device_id: "jest_test_device_01_updated", - name: "Updated Jest Device", - manufacturer: "Jest", - model: "Supertest v2", + it("GET /api/permissions lists permissions", async () => { + const res = await auth(request(API_BASE_URL).get("/api/permissions")); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.some((p) => p.id === permissionId)).toBe(true); + }); + + describe("Roles API", () => { + let roleId; + let extraPermissionId; + + it("creates an additional permission for role tests", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/permissions") + ).send({ + name: uniq("perm.role.test"), + description: null, + }); + expect(res.status).toBe(201); + extraPermissionId = res.body.id; + }); + + it("POST /api/roles creates role with permissions", async () => { + const res = await auth(request(API_BASE_URL).post("/api/roles")).send({ + name: uniq("role.test"), + description: "role created by jest", + permission_ids: [permissionId], + }); + expect(res.status).toBe(201); + expect(res.body).toHaveProperty("id"); + expect(res.body.name).toBe(uniq("role.test")); + roleId = res.body.id; + }); + + it("GET /api/roles lists roles with permissions", async () => { + const res = await auth(request(API_BASE_URL).get("/api/roles")); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.some((r) => r.id === roleId)).toBe(true); + }); + + it("PUT /api/roles/:id updates role", async () => { + const res = await auth( + request(API_BASE_URL).put(`/api/roles/${roleId}`) + ).send({ + name: uniq("role.test.updated"), + description: "updated", + permission_ids: [permissionId], + }); + expect(res.status).toBe(200); + expect(res.body.name).toBe(uniq("role.test.updated")); + }); + + it("POST /api/roles/:id/permissions grants permission", async () => { + const res = await auth( + request(API_BASE_URL).post(`/api/roles/${roleId}/permissions`) + ).send({ permission_id: extraPermissionId }); + expect(res.status).toBe(201); + }); + + it("GET /api/roles/:id/permissions lists role permissions", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/roles/${roleId}/permissions`) + ); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.some((p) => p.id === extraPermissionId)).toBe(true); + }); + + it("DELETE /api/roles/:id/permissions/:permission_id revokes permission", async () => { + const res = await auth( + request(API_BASE_URL).delete( + `/api/roles/${roleId}/permissions/${extraPermissionId}` + ) + ); + expect(res.status).toBe(204); + }); + + describe("User-role assignment", () => { + let userId; + + beforeAll(async () => { + const res = await request(API_BASE_URL) + .post("/api/users") + .send({ + username: uniq("rbac_user"), + email: `${uniq("rbac_user")}@example.com`, + password: "rbacpass", + }); + expect(res.status).toBe(201); + userId = res.body.id; }); - expect(response.status).toBe(200); - expect(response.body.id).toBe(newDeviceId); - expect(response.body.name).toBe("Updated Jest Device"); + it("POST /api/users/:id/roles assigns role", async () => { + const res = await auth( + request(API_BASE_URL).post(`/api/users/${userId}/roles`) + ).send({ role_id: roleId }); + expect(res.status).toBe(201); + }); + + it("GET /api/users/:id/roles lists user roles", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/users/${userId}/roles`) + ); + expect(res.status).toBe(200); + expect(res.body.some((r) => r.id === roleId)).toBe(true); + }); + + it("DELETE /api/users/:id/roles/:role_id removes role", async () => { + const res = await auth( + request(API_BASE_URL).delete( + `/api/users/${userId}/roles/${roleId}` + ) + ); + expect(res.status).toBe(204); + }); + + afterAll(async () => { + if (userId) { + await auth( + request(API_BASE_URL).delete(`/api/users/${userId}`) + ); + } + }); + }); + + afterAll(async () => { + if (roleId) { + await auth(request(API_BASE_URL).delete(`/api/roles/${roleId}`)); + } + }); + }); + }); + + describe("Users API", () => { + let userId; + const username = uniq("user"); + + it("POST /api/users creates user (no auth required)", async () => { + const res = await request(API_BASE_URL).post("/api/users").send({ + username, + email: `${username}@example.com`, + password: "secret123", + }); + expect(res.status).toBe(201); + expect(res.body).toHaveProperty("id"); + expect(res.body.username).toBe(username); + userId = res.body.id; + }); + + it("GET /api/users lists users", async () => { + const res = await auth(request(API_BASE_URL).get("/api/users")); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.some((u) => u.id === userId)).toBe(true); + }); + + it("GET /api/users/:id returns single user", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/users/${userId}`) + ); + expect(res.status).toBe(200); + expect(res.body.id).toBe(userId); + expect(res.body.username).toBe(username); + }); + + it("PUT /api/users/:id updates user", async () => { + const res = await auth( + request(API_BASE_URL).put(`/api/users/${userId}`) + ).send({ + username: uniq("user_renamed"), + email: `${uniq("user_renamed")}@example.com`, + }); + expect(res.status).toBe(200); + expect(res.body.username).toBe(uniq("user_renamed")); + }); + + it("DELETE /api/users/:id deletes user", async () => { + const res = await auth( + request(API_BASE_URL).delete(`/api/users/${userId}`) + ); + expect(res.status).toBe(204); + }); + }); + + describe("Devices API", () => { + let newDeviceId; + const deviceIdStr = uniq("dev"); + + it("POST /api/devices creates device", async () => { + const res = await auth(request(API_BASE_URL).post("/api/devices")).send({ + device_id: deviceIdStr, + name: "Jest Test Device", + manufacturer: "Jest", + model: "Supertest v1", + }); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("id"); + expect(res.body.name).toBe("Jest Test Device"); + newDeviceId = res.body.id; + }); + + it("GET /api/devices lists devices", async () => { + const res = await auth(request(API_BASE_URL).get("/api/devices")); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.length).toBeGreaterThan(0); + }); + + it("GET /api/devices/id/:device_pk_id returns device with entities", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/devices/id/${newDeviceId}`) + ); + expect(res.status).toBe(200); + expect(res.body.id).toBe(newDeviceId); + expect(Array.isArray(res.body.entities)).toBe(true); + }); + + it("PUT /api/devices/:id updates device", async () => { + const res = await auth( + request(API_BASE_URL).put(`/api/devices/${newDeviceId}`) + ).send({ + device_id: deviceIdStr, + name: "Updated Jest Device", + manufacturer: "Jest", + model: "Supertest v2", + }); + expect(res.status).toBe(200); + expect(res.body.id).toBe(newDeviceId); + expect(res.body.name).toBe("Updated Jest Device"); + }); + + it("DELETE /api/devices/:id deletes device", async () => { + const res = await auth( + request(API_BASE_URL).delete(`/api/devices/${newDeviceId}`) + ); + expect(res.status).toBe(200); + expect(res.body.message).toBe("Device deleted"); + }); + }); + + describe("Device Tokens + Streams + States", () => { + let deviceId; + let deviceIdStr; + let rawToken; + + beforeAll(async () => { + deviceIdStr = uniq("token_dev"); + const create = await auth( + request(API_BASE_URL).post("/api/devices") + ).send({ + device_id: deviceIdStr, + name: "token device", + }); + expect(create.status).toBe(200); + deviceId = create.body.id; + }); + + afterAll(async () => { + if (deviceId) { + await auth(request(API_BASE_URL).delete(`/api/devices/${deviceId}`)); + } + }); + + it("POST /api/devices/:id/token issues token", async () => { + const res = await auth( + request(API_BASE_URL).post(`/api/devices/${deviceId}/token`) + ); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("token"); + expect(typeof res.body.token).toBe("string"); + rawToken = res.body.token; + }); + + it("GET /api/devices/:id/token returns token info", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/devices/${deviceId}/token`) + ); + expect(res.status).toBe(200); + expect(res.body).toBeDefined(); + }); + + it("POST /api/streams/register registers a stream with device token", async () => { + const res = await request(API_BASE_URL) + .post("/api/streams/register") + .set("Authorization", `Bearer ${rawToken}`) + .set("X-Device-Id", deviceIdStr) + .send({ topic: uniq("topic"), media_type: "video" }); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("ssrc"); + expect(res.body).toHaveProperty("rtp_port"); + }); + + it("POST /api/states/:topic accepts state update with device token", async () => { + const res = await request(API_BASE_URL) + .post(`/api/states/${uniq("any.topic")}`) + .set("Authorization", `Bearer ${rawToken}`) + .set("X-Device-Id", deviceIdStr) + .send({ state: "on" }); + expect(res.status).toBe(200); + expect(res.body.status).toBe("true"); + }); + + it("DELETE /api/devices/:id/token revokes token", async () => { + const res = await auth( + request(API_BASE_URL).delete(`/api/devices/${deviceId}/token`) + ); + expect(res.status).toBe(200); + expect(res.body.message).toMatch(/revoked/i); + }); + }); + + describe("Entities API", () => { + let entityPkId; + const entityIdStr = uniq("entity"); + + it("POST /api/entities creates entity", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/entities") + ).send({ + entity_id: entityIdStr, + friendly_name: "Test Entity", + platform: "jest", + entity_type: "sensor", + configuration: { unit: "C" }, + }); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("id"); + expect(res.body.entity_id).toBe(entityIdStr); + entityPkId = res.body.id; + }); + + it("GET /api/entities lists entities", async () => { + const res = await auth(request(API_BASE_URL).get("/api/entities")); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it("GET /api/entities supports entity_type filter", async () => { + const res = await auth( + request(API_BASE_URL) + .get("/api/entities") + .query({ entity_type: "sensor" }) + ); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it("GET /api/entities/all returns entities with states", async () => { + const res = await auth(request(API_BASE_URL).get("/api/entities/all")); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it("GET /api/entities/:entity_id/history returns history list", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/entities/${entityIdStr}/history`) + ); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it("PUT /api/entities/:id updates entity", async () => { + const res = await auth( + request(API_BASE_URL).put(`/api/entities/${entityPkId}`) + ).send({ + entity_id: entityIdStr, + friendly_name: "Renamed Entity", + platform: "jest", + entity_type: "sensor", + configuration: {}, + }); + expect(res.status).toBe(200); + expect(res.body.friendly_name).toBe("Renamed Entity"); + }); + + it("DELETE /api/entities/:id deletes entity", async () => { + const res = await auth( + request(API_BASE_URL).delete(`/api/entities/${entityPkId}`) + ); + expect(res.status).toBe(200); + expect(res.body.message).toBe("Entity deleted"); + }); + }); + + describe("Configurations API", () => { + let configId; + const key = uniq("cfg.key"); + + it("POST /api/configurations creates configuration", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/configurations") + ).send({ + key, + value: "v1", + enabled: 1, + description: "jest cfg", + }); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("id"); + expect(res.body.key).toBe(key); + configId = res.body.id; + }); + + it("GET /api/configurations lists configurations", async () => { + const res = await auth( + request(API_BASE_URL).get("/api/configurations") + ); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.some((c) => c.id === configId)).toBe(true); + }); + + it("PUT /api/configurations/:id updates configuration", async () => { + const res = await auth( + request(API_BASE_URL).put(`/api/configurations/${configId}`) + ).send({ + key, + value: "v2", + enabled: 0, + description: "updated", + }); + expect(res.status).toBe(200); + expect(res.body.value).toBe("v2"); + }); + + it("DELETE /api/configurations/:id deletes configuration", async () => { + const res = await auth( + request(API_BASE_URL).delete(`/api/configurations/${configId}`) + ); + expect(res.status).toBe(200); + expect(res.body.message).toMatch(/deleted/i); + }); + }); + + describe("Flows API", () => { + let flowId; + + it("POST /api/flows creates flow", async () => { + const res = await auth(request(API_BASE_URL).post("/api/flows")).send({ + name: uniq("flow"), + description: "jest flow", + enabled: 1, + }); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("id"); + flowId = res.body.id; + }); + + it("GET /api/flows lists flows", async () => { + const res = await auth(request(API_BASE_URL).get("/api/flows")); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.some((f) => f.id === flowId)).toBe(true); + }); + + it("PUT /api/flows/:id updates flow", async () => { + const res = await auth( + request(API_BASE_URL).put(`/api/flows/${flowId}`) + ).send({ + name: uniq("flow_renamed"), + description: "updated", + enabled: 0, + }); + expect(res.status).toBe(200); + expect(res.body.message).toMatch(/updated/i); + }); + + it("POST /api/flows/:id/versions creates flow version", async () => { + const res = await auth( + request(API_BASE_URL).post(`/api/flows/${flowId}/versions`) + ).send({ + graph_json: JSON.stringify({ nodes: [], edges: [] }), + comment: "initial", + }); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("id"); + expect(res.body.flow_id).toBe(flowId); + }); + + it("GET /api/flows/:id/versions lists versions", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/flows/${flowId}/versions`) + ); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.length).toBeGreaterThan(0); }); - it("should delete the created device", async () => { - const response = await request(API_BASE_URL) - .delete(`/api/devices/${newDeviceId}`) - .set("Authorization", `Bearer ${authToken}`); + it("DELETE /api/flows/:id deletes flow", async () => { + const res = await auth( + request(API_BASE_URL).delete(`/api/flows/${flowId}`) + ); + expect(res.status).toBe(200); + expect(res.body.message).toMatch(/deleted/i); + }); + }); - expect(response.status).toBe(200); - expect(response.body.message).toBe("Device deleted"); + describe("Stat API", () => { + it("GET /api/stat returns counts", async () => { + const res = await auth(request(API_BASE_URL).get("/api/stat")); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("count"); + expect(res.body.count).toHaveProperty("entities"); + expect(res.body.count).toHaveProperty("devices"); + }); + }); + + describe("Tunnel API", () => { + it("GET /api/tunnel/status returns tunnel status (no auth required)", async () => { + const res = await request(API_BASE_URL).get("/api/tunnel/status"); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("active"); + expect(typeof res.body.active).toBe("boolean"); + }); + + it("POST /api/tunnel/stop is idempotent", async () => { + const res = await request(API_BASE_URL).post("/api/tunnel/stop"); + expect([204, 502]).toContain(res.status); + }); + + it("POST /api/tunnel/start with bogus server returns 200 or 502", async () => { + const res = await request(API_BASE_URL).post("/api/tunnel/start").send({ + server: "http://127.0.0.1:1", + target: "http://127.0.0.1:1", + }); + expect([200, 502]).toContain(res.status); + }); + }); + + describe("Logs API", () => { + it("GET /api/logs lists log files", async () => { + const res = await auth(request(API_BASE_URL).get("/api/logs")); + expect([200, 500]).toContain(res.status); + if (res.status === 200) { + expect(Array.isArray(res.body.files)).toBe(true); + } + }); + + it("GET /api/logs/latest returns latest log or 404", async () => { + const res = await auth(request(API_BASE_URL).get("/api/logs/latest")); + expect([200, 404, 500]).toContain(res.status); + }); + + it("GET /api/logs/:filename returns 404 for missing file", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/logs/${uniq("nope")}.log`) + ); + expect(res.status).toBe(404); }); }); describe("Map API", () => { - it("should create a new map layer", async () => { - const response = await request(API_BASE_URL) - .post("/api/map/layers") - .set("Authorization", `Bearer ${authToken}`) - .send({ - name: "Test Layer", - description: "A layer created by Jest", - is_visible: true, - }); + let layerId; + let featureId; + + it("POST /api/map/layers creates layer", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/map/layers") + ).send({ + name: uniq("layer"), + description: "Layer by jest", + is_visible: true, + }); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("id"); + layerId = res.body.id; + }); - expect(response.status).toBe(200); - expect(response.body).toHaveProperty("id"); - expect(response.body.name).toBe("Test Layer"); - newLayerId = response.body.id; + it("GET /api/map/layers lists layers", async () => { + const res = await auth(request(API_BASE_URL).get("/api/map/layers")); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); }); - it("should get a list of all map layers", async () => { - const response = await request(API_BASE_URL) - .get("/api/map/layers") - .set("Authorization", `Bearer ${authToken}`); + it("GET /api/map/layers/:id returns layer with empty features", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/map/layers/${layerId}`) + ); + expect(res.status).toBe(200); + expect(res.body.id).toBe(layerId); + expect(res.body.features).toEqual([]); + }); - expect(response.status).toBe(200); - expect(Array.isArray(response.body)).toBe(true); - expect(response.body.length).toBeGreaterThan(0); + it("PUT /api/map/layers/:id updates layer", async () => { + const res = await auth( + request(API_BASE_URL).put(`/api/map/layers/${layerId}`) + ).send({ + name: uniq("layer_renamed"), + description: "updated", + is_visible: false, + }); + expect(res.status).toBe(200); + expect(res.body.name).toBe(uniq("layer_renamed")); }); - it("should get a specific map layer by ID", async () => { - const response = await request(API_BASE_URL) - .get(`/api/map/layers/${newLayerId}`) - .set("Authorization", `Bearer ${authToken}`); + it("POST /api/map/features creates point feature", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/map/features") + ).send({ + layer_id: layerId, + feature_type: "POINT", + name: "Test Point", + style_properties: JSON.stringify({ color: "red" }), + vertices: [{ latitude: 36.635, longitude: 127.456 }], + }); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("id"); + featureId = res.body.id; + }); - expect(response.status).toBe(200); - expect(response.body.id).toBe(newLayerId); - expect(response.body.features).toEqual([]); + it("GET /api/map/features/:id returns feature with vertices", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/map/features/${featureId}`) + ); + expect(res.status).toBe(200); + expect(res.body.vertices.length).toBe(1); + expect(res.body.vertices[0].latitude).toBeCloseTo(36.635, 3); }); - it("should create a new point feature in the layer", async () => { - const response = await request(API_BASE_URL) - .post("/api/map/features") - .set("Authorization", `Bearer ${authToken}`) - .send({ - layer_id: newLayerId, - feature_type: "POINT", - name: "Test Point 1", - style_properties: JSON.stringify({ color: "red" }), - vertices: [{ latitude: 36.635, longitude: 127.456 }], - }); + it("GET /api/map/layers/:id again includes new feature", async () => { + const res = await auth( + request(API_BASE_URL).get(`/api/map/layers/${layerId}`) + ); + expect(res.status).toBe(200); + expect(res.body.features.length).toBe(1); + }); + + it("PUT /api/map/features/:id updates feature", async () => { + const res = await auth( + request(API_BASE_URL).put(`/api/map/features/${featureId}`) + ).send({ + name: "Updated Point", + vertices: [{ latitude: 37.5665, longitude: 126.978 }], + }); + expect(res.status).toBe(200); + expect(res.body.name).toBe("Updated Point"); + }); - expect(response.status).toBe(200); - expect(response.body).toHaveProperty("id"); - expect(response.body.name).toBe("Test Point 1"); - expect(response.body.feature_type).toBe("POINT"); - newFeatureId = response.body.id; + it("DELETE /api/map/features/:id deletes feature", async () => { + const res = await auth( + request(API_BASE_URL).delete(`/api/map/features/${featureId}`) + ); + expect(res.status).toBe(200); + expect(res.body.message).toBe("Feature deleted"); }); - it("should get the layer again with the new feature", async () => { - const response = await request(API_BASE_URL) - .get(`/api/map/layers/${newLayerId}`) - .set("Authorization", `Bearer ${authToken}`); + it("DELETE /api/map/layers/:id deletes layer", async () => { + const res = await auth( + request(API_BASE_URL).delete(`/api/map/layers/${layerId}`) + ); + expect(res.status).toBe(200); + expect(res.body.message).toBe("Layer deleted"); + }); + }); - expect(response.status).toBe(200); - expect(response.body.features.length).toBe(1); + describe("Custom Nodes API", () => { + const nodeType = uniq("node.type"); + + it("POST /api/custom-nodes creates custom node", async () => { + const res = await request(API_BASE_URL).post("/api/custom-nodes").send({ + node_type: nodeType, + data: { foo: "bar" }, + }); + expect(res.status).toBe(201); + expect(res.body.node_type).toBe(nodeType); }); - it("should get a specific feature by ID", async () => { - const response = await request(API_BASE_URL) - .get(`/api/map/features/${newFeatureId}`) - .set("Authorization", `Bearer ${authToken}`); + it("GET /api/custom-nodes lists nodes", async () => { + const res = await request(API_BASE_URL).get("/api/custom-nodes"); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + }); - expect(response.status).toBe(200); - expect(response.body.vertices.length).toBe(1); - expect(response.body.vertices[0].latitude).toBe(36.635); + it("GET /api/custom-nodes/:node_type returns node", async () => { + const res = await request(API_BASE_URL).get( + `/api/custom-nodes/${nodeType}` + ); + expect(res.status).toBe(200); + expect(res.body.node_type).toBe(nodeType); }); - it("should update an existing feature", async () => { - const response = await request(API_BASE_URL) - .put(`/api/map/features/${newFeatureId}`) - .set("Authorization", `Bearer ${authToken}`) + it("PUT /api/custom-nodes/:node_type updates node", async () => { + const res = await request(API_BASE_URL) + .put(`/api/custom-nodes/${nodeType}`) + .send({ data: { foo: "baz" } }); + expect(res.status).toBe(200); + }); + + it("DELETE /api/custom-nodes/:node_type deletes node", async () => { + const res = await request(API_BASE_URL).delete( + `/api/custom-nodes/${nodeType}` + ); + expect(res.status).toBe(204); + }); + }); + + describe("Dynamic Dashboards API", () => { + let dashboardId; + + it("POST /api/dynamic-dashboards creates dashboard", async () => { + const res = await request(API_BASE_URL) + .post("/api/dynamic-dashboards") .send({ - name: "Updated Test Point", - vertices: [{ latitude: 37.5665, longitude: 126.978 }], + name: uniq("dashboard"), + layout: { widgets: [] }, }); + expect(res.status).toBe(201); + expect(res.body).toHaveProperty("id"); + dashboardId = res.body.id; + }); - expect(response.status).toBe(200); - expect(response.body.name).toBe("Updated Test Point"); + it("GET /api/dynamic-dashboards lists dashboards", async () => { + const res = await request(API_BASE_URL).get("/api/dynamic-dashboards"); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); }); - it("should delete the created feature", async () => { - const response = await request(API_BASE_URL) - .delete(`/api/map/features/${newFeatureId}`) - .set("Authorization", `Bearer ${authToken}`); + it("GET /api/dynamic-dashboards/:id returns dashboard", async () => { + const res = await request(API_BASE_URL).get( + `/api/dynamic-dashboards/${dashboardId}` + ); + expect(res.status).toBe(200); + expect(res.body.id).toBe(dashboardId); + }); - expect(response.status).toBe(200); - expect(response.body.message).toBe("Feature deleted"); + it("PUT /api/dynamic-dashboards/:id updates dashboard", async () => { + const res = await request(API_BASE_URL) + .put(`/api/dynamic-dashboards/${dashboardId}`) + .send({ + name: uniq("dashboard_renamed"), + layout: { widgets: [{ id: "w1" }] }, + }); + expect(res.status).toBe(200); + expect(res.body.name).toBe(uniq("dashboard_renamed")); }); - it("should delete the created layer", async () => { - const response = await request(API_BASE_URL) - .delete(`/api/map/layers/${newLayerId}`) - .set("Authorization", `Bearer ${authToken}`); + it("DELETE /api/dynamic-dashboards/:id deletes dashboard", async () => { + const res = await request(API_BASE_URL).delete( + `/api/dynamic-dashboards/${dashboardId}` + ); + expect(res.status).toBe(204); + }); + + it("GET /api/dynamic-dashboards/:id returns 404 for missing", async () => { + const res = await request(API_BASE_URL).get( + `/api/dynamic-dashboards/${uniq("missing")}` + ); + expect(res.status).toBe(404); + }); + }); + + describe("Integrations API", () => { + afterAll(async () => { + for (const id of ["home_assistant", "ros2", "sdr"]) { + await auth( + request(API_BASE_URL).delete(`/api/integrations/${id}`) + ); + } + }); + + it("POST /api/integrations/register registers ros2", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/integrations/register") + ).send({ + integration_id: "ros2", + config: { websocket_url: "ws://127.0.0.1:9090" }, + }); + expect(res.status).toBe(200); + expect(res.body.integration_id).toBe("ros2"); + }); + + it("POST /api/integrations/register registers home_assistant", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/integrations/register") + ).send({ + integration_id: "home_assistant", + config: { + url: "http://127.0.0.1:8123", + token: "fake-token", + }, + }); + expect(res.status).toBe(200); + expect(res.body.integration_id).toBe("home_assistant"); + }); + + it("POST /api/integrations/register registers sdr", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/integrations/register") + ).send({ + integration_id: "sdr", + config: { host: "127.0.0.1", port: "1234" }, + }); + expect(res.status).toBe(200); + expect(res.body.integration_id).toBe("sdr"); + }); + + it("GET /api/integrations/status returns connected flags", async () => { + const res = await auth( + request(API_BASE_URL).get("/api/integrations/status") + ); + expect(res.status).toBe(200); + expect(res.body.home_assistant.connected).toBe(true); + expect(res.body.ros2.connected).toBe(true); + expect(res.body.sdr.connected).toBe(true); + }); + + it("POST /api/integrations/register rejects unknown id", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/integrations/register") + ).send({ integration_id: "unknown_xyz", config: {} }); + expect(res.status).toBeGreaterThanOrEqual(400); + }); + + describe("HA proxy endpoints (HA at fake URL)", () => { + it("GET /api/ha/states fails because HA is unreachable", async () => { + const res = await auth(request(API_BASE_URL).get("/api/ha/states")); + expect(res.status).toBeGreaterThanOrEqual(400); + }); + + it("POST /api/ha/states/:entity_id fails because HA is unreachable", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/ha/states/light.test") + ).send({ state: "on", attributes: {} }); + expect(res.status).toBeGreaterThanOrEqual(400); + }); + }); + + describe("SDR REST endpoints", () => { + it("GET /api/sdr/samplerate returns default samplerate", async () => { + const res = await auth( + request(API_BASE_URL).get("/api/sdr/samplerate") + ); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("samplerate"); + }); + + it("POST /api/sdr/start returns streaming acknowledgement", async () => { + const res = await auth(request(API_BASE_URL).post("/api/sdr/start")); + expect(res.status).toBe(200); + expect(res.body.status).toBe("streaming"); + }); + + it("POST /api/sdr/stop returns stopped", async () => { + const res = await auth(request(API_BASE_URL).post("/api/sdr/stop")); + expect(res.status).toBe(200); + expect(res.body.status).toBe("stopped"); + }); + + it("POST /api/sdr/frequency fails with stub host", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/sdr/frequency") + ).send({ frequency: 100000000 }); + expect(res.status).toBeGreaterThanOrEqual(400); + }); + }); + + it("DELETE /api/integrations/:id removes ros2 integration", async () => { + const res = await auth( + request(API_BASE_URL).delete("/api/integrations/ros2") + ); + expect(res.status).toBe(200); + }); + + it("DELETE /api/integrations/:id rejects unknown id", async () => { + const res = await auth( + request(API_BASE_URL).delete("/api/integrations/unknown_xyz") + ); + expect(res.status).toBeGreaterThanOrEqual(400); + }); + }); + + describe("Storage API", () => { + const dirPath = uniq("jest_dir"); + const filePath = `${dirPath}/hello.txt`; + let codeServiceEnabled = true; + + it("PUT /api/storage/:path creates file (or 403 if disabled)", async () => { + const res = await auth( + request(API_BASE_URL).put(`/api/storage/${filePath}`) + ).send({ content: "hello world" }); + if (res.status === 403) { + codeServiceEnabled = false; + expect(res.body.error).toMatch(/disabled/i); + return; + } + expect(res.status).toBe(200); + expect(res.body.message).toMatch(/file/i); + }); + + it("GET /api/storage/:path reads file content", async () => { + if (!codeServiceEnabled) return; + const res = await auth( + request(API_BASE_URL).get(`/api/storage/${filePath}`) + ); + expect(res.status).toBe(200); + expect(res.text).toBe("hello world"); + }); + + it("GET /api/storage/ lists root directory", async () => { + if (!codeServiceEnabled) return; + const res = await auth(request(API_BASE_URL).get("/api/storage/")); + expect(res.status).toBe(200); + expect(Array.isArray(res.body.entries)).toBe(true); + }); + + it("POST /api/storage/mkdir/:path creates directory", async () => { + if (!codeServiceEnabled) return; + const res = await auth( + request(API_BASE_URL).post(`/api/storage/mkdir/${dirPath}/sub`) + ); + expect(res.status).toBe(201); + }); + + it("POST /api/storage/rename/:from renames file", async () => { + if (!codeServiceEnabled) return; + const res = await auth( + request(API_BASE_URL).post(`/api/storage/rename/${filePath}`) + ).send({ to: `${dirPath}/hello_renamed.txt` }); + expect(res.status).toBe(200); + }); + + it("DELETE /api/storage/:path deletes path", async () => { + if (!codeServiceEnabled) return; + const res = await auth( + request(API_BASE_URL).delete(`/api/storage/${dirPath}`) + ); + expect(res.status).toBe(200); + }); + }); + + describe("Recordings API", () => { + it("GET /api/recordings lists recordings", async () => { + const res = await auth(request(API_BASE_URL).get("/api/recordings")); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it("GET /api/recordings supports topic filter", async () => { + const res = await auth( + request(API_BASE_URL) + .get("/api/recordings") + .query({ topic: uniq("nope") }) + ); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it("GET /api/recordings/active lists active recordings", async () => { + const res = await auth( + request(API_BASE_URL).get("/api/recordings/active") + ); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it("GET /api/recordings/active/:topic reports recording state", async () => { + const res = await auth( + request(API_BASE_URL).get( + `/api/recordings/active/${uniq("topic")}` + ) + ); + expect(res.status).toBe(200); + expect(res.body).toHaveProperty("is_recording"); + expect(res.body.is_recording).toBe(false); + }); + + it("POST /api/recordings without active stream returns 201 or 400", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/recordings") + ).send({ topic: uniq("ghost.topic") }); + expect([201, 400]).toContain(res.status); + }); + + it("GET /api/recordings/:id returns 404 for missing id", async () => { + const res = await auth( + request(API_BASE_URL).get("/api/recordings/999999999") + ); + expect(res.status).toBe(404); + }); + + it("POST /api/recordings/:id/stop returns 400 for missing id", async () => { + const res = await auth( + request(API_BASE_URL).post("/api/recordings/999999999/stop") + ); + expect(res.status).toBe(400); + }); + + it("DELETE /api/recordings/:id returns 404 for missing id", async () => { + const res = await auth( + request(API_BASE_URL).delete("/api/recordings/999999999") + ); + expect(res.status).toBe(404); + }); - expect(response.status).toBe(200); - expect(response.body.message).toBe("Layer deleted"); + it("GET /api/recordings/:id/stream returns 404 for missing id", async () => { + const res = await auth( + request(API_BASE_URL).get("/api/recordings/999999999/stream") + ); + expect(res.status).toBe(404); }); }); }); From 23d9938ef81637558e36a3992a5ec105cc76519f Mon Sep 17 00:00:00 2001 From: DipokalLab Date: Tue, 28 Apr 2026 17:34:03 +0900 Subject: [PATCH 04/10] feat: add claude markdown --- CLAUDE.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..76ce0f3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,66 @@ +# CLAUDE.md + +Guidance for Claude Code working in the Vessel repository. + +## Project + +Vessel is C2 (Command & Control) software for connecting, monitoring, and orchestrating physical sensors through a visual flow-based interface. Local-first, offline-first. Apache-2.0. + +## Repository layout + +Cargo + npm workspaces (monorepo). + +- `apps/server` — Rust (axum, tokio) backend. SQLite via Diesel (`migrations/`, schema in `src/db/schema.rs`). MQTT broker (rumqttd) + client (rumqttc), WebRTC, RTP/RTSP via GStreamer, ONNX inference (ort/tract). Entry: `src/main.rs`. Routes wired in `src/routes.rs`, handlers under `src/handler/`. Flow engine in `src/flow/` (engine, manager, nodes). Built-in flow nodes live in `src/flow/nodes/`. The compiled server embeds the client `dist/` via `rust-embed`. +- `apps/client` — React 19 + Vite + TypeScript + Tailwind v4 + Radix + Zustand. FSD-ish layout: `app/`, `pages/`, `widgets/`, `features/`, `entities/`, `shared/`, plus `components/ui` (shadcn) and `hooks/`, `contexts/`, `lib/`. Routing in `src/App.tsx` (react-router v7). +- `apps/desktop` — Tauri v2 shell. The Tauri build runs `cargo build --release -p server` and bundles the server binary as a sidecar; frontend served from `apps/client/dist`. Crate at `apps/desktop/src-tauri`. +- `apps/landing` — Marketing site (React + Vite). Deployed at vessel.cartesiancs.com. +- `apps/capsule` — Standalone Rust service for "Capsule" zero-knowledge LLM proxy (X25519 + ChaCha20-Poly1305). Has its own Dockerfile/docker-compose. +- `packages/capsule-client` — TS client SDK for Capsule (`@vessel/capsule-client`). Built before `client` in the build chain. +- `packages/custom-node-utils` — Python helpers for user-authored custom flow nodes. +- `packages/shared-types` — empty placeholder. +- `docs/` — VitePress site (vessel.cartesiancs.com/docs). +- `tests/` — Jest + supertest E2E against a running server at `http://localhost:6174`. Default creds `admin/admin1`. +- `configs/`, `config.toml`, `.env.example` — server config (jwt_secret, listen_address, database_url). +- `migrations/` lives under `apps/server/`. `database.db` at repo root is the dev DB. + +## Common commands + +Run from repo root unless noted. + +- `npm run server` — `cargo run -p server` (debug). `npm run server:prod` for release. +- `npm run client` — Vite dev server for the React client. +- `npm run desktop` — Tauri dev (builds capsule-client + client + release server, then launches shell). `npm run desktop:build` to package. +- `npm run landing` — landing site dev server. +- `npm run capsule` — capsule service. +- `npm run build` — `cargo build --release` of the server only. Output: `target/release/server` (needs a `.env` next to it to run). +- `npm run client:build` — builds capsule-client then the client. +- `npm test` — Jest E2E in `tests/`. Server must already be running on `:6174` with seeded admin. +- `npm run docs:dev` / `docs:build` — VitePress. +- Diesel: `cd apps/server && diesel setup && diesel migration run` (requires diesel_cli). Migrations are also embedded and auto-run on server boot via `MIGRATIONS`. + +## Architecture notes + +- The server is the integration point: HTTP/WS API, embedded client UI, MQTT broker + client, RTP receiver, RTSP puller, WebRTC, recording manager, flow engine, tunnel manager. All share `Arc` (`apps/server/src/state.rs`). Background tasks are spawned into a `JoinSet` driven by a `watch` shutdown channel — prefer that pattern for new long-running tasks. +- Flow runtime: `FlowManagerActor` (mpsc-driven) owns the live engine. New node types go in `src/flow/nodes/` and are registered in `mod.rs`. Engine/types in `src/flow/engine.rs` and `src/flow/types.rs`. +- DB access uses Diesel with an r2d2 pool. Repositories in `src/db/repository/`. When adding tables, add a migration *and* update `schema.rs` (`diesel print-schema`) and models. +- Auth is JWT (`jsonwebtoken`). Initial admin is seeded by `init::db_record::create_initial_admin`. RBAC tables created by the `2025-09-08_create_rbac_tables` migration; permissions seeded on boot. +- Client follows feature-sliced design. New screens: add a route in `src/App.tsx`, page under `pages/`, feature logic under `features//`, domain models under `entities//`, reusable UI in `components/ui` (shadcn-style) or `widgets/`. State is Zustand; data fetching is axios in `shared/api`. +- Maps use MapLibre/Leaflet. Code editor uses Monaco + CodeMirror. Charts via d3. +- Desktop sidecar: `apps/desktop/src-tauri/src/main.rs` launches the bundled server binary; client detects desktop via `useDesktopSidecar`. A separate `desktop_settings` window is dispatched in `App.tsx` based on URL params. + +## Coding rules (enforced by `CODE_RULE.md`) + +Mission-critical posture. Highlights to keep in mind: + +- Fail-safe: every fallible op returns `Result`/`Promise`; handle both arms — no silent failures. Lints fail builds on unhandled results. +- Deterministic: no recursion (use iterative forms), no magic numbers (use `const`/`enum`), fixed-size buffers, no float `==` (use epsilon). +- Security by design: validate all external input at runtime, default to deny, least privilege, keep control flow simple. +- Concurrency: prefer message passing (we already do — `mpsc`/`broadcast`/`watch`) over shared mutable state. +- Tooling: zero warnings, format with `rustfmt` / `prettier` (`.prettierrc` is at repo root: 2-space, semi, double-quote, trailing-comma all, jsxSingleQuote), pass clippy/eslint at strict settings. Lockfiles (`Cargo.lock`, `package-lock.json`) are committed and authoritative. + +## Conventions + +- UI copy is **English only**. Even when prompts/issues are in Korean, ship English strings in `apps/client`. +- Don't create docs/markdown unless asked. +- Don't add comments for the "what"; only for non-obvious "why". +- Branch is `develop` for active work; PRs target `main`. From 4f003875636272722727d546cbdbf562648dbc25 Mon Sep 17 00:00:00 2001 From: DipokalLab Date: Tue, 28 Apr 2026 18:48:16 +0900 Subject: [PATCH 05/10] test: add load and chaos test code --- .gitignore | 5 +- tests/chaos/README.md | 64 ++++++++++++ tests/chaos/docker-compose.yml | 37 +++++++ tests/chaos/run.sh | 32 ++++++ tests/chaos/scenarios/api-latency.sh | 43 ++++++++ tests/chaos/scenarios/api-slow-close.sh | 50 +++++++++ tests/chaos/scenarios/rtsp-bandwidth.sh | 35 +++++++ tests/chaos/scenarios/rtsp-down.sh | 40 ++++++++ tests/chaos/toxiproxy.json | 14 +++ tests/load/README.md | 72 +++++++++++++ tests/load/config.test.toml | 3 + tests/load/env.sh | 34 +++++++ tests/load/k6/01-api-smoke.js | 53 ++++++++++ tests/load/k6/02-api-write.js | 61 +++++++++++ tests/load/k6/03-auth-flood.js | 50 +++++++++ tests/load/k6/04-listing-large.js | 41 ++++++++ tests/load/k6/lib/auth.js | 27 +++++ tests/load/k6/lib/http.js | 12 +++ tests/load/rtp/gen-malformed.py | 71 +++++++++++++ tests/load/rtp/gen-multi-ssrc.sh | 48 +++++++++ tests/load/rtp/observe.sh | 24 +++++ tests/load/rtsp/mediamtx.yml | 15 +++ tests/load/rtsp/seed-rtsp-topics.sh | 65 ++++++++++++ tests/load/rtsp/start-rtsp-mock.sh | 28 +++++ tests/load/run.sh | 71 +++++++++++++ tests/load/seed.sh | 46 +++++++++ tests/load/start-test-server.sh | 130 ++++++++++++++++++++++++ tests/load/stop-test-server.sh | 35 +++++++ 28 files changed, 1205 insertions(+), 1 deletion(-) create mode 100644 tests/chaos/README.md create mode 100644 tests/chaos/docker-compose.yml create mode 100755 tests/chaos/run.sh create mode 100755 tests/chaos/scenarios/api-latency.sh create mode 100755 tests/chaos/scenarios/api-slow-close.sh create mode 100755 tests/chaos/scenarios/rtsp-bandwidth.sh create mode 100755 tests/chaos/scenarios/rtsp-down.sh create mode 100644 tests/chaos/toxiproxy.json create mode 100644 tests/load/README.md create mode 100644 tests/load/config.test.toml create mode 100755 tests/load/env.sh create mode 100644 tests/load/k6/01-api-smoke.js create mode 100644 tests/load/k6/02-api-write.js create mode 100644 tests/load/k6/03-auth-flood.js create mode 100644 tests/load/k6/04-listing-large.js create mode 100644 tests/load/k6/lib/auth.js create mode 100644 tests/load/k6/lib/http.js create mode 100755 tests/load/rtp/gen-malformed.py create mode 100755 tests/load/rtp/gen-multi-ssrc.sh create mode 100755 tests/load/rtp/observe.sh create mode 100644 tests/load/rtsp/mediamtx.yml create mode 100755 tests/load/rtsp/seed-rtsp-topics.sh create mode 100755 tests/load/rtsp/start-rtsp-mock.sh create mode 100755 tests/load/run.sh create mode 100755 tests/load/seed.sh create mode 100755 tests/load/start-test-server.sh create mode 100755 tests/load/stop-test-server.sh diff --git a/.gitignore b/.gitignore index dfac92c..92845bc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,7 @@ database.db-* /packages/*/dist /packages/*/**/node_modules /apps/*/**/node_modules -/supabase \ No newline at end of file +/supabase +/tests/load/.tmp +/tests/chaos/.tmp +/tests/load/config.test.toml.local \ No newline at end of file diff --git a/tests/chaos/README.md b/tests/chaos/README.md new file mode 100644 index 0000000..505e83c --- /dev/null +++ b/tests/chaos/README.md @@ -0,0 +1,64 @@ +# Chaos tests (Toxiproxy) + +Network-level fault injection in front of the vessel API and RTSP origin. +Server code is untouched — toxiproxy sits in between and applies toxics. + +## Prerequisites + +- `docker` + `docker compose` +- `k6` (for C1, C2) +- The load-test server already booted: `tests/load/start-test-server.sh` + +## Running + +```bash +# 1. Bring up toxiproxy + mediamtx +docker compose -f tests/chaos/docker-compose.yml up -d + +# 2. Run a scenario +tests/chaos/run.sh api-latency +tests/chaos/run.sh api-slow-close +tests/chaos/run.sh rtsp-bandwidth +tests/chaos/run.sh rtsp-down + +# 3. Tear down +docker compose -f tests/chaos/docker-compose.yml down +``` + +## Scenarios + +| ID | Scenario | Toxic | Pass criterion | +|----|----------|-------|----------------| +| C1 | `api-latency` | `latency` 200ms±100 on `vessel-api` proxy | k6 finishes; no 5xx; no server panic | +| C2 | `api-slow-close` | `slow_close` + `timeout` on `vessel-api` | FD count bounded before vs after | +| C3 | `rtsp-bandwidth` | `bandwidth=64kbps` on `rtsp-mock` | Pipelines stay alive; logs show throttling | +| C4 | `rtsp-down` | proxy `enabled=false` 30s × 5 cycles | Pipelines reconnect (likely surfaces real gap) | + +## Endpoints exposed by docker-compose + +- `http://localhost:8474` — Toxiproxy admin API +- `http://localhost:6175` — proxy in front of host:6174 (vessel API) +- `rtsp://localhost:8555` — proxy in front of mediamtx:8554 + +## Pointing the load suite at the proxy + +```bash +VESSEL_URL=http://localhost:6175 tests/load/run.sh api-smoke +``` + +For RTSP fleet against the proxy: + +```bash +RTSP_HOST=localhost RTSP_MOCK_PORT=8555 tests/load/rtsp/seed-rtsp-topics.sh 8 +``` + +## Adding a new toxic ad hoc + +```bash +curl -X POST http://localhost:8474/proxies/vessel-api/toxics \ + -H 'Content-Type: application/json' \ + -d '{"name":"my-toxic","type":"latency","attributes":{"latency":500}}' +``` + +Toxic types: `latency`, `bandwidth`, `slow_close`, `timeout`, `slicer`, +`limit_data`, `reset_peer`. See [Toxiproxy docs](https://github.com/Shopify/toxiproxy#toxics). diff --git a/tests/chaos/docker-compose.yml b/tests/chaos/docker-compose.yml new file mode 100644 index 0000000..5fcd923 --- /dev/null +++ b/tests/chaos/docker-compose.yml @@ -0,0 +1,37 @@ +# Toxiproxy in front of the host's vessel server (and optionally mediamtx). +# +# Why host networking-ish setup: the vessel server is run on the host by +# tests/load/start-test-server.sh, not in docker, so toxiproxy must reach +# host.docker.internal:6174. +# +# Brings up: +# toxiproxy :8474 admin API, :6175 vessel-api proxy, :8555 rtsp-mock proxy +# mediamtx :8556 internal RTSP origin (the proxy fronts this on :8555) +# +# Start: docker compose -f tests/chaos/docker-compose.yml up -d +# Stop : docker compose -f tests/chaos/docker-compose.yml down +# +# After up, the chaos run.sh script POSTs proxy + toxic definitions to +# http://localhost:8474. + +services: + toxiproxy: + image: ghcr.io/shopify/toxiproxy:latest + container_name: vessel-toxiproxy + command: ["-host=0.0.0.0", "-config=/config/toxiproxy.json"] + volumes: + - ./toxiproxy.json:/config/toxiproxy.json:ro + ports: + - "8474:8474" # admin API + - "6175:6175" # vessel-api proxy + - "8555:8555" # rtsp-mock proxy + extra_hosts: + - "host.docker.internal:host-gateway" + + mediamtx: + image: bluenviron/mediamtx:latest + container_name: vessel-rtsp-origin + ports: + - "8556:8554" # exposed only so it's reachable; toxiproxy proxies to mediamtx:8554 + volumes: + - ../load/rtsp/mediamtx.yml:/mediamtx.yml:ro diff --git a/tests/chaos/run.sh b/tests/chaos/run.sh new file mode 100755 index 0000000..c69a85e --- /dev/null +++ b/tests/chaos/run.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Entry for chaos scenarios. Assumes: +# - vessel server running on host :6174 (tests/load/start-test-server.sh) +# - docker compose up -d in this directory (toxiproxy + mediamtx) +# +# Usage: tests/chaos/run.sh +# +# Scenarios: +# api-latency C1 — 200ms±100 latency on /api proxy, replay api-smoke +# api-slow-close C2 — slow_close + timeout, replay api-smoke +# rtsp-bandwidth C3 — 64kbps cap on RTSP origin proxy +# rtsp-down C4 — toggle RTSP proxy down/up 5x + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +scenario="${1:-}" +if [[ -z "$scenario" ]]; then + grep -E '^# [a-z]' "$0" | sed 's/^# //' + exit 2 +fi + +case "$scenario" in + api-latency|api-slow-close|rtsp-bandwidth|rtsp-down) + exec "$SCRIPT_DIR/scenarios/${scenario}.sh" + ;; + *) + echo "[chaos] unknown scenario: $scenario" >&2 + exit 2 + ;; +esac diff --git a/tests/chaos/scenarios/api-latency.sh b/tests/chaos/scenarios/api-latency.sh new file mode 100755 index 0000000..a57b461 --- /dev/null +++ b/tests/chaos/scenarios/api-latency.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# C1 — Inject 200ms ± 100 latency on the vessel-api proxy and replay api-smoke. +# +# Pass criterion: k6 still completes; no 5xx; client-side timeouts surface as +# 504s, not server panics in log/app.log. + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOAD_DIR="$(cd "$SCRIPT_DIR/../../load" && pwd)" +# shellcheck source=../../load/env.sh +source "$LOAD_DIR/env.sh" + +TOXI="${TOXI:-http://localhost:8474}" +PROXY_URL="http://localhost:6175" + +require() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "[chaos] missing dependency: $1" >&2; exit 1 + fi +} +require curl +require k6 + +cleanup() { + curl -fsS -X DELETE "$TOXI/proxies/vessel-api/toxics/lat" >/dev/null || true +} +trap cleanup EXIT + +echo "[chaos:api-latency] adding latency toxic 200ms±100 on vessel-api" +curl -fsS -X POST "$TOXI/proxies/vessel-api/toxics" \ + -H 'Content-Type: application/json' \ + -d '{"name":"lat","type":"latency","stream":"downstream","attributes":{"latency":200,"jitter":100}}' \ + >/dev/null + +ts="$(date +%Y%m%d-%H%M%S)" +out="$VESSEL_RESULTS_DIR/chaos-api-latency-${ts}.json" + +VESSEL_URL="$PROXY_URL" \ +VESSEL_ADMIN_USER="$VESSEL_ADMIN_USER" \ +VESSEL_ADMIN_PASS="$VESSEL_ADMIN_PASS" \ + k6 run --summary-export="$out" "$LOAD_DIR/k6/01-api-smoke.js" + +echo "[chaos:api-latency] result: $out" diff --git a/tests/chaos/scenarios/api-slow-close.sh b/tests/chaos/scenarios/api-slow-close.sh new file mode 100755 index 0000000..171069e --- /dev/null +++ b/tests/chaos/scenarios/api-slow-close.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# C2 — slow_close + timeout toxic on vessel-api proxy. +# +# Pass criterion: server-side connection count and FD count stay bounded +# during the run. Verify with `lsof -p $(cat tests/load/.tmp/server.pid) | wc -l` +# before and after. + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOAD_DIR="$(cd "$SCRIPT_DIR/../../load" && pwd)" +# shellcheck source=../../load/env.sh +source "$LOAD_DIR/env.sh" + +TOXI="${TOXI:-http://localhost:8474}" +PROXY_URL="http://localhost:6175" + +cleanup() { + curl -fsS -X DELETE "$TOXI/proxies/vessel-api/toxics/slowclose" >/dev/null || true + curl -fsS -X DELETE "$TOXI/proxies/vessel-api/toxics/timeout" >/dev/null || true +} +trap cleanup EXIT + +echo "[chaos:api-slow-close] FD count before:" +if [[ -f "$VESSEL_PID_FILE" ]]; then + PID="$(cat "$VESSEL_PID_FILE")" + lsof -p "$PID" 2>/dev/null | wc -l || true +fi + +echo "[chaos:api-slow-close] adding slow_close=5000 + timeout=2000 toxics" +curl -fsS -X POST "$TOXI/proxies/vessel-api/toxics" \ + -H 'Content-Type: application/json' \ + -d '{"name":"slowclose","type":"slow_close","stream":"downstream","attributes":{"delay":5000}}' \ + >/dev/null +curl -fsS -X POST "$TOXI/proxies/vessel-api/toxics" \ + -H 'Content-Type: application/json' \ + -d '{"name":"timeout","type":"timeout","stream":"downstream","attributes":{"timeout":2000}}' \ + >/dev/null + +ts="$(date +%Y%m%d-%H%M%S)" +out="$VESSEL_RESULTS_DIR/chaos-api-slow-close-${ts}.json" + +VESSEL_URL="$PROXY_URL" \ + k6 run --summary-export="$out" "$LOAD_DIR/k6/01-api-smoke.js" || true + +echo "[chaos:api-slow-close] FD count after:" +if [[ -f "$VESSEL_PID_FILE" ]]; then + PID="$(cat "$VESSEL_PID_FILE")" + lsof -p "$PID" 2>/dev/null | wc -l || true +fi +echo "[chaos:api-slow-close] result: $out" diff --git a/tests/chaos/scenarios/rtsp-bandwidth.sh b/tests/chaos/scenarios/rtsp-bandwidth.sh new file mode 100755 index 0000000..6126bb8 --- /dev/null +++ b/tests/chaos/scenarios/rtsp-bandwidth.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# C3 — Bandwidth cap on the RTSP origin proxy (64kbps). +# +# Setup: +# 1. tests/load/start-test-server.sh +# 2. docker compose -f tests/chaos/docker-compose.yml up -d +# 3. (separately, register a few entities pointing at rtsp://localhost:8555/cam0) +# Use tests/load/rtsp/seed-rtsp-topics.sh with RTSP_HOST=localhost RTSP_MOCK_PORT=8555 +# +# Pass criterion: gst pipelines stay alive (DashMap entries unchanged); logs +# show throttling, not panics. + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOAD_DIR="$(cd "$SCRIPT_DIR/../../load" && pwd)" +# shellcheck source=../../load/env.sh +source "$LOAD_DIR/env.sh" + +TOXI="${TOXI:-http://localhost:8474}" +DURATION="${DURATION:-180}" + +cleanup() { + curl -fsS -X DELETE "$TOXI/proxies/rtsp-mock/toxics/bw" >/dev/null || true +} +trap cleanup EXIT + +echo "[chaos:rtsp-bandwidth] adding bandwidth=64kbps to rtsp-mock for ${DURATION}s" +curl -fsS -X POST "$TOXI/proxies/rtsp-mock/toxics" \ + -H 'Content-Type: application/json' \ + -d '{"name":"bw","type":"bandwidth","stream":"downstream","attributes":{"rate":64}}' \ + >/dev/null + +echo "[chaos:rtsp-bandwidth] running for ${DURATION}s. tail log/app.log in another shell." +sleep "$DURATION" +echo "[chaos:rtsp-bandwidth] done" diff --git a/tests/chaos/scenarios/rtsp-down.sh b/tests/chaos/scenarios/rtsp-down.sh new file mode 100755 index 0000000..9eb6743 --- /dev/null +++ b/tests/chaos/scenarios/rtsp-down.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# C4 — Toggle the RTSP proxy down/up 5x to test pipeline reconnect behavior. +# +# Expected to surface a real gap: RtspPullAdapter currently respawns +# pipelines only when topic_map_notify fires (apps/server/src/media/rtsp_pull.rs:54-69), +# not on per-pipeline upstream loss. Document the observed behavior. + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOAD_DIR="$(cd "$SCRIPT_DIR/../../load" && pwd)" +# shellcheck source=../../load/env.sh +source "$LOAD_DIR/env.sh" + +TOXI="${TOXI:-http://localhost:8474}" +CYCLES="${CYCLES:-5}" +DOWN_SEC="${DOWN_SEC:-30}" +UP_SEC="${UP_SEC:-30}" + +restore() { + curl -fsS -X POST "$TOXI/proxies/rtsp-mock" \ + -H 'Content-Type: application/json' \ + -d '{"enabled":true}' >/dev/null || true +} +trap restore EXIT + +for c in $(seq 1 "$CYCLES"); do + echo "[chaos:rtsp-down] cycle $c/$CYCLES — proxy DOWN for ${DOWN_SEC}s" + curl -fsS -X POST "$TOXI/proxies/rtsp-mock" \ + -H 'Content-Type: application/json' \ + -d '{"enabled":false}' >/dev/null + sleep "$DOWN_SEC" + + echo "[chaos:rtsp-down] cycle $c/$CYCLES — proxy UP for ${UP_SEC}s" + curl -fsS -X POST "$TOXI/proxies/rtsp-mock" \ + -H 'Content-Type: application/json' \ + -d '{"enabled":true}' >/dev/null + sleep "$UP_SEC" +done + +echo "[chaos:rtsp-down] done. Inspect log/app.log for pipeline-restart behavior." diff --git a/tests/chaos/toxiproxy.json b/tests/chaos/toxiproxy.json new file mode 100644 index 0000000..37b294a --- /dev/null +++ b/tests/chaos/toxiproxy.json @@ -0,0 +1,14 @@ +[ + { + "name": "vessel-api", + "listen": "0.0.0.0:6175", + "upstream": "host.docker.internal:6174", + "enabled": true + }, + { + "name": "rtsp-mock", + "listen": "0.0.0.0:8555", + "upstream": "mediamtx:8554", + "enabled": true + } +] diff --git a/tests/load/README.md b/tests/load/README.md new file mode 100644 index 0000000..a1acd18 --- /dev/null +++ b/tests/load/README.md @@ -0,0 +1,72 @@ +# Load tests + +Runnable scripts. No CI integration. All artifacts land in `.tmp/` (gitignored). + +## Quick start + +```bash +# 1. Boot an isolated server (own DB at tests/load/.tmp/database.db) +tests/load/start-test-server.sh + +# 2. Run a scenario +tests/load/run.sh api-smoke + +# 3. Stop +tests/load/stop-test-server.sh +``` + +> Admin credentials are `admin / admin` (the value seeded by +> `create_initial_admin` in `apps/server/src/init/db_record.rs`). This +> intentionally differs from `tests/api.test.js`, which targets the dev DB at +> the repo root where the password has been rotated. The load suite always +> boots a fresh `.tmp/database.db` so the seeded value applies. + +For RTP scenarios, start the server with `--enable-rtp`: + +```bash +tests/load/start-test-server.sh --enable-rtp +tests/load/run.sh rtp-fanout +``` + +For listing scenarios, populate bulk rows first: + +```bash +tests/load/start-test-server.sh +tests/load/seed.sh # or: SEED_DEVICES=20000 tests/load/seed.sh +tests/load/run.sh listing +``` + +## Scenarios + +| ID | `run.sh` arg | What it does | +|----|--------------|--------------| +| L1 | `api-smoke` | k6 ramps 50→500 VU on hot GET endpoints. | +| L2 | `api-write` | k6 sustained POST/PUT for 3m. | +| L3 | `auth-flood` | k6 ramps `POST /api/auth` to surface bcrypt CPU pressure. | +| L4 | `listing` | k6 paginates large lists; run `seed.sh` first. | +| L5 | `rtp-fanout` | gst-launch fans out N synthetic SSRCs to UDP:5004. | +| L6 | `rtp-malformed` | Python flood of unknown-SSRC and random bytes to UDP:5004. | +| L7 | `rtsp-fleet` | Registers N RTSP URLs as streams; needs `rtsp/start-rtsp-mock.sh`. | + +## Dependencies + +- `k6` for L1–L4 (`brew install k6` / [k6.io](https://k6.io)) +- `gst-launch-1.0` for L5 (already required by the server) +- `python3` for L6 +- `docker` for L7 (mediamtx mock) +- `sqlite3` only if you pass `--enable-rtp` / `--enable-mqtt` to the start script + +## Config + +`env.sh` exports defaults. Override before running, e.g.: + +```bash +VESSEL_LISTEN_PORT=6175 tests/load/start-test-server.sh +VESSEL_URL=http://localhost:6175 tests/load/run.sh api-smoke +``` + +## Results + +k6 summaries → `tests/load/.tmp/results/-.json`. +Server stdout → `tests/load/.tmp/server.stdout.log`. +Server tracing log → `log/app.log` at repo root (rotated daily by the server). diff --git a/tests/load/config.test.toml b/tests/load/config.test.toml new file mode 100644 index 0000000..f6aacfd --- /dev/null +++ b/tests/load/config.test.toml @@ -0,0 +1,3 @@ +jwt_secret = "__JWT_SECRET__" +listen_address = "__LISTEN_ADDRESS__" +database_url = "__DATABASE_URL__" diff --git a/tests/load/env.sh b/tests/load/env.sh new file mode 100755 index 0000000..000be61 --- /dev/null +++ b/tests/load/env.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Shared env for tests/load and tests/chaos. Source this from other scripts. +# Override any value via environment before sourcing. +# +# Designed to be sourced from bash. zsh fallback uses $0 when BASH_SOURCE is +# unset. + +set -eu + +_env_self="${BASH_SOURCE[0]:-$0}" +VESSEL_REPO_ROOT="$(cd "$(dirname "$_env_self")/../.." && pwd)" +export VESSEL_REPO_ROOT + +export VESSEL_LOAD_DIR="${VESSEL_LOAD_DIR:-$VESSEL_REPO_ROOT/tests/load}" +export VESSEL_TMP_DIR="${VESSEL_TMP_DIR:-$VESSEL_LOAD_DIR/.tmp}" +export VESSEL_RESULTS_DIR="${VESSEL_RESULTS_DIR:-$VESSEL_TMP_DIR/results}" +export VESSEL_PID_FILE="${VESSEL_PID_FILE:-$VESSEL_TMP_DIR/server.pid}" +export VESSEL_DB_PATH="${VESSEL_DB_PATH:-$VESSEL_TMP_DIR/database.db}" +export VESSEL_CONFIG_PATH="${VESSEL_CONFIG_PATH:-$VESSEL_TMP_DIR/config.toml}" +export VESSEL_SERVER_BIN="${VESSEL_SERVER_BIN:-$VESSEL_REPO_ROOT/target/release/server}" + +export VESSEL_LISTEN_HOST="${VESSEL_LISTEN_HOST:-127.0.0.1}" +export VESSEL_LISTEN_PORT="${VESSEL_LISTEN_PORT:-6174}" +export VESSEL_URL="${VESSEL_URL:-http://${VESSEL_LISTEN_HOST}:${VESSEL_LISTEN_PORT}}" + +export VESSEL_ADMIN_USER="${VESSEL_ADMIN_USER:-admin}" +# This is the password seeded by create_initial_admin (apps/server/src/init/db_record.rs). +# Note: tests/api.test.js uses "admin1" because the dev DB has had its password +# rotated manually. The load suite always boots a fresh DB, so it uses the seed. +export VESSEL_ADMIN_PASS="${VESSEL_ADMIN_PASS:-admin}" + +export VESSEL_RTP_PORT="${VESSEL_RTP_PORT:-5004}" + +mkdir -p "$VESSEL_TMP_DIR" "$VESSEL_RESULTS_DIR" diff --git a/tests/load/k6/01-api-smoke.js b/tests/load/k6/01-api-smoke.js new file mode 100644 index 0000000..3465aee --- /dev/null +++ b/tests/load/k6/01-api-smoke.js @@ -0,0 +1,53 @@ +// L1 — REST API smoke +// +// Targets: GET /info, /api/devices, /api/flows, /api/streams, /api/recordings +// Pressure: ramp 50→500 VU over 5 minutes. +// Watch: p95 < 200ms, error rate < 1%, r2d2 pool exhaustion (5xx, timeout), +// SQLite "database is locked" log lines on the server side. + +import http from "k6/http"; +import { sleep } from "k6"; +import { login, authHeaders, baseUrl } from "./lib/auth.js"; +import { standardThresholds, ok2xx } from "./lib/http.js"; + +export const options = { + thresholds: standardThresholds, + scenarios: { + ramp: { + executor: "ramping-vus", + startVUs: 0, + stages: [ + { duration: "30s", target: 50 }, + { duration: "1m", target: 200 }, + { duration: "2m", target: 500 }, + { duration: "1m", target: 500 }, + { duration: "30s", target: 0 }, + ], + gracefulRampDown: "10s", + }, + }, +}; + +export function setup() { + return { token: login() }; +} + +const READ_PATHS = [ + "/api/devices", + "/api/flows", + "/api/streams", + "/api/recordings", +]; + +export default function (data) { + const headers = authHeaders(data.token); + + const info = http.get(`${baseUrl}/info`); + ok2xx(info, "info"); + + const path = READ_PATHS[Math.floor(Math.random() * READ_PATHS.length)]; + const res = http.get(`${baseUrl}${path}`, { headers }); + ok2xx(res, path); + + sleep(0.2 + Math.random() * 0.3); +} diff --git a/tests/load/k6/02-api-write.js b/tests/load/k6/02-api-write.js new file mode 100644 index 0000000..33063a6 --- /dev/null +++ b/tests/load/k6/02-api-write.js @@ -0,0 +1,61 @@ +// L2 — Write-heavy +// +// Targets: POST /api/devices, POST /api/flows +// Pressure: ~50 RPS sustained, 3 minutes. +// Watch: SQLite WAL contention, busy_timeout (5s) hits, lingering writes +// after stop, response time degradation across the run. + +import http from "k6/http"; +import { login, authHeaders, baseUrl } from "./lib/auth.js"; +import { ok2xx } from "./lib/http.js"; + +export const options = { + thresholds: { + http_req_failed: ["rate<0.02"], + http_req_duration: ["p(95)<500"], + }, + scenarios: { + writes: { + executor: "constant-arrival-rate", + rate: 50, + timeUnit: "1s", + duration: "3m", + preAllocatedVUs: 50, + maxVUs: 200, + }, + }, +}; + +export function setup() { + return { token: login() }; +} + +export default function (data) { + const headers = authHeaders(data.token); + const id = `${__VU}-${__ITER}-${Date.now()}`; + + if (Math.random() < 0.7) { + const res = http.post( + `${baseUrl}/api/devices`, + JSON.stringify({ + device_id: `w-${id}`, + name: `w-${id}`, + manufacturer: "loadgen", + model: "writer", + }), + { headers }, + ); + ok2xx(res, "POST /api/devices"); + } else { + const res = http.post( + `${baseUrl}/api/flows`, + JSON.stringify({ + name: `wf-${id}`, + description: "load-test", + enabled: 0, + }), + { headers }, + ); + ok2xx(res, "POST /api/flows"); + } +} diff --git a/tests/load/k6/03-auth-flood.js b/tests/load/k6/03-auth-flood.js new file mode 100644 index 0000000..442b325 --- /dev/null +++ b/tests/load/k6/03-auth-flood.js @@ -0,0 +1,50 @@ +// L3 — Auth flood (bcrypt CPU pressure) +// +// Targets: POST /api/auth +// Pressure: ramp 5→100 RPS over 3 minutes. +// Watch: server CPU saturates on bcrypt, p95 latency for unrelated GETs +// climbs (read /info from a side scenario in a separate run to +// confirm runtime starvation). +// +// Note: this is expected to surface a real DoS vector. Treat failures as +// findings, not as broken tests. + +import http from "k6/http"; +import { check } from "k6"; +import { baseUrl } from "./lib/auth.js"; + +export const options = { + thresholds: { + // Allow server to reject; we want to observe behavior, not assert success. + http_req_failed: ["rate<0.50"], + }, + scenarios: { + flood: { + executor: "ramping-arrival-rate", + startRate: 5, + timeUnit: "1s", + stages: [ + { duration: "30s", target: 20 }, + { duration: "1m", target: 60 }, + { duration: "1m", target: 100 }, + { duration: "30s", target: 100 }, + ], + preAllocatedVUs: 50, + maxVUs: 300, + }, + }, +}; + +const ADMIN_USER = __ENV.VESSEL_ADMIN_USER || "admin"; +const ADMIN_PASS = __ENV.VESSEL_ADMIN_PASS || "admin1"; + +export default function () { + const res = http.post( + `${baseUrl}/api/auth`, + JSON.stringify({ id: ADMIN_USER, password: ADMIN_PASS }), + { headers: { "Content-Type": "application/json" } }, + ); + check(res, { + "status not 5xx": (r) => r.status < 500, + }); +} diff --git a/tests/load/k6/04-listing-large.js b/tests/load/k6/04-listing-large.js new file mode 100644 index 0000000..3ee69a0 --- /dev/null +++ b/tests/load/k6/04-listing-large.js @@ -0,0 +1,41 @@ +// L4 — Listing under volume +// +// Targets: GET /api/devices, /api/flows, /api/recordings +// Pressure: 100 RPS sustained for 2 minutes, after seed.sh has populated +// ~10k device rows. +// Watch: per-row JSON serialization cost, query cost without indexes, +// memory growth during full-table reads. + +import http from "k6/http"; +import { login, authHeaders, baseUrl } from "./lib/auth.js"; +import { ok2xx } from "./lib/http.js"; + +export const options = { + thresholds: { + http_req_failed: ["rate<0.01"], + http_req_duration: ["p(95)<2000"], + }, + scenarios: { + list: { + executor: "constant-arrival-rate", + rate: 100, + timeUnit: "1s", + duration: "2m", + preAllocatedVUs: 100, + maxVUs: 400, + }, + }, +}; + +export function setup() { + return { token: login() }; +} + +const PATHS = ["/api/devices", "/api/flows", "/api/recordings"]; + +export default function (data) { + const headers = authHeaders(data.token); + const path = PATHS[Math.floor(Math.random() * PATHS.length)]; + const res = http.get(`${baseUrl}${path}`, { headers }); + ok2xx(res, path); +} diff --git a/tests/load/k6/lib/auth.js b/tests/load/k6/lib/auth.js new file mode 100644 index 0000000..0376437 --- /dev/null +++ b/tests/load/k6/lib/auth.js @@ -0,0 +1,27 @@ +import http from "k6/http"; +import { check, fail } from "k6"; + +const VESSEL_URL = __ENV.VESSEL_URL || "http://127.0.0.1:6174"; +const ADMIN_USER = __ENV.VESSEL_ADMIN_USER || "admin"; +const ADMIN_PASS = __ENV.VESSEL_ADMIN_PASS || "admin1"; + +export function login() { + const res = http.post( + `${VESSEL_URL}/api/auth`, + JSON.stringify({ id: ADMIN_USER, password: ADMIN_PASS }), + { headers: { "Content-Type": "application/json" } }, + ); + if (!check(res, { "auth 200": (r) => r.status === 200 })) { + fail(`auth failed: ${res.status} ${res.body}`); + } + return res.json("token"); +} + +export function authHeaders(token) { + return { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }; +} + +export const baseUrl = VESSEL_URL; diff --git a/tests/load/k6/lib/http.js b/tests/load/k6/lib/http.js new file mode 100644 index 0000000..b59f4e2 --- /dev/null +++ b/tests/load/k6/lib/http.js @@ -0,0 +1,12 @@ +import { check } from "k6"; + +export const standardThresholds = { + http_req_failed: ["rate<0.01"], + http_req_duration: ["p(95)<200", "p(99)<500"], +}; + +export function ok2xx(res, name) { + return check(res, { + [`${name} 2xx`]: (r) => r.status >= 200 && r.status < 300, + }); +} diff --git a/tests/load/rtp/gen-malformed.py b/tests/load/rtp/gen-malformed.py new file mode 100755 index 0000000..0604911 --- /dev/null +++ b/tests/load/rtp/gen-malformed.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""L6 — Malformed and unknown-SSRC RTP flood. + +Sends a mix of (a) packets with valid RTP headers but random SSRCs that aren't +registered with the server, and (b) raw random bytes. Both should be discarded +by the receiver path in apps/server/src/media/rtp_push.rs without taking the +task down or growing memory. + +Usage: + gen-malformed.py --duration 120 [--rate 5000] +""" + +import argparse +import os +import random +import socket +import struct +import sys +import time + + +def make_rtp_packet(ssrc: int, seq: int, timestamp: int) -> bytes: + # RFC 3550: V=2, P=0, X=0, CC=0, M=0, PT=96 + first = (2 << 6) | 0 + second = 96 + header = struct.pack("!BBHII", first, second, seq & 0xFFFF, timestamp & 0xFFFFFFFF, ssrc & 0xFFFFFFFF) + payload = os.urandom(random.randint(64, 1200)) + return header + payload + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--host", default=os.environ.get("VESSEL_LISTEN_HOST", "127.0.0.1")) + parser.add_argument("--port", type=int, default=int(os.environ.get("VESSEL_RTP_PORT", "5004"))) + parser.add_argument("--duration", type=int, default=120) + parser.add_argument("--rate", type=int, default=5000, help="packets per second total") + args = parser.parse_args() + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setblocking(False) + + deadline = time.time() + args.duration + interval = 1.0 / max(args.rate, 1) + seq = 0 + ts = 0 + sent = 0 + print(f"[rtp-malformed] {args.host}:{args.port} for {args.duration}s at ~{args.rate} pps", flush=True) + + while time.time() < deadline: + if random.random() < 0.5: + # Unknown-SSRC valid packets + ssrc = random.randint(0x1_0000_0000 - 1, 0xFFFFFFFF) + pkt = make_rtp_packet(ssrc, seq, ts) + else: + # Random garbage + pkt = os.urandom(random.randint(8, 1400)) + try: + sock.sendto(pkt, (args.host, args.port)) + sent += 1 + except (BlockingIOError, OSError): + pass + seq += 1 + ts += 3000 + time.sleep(interval) + + print(f"[rtp-malformed] sent {sent} packets") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/load/rtp/gen-multi-ssrc.sh b/tests/load/rtp/gen-multi-ssrc.sh new file mode 100755 index 0000000..42c00f5 --- /dev/null +++ b/tests/load/rtp/gen-multi-ssrc.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# L5 — RTP multi-SSRC fan-out +# +# Spawns N parallel gst-launch pipelines, each producing a different SSRC, +# all targeting UDP:$VESSEL_RTP_PORT (default 5004). +# +# Usage: gen-multi-ssrc.sh [num_ssrc=64] [duration_sec=300] +# +# Requirements: server started with --enable-rtp; gst-launch-1.0 in PATH; +# for each SSRC there should be a registered stream in the DB +# with the matching ssrc — otherwise the server will silently +# drop packets for that SSRC. Use this script alongside the +# server's stream creation API or the "Devices/Streams" UI. +# +# Watch on the server side: +# tail -f log/app.log | grep -E 'ONLINE|lagging|unmarshal' + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=../env.sh +source "$SCRIPT_DIR/../env.sh" + +NUM="${1:-64}" +DURATION="${2:-300}" +HOST="${VESSEL_LISTEN_HOST}" +PORT="${VESSEL_RTP_PORT}" + +if ! command -v gst-launch-1.0 >/dev/null 2>&1; then + echo "[rtp] gst-launch-1.0 not found" >&2; exit 1 +fi + +echo "[rtp] sending $NUM SSRCs to ${HOST}:${PORT} for ${DURATION}s" + +pids=() +for i in $(seq 1 "$NUM"); do + ssrc=$((1000 + i)) + gst-launch-1.0 -q \ + videotestsrc is-live=true pattern=ball ! \ + video/x-raw,width=320,height=240,framerate=25/1 ! \ + x264enc tune=zerolatency speed-preset=ultrafast bitrate=200 ! \ + rtph264pay config-interval=1 pt=96 ssrc="$ssrc" ! \ + udpsink host="$HOST" port="$PORT" sync=false & + pids+=($!) +done + +trap 'echo "[rtp] stopping"; kill "${pids[@]}" 2>/dev/null || true; wait 2>/dev/null || true' EXIT INT TERM + +sleep "$DURATION" diff --git a/tests/load/rtp/observe.sh b/tests/load/rtp/observe.sh new file mode 100755 index 0000000..a476a53 --- /dev/null +++ b/tests/load/rtp/observe.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Tails the server log for events relevant to RTP load runs: +# ONLINE / OFFLINE — stream state transitions (rtp_push.rs) +# lagging — broadcast::Sender backpressure (state.rs) +# unmarshal — malformed-packet warnings +# panic / FATAL — crashes +# +# Usage: tests/load/rtp/observe.sh [logfile] +# default logfile: log/app.log under repo root. + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=../env.sh +source "$SCRIPT_DIR/../env.sh" + +LOG="${1:-$VESSEL_REPO_ROOT/log/app.log}" + +if [[ ! -f "$LOG" ]]; then + echo "[observe] waiting for $LOG to appear..." + while [[ ! -f "$LOG" ]]; do sleep 1; done +fi + +echo "[observe] tailing $LOG" +exec tail -F "$LOG" | grep --line-buffered -E 'ONLINE|OFFLINE|lagging|unmarshal|panic|FATAL' diff --git a/tests/load/rtsp/mediamtx.yml b/tests/load/rtsp/mediamtx.yml new file mode 100644 index 0000000..97c427c --- /dev/null +++ b/tests/load/rtsp/mediamtx.yml @@ -0,0 +1,15 @@ +# mediamtx config for L7 — RTSP fleet load test. +# +# Synthesizes 32 RTSP feeds at rtsp://localhost:8554/cam{0..31}. +# Each runs an internal ffmpeg loop producing a test pattern. +# This is intentionally minimal — adjust if you need authentication +# or different codecs. + +paths: + ~^cam[0-9]+$: + runOnDemand: > + ffmpeg -re -f lavfi -i testsrc2=size=320x240:rate=15 + -c:v libx264 -preset ultrafast -tune zerolatency -g 30 + -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH + runOnDemandRestart: yes + runOnDemandCloseAfter: 10s diff --git a/tests/load/rtsp/seed-rtsp-topics.sh b/tests/load/rtsp/seed-rtsp-topics.sh new file mode 100755 index 0000000..4e11281 --- /dev/null +++ b/tests/load/rtsp/seed-rtsp-topics.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# L7 — Register N RTSP feeds as entities so RtspPullAdapter spawns pipelines. +# +# Pipeline: device → entity (platform=RTSP) → entity_configurations.rtsp_url +# After remap_topics fires (on entity create), the RTSP puller picks up new +# topics on the next topic_map_notify tick. +# +# Usage: +# tests/load/rtsp/seed-rtsp-topics.sh [num=32] +# +# Env: +# RTSP_HOST default: localhost +# RTSP_MOCK_PORT default: 8554 + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=../env.sh +source "$SCRIPT_DIR/../env.sh" + +NUM="${1:-32}" +RTSP_HOST="${RTSP_HOST:-localhost}" +RTSP_MOCK_PORT="${RTSP_MOCK_PORT:-8554}" + +token="$(curl -fsS -X POST "$VESSEL_URL/api/auth" \ + -H 'Content-Type: application/json' \ + -d "{\"id\":\"$VESSEL_ADMIN_USER\",\"password\":\"$VESSEL_ADMIN_PASS\"}" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')" + +if [[ -z "$token" ]]; then + echo "[rtsp-seed] auth failed" >&2; exit 1 +fi + +run_id="$(date +%s)" +echo "[rtsp-seed] registering $NUM RTSP entities (run=$run_id)" + +for i in $(seq 0 $((NUM - 1))); do + device_id="rtsp-${run_id}-${i}" + rtsp_url="rtsp://${RTSP_HOST}:${RTSP_MOCK_PORT}/cam${i}" + + device_resp="$(curl -fsS -X POST "$VESSEL_URL/api/devices" \ + -H "Authorization: Bearer $token" \ + -H 'Content-Type: application/json' \ + -d "{\"device_id\":\"$device_id\",\"name\":\"$device_id\",\"manufacturer\":\"loadgen\",\"model\":\"rtsp\"}")" + + device_pk="$(echo "$device_resp" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')" + + curl -fsS -X POST "$VESSEL_URL/api/entities" \ + -H "Authorization: Bearer $token" \ + -H 'Content-Type: application/json' \ + -d "$(cat </dev/null + + if (( (i + 1) % 8 == 0 )); then echo "[rtsp-seed] $((i + 1))/$NUM"; fi +done + +echo "[rtsp-seed] done. RtspPullAdapter should spawn pipelines on the next topic_map tick." diff --git a/tests/load/rtsp/start-rtsp-mock.sh b/tests/load/rtsp/start-rtsp-mock.sh new file mode 100755 index 0000000..feb7617 --- /dev/null +++ b/tests/load/rtsp/start-rtsp-mock.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Brings up a mediamtx container that serves N synthetic RTSP feeds at +# rtsp://localhost:${RTSP_MOCK_PORT}/cam0..cam{N-1}. +# +# Usage: +# tests/load/rtsp/start-rtsp-mock.sh # foreground, default port 8554 +# +# Stop with Ctrl-C or `docker stop vessel-rtsp-mock`. + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=../env.sh +source "$SCRIPT_DIR/../env.sh" + +PORT="${RTSP_MOCK_PORT:-8554}" +NAME="${RTSP_MOCK_NAME:-vessel-rtsp-mock}" + +if ! command -v docker >/dev/null 2>&1; then + echo "[rtsp-mock] docker not found" >&2; exit 1 +fi + +docker rm -f "$NAME" >/dev/null 2>&1 || true + +echo "[rtsp-mock] starting $NAME on rtsp://localhost:${PORT}" +exec docker run --rm --name "$NAME" \ + -p "${PORT}:8554" \ + -v "$SCRIPT_DIR/mediamtx.yml:/mediamtx.yml" \ + bluenviron/mediamtx:latest diff --git a/tests/load/run.sh b/tests/load/run.sh new file mode 100755 index 0000000..d486c29 --- /dev/null +++ b/tests/load/run.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Entrypoint for load scenarios. Assumes start-test-server.sh has been run. +# +# Usage: +# tests/load/run.sh +# +# Scenarios: +# api-smoke L1 — ramped GET hot reads +# api-write L2 — sustained writes +# auth-flood L3 — bcrypt CPU pressure +# listing L4 — large-list pagination (run seed.sh first) +# rtp-fanout L5 — gst-launch multi-SSRC (requires --enable-rtp) +# rtp-malformed L6 — unknown SSRC + random byte UDP flood +# rtsp-fleet L7 — register N RTSP topics (requires rtsp mock) + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=env.sh +source "$SCRIPT_DIR/env.sh" + +scenario="${1:-}" +if [[ -z "$scenario" ]]; then + grep -E '^# [a-z]' "$0" | sed 's/^# //' + exit 2 +fi + +ts="$(date +%Y%m%d-%H%M%S)" +out="$VESSEL_RESULTS_DIR/${scenario}-${ts}.json" + +require() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "[run] missing dependency: $1" >&2 + exit 1 + fi +} + +case "$scenario" in + api-smoke|api-write|auth-flood|listing) + require k6 + case "$scenario" in + api-smoke) script="$SCRIPT_DIR/k6/01-api-smoke.js" ;; + api-write) script="$SCRIPT_DIR/k6/02-api-write.js" ;; + auth-flood) script="$SCRIPT_DIR/k6/03-auth-flood.js" ;; + listing) script="$SCRIPT_DIR/k6/04-listing-large.js" ;; + esac + echo "[run] k6 $script → $out" + VESSEL_URL="$VESSEL_URL" \ + VESSEL_ADMIN_USER="$VESSEL_ADMIN_USER" \ + VESSEL_ADMIN_PASS="$VESSEL_ADMIN_PASS" \ + k6 run --summary-export="$out" "$script" + ;; + + rtp-fanout) + require gst-launch-1.0 + "$SCRIPT_DIR/rtp/gen-multi-ssrc.sh" "${RTP_NUM_SSRC:-64}" "${RTP_DURATION:-300}" + ;; + + rtp-malformed) + require python3 + "$SCRIPT_DIR/rtp/gen-malformed.py" --duration "${RTP_DURATION:-120}" + ;; + + rtsp-fleet) + "$SCRIPT_DIR/rtsp/seed-rtsp-topics.sh" "${RTSP_NUM:-32}" + ;; + + *) + echo "[run] unknown scenario: $scenario" >&2 + exit 2 + ;; +esac diff --git a/tests/load/seed.sh b/tests/load/seed.sh new file mode 100755 index 0000000..91734f0 --- /dev/null +++ b/tests/load/seed.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Inflate the test DB with bulk rows so listing scenarios surface query cost. +# Idempotent: re-running adds another batch (uses unique suffixes from $RANDOM). +# +# Env knobs: +# SEED_DEVICES=10000 number of devices to create +# SEED_FLOWS=200 number of flows to create + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=env.sh +source "$SCRIPT_DIR/env.sh" + +SEED_DEVICES="${SEED_DEVICES:-10000}" +SEED_FLOWS="${SEED_FLOWS:-200}" + +token="$(curl -fsS -X POST "$VESSEL_URL/api/auth" \ + -H 'Content-Type: application/json' \ + -d "{\"id\":\"$VESSEL_ADMIN_USER\",\"password\":\"$VESSEL_ADMIN_PASS\"}" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')" + +if [[ -z "$token" ]]; then + echo "[seed] auth failed" >&2; exit 1 +fi + +echo "[seed] inserting $SEED_DEVICES devices" +suffix="$RANDOM" +for i in $(seq 1 "$SEED_DEVICES"); do + curl -fsS -X POST "$VESSEL_URL/api/devices" \ + -H "Authorization: Bearer $token" \ + -H 'Content-Type: application/json' \ + -d "{\"device_id\":\"seed-${suffix}-${i}\",\"name\":\"seed-${suffix}-${i}\",\"manufacturer\":\"loadgen\",\"model\":\"L-${i}\"}" \ + >/dev/null || { echo "[seed] device $i failed (continuing)"; } + if (( i % 500 == 0 )); then echo "[seed] devices: $i"; fi +done + +echo "[seed] inserting $SEED_FLOWS flows" +for i in $(seq 1 "$SEED_FLOWS"); do + curl -fsS -X POST "$VESSEL_URL/api/flows" \ + -H "Authorization: Bearer $token" \ + -H 'Content-Type: application/json' \ + -d "{\"name\":\"seed-flow-${suffix}-${i}\",\"description\":\"load-test\",\"enabled\":0}" \ + >/dev/null || { echo "[seed] flow $i failed (continuing)"; } +done + +echo "[seed] done" diff --git a/tests/load/start-test-server.sh b/tests/load/start-test-server.sh new file mode 100755 index 0000000..2630f88 --- /dev/null +++ b/tests/load/start-test-server.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# Boots a release server with an isolated config and temp DB so load/chaos +# tests don't pollute the dev DB at the repo root. +# +# Steps (mirrors plan): +# 1. Wipe ./tmp DB. +# 2. Render config.test.toml with fresh secret. +# 3. cargo build --release -p server (idempotent). +# 4. Boot once briefly to let migrations + seed run, then stop. +# 5. Optional: enable rtp_broker_port via direct UPDATE on system_configurations. +# 6. Boot for real, write pid, poll /info. +# +# Flags: +# --enable-rtp flip rtp_broker_port enabled=1 (default off) +# --enable-mqtt flip mqtt_broker_url enabled=1 (default off) +# --skip-build reuse existing target/release/server +# +# Usage: +# tests/load/start-test-server.sh [--enable-rtp] [--enable-mqtt] [--skip-build] + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=env.sh +source "$SCRIPT_DIR/env.sh" + +ENABLE_RTP=0 +ENABLE_MQTT=0 +SKIP_BUILD=0 +for arg in "$@"; do + case "$arg" in + --enable-rtp) ENABLE_RTP=1 ;; + --enable-mqtt) ENABLE_MQTT=1 ;; + --skip-build) SKIP_BUILD=1 ;; + *) echo "unknown flag: $arg" >&2; exit 2 ;; + esac +done + +echo "[start] repo=$VESSEL_REPO_ROOT tmp=$VESSEL_TMP_DIR" + +if [[ -f "$VESSEL_PID_FILE" ]] && kill -0 "$(cat "$VESSEL_PID_FILE")" 2>/dev/null; then + echo "[start] server already running with pid $(cat "$VESSEL_PID_FILE"). Run stop-test-server.sh first." >&2 + exit 1 +fi + +# Detect a different process holding the port (e.g. the user's dev server). +if command -v lsof >/dev/null 2>&1; then + if lsof -nP -iTCP:"$VESSEL_LISTEN_PORT" -sTCP:LISTEN >/dev/null 2>&1; then + echo "[start] port $VESSEL_LISTEN_PORT is already bound by another process:" >&2 + lsof -nP -iTCP:"$VESSEL_LISTEN_PORT" -sTCP:LISTEN >&2 || true + echo "[start] stop that process or set VESSEL_LISTEN_PORT to a free port." >&2 + exit 1 + fi +fi + +rm -f "$VESSEL_DB_PATH" "$VESSEL_DB_PATH-shm" "$VESSEL_DB_PATH-wal" + +# `tr | head` triggers SIGPIPE under pipefail; isolate it. +JWT_SECRET="$(set +o pipefail; LC_ALL=C tr -dc 'A-Za-z0-9' "$VESSEL_CONFIG_PATH" + +if [[ "$SKIP_BUILD" -eq 0 ]]; then + echo "[start] cargo build --release -p server" + (cd "$VESSEL_REPO_ROOT" && cargo build --release -p server) +fi + +if [[ ! -x "$VESSEL_SERVER_BIN" ]]; then + echo "[start] missing $VESSEL_SERVER_BIN" >&2 + exit 1 +fi + +# First boot: let migrations + seed run, then stop. +echo "[start] priming DB (migrations + seed)" +pushd "$VESSEL_TMP_DIR" >/dev/null +"$VESSEL_SERVER_BIN" >"$VESSEL_TMP_DIR/server.prime.log" 2>&1 & +PRIME_PID=$! +popd >/dev/null +sleep 4 +kill -INT "$PRIME_PID" 2>/dev/null || true +for _ in $(seq 1 10); do + kill -0 "$PRIME_PID" 2>/dev/null || break + sleep 0.5 +done +kill -KILL "$PRIME_PID" 2>/dev/null || true +wait "$PRIME_PID" 2>/dev/null || true + +if [[ "$ENABLE_RTP" -eq 1 || "$ENABLE_MQTT" -eq 1 ]]; then + if ! command -v sqlite3 >/dev/null 2>&1; then + echo "[start] sqlite3 not found; cannot toggle config flags" >&2 + exit 1 + fi + if [[ "$ENABLE_RTP" -eq 1 ]]; then + sqlite3 "$VESSEL_DB_PATH" "UPDATE system_configurations SET enabled=1 WHERE key='rtp_broker_port';" + echo "[start] enabled rtp_broker_port" + fi + if [[ "$ENABLE_MQTT" -eq 1 ]]; then + sqlite3 "$VESSEL_DB_PATH" "UPDATE system_configurations SET enabled=1 WHERE key='mqtt_broker_url';" + echo "[start] enabled mqtt_broker_url" + fi +fi + +echo "[start] launching server" +pushd "$VESSEL_TMP_DIR" >/dev/null +"$VESSEL_SERVER_BIN" >"$VESSEL_TMP_DIR/server.stdout.log" 2>&1 & +SERVER_PID=$! +popd >/dev/null +echo "$SERVER_PID" > "$VESSEL_PID_FILE" + +echo "[start] pid=$SERVER_PID, polling $VESSEL_URL/info" +for _ in $(seq 1 30); do + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + echo "[start] server pid $SERVER_PID exited prematurely; tail of stdout:" >&2 + tail -n 50 "$VESSEL_TMP_DIR/server.stdout.log" >&2 || true + rm -f "$VESSEL_PID_FILE" + exit 1 + fi + if curl -fsS "$VESSEL_URL/info" >/dev/null 2>&1; then + echo "[start] ready at $VESSEL_URL" + exit 0 + fi + sleep 1 +done + +echo "[start] server failed to become ready in 30s; tail of stdout:" >&2 +tail -n 50 "$VESSEL_TMP_DIR/server.stdout.log" >&2 || true +exit 1 diff --git a/tests/load/stop-test-server.sh b/tests/load/stop-test-server.sh new file mode 100755 index 0000000..738790b --- /dev/null +++ b/tests/load/stop-test-server.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Stop the load-test server started by start-test-server.sh + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=env.sh +source "$SCRIPT_DIR/env.sh" + +if [[ ! -f "$VESSEL_PID_FILE" ]]; then + echo "[stop] no pid file at $VESSEL_PID_FILE; nothing to do" + exit 0 +fi + +PID="$(cat "$VESSEL_PID_FILE")" +if ! kill -0 "$PID" 2>/dev/null; then + echo "[stop] pid $PID not running; cleaning pid file" + rm -f "$VESSEL_PID_FILE" + exit 0 +fi + +echo "[stop] sending SIGINT to $PID" +kill -INT "$PID" || true + +for _ in $(seq 1 30); do + if ! kill -0 "$PID" 2>/dev/null; then + rm -f "$VESSEL_PID_FILE" + echo "[stop] stopped" + exit 0 + fi + sleep 1 +done + +echo "[stop] SIGINT timed out; sending SIGKILL" +kill -KILL "$PID" 2>/dev/null || true +rm -f "$VESSEL_PID_FILE" From 5b1facc5d16d29f0f4efe2b15c8dd71924385ecc Mon Sep 17 00:00:00 2001 From: DipokalLab Date: Thu, 30 Apr 2026 16:38:11 +0900 Subject: [PATCH 06/10] refactor: update fsd pattern --- .../src/{contexts => app/providers}/SupabaseAuthContext.tsx | 0 .../src/entities/configurations/{api.ts => api/index.ts} | 0 .../src/entities/configurations/{ => lib}/codeService.ts | 0 apps/client/src/entities/configurations/{ => model}/store.ts | 0 apps/client/src/entities/configurations/{ => model}/types.ts | 0 .../client/src/entities/custom-nodes/{api.ts => api/index.ts} | 0 apps/client/src/entities/custom-nodes/{ => lib}/presets.ts | 0 apps/client/src/entities/custom-nodes/{ => model}/store.ts | 0 apps/client/src/entities/custom-nodes/{ => model}/types.ts | 0 .../client/src/entities/device-token/{api.ts => api/index.ts} | 0 apps/client/src/entities/device-token/{ => model}/store.ts | 0 apps/client/src/entities/device-token/{ => model}/types.ts | 0 apps/client/src/entities/device/{api.ts => api/index.ts} | 0 apps/client/src/entities/device/{ => model}/store.ts | 0 apps/client/src/entities/device/{ => model}/types.ts | 0 .../src/entities/dynamic-dashboard/{api.ts => api/index.ts} | 0 .../src/entities/dynamic-dashboard/{ => lib}/interaction.ts | 0 .../src/entities/dynamic-dashboard/{ => lib}/layoutResolve.ts | 0 .../src/entities/dynamic-dashboard/{ => model}/store.ts | 0 apps/client/src/entities/entity/{api.ts => api/index.ts} | 0 apps/client/src/entities/entity/{ => model}/store.ts | 0 apps/client/src/entities/entity/{ => model}/types.ts | 0 apps/client/src/entities/file/{api.ts => api/index.ts} | 0 apps/client/src/entities/file/{ => model}/store.ts | 0 apps/client/src/entities/file/{ => model}/types.ts | 0 apps/client/src/entities/flow/{api.ts => api/index.ts} | 0 apps/client/src/entities/flow/{ => model}/store.ts | 0 apps/client/src/entities/flow/{ => model}/types.ts | 0 apps/client/src/entities/ha/{api.ts => api/index.ts} | 0 apps/client/src/entities/ha/{ => model}/store.ts | 0 apps/client/src/entities/ha/{ => model}/types.ts | 0 .../client/src/entities/integrations/{api.ts => api/index.ts} | 0 apps/client/src/entities/integrations/{ => model}/store.ts | 0 apps/client/src/entities/integrations/{ => model}/types.ts | 0 apps/client/src/entities/log/{api.ts => api/index.ts} | 0 apps/client/src/entities/log/{ => model}/types.ts | 0 apps/client/src/entities/map/{api.ts => api/index.ts} | 0 apps/client/src/entities/map/{ => model}/store.ts | 0 apps/client/src/entities/map/{ => model}/types.ts | 0 apps/client/src/entities/permission/{api.ts => api/index.ts} | 0 apps/client/src/entities/permission/{ => model}/store.ts | 0 apps/client/src/entities/permission/{ => model}/types.ts | 0 apps/client/src/entities/recording/{api.ts => api/index.ts} | 0 apps/client/src/entities/recording/{ => model}/store.ts | 0 apps/client/src/entities/recording/{ => model}/types.ts | 0 apps/client/src/entities/role/{api.ts => api/index.ts} | 0 apps/client/src/entities/role/{ => model}/store.ts | 0 apps/client/src/entities/role/{ => model}/types.ts | 0 apps/client/src/entities/stat/{api.ts => api/index.ts} | 0 apps/client/src/entities/stat/{ => model}/store.ts | 0 apps/client/src/entities/stat/{ => model}/types.ts | 0 apps/client/src/entities/tunnel/{api.ts => api/index.ts} | 0 apps/client/src/entities/tunnel/{ => model}/store.ts | 0 apps/client/src/entities/tunnel/{ => model}/types.ts | 0 apps/client/src/entities/user/{api.ts => api/index.ts} | 0 apps/client/src/entities/user/{ => model}/store.ts | 0 apps/client/src/entities/user/{ => model}/types.ts | 0 .../account-switcher/{index.tsx => ui/AccountSwitcher.tsx} | 0 apps/client/src/features/auth/{api.ts => api/index.ts} | 0 apps/client/src/features/auth/{ => model}/hook.ts | 0 apps/client/src/features/auth/{ => ui}/AuthInterceptor.tsx | 0 .../src/features/auth/{ => ui}/DefaultAdminPasswordDialog.tsx | 0 apps/client/src/features/auth/{index.tsx => ui/LoginForm.tsx} | 0 apps/client/src/features/code/{ => ui}/CreateItemDialog.tsx | 0 apps/client/src/features/code/{ => ui}/FileEditor.tsx | 0 apps/client/src/features/code/{ => ui}/FileTree.tsx | 0 .../configurations/{ => ui}/ConfigurationActionButton.tsx | 0 .../features/configurations/{ => ui}/ConfigurationCreate.tsx | 0 .../configurations/{ => ui}/ConfigurationCreateButton.tsx | 0 .../features/darkmode/{mode-toggle.tsx => ui/ModeToggle.tsx} | 0 .../dashboard-swipe/{ => ui}/DashboardSwipeHeader.tsx | 0 .../dashboard-swipe/{ => ui}/DashboardSwipeLayout.tsx | 0 .../src/features/device-token/{ => ui}/DeviceTokenManager.tsx | 0 .../src/features/device/{ => ui}/DeviceCreateButton.tsx | 0 .../src/features/device/{ => ui}/DeviceDeleteButton.tsx | 0 apps/client/src/features/device/{ => ui}/DeviceKeyButton.tsx | 0 .../src/features/device/{ => ui}/DeviceUpdateButton.tsx | 0 .../dynamic-dashboard/{ => lib}/events/dispatcher.test.ts | 0 .../features/dynamic-dashboard/{ => lib}/events/dispatcher.ts | 0 .../src/features/dynamic-dashboard/{ => ui}/GroupCanvas.tsx | 0 .../dynamic-dashboard/{ => ui}/panels/ButtonPanel.tsx | 0 .../features/dynamic-dashboard/{ => ui}/panels/FlowPanel.tsx | 0 .../features/dynamic-dashboard/{ => ui}/panels/MapPanel.tsx | 0 .../client/src/features/entity/{ => model}/useEntitiesData.ts | 0 apps/client/src/features/entity/{ => ui}/AllEntities.tsx | 0 apps/client/src/features/entity/{ => ui}/AnalyzeMenuItem.tsx | 0 apps/client/src/features/entity/{ => ui}/Card.tsx | 0 .../src/features/entity/{ => ui}/EntityCreateButton.tsx | 0 .../src/features/entity/{ => ui}/EntityDeleteButton.tsx | 0 .../src/features/entity/{ => ui}/EntityUpdateButton.tsx | 0 apps/client/src/features/entity/{ => ui}/SelectPlatforms.tsx | 0 apps/client/src/features/entity/{ => ui}/SelectTypes.tsx | 0 .../client/src/features/entity/{ => ui}/StateHistorySheet.tsx | 0 .../src/features/error/{index.tsx => ui/ErrorRender.tsx} | 0 apps/client/src/features/flow-log/{ => ui}/FlowLog.tsx | 0 .../features/flow/{ => lib}/flow-chat/buildSystemPrompt.ts | 0 .../src/features/flow/{ => lib}/flow-chat/executeToolCalls.ts | 0 .../client/src/features/flow/{ => lib}/flow-chat/flowTools.ts | 0 apps/client/src/features/flow/{ => lib}/flow-chat/index.ts | 0 apps/client/src/features/flow/{ => lib}/flowNode.ts | 0 apps/client/src/features/flow/{ => lib}/flowUtils.ts | 0 .../client/src/features/flow/{flowTypes.ts => model/types.ts} | 0 apps/client/src/features/flow/{ => ui}/AddCustomNode.tsx | 0 apps/client/src/features/flow/{ => ui}/Flow.tsx | 0 apps/client/src/features/flow/{ => ui}/Graph.tsx | 0 apps/client/src/features/flow/{ => ui}/Options.tsx | 0 apps/client/src/features/flow/{ => ui}/OptionsVariation.tsx | 0 apps/client/src/features/flow/{ => ui}/RunFlow.tsx | 0 .../client/src/features/flow/{ => ui}/SelectedItemActions.tsx | 0 apps/client/src/features/flow/{ => ui}/nodes/ButtonNode.tsx | 0 apps/client/src/features/flow/{ => ui}/nodes/CalcNode.tsx | 0 apps/client/src/features/flow/{ => ui}/nodes/HttpNode.tsx | 0 apps/client/src/features/flow/{ => ui}/nodes/IntervalNode.tsx | 0 apps/client/src/features/flow/{ => ui}/nodes/LogicNode.tsx | 0 apps/client/src/features/flow/{ => ui}/nodes/LoopNode.tsx | 0 apps/client/src/features/flow/{ => ui}/nodes/MQTTNode.tsx | 0 apps/client/src/features/flow/{ => ui}/nodes/NumberNode.tsx | 0 .../src/features/flow/{ => ui}/nodes/ProcessingNode.tsx | 0 apps/client/src/features/flow/{ => ui}/nodes/TitleNode.tsx | 0 apps/client/src/features/flow/{ => ui}/nodes/VarNode.tsx | 0 apps/client/src/features/footer/{index.tsx => ui/Footer.tsx} | 0 apps/client/src/features/gps/{ => lib}/parseGps.ts | 0 apps/client/src/features/ha/{index.tsx => ui/HaDashboard.tsx} | 0 apps/client/src/features/ha/{ => ui}/HaEntitiesTable.tsx | 0 apps/client/src/features/ha/{ => ui}/HaStatBlock.tsx | 0 .../client/src/features/integration/{ => config}/constants.ts | 0 apps/client/src/features/integration/{ => model}/types.ts | 0 apps/client/src/features/integration/{ => ui}/HA.tsx | 0 apps/client/src/features/integration/{ => ui}/Integration.tsx | 0 apps/client/src/features/integration/{ => ui}/ROS.tsx | 0 apps/client/src/features/integration/{ => ui}/SDR.tsx | 0 apps/client/src/features/json/{ => ui}/JsonEditor.tsx | 0 apps/client/src/features/llm-chat/index.tsx | 4 ---- apps/client/src/features/llm-chat/{ => model}/store.ts | 0 apps/client/src/features/llm-chat/{ => model}/types.ts | 0 .../src/features/llm-chat/{ => model}/useChatKeyboard.ts | 0 apps/client/src/features/llm-chat/{ => ui}/ChatInput.tsx | 0 apps/client/src/features/llm-chat/{ => ui}/ChatMessage.tsx | 0 apps/client/src/features/llm-chat/{ => ui}/ChatMessages.tsx | 0 apps/client/src/features/llm-chat/{ => ui}/ChatPanel.tsx | 0 .../src/features/llm-chat/{ => ui}/ChatPanelContainer.tsx | 0 .../client/src/features/llm-chat/{ => ui}/ChatPanelMobile.tsx | 0 apps/client/src/features/log/{index.tsx => ui/Logs.tsx} | 0 .../src/features/map-draw/{ => ui}/FeatureDetailsPanel.tsx | 0 .../src/features/map-draw/{ => ui}/FeatureDrawingPreview.tsx | 0 apps/client/src/features/map-draw/{ => ui}/FeatureEditor.tsx | 0 .../client/src/features/map-draw/{ => ui}/FeatureRenderer.tsx | 0 apps/client/src/features/map-draw/{ => ui}/LayerDialog.tsx | 0 apps/client/src/features/map-draw/{ => ui}/LayerSidebar.tsx | 0 apps/client/src/features/map-draw/{ => ui}/MapEvents.tsx | 0 apps/client/src/features/map-draw/{ => ui}/MapToolbar.tsx | 0 apps/client/src/features/map-entity/{ => model}/store.ts | 0 .../src/features/map-entity/{ => ui}/EntityDetailsPanel.tsx | 0 apps/client/src/features/map-entity/{ => ui}/render.tsx | 0 .../src/features/map/{ => ui}/CurrentLocationMarker.tsx | 0 apps/client/src/features/map/{index.tsx => ui/MapView.tsx} | 0 apps/client/src/features/map/{ => ui}/MapViewPersistence.tsx | 0 apps/client/src/features/map/{ => ui}/style.css | 0 .../features/recording/{hooks => model}/useAudioWaveform.ts | 0 .../features/recording/{hooks => model}/useMediaPlayback.ts | 0 .../src/features/recording/{hooks => model}/useVideoFrames.ts | 0 .../recording/{components => ui}/AudioWaveformPlayer.tsx | 0 .../features/recording/{components => ui}/FrameTimeline.tsx | 0 .../recording/{components => ui}/PlaybackControls.tsx | 0 .../src/features/recording/{ => ui}/RecordingButton.tsx | 0 .../client/src/features/recording/{ => ui}/RecordingsList.tsx | 0 .../src/features/recording/{components => ui}/TimeRuler.tsx | 0 .../features/recording/{components => ui}/VideoControlBar.tsx | 0 .../src/features/recording/{ => ui}/VideoPlaybackDialog.tsx | 0 .../features/recording/{components => ui}/WaveformCanvas.tsx | 0 apps/client/src/features/role/{ => ui}/RoleDialogs.tsx | 0 apps/client/src/features/role/{ => ui}/RoleForm.tsx | 0 apps/client/src/features/ros2/{ => ui}/Ros2Dashboard.tsx | 0 apps/client/src/features/rtc/{ => lib}/captureFrame.ts | 0 apps/client/src/features/rtc/{ => lib}/rtc.ts | 0 apps/client/src/features/rtc/{ => lib}/turnService.ts | 0 apps/client/src/features/rtc/{ => ui}/AudioLevelBar.tsx | 0 apps/client/src/features/rtc/{ => ui}/StreamReceiver.tsx | 0 apps/client/src/features/rtc/{ => ui}/WebRTCProvider.tsx | 0 apps/client/src/features/sdr/{api.ts => api/index.ts} | 0 apps/client/src/features/sdr/{ => ui}/SdrAudioPlayer.tsx | 0 apps/client/src/features/sdr/{ => ui}/SdrDashboard.tsx | 0 .../features/search/{search-form.tsx => ui/SearchForm.tsx} | 0 .../{resourceUsage.tsx => ui/ResourceUsage.tsx} | 0 .../src/features/setup/{index.tsx => ui/SetupSteps.tsx} | 0 .../src/features/sidebar/{index.tsx => ui/AppSidebar.tsx} | 0 apps/client/src/features/sidebar/{ => ui}/footer.tsx | 0 apps/client/src/features/stat/{index.tsx => ui/StatBlock.tsx} | 0 apps/client/src/features/topbar/{index.tsx => ui/TopBar.tsx} | 0 apps/client/src/features/topbar/{ => ui}/style.css | 0 apps/client/src/features/user/{ => ui}/UserRoleAssigner.tsx | 0 apps/client/src/features/user/{ => ui}/userAdd.tsx | 0 apps/client/src/features/user/{ => ui}/userDelete.tsx | 0 apps/client/src/features/user/{ => ui}/userEdit.tsx | 0 apps/client/src/features/user/{ => ui}/userForm.tsx | 0 .../ws/{flowUiAdapters => lib/adapters}/toastFlowUiAdapter.ts | 0 .../src/features/ws/{ => lib}/flowUiEventRouter.test.ts | 0 apps/client/src/features/ws/{ => lib}/flowUiEventRouter.ts | 0 apps/client/src/features/ws/{ => lib}/ws.ts | 0 apps/client/src/features/ws/{ => lib}/wsMock.ts | 0 apps/client/src/features/ws/{ => ui}/FlowUiEventBridge.tsx | 0 apps/client/src/features/ws/{ => ui}/IsConnected.tsx | 0 apps/client/src/features/ws/{ => ui}/WebSocketProvider.tsx | 0 apps/client/src/pages/auth/{index.tsx => ui/AuthPage.tsx} | 0 apps/client/src/pages/code/{index.tsx => ui/CodePage.tsx} | 0 apps/client/src/pages/dashboard/index.tsx | 4 ---- .../src/pages/dashboard/{ => ui}/DashboardMainPanel.tsx | 0 .../{index.tsx => ui/DesktopSettingsPage.tsx} | 0 .../client/src/pages/devices/{index.tsx => ui/DevicePage.tsx} | 0 apps/client/src/pages/dynamic-dashboard/index.tsx | 2 -- .../dynamic-dashboard/{ => ui}/DynamicDashboardMainPanel.tsx | 0 .../dynamic-dashboard/{ => ui}/NewDynamicDashboardPanel.tsx | 0 apps/client/src/pages/flow/{index.tsx => ui/FlowPage.tsx} | 0 .../src/pages/landing/{index.tsx => ui/LandingPage.tsx} | 0 apps/client/src/pages/map/{index.tsx => ui/MapPage.tsx} | 0 apps/client/src/pages/notfound/{index.tsx => ui/NotFound.tsx} | 0 .../src/pages/recordings/{index.tsx => ui/RecordingsPage.tsx} | 0 .../settings/{account.tsx => ui/AccountSettingsPage.tsx} | 0 .../pages/settings/{config.tsx => ui/ConfigSettingsPage.tsx} | 0 .../{integration.tsx => ui/IntegrationSettingsPage.tsx} | 0 .../src/pages/settings/{log.tsx => ui/LogSettingsPage.tsx} | 0 .../settings/{networks.tsx => ui/NetworksSettingsPage.tsx} | 0 .../settings/{services.tsx => ui/ServicesSettingsPage.tsx} | 0 .../src/pages/settings/{index.tsx => ui/SettingsPage.tsx} | 0 .../pages/settings/{users.tsx => ui/UsersSettingsPage.tsx} | 0 apps/client/src/pages/setup/{index.tsx => ui/SetupPage.tsx} | 0 apps/client/src/shared/{ => config}/demo.ts | 0 apps/client/src/shared/{ => lib}/desktop.ts | 0 apps/client/src/{ => shared}/lib/electron.ts | 0 apps/client/src/{ => shared}/lib/geometry-precision.ts | 0 apps/client/src/{ => shared}/lib/geometry.ts | 0 apps/client/src/{ => shared/lib}/hooks/use-mobile.ts | 0 apps/client/src/{ => shared/lib}/hooks/useDesktopSidecar.ts | 0 .../src/{ => shared/lib}/hooks/usePreventBackNavigation.ts | 0 apps/client/src/{ => shared}/lib/jwt.ts | 0 apps/client/src/{ => shared}/lib/resetStores.ts | 0 apps/client/src/{ => shared}/lib/storage.ts | 0 apps/client/src/{ => shared}/lib/string.ts | 0 apps/client/src/{ => shared}/lib/supabase.ts | 0 apps/client/src/{ => shared}/lib/time.ts | 0 apps/client/src/{ => shared}/lib/utils.ts | 0 apps/client/src/{components => shared}/ui/alert-dialog.tsx | 0 apps/client/src/{components => shared}/ui/alert.tsx | 0 apps/client/src/{components => shared}/ui/avatar.tsx | 0 apps/client/src/{components => shared}/ui/badge.tsx | 0 apps/client/src/{components => shared}/ui/breadcrumb.tsx | 0 apps/client/src/{components => shared}/ui/button.tsx | 0 apps/client/src/{components => shared}/ui/card.tsx | 0 apps/client/src/{components => shared}/ui/command.tsx | 0 apps/client/src/{components => shared}/ui/context-menu.tsx | 0 apps/client/src/{components => shared}/ui/dialog.tsx | 0 apps/client/src/{components => shared}/ui/dropdown-menu.tsx | 0 apps/client/src/{components => shared/ui}/icon/Logo.tsx | 0 apps/client/src/{components => shared}/ui/input.tsx | 0 apps/client/src/{components => shared}/ui/label.tsx | 0 apps/client/src/{components => shared}/ui/navigation-menu.tsx | 0 apps/client/src/{components => shared}/ui/popover.tsx | 0 apps/client/src/{components => shared}/ui/resizable.tsx | 0 apps/client/src/{components => shared}/ui/scroll-area.tsx | 0 apps/client/src/{components => shared}/ui/select.tsx | 0 apps/client/src/{components => shared}/ui/separator.tsx | 0 apps/client/src/{components => shared}/ui/sheet.tsx | 0 apps/client/src/{components => shared}/ui/sidebar.tsx | 0 apps/client/src/{components => shared}/ui/skeleton.tsx | 0 apps/client/src/{components => shared}/ui/sonner.tsx | 0 apps/client/src/{components => shared}/ui/switch.tsx | 0 apps/client/src/{components => shared}/ui/table.tsx | 0 apps/client/src/{components => shared}/ui/tabs.tsx | 0 apps/client/src/{components => shared}/ui/textarea.tsx | 0 apps/client/src/{components => shared}/ui/tooltip.tsx | 0 apps/client/src/widgets/auth/{ => ui}/AuthenticatedLayout.tsx | 0 apps/client/src/widgets/auth/{ => ui}/TopBarWrapper.tsx | 0 apps/client/src/widgets/device-list/{ => ui}/DeviceList.tsx | 0 apps/client/src/widgets/entity-list/{ => ui}/EntityList.tsx | 0 apps/client/src/widgets/role-table/{ => ui}/RoleList.tsx | 0 apps/client/src/widgets/user-table/{ => ui}/UserList.tsx | 0 276 files changed, 10 deletions(-) rename apps/client/src/{contexts => app/providers}/SupabaseAuthContext.tsx (100%) rename apps/client/src/entities/configurations/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/configurations/{ => lib}/codeService.ts (100%) rename apps/client/src/entities/configurations/{ => model}/store.ts (100%) rename apps/client/src/entities/configurations/{ => model}/types.ts (100%) rename apps/client/src/entities/custom-nodes/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/custom-nodes/{ => lib}/presets.ts (100%) rename apps/client/src/entities/custom-nodes/{ => model}/store.ts (100%) rename apps/client/src/entities/custom-nodes/{ => model}/types.ts (100%) rename apps/client/src/entities/device-token/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/device-token/{ => model}/store.ts (100%) rename apps/client/src/entities/device-token/{ => model}/types.ts (100%) rename apps/client/src/entities/device/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/device/{ => model}/store.ts (100%) rename apps/client/src/entities/device/{ => model}/types.ts (100%) rename apps/client/src/entities/dynamic-dashboard/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/dynamic-dashboard/{ => lib}/interaction.ts (100%) rename apps/client/src/entities/dynamic-dashboard/{ => lib}/layoutResolve.ts (100%) rename apps/client/src/entities/dynamic-dashboard/{ => model}/store.ts (100%) rename apps/client/src/entities/entity/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/entity/{ => model}/store.ts (100%) rename apps/client/src/entities/entity/{ => model}/types.ts (100%) rename apps/client/src/entities/file/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/file/{ => model}/store.ts (100%) rename apps/client/src/entities/file/{ => model}/types.ts (100%) rename apps/client/src/entities/flow/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/flow/{ => model}/store.ts (100%) rename apps/client/src/entities/flow/{ => model}/types.ts (100%) rename apps/client/src/entities/ha/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/ha/{ => model}/store.ts (100%) rename apps/client/src/entities/ha/{ => model}/types.ts (100%) rename apps/client/src/entities/integrations/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/integrations/{ => model}/store.ts (100%) rename apps/client/src/entities/integrations/{ => model}/types.ts (100%) rename apps/client/src/entities/log/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/log/{ => model}/types.ts (100%) rename apps/client/src/entities/map/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/map/{ => model}/store.ts (100%) rename apps/client/src/entities/map/{ => model}/types.ts (100%) rename apps/client/src/entities/permission/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/permission/{ => model}/store.ts (100%) rename apps/client/src/entities/permission/{ => model}/types.ts (100%) rename apps/client/src/entities/recording/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/recording/{ => model}/store.ts (100%) rename apps/client/src/entities/recording/{ => model}/types.ts (100%) rename apps/client/src/entities/role/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/role/{ => model}/store.ts (100%) rename apps/client/src/entities/role/{ => model}/types.ts (100%) rename apps/client/src/entities/stat/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/stat/{ => model}/store.ts (100%) rename apps/client/src/entities/stat/{ => model}/types.ts (100%) rename apps/client/src/entities/tunnel/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/tunnel/{ => model}/store.ts (100%) rename apps/client/src/entities/tunnel/{ => model}/types.ts (100%) rename apps/client/src/entities/user/{api.ts => api/index.ts} (100%) rename apps/client/src/entities/user/{ => model}/store.ts (100%) rename apps/client/src/entities/user/{ => model}/types.ts (100%) rename apps/client/src/features/account-switcher/{index.tsx => ui/AccountSwitcher.tsx} (100%) rename apps/client/src/features/auth/{api.ts => api/index.ts} (100%) rename apps/client/src/features/auth/{ => model}/hook.ts (100%) rename apps/client/src/features/auth/{ => ui}/AuthInterceptor.tsx (100%) rename apps/client/src/features/auth/{ => ui}/DefaultAdminPasswordDialog.tsx (100%) rename apps/client/src/features/auth/{index.tsx => ui/LoginForm.tsx} (100%) rename apps/client/src/features/code/{ => ui}/CreateItemDialog.tsx (100%) rename apps/client/src/features/code/{ => ui}/FileEditor.tsx (100%) rename apps/client/src/features/code/{ => ui}/FileTree.tsx (100%) rename apps/client/src/features/configurations/{ => ui}/ConfigurationActionButton.tsx (100%) rename apps/client/src/features/configurations/{ => ui}/ConfigurationCreate.tsx (100%) rename apps/client/src/features/configurations/{ => ui}/ConfigurationCreateButton.tsx (100%) rename apps/client/src/features/darkmode/{mode-toggle.tsx => ui/ModeToggle.tsx} (100%) rename apps/client/src/features/dashboard-swipe/{ => ui}/DashboardSwipeHeader.tsx (100%) rename apps/client/src/features/dashboard-swipe/{ => ui}/DashboardSwipeLayout.tsx (100%) rename apps/client/src/features/device-token/{ => ui}/DeviceTokenManager.tsx (100%) rename apps/client/src/features/device/{ => ui}/DeviceCreateButton.tsx (100%) rename apps/client/src/features/device/{ => ui}/DeviceDeleteButton.tsx (100%) rename apps/client/src/features/device/{ => ui}/DeviceKeyButton.tsx (100%) rename apps/client/src/features/device/{ => ui}/DeviceUpdateButton.tsx (100%) rename apps/client/src/features/dynamic-dashboard/{ => lib}/events/dispatcher.test.ts (100%) rename apps/client/src/features/dynamic-dashboard/{ => lib}/events/dispatcher.ts (100%) rename apps/client/src/features/dynamic-dashboard/{ => ui}/GroupCanvas.tsx (100%) rename apps/client/src/features/dynamic-dashboard/{ => ui}/panels/ButtonPanel.tsx (100%) rename apps/client/src/features/dynamic-dashboard/{ => ui}/panels/FlowPanel.tsx (100%) rename apps/client/src/features/dynamic-dashboard/{ => ui}/panels/MapPanel.tsx (100%) rename apps/client/src/features/entity/{ => model}/useEntitiesData.ts (100%) rename apps/client/src/features/entity/{ => ui}/AllEntities.tsx (100%) rename apps/client/src/features/entity/{ => ui}/AnalyzeMenuItem.tsx (100%) rename apps/client/src/features/entity/{ => ui}/Card.tsx (100%) rename apps/client/src/features/entity/{ => ui}/EntityCreateButton.tsx (100%) rename apps/client/src/features/entity/{ => ui}/EntityDeleteButton.tsx (100%) rename apps/client/src/features/entity/{ => ui}/EntityUpdateButton.tsx (100%) rename apps/client/src/features/entity/{ => ui}/SelectPlatforms.tsx (100%) rename apps/client/src/features/entity/{ => ui}/SelectTypes.tsx (100%) rename apps/client/src/features/entity/{ => ui}/StateHistorySheet.tsx (100%) rename apps/client/src/features/error/{index.tsx => ui/ErrorRender.tsx} (100%) rename apps/client/src/features/flow-log/{ => ui}/FlowLog.tsx (100%) rename apps/client/src/features/flow/{ => lib}/flow-chat/buildSystemPrompt.ts (100%) rename apps/client/src/features/flow/{ => lib}/flow-chat/executeToolCalls.ts (100%) rename apps/client/src/features/flow/{ => lib}/flow-chat/flowTools.ts (100%) rename apps/client/src/features/flow/{ => lib}/flow-chat/index.ts (100%) rename apps/client/src/features/flow/{ => lib}/flowNode.ts (100%) rename apps/client/src/features/flow/{ => lib}/flowUtils.ts (100%) rename apps/client/src/features/flow/{flowTypes.ts => model/types.ts} (100%) rename apps/client/src/features/flow/{ => ui}/AddCustomNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/Flow.tsx (100%) rename apps/client/src/features/flow/{ => ui}/Graph.tsx (100%) rename apps/client/src/features/flow/{ => ui}/Options.tsx (100%) rename apps/client/src/features/flow/{ => ui}/OptionsVariation.tsx (100%) rename apps/client/src/features/flow/{ => ui}/RunFlow.tsx (100%) rename apps/client/src/features/flow/{ => ui}/SelectedItemActions.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/ButtonNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/CalcNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/HttpNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/IntervalNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/LogicNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/LoopNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/MQTTNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/NumberNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/ProcessingNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/TitleNode.tsx (100%) rename apps/client/src/features/flow/{ => ui}/nodes/VarNode.tsx (100%) rename apps/client/src/features/footer/{index.tsx => ui/Footer.tsx} (100%) rename apps/client/src/features/gps/{ => lib}/parseGps.ts (100%) rename apps/client/src/features/ha/{index.tsx => ui/HaDashboard.tsx} (100%) rename apps/client/src/features/ha/{ => ui}/HaEntitiesTable.tsx (100%) rename apps/client/src/features/ha/{ => ui}/HaStatBlock.tsx (100%) rename apps/client/src/features/integration/{ => config}/constants.ts (100%) rename apps/client/src/features/integration/{ => model}/types.ts (100%) rename apps/client/src/features/integration/{ => ui}/HA.tsx (100%) rename apps/client/src/features/integration/{ => ui}/Integration.tsx (100%) rename apps/client/src/features/integration/{ => ui}/ROS.tsx (100%) rename apps/client/src/features/integration/{ => ui}/SDR.tsx (100%) rename apps/client/src/features/json/{ => ui}/JsonEditor.tsx (100%) delete mode 100644 apps/client/src/features/llm-chat/index.tsx rename apps/client/src/features/llm-chat/{ => model}/store.ts (100%) rename apps/client/src/features/llm-chat/{ => model}/types.ts (100%) rename apps/client/src/features/llm-chat/{ => model}/useChatKeyboard.ts (100%) rename apps/client/src/features/llm-chat/{ => ui}/ChatInput.tsx (100%) rename apps/client/src/features/llm-chat/{ => ui}/ChatMessage.tsx (100%) rename apps/client/src/features/llm-chat/{ => ui}/ChatMessages.tsx (100%) rename apps/client/src/features/llm-chat/{ => ui}/ChatPanel.tsx (100%) rename apps/client/src/features/llm-chat/{ => ui}/ChatPanelContainer.tsx (100%) rename apps/client/src/features/llm-chat/{ => ui}/ChatPanelMobile.tsx (100%) rename apps/client/src/features/log/{index.tsx => ui/Logs.tsx} (100%) rename apps/client/src/features/map-draw/{ => ui}/FeatureDetailsPanel.tsx (100%) rename apps/client/src/features/map-draw/{ => ui}/FeatureDrawingPreview.tsx (100%) rename apps/client/src/features/map-draw/{ => ui}/FeatureEditor.tsx (100%) rename apps/client/src/features/map-draw/{ => ui}/FeatureRenderer.tsx (100%) rename apps/client/src/features/map-draw/{ => ui}/LayerDialog.tsx (100%) rename apps/client/src/features/map-draw/{ => ui}/LayerSidebar.tsx (100%) rename apps/client/src/features/map-draw/{ => ui}/MapEvents.tsx (100%) rename apps/client/src/features/map-draw/{ => ui}/MapToolbar.tsx (100%) rename apps/client/src/features/map-entity/{ => model}/store.ts (100%) rename apps/client/src/features/map-entity/{ => ui}/EntityDetailsPanel.tsx (100%) rename apps/client/src/features/map-entity/{ => ui}/render.tsx (100%) rename apps/client/src/features/map/{ => ui}/CurrentLocationMarker.tsx (100%) rename apps/client/src/features/map/{index.tsx => ui/MapView.tsx} (100%) rename apps/client/src/features/map/{ => ui}/MapViewPersistence.tsx (100%) rename apps/client/src/features/map/{ => ui}/style.css (100%) rename apps/client/src/features/recording/{hooks => model}/useAudioWaveform.ts (100%) rename apps/client/src/features/recording/{hooks => model}/useMediaPlayback.ts (100%) rename apps/client/src/features/recording/{hooks => model}/useVideoFrames.ts (100%) rename apps/client/src/features/recording/{components => ui}/AudioWaveformPlayer.tsx (100%) rename apps/client/src/features/recording/{components => ui}/FrameTimeline.tsx (100%) rename apps/client/src/features/recording/{components => ui}/PlaybackControls.tsx (100%) rename apps/client/src/features/recording/{ => ui}/RecordingButton.tsx (100%) rename apps/client/src/features/recording/{ => ui}/RecordingsList.tsx (100%) rename apps/client/src/features/recording/{components => ui}/TimeRuler.tsx (100%) rename apps/client/src/features/recording/{components => ui}/VideoControlBar.tsx (100%) rename apps/client/src/features/recording/{ => ui}/VideoPlaybackDialog.tsx (100%) rename apps/client/src/features/recording/{components => ui}/WaveformCanvas.tsx (100%) rename apps/client/src/features/role/{ => ui}/RoleDialogs.tsx (100%) rename apps/client/src/features/role/{ => ui}/RoleForm.tsx (100%) rename apps/client/src/features/ros2/{ => ui}/Ros2Dashboard.tsx (100%) rename apps/client/src/features/rtc/{ => lib}/captureFrame.ts (100%) rename apps/client/src/features/rtc/{ => lib}/rtc.ts (100%) rename apps/client/src/features/rtc/{ => lib}/turnService.ts (100%) rename apps/client/src/features/rtc/{ => ui}/AudioLevelBar.tsx (100%) rename apps/client/src/features/rtc/{ => ui}/StreamReceiver.tsx (100%) rename apps/client/src/features/rtc/{ => ui}/WebRTCProvider.tsx (100%) rename apps/client/src/features/sdr/{api.ts => api/index.ts} (100%) rename apps/client/src/features/sdr/{ => ui}/SdrAudioPlayer.tsx (100%) rename apps/client/src/features/sdr/{ => ui}/SdrDashboard.tsx (100%) rename apps/client/src/features/search/{search-form.tsx => ui/SearchForm.tsx} (100%) rename apps/client/src/features/server-resource/{resourceUsage.tsx => ui/ResourceUsage.tsx} (100%) rename apps/client/src/features/setup/{index.tsx => ui/SetupSteps.tsx} (100%) rename apps/client/src/features/sidebar/{index.tsx => ui/AppSidebar.tsx} (100%) rename apps/client/src/features/sidebar/{ => ui}/footer.tsx (100%) rename apps/client/src/features/stat/{index.tsx => ui/StatBlock.tsx} (100%) rename apps/client/src/features/topbar/{index.tsx => ui/TopBar.tsx} (100%) rename apps/client/src/features/topbar/{ => ui}/style.css (100%) rename apps/client/src/features/user/{ => ui}/UserRoleAssigner.tsx (100%) rename apps/client/src/features/user/{ => ui}/userAdd.tsx (100%) rename apps/client/src/features/user/{ => ui}/userDelete.tsx (100%) rename apps/client/src/features/user/{ => ui}/userEdit.tsx (100%) rename apps/client/src/features/user/{ => ui}/userForm.tsx (100%) rename apps/client/src/features/ws/{flowUiAdapters => lib/adapters}/toastFlowUiAdapter.ts (100%) rename apps/client/src/features/ws/{ => lib}/flowUiEventRouter.test.ts (100%) rename apps/client/src/features/ws/{ => lib}/flowUiEventRouter.ts (100%) rename apps/client/src/features/ws/{ => lib}/ws.ts (100%) rename apps/client/src/features/ws/{ => lib}/wsMock.ts (100%) rename apps/client/src/features/ws/{ => ui}/FlowUiEventBridge.tsx (100%) rename apps/client/src/features/ws/{ => ui}/IsConnected.tsx (100%) rename apps/client/src/features/ws/{ => ui}/WebSocketProvider.tsx (100%) rename apps/client/src/pages/auth/{index.tsx => ui/AuthPage.tsx} (100%) rename apps/client/src/pages/code/{index.tsx => ui/CodePage.tsx} (100%) delete mode 100644 apps/client/src/pages/dashboard/index.tsx rename apps/client/src/pages/dashboard/{ => ui}/DashboardMainPanel.tsx (100%) rename apps/client/src/pages/desktop-settings/{index.tsx => ui/DesktopSettingsPage.tsx} (100%) rename apps/client/src/pages/devices/{index.tsx => ui/DevicePage.tsx} (100%) delete mode 100644 apps/client/src/pages/dynamic-dashboard/index.tsx rename apps/client/src/pages/dynamic-dashboard/{ => ui}/DynamicDashboardMainPanel.tsx (100%) rename apps/client/src/pages/dynamic-dashboard/{ => ui}/NewDynamicDashboardPanel.tsx (100%) rename apps/client/src/pages/flow/{index.tsx => ui/FlowPage.tsx} (100%) rename apps/client/src/pages/landing/{index.tsx => ui/LandingPage.tsx} (100%) rename apps/client/src/pages/map/{index.tsx => ui/MapPage.tsx} (100%) rename apps/client/src/pages/notfound/{index.tsx => ui/NotFound.tsx} (100%) rename apps/client/src/pages/recordings/{index.tsx => ui/RecordingsPage.tsx} (100%) rename apps/client/src/pages/settings/{account.tsx => ui/AccountSettingsPage.tsx} (100%) rename apps/client/src/pages/settings/{config.tsx => ui/ConfigSettingsPage.tsx} (100%) rename apps/client/src/pages/settings/{integration.tsx => ui/IntegrationSettingsPage.tsx} (100%) rename apps/client/src/pages/settings/{log.tsx => ui/LogSettingsPage.tsx} (100%) rename apps/client/src/pages/settings/{networks.tsx => ui/NetworksSettingsPage.tsx} (100%) rename apps/client/src/pages/settings/{services.tsx => ui/ServicesSettingsPage.tsx} (100%) rename apps/client/src/pages/settings/{index.tsx => ui/SettingsPage.tsx} (100%) rename apps/client/src/pages/settings/{users.tsx => ui/UsersSettingsPage.tsx} (100%) rename apps/client/src/pages/setup/{index.tsx => ui/SetupPage.tsx} (100%) rename apps/client/src/shared/{ => config}/demo.ts (100%) rename apps/client/src/shared/{ => lib}/desktop.ts (100%) rename apps/client/src/{ => shared}/lib/electron.ts (100%) rename apps/client/src/{ => shared}/lib/geometry-precision.ts (100%) rename apps/client/src/{ => shared}/lib/geometry.ts (100%) rename apps/client/src/{ => shared/lib}/hooks/use-mobile.ts (100%) rename apps/client/src/{ => shared/lib}/hooks/useDesktopSidecar.ts (100%) rename apps/client/src/{ => shared/lib}/hooks/usePreventBackNavigation.ts (100%) rename apps/client/src/{ => shared}/lib/jwt.ts (100%) rename apps/client/src/{ => shared}/lib/resetStores.ts (100%) rename apps/client/src/{ => shared}/lib/storage.ts (100%) rename apps/client/src/{ => shared}/lib/string.ts (100%) rename apps/client/src/{ => shared}/lib/supabase.ts (100%) rename apps/client/src/{ => shared}/lib/time.ts (100%) rename apps/client/src/{ => shared}/lib/utils.ts (100%) rename apps/client/src/{components => shared}/ui/alert-dialog.tsx (100%) rename apps/client/src/{components => shared}/ui/alert.tsx (100%) rename apps/client/src/{components => shared}/ui/avatar.tsx (100%) rename apps/client/src/{components => shared}/ui/badge.tsx (100%) rename apps/client/src/{components => shared}/ui/breadcrumb.tsx (100%) rename apps/client/src/{components => shared}/ui/button.tsx (100%) rename apps/client/src/{components => shared}/ui/card.tsx (100%) rename apps/client/src/{components => shared}/ui/command.tsx (100%) rename apps/client/src/{components => shared}/ui/context-menu.tsx (100%) rename apps/client/src/{components => shared}/ui/dialog.tsx (100%) rename apps/client/src/{components => shared}/ui/dropdown-menu.tsx (100%) rename apps/client/src/{components => shared/ui}/icon/Logo.tsx (100%) rename apps/client/src/{components => shared}/ui/input.tsx (100%) rename apps/client/src/{components => shared}/ui/label.tsx (100%) rename apps/client/src/{components => shared}/ui/navigation-menu.tsx (100%) rename apps/client/src/{components => shared}/ui/popover.tsx (100%) rename apps/client/src/{components => shared}/ui/resizable.tsx (100%) rename apps/client/src/{components => shared}/ui/scroll-area.tsx (100%) rename apps/client/src/{components => shared}/ui/select.tsx (100%) rename apps/client/src/{components => shared}/ui/separator.tsx (100%) rename apps/client/src/{components => shared}/ui/sheet.tsx (100%) rename apps/client/src/{components => shared}/ui/sidebar.tsx (100%) rename apps/client/src/{components => shared}/ui/skeleton.tsx (100%) rename apps/client/src/{components => shared}/ui/sonner.tsx (100%) rename apps/client/src/{components => shared}/ui/switch.tsx (100%) rename apps/client/src/{components => shared}/ui/table.tsx (100%) rename apps/client/src/{components => shared}/ui/tabs.tsx (100%) rename apps/client/src/{components => shared}/ui/textarea.tsx (100%) rename apps/client/src/{components => shared}/ui/tooltip.tsx (100%) rename apps/client/src/widgets/auth/{ => ui}/AuthenticatedLayout.tsx (100%) rename apps/client/src/widgets/auth/{ => ui}/TopBarWrapper.tsx (100%) rename apps/client/src/widgets/device-list/{ => ui}/DeviceList.tsx (100%) rename apps/client/src/widgets/entity-list/{ => ui}/EntityList.tsx (100%) rename apps/client/src/widgets/role-table/{ => ui}/RoleList.tsx (100%) rename apps/client/src/widgets/user-table/{ => ui}/UserList.tsx (100%) diff --git a/apps/client/src/contexts/SupabaseAuthContext.tsx b/apps/client/src/app/providers/SupabaseAuthContext.tsx similarity index 100% rename from apps/client/src/contexts/SupabaseAuthContext.tsx rename to apps/client/src/app/providers/SupabaseAuthContext.tsx diff --git a/apps/client/src/entities/configurations/api.ts b/apps/client/src/entities/configurations/api/index.ts similarity index 100% rename from apps/client/src/entities/configurations/api.ts rename to apps/client/src/entities/configurations/api/index.ts diff --git a/apps/client/src/entities/configurations/codeService.ts b/apps/client/src/entities/configurations/lib/codeService.ts similarity index 100% rename from apps/client/src/entities/configurations/codeService.ts rename to apps/client/src/entities/configurations/lib/codeService.ts diff --git a/apps/client/src/entities/configurations/store.ts b/apps/client/src/entities/configurations/model/store.ts similarity index 100% rename from apps/client/src/entities/configurations/store.ts rename to apps/client/src/entities/configurations/model/store.ts diff --git a/apps/client/src/entities/configurations/types.ts b/apps/client/src/entities/configurations/model/types.ts similarity index 100% rename from apps/client/src/entities/configurations/types.ts rename to apps/client/src/entities/configurations/model/types.ts diff --git a/apps/client/src/entities/custom-nodes/api.ts b/apps/client/src/entities/custom-nodes/api/index.ts similarity index 100% rename from apps/client/src/entities/custom-nodes/api.ts rename to apps/client/src/entities/custom-nodes/api/index.ts diff --git a/apps/client/src/entities/custom-nodes/presets.ts b/apps/client/src/entities/custom-nodes/lib/presets.ts similarity index 100% rename from apps/client/src/entities/custom-nodes/presets.ts rename to apps/client/src/entities/custom-nodes/lib/presets.ts diff --git a/apps/client/src/entities/custom-nodes/store.ts b/apps/client/src/entities/custom-nodes/model/store.ts similarity index 100% rename from apps/client/src/entities/custom-nodes/store.ts rename to apps/client/src/entities/custom-nodes/model/store.ts diff --git a/apps/client/src/entities/custom-nodes/types.ts b/apps/client/src/entities/custom-nodes/model/types.ts similarity index 100% rename from apps/client/src/entities/custom-nodes/types.ts rename to apps/client/src/entities/custom-nodes/model/types.ts diff --git a/apps/client/src/entities/device-token/api.ts b/apps/client/src/entities/device-token/api/index.ts similarity index 100% rename from apps/client/src/entities/device-token/api.ts rename to apps/client/src/entities/device-token/api/index.ts diff --git a/apps/client/src/entities/device-token/store.ts b/apps/client/src/entities/device-token/model/store.ts similarity index 100% rename from apps/client/src/entities/device-token/store.ts rename to apps/client/src/entities/device-token/model/store.ts diff --git a/apps/client/src/entities/device-token/types.ts b/apps/client/src/entities/device-token/model/types.ts similarity index 100% rename from apps/client/src/entities/device-token/types.ts rename to apps/client/src/entities/device-token/model/types.ts diff --git a/apps/client/src/entities/device/api.ts b/apps/client/src/entities/device/api/index.ts similarity index 100% rename from apps/client/src/entities/device/api.ts rename to apps/client/src/entities/device/api/index.ts diff --git a/apps/client/src/entities/device/store.ts b/apps/client/src/entities/device/model/store.ts similarity index 100% rename from apps/client/src/entities/device/store.ts rename to apps/client/src/entities/device/model/store.ts diff --git a/apps/client/src/entities/device/types.ts b/apps/client/src/entities/device/model/types.ts similarity index 100% rename from apps/client/src/entities/device/types.ts rename to apps/client/src/entities/device/model/types.ts diff --git a/apps/client/src/entities/dynamic-dashboard/api.ts b/apps/client/src/entities/dynamic-dashboard/api/index.ts similarity index 100% rename from apps/client/src/entities/dynamic-dashboard/api.ts rename to apps/client/src/entities/dynamic-dashboard/api/index.ts diff --git a/apps/client/src/entities/dynamic-dashboard/interaction.ts b/apps/client/src/entities/dynamic-dashboard/lib/interaction.ts similarity index 100% rename from apps/client/src/entities/dynamic-dashboard/interaction.ts rename to apps/client/src/entities/dynamic-dashboard/lib/interaction.ts diff --git a/apps/client/src/entities/dynamic-dashboard/layoutResolve.ts b/apps/client/src/entities/dynamic-dashboard/lib/layoutResolve.ts similarity index 100% rename from apps/client/src/entities/dynamic-dashboard/layoutResolve.ts rename to apps/client/src/entities/dynamic-dashboard/lib/layoutResolve.ts diff --git a/apps/client/src/entities/dynamic-dashboard/store.ts b/apps/client/src/entities/dynamic-dashboard/model/store.ts similarity index 100% rename from apps/client/src/entities/dynamic-dashboard/store.ts rename to apps/client/src/entities/dynamic-dashboard/model/store.ts diff --git a/apps/client/src/entities/entity/api.ts b/apps/client/src/entities/entity/api/index.ts similarity index 100% rename from apps/client/src/entities/entity/api.ts rename to apps/client/src/entities/entity/api/index.ts diff --git a/apps/client/src/entities/entity/store.ts b/apps/client/src/entities/entity/model/store.ts similarity index 100% rename from apps/client/src/entities/entity/store.ts rename to apps/client/src/entities/entity/model/store.ts diff --git a/apps/client/src/entities/entity/types.ts b/apps/client/src/entities/entity/model/types.ts similarity index 100% rename from apps/client/src/entities/entity/types.ts rename to apps/client/src/entities/entity/model/types.ts diff --git a/apps/client/src/entities/file/api.ts b/apps/client/src/entities/file/api/index.ts similarity index 100% rename from apps/client/src/entities/file/api.ts rename to apps/client/src/entities/file/api/index.ts diff --git a/apps/client/src/entities/file/store.ts b/apps/client/src/entities/file/model/store.ts similarity index 100% rename from apps/client/src/entities/file/store.ts rename to apps/client/src/entities/file/model/store.ts diff --git a/apps/client/src/entities/file/types.ts b/apps/client/src/entities/file/model/types.ts similarity index 100% rename from apps/client/src/entities/file/types.ts rename to apps/client/src/entities/file/model/types.ts diff --git a/apps/client/src/entities/flow/api.ts b/apps/client/src/entities/flow/api/index.ts similarity index 100% rename from apps/client/src/entities/flow/api.ts rename to apps/client/src/entities/flow/api/index.ts diff --git a/apps/client/src/entities/flow/store.ts b/apps/client/src/entities/flow/model/store.ts similarity index 100% rename from apps/client/src/entities/flow/store.ts rename to apps/client/src/entities/flow/model/store.ts diff --git a/apps/client/src/entities/flow/types.ts b/apps/client/src/entities/flow/model/types.ts similarity index 100% rename from apps/client/src/entities/flow/types.ts rename to apps/client/src/entities/flow/model/types.ts diff --git a/apps/client/src/entities/ha/api.ts b/apps/client/src/entities/ha/api/index.ts similarity index 100% rename from apps/client/src/entities/ha/api.ts rename to apps/client/src/entities/ha/api/index.ts diff --git a/apps/client/src/entities/ha/store.ts b/apps/client/src/entities/ha/model/store.ts similarity index 100% rename from apps/client/src/entities/ha/store.ts rename to apps/client/src/entities/ha/model/store.ts diff --git a/apps/client/src/entities/ha/types.ts b/apps/client/src/entities/ha/model/types.ts similarity index 100% rename from apps/client/src/entities/ha/types.ts rename to apps/client/src/entities/ha/model/types.ts diff --git a/apps/client/src/entities/integrations/api.ts b/apps/client/src/entities/integrations/api/index.ts similarity index 100% rename from apps/client/src/entities/integrations/api.ts rename to apps/client/src/entities/integrations/api/index.ts diff --git a/apps/client/src/entities/integrations/store.ts b/apps/client/src/entities/integrations/model/store.ts similarity index 100% rename from apps/client/src/entities/integrations/store.ts rename to apps/client/src/entities/integrations/model/store.ts diff --git a/apps/client/src/entities/integrations/types.ts b/apps/client/src/entities/integrations/model/types.ts similarity index 100% rename from apps/client/src/entities/integrations/types.ts rename to apps/client/src/entities/integrations/model/types.ts diff --git a/apps/client/src/entities/log/api.ts b/apps/client/src/entities/log/api/index.ts similarity index 100% rename from apps/client/src/entities/log/api.ts rename to apps/client/src/entities/log/api/index.ts diff --git a/apps/client/src/entities/log/types.ts b/apps/client/src/entities/log/model/types.ts similarity index 100% rename from apps/client/src/entities/log/types.ts rename to apps/client/src/entities/log/model/types.ts diff --git a/apps/client/src/entities/map/api.ts b/apps/client/src/entities/map/api/index.ts similarity index 100% rename from apps/client/src/entities/map/api.ts rename to apps/client/src/entities/map/api/index.ts diff --git a/apps/client/src/entities/map/store.ts b/apps/client/src/entities/map/model/store.ts similarity index 100% rename from apps/client/src/entities/map/store.ts rename to apps/client/src/entities/map/model/store.ts diff --git a/apps/client/src/entities/map/types.ts b/apps/client/src/entities/map/model/types.ts similarity index 100% rename from apps/client/src/entities/map/types.ts rename to apps/client/src/entities/map/model/types.ts diff --git a/apps/client/src/entities/permission/api.ts b/apps/client/src/entities/permission/api/index.ts similarity index 100% rename from apps/client/src/entities/permission/api.ts rename to apps/client/src/entities/permission/api/index.ts diff --git a/apps/client/src/entities/permission/store.ts b/apps/client/src/entities/permission/model/store.ts similarity index 100% rename from apps/client/src/entities/permission/store.ts rename to apps/client/src/entities/permission/model/store.ts diff --git a/apps/client/src/entities/permission/types.ts b/apps/client/src/entities/permission/model/types.ts similarity index 100% rename from apps/client/src/entities/permission/types.ts rename to apps/client/src/entities/permission/model/types.ts diff --git a/apps/client/src/entities/recording/api.ts b/apps/client/src/entities/recording/api/index.ts similarity index 100% rename from apps/client/src/entities/recording/api.ts rename to apps/client/src/entities/recording/api/index.ts diff --git a/apps/client/src/entities/recording/store.ts b/apps/client/src/entities/recording/model/store.ts similarity index 100% rename from apps/client/src/entities/recording/store.ts rename to apps/client/src/entities/recording/model/store.ts diff --git a/apps/client/src/entities/recording/types.ts b/apps/client/src/entities/recording/model/types.ts similarity index 100% rename from apps/client/src/entities/recording/types.ts rename to apps/client/src/entities/recording/model/types.ts diff --git a/apps/client/src/entities/role/api.ts b/apps/client/src/entities/role/api/index.ts similarity index 100% rename from apps/client/src/entities/role/api.ts rename to apps/client/src/entities/role/api/index.ts diff --git a/apps/client/src/entities/role/store.ts b/apps/client/src/entities/role/model/store.ts similarity index 100% rename from apps/client/src/entities/role/store.ts rename to apps/client/src/entities/role/model/store.ts diff --git a/apps/client/src/entities/role/types.ts b/apps/client/src/entities/role/model/types.ts similarity index 100% rename from apps/client/src/entities/role/types.ts rename to apps/client/src/entities/role/model/types.ts diff --git a/apps/client/src/entities/stat/api.ts b/apps/client/src/entities/stat/api/index.ts similarity index 100% rename from apps/client/src/entities/stat/api.ts rename to apps/client/src/entities/stat/api/index.ts diff --git a/apps/client/src/entities/stat/store.ts b/apps/client/src/entities/stat/model/store.ts similarity index 100% rename from apps/client/src/entities/stat/store.ts rename to apps/client/src/entities/stat/model/store.ts diff --git a/apps/client/src/entities/stat/types.ts b/apps/client/src/entities/stat/model/types.ts similarity index 100% rename from apps/client/src/entities/stat/types.ts rename to apps/client/src/entities/stat/model/types.ts diff --git a/apps/client/src/entities/tunnel/api.ts b/apps/client/src/entities/tunnel/api/index.ts similarity index 100% rename from apps/client/src/entities/tunnel/api.ts rename to apps/client/src/entities/tunnel/api/index.ts diff --git a/apps/client/src/entities/tunnel/store.ts b/apps/client/src/entities/tunnel/model/store.ts similarity index 100% rename from apps/client/src/entities/tunnel/store.ts rename to apps/client/src/entities/tunnel/model/store.ts diff --git a/apps/client/src/entities/tunnel/types.ts b/apps/client/src/entities/tunnel/model/types.ts similarity index 100% rename from apps/client/src/entities/tunnel/types.ts rename to apps/client/src/entities/tunnel/model/types.ts diff --git a/apps/client/src/entities/user/api.ts b/apps/client/src/entities/user/api/index.ts similarity index 100% rename from apps/client/src/entities/user/api.ts rename to apps/client/src/entities/user/api/index.ts diff --git a/apps/client/src/entities/user/store.ts b/apps/client/src/entities/user/model/store.ts similarity index 100% rename from apps/client/src/entities/user/store.ts rename to apps/client/src/entities/user/model/store.ts diff --git a/apps/client/src/entities/user/types.ts b/apps/client/src/entities/user/model/types.ts similarity index 100% rename from apps/client/src/entities/user/types.ts rename to apps/client/src/entities/user/model/types.ts diff --git a/apps/client/src/features/account-switcher/index.tsx b/apps/client/src/features/account-switcher/ui/AccountSwitcher.tsx similarity index 100% rename from apps/client/src/features/account-switcher/index.tsx rename to apps/client/src/features/account-switcher/ui/AccountSwitcher.tsx diff --git a/apps/client/src/features/auth/api.ts b/apps/client/src/features/auth/api/index.ts similarity index 100% rename from apps/client/src/features/auth/api.ts rename to apps/client/src/features/auth/api/index.ts diff --git a/apps/client/src/features/auth/hook.ts b/apps/client/src/features/auth/model/hook.ts similarity index 100% rename from apps/client/src/features/auth/hook.ts rename to apps/client/src/features/auth/model/hook.ts diff --git a/apps/client/src/features/auth/AuthInterceptor.tsx b/apps/client/src/features/auth/ui/AuthInterceptor.tsx similarity index 100% rename from apps/client/src/features/auth/AuthInterceptor.tsx rename to apps/client/src/features/auth/ui/AuthInterceptor.tsx diff --git a/apps/client/src/features/auth/DefaultAdminPasswordDialog.tsx b/apps/client/src/features/auth/ui/DefaultAdminPasswordDialog.tsx similarity index 100% rename from apps/client/src/features/auth/DefaultAdminPasswordDialog.tsx rename to apps/client/src/features/auth/ui/DefaultAdminPasswordDialog.tsx diff --git a/apps/client/src/features/auth/index.tsx b/apps/client/src/features/auth/ui/LoginForm.tsx similarity index 100% rename from apps/client/src/features/auth/index.tsx rename to apps/client/src/features/auth/ui/LoginForm.tsx diff --git a/apps/client/src/features/code/CreateItemDialog.tsx b/apps/client/src/features/code/ui/CreateItemDialog.tsx similarity index 100% rename from apps/client/src/features/code/CreateItemDialog.tsx rename to apps/client/src/features/code/ui/CreateItemDialog.tsx diff --git a/apps/client/src/features/code/FileEditor.tsx b/apps/client/src/features/code/ui/FileEditor.tsx similarity index 100% rename from apps/client/src/features/code/FileEditor.tsx rename to apps/client/src/features/code/ui/FileEditor.tsx diff --git a/apps/client/src/features/code/FileTree.tsx b/apps/client/src/features/code/ui/FileTree.tsx similarity index 100% rename from apps/client/src/features/code/FileTree.tsx rename to apps/client/src/features/code/ui/FileTree.tsx diff --git a/apps/client/src/features/configurations/ConfigurationActionButton.tsx b/apps/client/src/features/configurations/ui/ConfigurationActionButton.tsx similarity index 100% rename from apps/client/src/features/configurations/ConfigurationActionButton.tsx rename to apps/client/src/features/configurations/ui/ConfigurationActionButton.tsx diff --git a/apps/client/src/features/configurations/ConfigurationCreate.tsx b/apps/client/src/features/configurations/ui/ConfigurationCreate.tsx similarity index 100% rename from apps/client/src/features/configurations/ConfigurationCreate.tsx rename to apps/client/src/features/configurations/ui/ConfigurationCreate.tsx diff --git a/apps/client/src/features/configurations/ConfigurationCreateButton.tsx b/apps/client/src/features/configurations/ui/ConfigurationCreateButton.tsx similarity index 100% rename from apps/client/src/features/configurations/ConfigurationCreateButton.tsx rename to apps/client/src/features/configurations/ui/ConfigurationCreateButton.tsx diff --git a/apps/client/src/features/darkmode/mode-toggle.tsx b/apps/client/src/features/darkmode/ui/ModeToggle.tsx similarity index 100% rename from apps/client/src/features/darkmode/mode-toggle.tsx rename to apps/client/src/features/darkmode/ui/ModeToggle.tsx diff --git a/apps/client/src/features/dashboard-swipe/DashboardSwipeHeader.tsx b/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeHeader.tsx similarity index 100% rename from apps/client/src/features/dashboard-swipe/DashboardSwipeHeader.tsx rename to apps/client/src/features/dashboard-swipe/ui/DashboardSwipeHeader.tsx diff --git a/apps/client/src/features/dashboard-swipe/DashboardSwipeLayout.tsx b/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeLayout.tsx similarity index 100% rename from apps/client/src/features/dashboard-swipe/DashboardSwipeLayout.tsx rename to apps/client/src/features/dashboard-swipe/ui/DashboardSwipeLayout.tsx diff --git a/apps/client/src/features/device-token/DeviceTokenManager.tsx b/apps/client/src/features/device-token/ui/DeviceTokenManager.tsx similarity index 100% rename from apps/client/src/features/device-token/DeviceTokenManager.tsx rename to apps/client/src/features/device-token/ui/DeviceTokenManager.tsx diff --git a/apps/client/src/features/device/DeviceCreateButton.tsx b/apps/client/src/features/device/ui/DeviceCreateButton.tsx similarity index 100% rename from apps/client/src/features/device/DeviceCreateButton.tsx rename to apps/client/src/features/device/ui/DeviceCreateButton.tsx diff --git a/apps/client/src/features/device/DeviceDeleteButton.tsx b/apps/client/src/features/device/ui/DeviceDeleteButton.tsx similarity index 100% rename from apps/client/src/features/device/DeviceDeleteButton.tsx rename to apps/client/src/features/device/ui/DeviceDeleteButton.tsx diff --git a/apps/client/src/features/device/DeviceKeyButton.tsx b/apps/client/src/features/device/ui/DeviceKeyButton.tsx similarity index 100% rename from apps/client/src/features/device/DeviceKeyButton.tsx rename to apps/client/src/features/device/ui/DeviceKeyButton.tsx diff --git a/apps/client/src/features/device/DeviceUpdateButton.tsx b/apps/client/src/features/device/ui/DeviceUpdateButton.tsx similarity index 100% rename from apps/client/src/features/device/DeviceUpdateButton.tsx rename to apps/client/src/features/device/ui/DeviceUpdateButton.tsx diff --git a/apps/client/src/features/dynamic-dashboard/events/dispatcher.test.ts b/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.test.ts similarity index 100% rename from apps/client/src/features/dynamic-dashboard/events/dispatcher.test.ts rename to apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.test.ts diff --git a/apps/client/src/features/dynamic-dashboard/events/dispatcher.ts b/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.ts similarity index 100% rename from apps/client/src/features/dynamic-dashboard/events/dispatcher.ts rename to apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.ts diff --git a/apps/client/src/features/dynamic-dashboard/GroupCanvas.tsx b/apps/client/src/features/dynamic-dashboard/ui/GroupCanvas.tsx similarity index 100% rename from apps/client/src/features/dynamic-dashboard/GroupCanvas.tsx rename to apps/client/src/features/dynamic-dashboard/ui/GroupCanvas.tsx diff --git a/apps/client/src/features/dynamic-dashboard/panels/ButtonPanel.tsx b/apps/client/src/features/dynamic-dashboard/ui/panels/ButtonPanel.tsx similarity index 100% rename from apps/client/src/features/dynamic-dashboard/panels/ButtonPanel.tsx rename to apps/client/src/features/dynamic-dashboard/ui/panels/ButtonPanel.tsx diff --git a/apps/client/src/features/dynamic-dashboard/panels/FlowPanel.tsx b/apps/client/src/features/dynamic-dashboard/ui/panels/FlowPanel.tsx similarity index 100% rename from apps/client/src/features/dynamic-dashboard/panels/FlowPanel.tsx rename to apps/client/src/features/dynamic-dashboard/ui/panels/FlowPanel.tsx diff --git a/apps/client/src/features/dynamic-dashboard/panels/MapPanel.tsx b/apps/client/src/features/dynamic-dashboard/ui/panels/MapPanel.tsx similarity index 100% rename from apps/client/src/features/dynamic-dashboard/panels/MapPanel.tsx rename to apps/client/src/features/dynamic-dashboard/ui/panels/MapPanel.tsx diff --git a/apps/client/src/features/entity/useEntitiesData.ts b/apps/client/src/features/entity/model/useEntitiesData.ts similarity index 100% rename from apps/client/src/features/entity/useEntitiesData.ts rename to apps/client/src/features/entity/model/useEntitiesData.ts diff --git a/apps/client/src/features/entity/AllEntities.tsx b/apps/client/src/features/entity/ui/AllEntities.tsx similarity index 100% rename from apps/client/src/features/entity/AllEntities.tsx rename to apps/client/src/features/entity/ui/AllEntities.tsx diff --git a/apps/client/src/features/entity/AnalyzeMenuItem.tsx b/apps/client/src/features/entity/ui/AnalyzeMenuItem.tsx similarity index 100% rename from apps/client/src/features/entity/AnalyzeMenuItem.tsx rename to apps/client/src/features/entity/ui/AnalyzeMenuItem.tsx diff --git a/apps/client/src/features/entity/Card.tsx b/apps/client/src/features/entity/ui/Card.tsx similarity index 100% rename from apps/client/src/features/entity/Card.tsx rename to apps/client/src/features/entity/ui/Card.tsx diff --git a/apps/client/src/features/entity/EntityCreateButton.tsx b/apps/client/src/features/entity/ui/EntityCreateButton.tsx similarity index 100% rename from apps/client/src/features/entity/EntityCreateButton.tsx rename to apps/client/src/features/entity/ui/EntityCreateButton.tsx diff --git a/apps/client/src/features/entity/EntityDeleteButton.tsx b/apps/client/src/features/entity/ui/EntityDeleteButton.tsx similarity index 100% rename from apps/client/src/features/entity/EntityDeleteButton.tsx rename to apps/client/src/features/entity/ui/EntityDeleteButton.tsx diff --git a/apps/client/src/features/entity/EntityUpdateButton.tsx b/apps/client/src/features/entity/ui/EntityUpdateButton.tsx similarity index 100% rename from apps/client/src/features/entity/EntityUpdateButton.tsx rename to apps/client/src/features/entity/ui/EntityUpdateButton.tsx diff --git a/apps/client/src/features/entity/SelectPlatforms.tsx b/apps/client/src/features/entity/ui/SelectPlatforms.tsx similarity index 100% rename from apps/client/src/features/entity/SelectPlatforms.tsx rename to apps/client/src/features/entity/ui/SelectPlatforms.tsx diff --git a/apps/client/src/features/entity/SelectTypes.tsx b/apps/client/src/features/entity/ui/SelectTypes.tsx similarity index 100% rename from apps/client/src/features/entity/SelectTypes.tsx rename to apps/client/src/features/entity/ui/SelectTypes.tsx diff --git a/apps/client/src/features/entity/StateHistorySheet.tsx b/apps/client/src/features/entity/ui/StateHistorySheet.tsx similarity index 100% rename from apps/client/src/features/entity/StateHistorySheet.tsx rename to apps/client/src/features/entity/ui/StateHistorySheet.tsx diff --git a/apps/client/src/features/error/index.tsx b/apps/client/src/features/error/ui/ErrorRender.tsx similarity index 100% rename from apps/client/src/features/error/index.tsx rename to apps/client/src/features/error/ui/ErrorRender.tsx diff --git a/apps/client/src/features/flow-log/FlowLog.tsx b/apps/client/src/features/flow-log/ui/FlowLog.tsx similarity index 100% rename from apps/client/src/features/flow-log/FlowLog.tsx rename to apps/client/src/features/flow-log/ui/FlowLog.tsx diff --git a/apps/client/src/features/flow/flow-chat/buildSystemPrompt.ts b/apps/client/src/features/flow/lib/flow-chat/buildSystemPrompt.ts similarity index 100% rename from apps/client/src/features/flow/flow-chat/buildSystemPrompt.ts rename to apps/client/src/features/flow/lib/flow-chat/buildSystemPrompt.ts diff --git a/apps/client/src/features/flow/flow-chat/executeToolCalls.ts b/apps/client/src/features/flow/lib/flow-chat/executeToolCalls.ts similarity index 100% rename from apps/client/src/features/flow/flow-chat/executeToolCalls.ts rename to apps/client/src/features/flow/lib/flow-chat/executeToolCalls.ts diff --git a/apps/client/src/features/flow/flow-chat/flowTools.ts b/apps/client/src/features/flow/lib/flow-chat/flowTools.ts similarity index 100% rename from apps/client/src/features/flow/flow-chat/flowTools.ts rename to apps/client/src/features/flow/lib/flow-chat/flowTools.ts diff --git a/apps/client/src/features/flow/flow-chat/index.ts b/apps/client/src/features/flow/lib/flow-chat/index.ts similarity index 100% rename from apps/client/src/features/flow/flow-chat/index.ts rename to apps/client/src/features/flow/lib/flow-chat/index.ts diff --git a/apps/client/src/features/flow/flowNode.ts b/apps/client/src/features/flow/lib/flowNode.ts similarity index 100% rename from apps/client/src/features/flow/flowNode.ts rename to apps/client/src/features/flow/lib/flowNode.ts diff --git a/apps/client/src/features/flow/flowUtils.ts b/apps/client/src/features/flow/lib/flowUtils.ts similarity index 100% rename from apps/client/src/features/flow/flowUtils.ts rename to apps/client/src/features/flow/lib/flowUtils.ts diff --git a/apps/client/src/features/flow/flowTypes.ts b/apps/client/src/features/flow/model/types.ts similarity index 100% rename from apps/client/src/features/flow/flowTypes.ts rename to apps/client/src/features/flow/model/types.ts diff --git a/apps/client/src/features/flow/AddCustomNode.tsx b/apps/client/src/features/flow/ui/AddCustomNode.tsx similarity index 100% rename from apps/client/src/features/flow/AddCustomNode.tsx rename to apps/client/src/features/flow/ui/AddCustomNode.tsx diff --git a/apps/client/src/features/flow/Flow.tsx b/apps/client/src/features/flow/ui/Flow.tsx similarity index 100% rename from apps/client/src/features/flow/Flow.tsx rename to apps/client/src/features/flow/ui/Flow.tsx diff --git a/apps/client/src/features/flow/Graph.tsx b/apps/client/src/features/flow/ui/Graph.tsx similarity index 100% rename from apps/client/src/features/flow/Graph.tsx rename to apps/client/src/features/flow/ui/Graph.tsx diff --git a/apps/client/src/features/flow/Options.tsx b/apps/client/src/features/flow/ui/Options.tsx similarity index 100% rename from apps/client/src/features/flow/Options.tsx rename to apps/client/src/features/flow/ui/Options.tsx diff --git a/apps/client/src/features/flow/OptionsVariation.tsx b/apps/client/src/features/flow/ui/OptionsVariation.tsx similarity index 100% rename from apps/client/src/features/flow/OptionsVariation.tsx rename to apps/client/src/features/flow/ui/OptionsVariation.tsx diff --git a/apps/client/src/features/flow/RunFlow.tsx b/apps/client/src/features/flow/ui/RunFlow.tsx similarity index 100% rename from apps/client/src/features/flow/RunFlow.tsx rename to apps/client/src/features/flow/ui/RunFlow.tsx diff --git a/apps/client/src/features/flow/SelectedItemActions.tsx b/apps/client/src/features/flow/ui/SelectedItemActions.tsx similarity index 100% rename from apps/client/src/features/flow/SelectedItemActions.tsx rename to apps/client/src/features/flow/ui/SelectedItemActions.tsx diff --git a/apps/client/src/features/flow/nodes/ButtonNode.tsx b/apps/client/src/features/flow/ui/nodes/ButtonNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/ButtonNode.tsx rename to apps/client/src/features/flow/ui/nodes/ButtonNode.tsx diff --git a/apps/client/src/features/flow/nodes/CalcNode.tsx b/apps/client/src/features/flow/ui/nodes/CalcNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/CalcNode.tsx rename to apps/client/src/features/flow/ui/nodes/CalcNode.tsx diff --git a/apps/client/src/features/flow/nodes/HttpNode.tsx b/apps/client/src/features/flow/ui/nodes/HttpNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/HttpNode.tsx rename to apps/client/src/features/flow/ui/nodes/HttpNode.tsx diff --git a/apps/client/src/features/flow/nodes/IntervalNode.tsx b/apps/client/src/features/flow/ui/nodes/IntervalNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/IntervalNode.tsx rename to apps/client/src/features/flow/ui/nodes/IntervalNode.tsx diff --git a/apps/client/src/features/flow/nodes/LogicNode.tsx b/apps/client/src/features/flow/ui/nodes/LogicNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/LogicNode.tsx rename to apps/client/src/features/flow/ui/nodes/LogicNode.tsx diff --git a/apps/client/src/features/flow/nodes/LoopNode.tsx b/apps/client/src/features/flow/ui/nodes/LoopNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/LoopNode.tsx rename to apps/client/src/features/flow/ui/nodes/LoopNode.tsx diff --git a/apps/client/src/features/flow/nodes/MQTTNode.tsx b/apps/client/src/features/flow/ui/nodes/MQTTNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/MQTTNode.tsx rename to apps/client/src/features/flow/ui/nodes/MQTTNode.tsx diff --git a/apps/client/src/features/flow/nodes/NumberNode.tsx b/apps/client/src/features/flow/ui/nodes/NumberNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/NumberNode.tsx rename to apps/client/src/features/flow/ui/nodes/NumberNode.tsx diff --git a/apps/client/src/features/flow/nodes/ProcessingNode.tsx b/apps/client/src/features/flow/ui/nodes/ProcessingNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/ProcessingNode.tsx rename to apps/client/src/features/flow/ui/nodes/ProcessingNode.tsx diff --git a/apps/client/src/features/flow/nodes/TitleNode.tsx b/apps/client/src/features/flow/ui/nodes/TitleNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/TitleNode.tsx rename to apps/client/src/features/flow/ui/nodes/TitleNode.tsx diff --git a/apps/client/src/features/flow/nodes/VarNode.tsx b/apps/client/src/features/flow/ui/nodes/VarNode.tsx similarity index 100% rename from apps/client/src/features/flow/nodes/VarNode.tsx rename to apps/client/src/features/flow/ui/nodes/VarNode.tsx diff --git a/apps/client/src/features/footer/index.tsx b/apps/client/src/features/footer/ui/Footer.tsx similarity index 100% rename from apps/client/src/features/footer/index.tsx rename to apps/client/src/features/footer/ui/Footer.tsx diff --git a/apps/client/src/features/gps/parseGps.ts b/apps/client/src/features/gps/lib/parseGps.ts similarity index 100% rename from apps/client/src/features/gps/parseGps.ts rename to apps/client/src/features/gps/lib/parseGps.ts diff --git a/apps/client/src/features/ha/index.tsx b/apps/client/src/features/ha/ui/HaDashboard.tsx similarity index 100% rename from apps/client/src/features/ha/index.tsx rename to apps/client/src/features/ha/ui/HaDashboard.tsx diff --git a/apps/client/src/features/ha/HaEntitiesTable.tsx b/apps/client/src/features/ha/ui/HaEntitiesTable.tsx similarity index 100% rename from apps/client/src/features/ha/HaEntitiesTable.tsx rename to apps/client/src/features/ha/ui/HaEntitiesTable.tsx diff --git a/apps/client/src/features/ha/HaStatBlock.tsx b/apps/client/src/features/ha/ui/HaStatBlock.tsx similarity index 100% rename from apps/client/src/features/ha/HaStatBlock.tsx rename to apps/client/src/features/ha/ui/HaStatBlock.tsx diff --git a/apps/client/src/features/integration/constants.ts b/apps/client/src/features/integration/config/constants.ts similarity index 100% rename from apps/client/src/features/integration/constants.ts rename to apps/client/src/features/integration/config/constants.ts diff --git a/apps/client/src/features/integration/types.ts b/apps/client/src/features/integration/model/types.ts similarity index 100% rename from apps/client/src/features/integration/types.ts rename to apps/client/src/features/integration/model/types.ts diff --git a/apps/client/src/features/integration/HA.tsx b/apps/client/src/features/integration/ui/HA.tsx similarity index 100% rename from apps/client/src/features/integration/HA.tsx rename to apps/client/src/features/integration/ui/HA.tsx diff --git a/apps/client/src/features/integration/Integration.tsx b/apps/client/src/features/integration/ui/Integration.tsx similarity index 100% rename from apps/client/src/features/integration/Integration.tsx rename to apps/client/src/features/integration/ui/Integration.tsx diff --git a/apps/client/src/features/integration/ROS.tsx b/apps/client/src/features/integration/ui/ROS.tsx similarity index 100% rename from apps/client/src/features/integration/ROS.tsx rename to apps/client/src/features/integration/ui/ROS.tsx diff --git a/apps/client/src/features/integration/SDR.tsx b/apps/client/src/features/integration/ui/SDR.tsx similarity index 100% rename from apps/client/src/features/integration/SDR.tsx rename to apps/client/src/features/integration/ui/SDR.tsx diff --git a/apps/client/src/features/json/JsonEditor.tsx b/apps/client/src/features/json/ui/JsonEditor.tsx similarity index 100% rename from apps/client/src/features/json/JsonEditor.tsx rename to apps/client/src/features/json/ui/JsonEditor.tsx diff --git a/apps/client/src/features/llm-chat/index.tsx b/apps/client/src/features/llm-chat/index.tsx deleted file mode 100644 index 85e73f3..0000000 --- a/apps/client/src/features/llm-chat/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export { ChatPanelContainer } from "./ChatPanelContainer"; -export { useChatStore } from "./store"; -export { PANEL_WIDTH } from "./ChatPanel"; -export type { ChatMessage, ChatPanelState, FlowChatContext } from "./types"; diff --git a/apps/client/src/features/llm-chat/store.ts b/apps/client/src/features/llm-chat/model/store.ts similarity index 100% rename from apps/client/src/features/llm-chat/store.ts rename to apps/client/src/features/llm-chat/model/store.ts diff --git a/apps/client/src/features/llm-chat/types.ts b/apps/client/src/features/llm-chat/model/types.ts similarity index 100% rename from apps/client/src/features/llm-chat/types.ts rename to apps/client/src/features/llm-chat/model/types.ts diff --git a/apps/client/src/features/llm-chat/useChatKeyboard.ts b/apps/client/src/features/llm-chat/model/useChatKeyboard.ts similarity index 100% rename from apps/client/src/features/llm-chat/useChatKeyboard.ts rename to apps/client/src/features/llm-chat/model/useChatKeyboard.ts diff --git a/apps/client/src/features/llm-chat/ChatInput.tsx b/apps/client/src/features/llm-chat/ui/ChatInput.tsx similarity index 100% rename from apps/client/src/features/llm-chat/ChatInput.tsx rename to apps/client/src/features/llm-chat/ui/ChatInput.tsx diff --git a/apps/client/src/features/llm-chat/ChatMessage.tsx b/apps/client/src/features/llm-chat/ui/ChatMessage.tsx similarity index 100% rename from apps/client/src/features/llm-chat/ChatMessage.tsx rename to apps/client/src/features/llm-chat/ui/ChatMessage.tsx diff --git a/apps/client/src/features/llm-chat/ChatMessages.tsx b/apps/client/src/features/llm-chat/ui/ChatMessages.tsx similarity index 100% rename from apps/client/src/features/llm-chat/ChatMessages.tsx rename to apps/client/src/features/llm-chat/ui/ChatMessages.tsx diff --git a/apps/client/src/features/llm-chat/ChatPanel.tsx b/apps/client/src/features/llm-chat/ui/ChatPanel.tsx similarity index 100% rename from apps/client/src/features/llm-chat/ChatPanel.tsx rename to apps/client/src/features/llm-chat/ui/ChatPanel.tsx diff --git a/apps/client/src/features/llm-chat/ChatPanelContainer.tsx b/apps/client/src/features/llm-chat/ui/ChatPanelContainer.tsx similarity index 100% rename from apps/client/src/features/llm-chat/ChatPanelContainer.tsx rename to apps/client/src/features/llm-chat/ui/ChatPanelContainer.tsx diff --git a/apps/client/src/features/llm-chat/ChatPanelMobile.tsx b/apps/client/src/features/llm-chat/ui/ChatPanelMobile.tsx similarity index 100% rename from apps/client/src/features/llm-chat/ChatPanelMobile.tsx rename to apps/client/src/features/llm-chat/ui/ChatPanelMobile.tsx diff --git a/apps/client/src/features/log/index.tsx b/apps/client/src/features/log/ui/Logs.tsx similarity index 100% rename from apps/client/src/features/log/index.tsx rename to apps/client/src/features/log/ui/Logs.tsx diff --git a/apps/client/src/features/map-draw/FeatureDetailsPanel.tsx b/apps/client/src/features/map-draw/ui/FeatureDetailsPanel.tsx similarity index 100% rename from apps/client/src/features/map-draw/FeatureDetailsPanel.tsx rename to apps/client/src/features/map-draw/ui/FeatureDetailsPanel.tsx diff --git a/apps/client/src/features/map-draw/FeatureDrawingPreview.tsx b/apps/client/src/features/map-draw/ui/FeatureDrawingPreview.tsx similarity index 100% rename from apps/client/src/features/map-draw/FeatureDrawingPreview.tsx rename to apps/client/src/features/map-draw/ui/FeatureDrawingPreview.tsx diff --git a/apps/client/src/features/map-draw/FeatureEditor.tsx b/apps/client/src/features/map-draw/ui/FeatureEditor.tsx similarity index 100% rename from apps/client/src/features/map-draw/FeatureEditor.tsx rename to apps/client/src/features/map-draw/ui/FeatureEditor.tsx diff --git a/apps/client/src/features/map-draw/FeatureRenderer.tsx b/apps/client/src/features/map-draw/ui/FeatureRenderer.tsx similarity index 100% rename from apps/client/src/features/map-draw/FeatureRenderer.tsx rename to apps/client/src/features/map-draw/ui/FeatureRenderer.tsx diff --git a/apps/client/src/features/map-draw/LayerDialog.tsx b/apps/client/src/features/map-draw/ui/LayerDialog.tsx similarity index 100% rename from apps/client/src/features/map-draw/LayerDialog.tsx rename to apps/client/src/features/map-draw/ui/LayerDialog.tsx diff --git a/apps/client/src/features/map-draw/LayerSidebar.tsx b/apps/client/src/features/map-draw/ui/LayerSidebar.tsx similarity index 100% rename from apps/client/src/features/map-draw/LayerSidebar.tsx rename to apps/client/src/features/map-draw/ui/LayerSidebar.tsx diff --git a/apps/client/src/features/map-draw/MapEvents.tsx b/apps/client/src/features/map-draw/ui/MapEvents.tsx similarity index 100% rename from apps/client/src/features/map-draw/MapEvents.tsx rename to apps/client/src/features/map-draw/ui/MapEvents.tsx diff --git a/apps/client/src/features/map-draw/MapToolbar.tsx b/apps/client/src/features/map-draw/ui/MapToolbar.tsx similarity index 100% rename from apps/client/src/features/map-draw/MapToolbar.tsx rename to apps/client/src/features/map-draw/ui/MapToolbar.tsx diff --git a/apps/client/src/features/map-entity/store.ts b/apps/client/src/features/map-entity/model/store.ts similarity index 100% rename from apps/client/src/features/map-entity/store.ts rename to apps/client/src/features/map-entity/model/store.ts diff --git a/apps/client/src/features/map-entity/EntityDetailsPanel.tsx b/apps/client/src/features/map-entity/ui/EntityDetailsPanel.tsx similarity index 100% rename from apps/client/src/features/map-entity/EntityDetailsPanel.tsx rename to apps/client/src/features/map-entity/ui/EntityDetailsPanel.tsx diff --git a/apps/client/src/features/map-entity/render.tsx b/apps/client/src/features/map-entity/ui/render.tsx similarity index 100% rename from apps/client/src/features/map-entity/render.tsx rename to apps/client/src/features/map-entity/ui/render.tsx diff --git a/apps/client/src/features/map/CurrentLocationMarker.tsx b/apps/client/src/features/map/ui/CurrentLocationMarker.tsx similarity index 100% rename from apps/client/src/features/map/CurrentLocationMarker.tsx rename to apps/client/src/features/map/ui/CurrentLocationMarker.tsx diff --git a/apps/client/src/features/map/index.tsx b/apps/client/src/features/map/ui/MapView.tsx similarity index 100% rename from apps/client/src/features/map/index.tsx rename to apps/client/src/features/map/ui/MapView.tsx diff --git a/apps/client/src/features/map/MapViewPersistence.tsx b/apps/client/src/features/map/ui/MapViewPersistence.tsx similarity index 100% rename from apps/client/src/features/map/MapViewPersistence.tsx rename to apps/client/src/features/map/ui/MapViewPersistence.tsx diff --git a/apps/client/src/features/map/style.css b/apps/client/src/features/map/ui/style.css similarity index 100% rename from apps/client/src/features/map/style.css rename to apps/client/src/features/map/ui/style.css diff --git a/apps/client/src/features/recording/hooks/useAudioWaveform.ts b/apps/client/src/features/recording/model/useAudioWaveform.ts similarity index 100% rename from apps/client/src/features/recording/hooks/useAudioWaveform.ts rename to apps/client/src/features/recording/model/useAudioWaveform.ts diff --git a/apps/client/src/features/recording/hooks/useMediaPlayback.ts b/apps/client/src/features/recording/model/useMediaPlayback.ts similarity index 100% rename from apps/client/src/features/recording/hooks/useMediaPlayback.ts rename to apps/client/src/features/recording/model/useMediaPlayback.ts diff --git a/apps/client/src/features/recording/hooks/useVideoFrames.ts b/apps/client/src/features/recording/model/useVideoFrames.ts similarity index 100% rename from apps/client/src/features/recording/hooks/useVideoFrames.ts rename to apps/client/src/features/recording/model/useVideoFrames.ts diff --git a/apps/client/src/features/recording/components/AudioWaveformPlayer.tsx b/apps/client/src/features/recording/ui/AudioWaveformPlayer.tsx similarity index 100% rename from apps/client/src/features/recording/components/AudioWaveformPlayer.tsx rename to apps/client/src/features/recording/ui/AudioWaveformPlayer.tsx diff --git a/apps/client/src/features/recording/components/FrameTimeline.tsx b/apps/client/src/features/recording/ui/FrameTimeline.tsx similarity index 100% rename from apps/client/src/features/recording/components/FrameTimeline.tsx rename to apps/client/src/features/recording/ui/FrameTimeline.tsx diff --git a/apps/client/src/features/recording/components/PlaybackControls.tsx b/apps/client/src/features/recording/ui/PlaybackControls.tsx similarity index 100% rename from apps/client/src/features/recording/components/PlaybackControls.tsx rename to apps/client/src/features/recording/ui/PlaybackControls.tsx diff --git a/apps/client/src/features/recording/RecordingButton.tsx b/apps/client/src/features/recording/ui/RecordingButton.tsx similarity index 100% rename from apps/client/src/features/recording/RecordingButton.tsx rename to apps/client/src/features/recording/ui/RecordingButton.tsx diff --git a/apps/client/src/features/recording/RecordingsList.tsx b/apps/client/src/features/recording/ui/RecordingsList.tsx similarity index 100% rename from apps/client/src/features/recording/RecordingsList.tsx rename to apps/client/src/features/recording/ui/RecordingsList.tsx diff --git a/apps/client/src/features/recording/components/TimeRuler.tsx b/apps/client/src/features/recording/ui/TimeRuler.tsx similarity index 100% rename from apps/client/src/features/recording/components/TimeRuler.tsx rename to apps/client/src/features/recording/ui/TimeRuler.tsx diff --git a/apps/client/src/features/recording/components/VideoControlBar.tsx b/apps/client/src/features/recording/ui/VideoControlBar.tsx similarity index 100% rename from apps/client/src/features/recording/components/VideoControlBar.tsx rename to apps/client/src/features/recording/ui/VideoControlBar.tsx diff --git a/apps/client/src/features/recording/VideoPlaybackDialog.tsx b/apps/client/src/features/recording/ui/VideoPlaybackDialog.tsx similarity index 100% rename from apps/client/src/features/recording/VideoPlaybackDialog.tsx rename to apps/client/src/features/recording/ui/VideoPlaybackDialog.tsx diff --git a/apps/client/src/features/recording/components/WaveformCanvas.tsx b/apps/client/src/features/recording/ui/WaveformCanvas.tsx similarity index 100% rename from apps/client/src/features/recording/components/WaveformCanvas.tsx rename to apps/client/src/features/recording/ui/WaveformCanvas.tsx diff --git a/apps/client/src/features/role/RoleDialogs.tsx b/apps/client/src/features/role/ui/RoleDialogs.tsx similarity index 100% rename from apps/client/src/features/role/RoleDialogs.tsx rename to apps/client/src/features/role/ui/RoleDialogs.tsx diff --git a/apps/client/src/features/role/RoleForm.tsx b/apps/client/src/features/role/ui/RoleForm.tsx similarity index 100% rename from apps/client/src/features/role/RoleForm.tsx rename to apps/client/src/features/role/ui/RoleForm.tsx diff --git a/apps/client/src/features/ros2/Ros2Dashboard.tsx b/apps/client/src/features/ros2/ui/Ros2Dashboard.tsx similarity index 100% rename from apps/client/src/features/ros2/Ros2Dashboard.tsx rename to apps/client/src/features/ros2/ui/Ros2Dashboard.tsx diff --git a/apps/client/src/features/rtc/captureFrame.ts b/apps/client/src/features/rtc/lib/captureFrame.ts similarity index 100% rename from apps/client/src/features/rtc/captureFrame.ts rename to apps/client/src/features/rtc/lib/captureFrame.ts diff --git a/apps/client/src/features/rtc/rtc.ts b/apps/client/src/features/rtc/lib/rtc.ts similarity index 100% rename from apps/client/src/features/rtc/rtc.ts rename to apps/client/src/features/rtc/lib/rtc.ts diff --git a/apps/client/src/features/rtc/turnService.ts b/apps/client/src/features/rtc/lib/turnService.ts similarity index 100% rename from apps/client/src/features/rtc/turnService.ts rename to apps/client/src/features/rtc/lib/turnService.ts diff --git a/apps/client/src/features/rtc/AudioLevelBar.tsx b/apps/client/src/features/rtc/ui/AudioLevelBar.tsx similarity index 100% rename from apps/client/src/features/rtc/AudioLevelBar.tsx rename to apps/client/src/features/rtc/ui/AudioLevelBar.tsx diff --git a/apps/client/src/features/rtc/StreamReceiver.tsx b/apps/client/src/features/rtc/ui/StreamReceiver.tsx similarity index 100% rename from apps/client/src/features/rtc/StreamReceiver.tsx rename to apps/client/src/features/rtc/ui/StreamReceiver.tsx diff --git a/apps/client/src/features/rtc/WebRTCProvider.tsx b/apps/client/src/features/rtc/ui/WebRTCProvider.tsx similarity index 100% rename from apps/client/src/features/rtc/WebRTCProvider.tsx rename to apps/client/src/features/rtc/ui/WebRTCProvider.tsx diff --git a/apps/client/src/features/sdr/api.ts b/apps/client/src/features/sdr/api/index.ts similarity index 100% rename from apps/client/src/features/sdr/api.ts rename to apps/client/src/features/sdr/api/index.ts diff --git a/apps/client/src/features/sdr/SdrAudioPlayer.tsx b/apps/client/src/features/sdr/ui/SdrAudioPlayer.tsx similarity index 100% rename from apps/client/src/features/sdr/SdrAudioPlayer.tsx rename to apps/client/src/features/sdr/ui/SdrAudioPlayer.tsx diff --git a/apps/client/src/features/sdr/SdrDashboard.tsx b/apps/client/src/features/sdr/ui/SdrDashboard.tsx similarity index 100% rename from apps/client/src/features/sdr/SdrDashboard.tsx rename to apps/client/src/features/sdr/ui/SdrDashboard.tsx diff --git a/apps/client/src/features/search/search-form.tsx b/apps/client/src/features/search/ui/SearchForm.tsx similarity index 100% rename from apps/client/src/features/search/search-form.tsx rename to apps/client/src/features/search/ui/SearchForm.tsx diff --git a/apps/client/src/features/server-resource/resourceUsage.tsx b/apps/client/src/features/server-resource/ui/ResourceUsage.tsx similarity index 100% rename from apps/client/src/features/server-resource/resourceUsage.tsx rename to apps/client/src/features/server-resource/ui/ResourceUsage.tsx diff --git a/apps/client/src/features/setup/index.tsx b/apps/client/src/features/setup/ui/SetupSteps.tsx similarity index 100% rename from apps/client/src/features/setup/index.tsx rename to apps/client/src/features/setup/ui/SetupSteps.tsx diff --git a/apps/client/src/features/sidebar/index.tsx b/apps/client/src/features/sidebar/ui/AppSidebar.tsx similarity index 100% rename from apps/client/src/features/sidebar/index.tsx rename to apps/client/src/features/sidebar/ui/AppSidebar.tsx diff --git a/apps/client/src/features/sidebar/footer.tsx b/apps/client/src/features/sidebar/ui/footer.tsx similarity index 100% rename from apps/client/src/features/sidebar/footer.tsx rename to apps/client/src/features/sidebar/ui/footer.tsx diff --git a/apps/client/src/features/stat/index.tsx b/apps/client/src/features/stat/ui/StatBlock.tsx similarity index 100% rename from apps/client/src/features/stat/index.tsx rename to apps/client/src/features/stat/ui/StatBlock.tsx diff --git a/apps/client/src/features/topbar/index.tsx b/apps/client/src/features/topbar/ui/TopBar.tsx similarity index 100% rename from apps/client/src/features/topbar/index.tsx rename to apps/client/src/features/topbar/ui/TopBar.tsx diff --git a/apps/client/src/features/topbar/style.css b/apps/client/src/features/topbar/ui/style.css similarity index 100% rename from apps/client/src/features/topbar/style.css rename to apps/client/src/features/topbar/ui/style.css diff --git a/apps/client/src/features/user/UserRoleAssigner.tsx b/apps/client/src/features/user/ui/UserRoleAssigner.tsx similarity index 100% rename from apps/client/src/features/user/UserRoleAssigner.tsx rename to apps/client/src/features/user/ui/UserRoleAssigner.tsx diff --git a/apps/client/src/features/user/userAdd.tsx b/apps/client/src/features/user/ui/userAdd.tsx similarity index 100% rename from apps/client/src/features/user/userAdd.tsx rename to apps/client/src/features/user/ui/userAdd.tsx diff --git a/apps/client/src/features/user/userDelete.tsx b/apps/client/src/features/user/ui/userDelete.tsx similarity index 100% rename from apps/client/src/features/user/userDelete.tsx rename to apps/client/src/features/user/ui/userDelete.tsx diff --git a/apps/client/src/features/user/userEdit.tsx b/apps/client/src/features/user/ui/userEdit.tsx similarity index 100% rename from apps/client/src/features/user/userEdit.tsx rename to apps/client/src/features/user/ui/userEdit.tsx diff --git a/apps/client/src/features/user/userForm.tsx b/apps/client/src/features/user/ui/userForm.tsx similarity index 100% rename from apps/client/src/features/user/userForm.tsx rename to apps/client/src/features/user/ui/userForm.tsx diff --git a/apps/client/src/features/ws/flowUiAdapters/toastFlowUiAdapter.ts b/apps/client/src/features/ws/lib/adapters/toastFlowUiAdapter.ts similarity index 100% rename from apps/client/src/features/ws/flowUiAdapters/toastFlowUiAdapter.ts rename to apps/client/src/features/ws/lib/adapters/toastFlowUiAdapter.ts diff --git a/apps/client/src/features/ws/flowUiEventRouter.test.ts b/apps/client/src/features/ws/lib/flowUiEventRouter.test.ts similarity index 100% rename from apps/client/src/features/ws/flowUiEventRouter.test.ts rename to apps/client/src/features/ws/lib/flowUiEventRouter.test.ts diff --git a/apps/client/src/features/ws/flowUiEventRouter.ts b/apps/client/src/features/ws/lib/flowUiEventRouter.ts similarity index 100% rename from apps/client/src/features/ws/flowUiEventRouter.ts rename to apps/client/src/features/ws/lib/flowUiEventRouter.ts diff --git a/apps/client/src/features/ws/ws.ts b/apps/client/src/features/ws/lib/ws.ts similarity index 100% rename from apps/client/src/features/ws/ws.ts rename to apps/client/src/features/ws/lib/ws.ts diff --git a/apps/client/src/features/ws/wsMock.ts b/apps/client/src/features/ws/lib/wsMock.ts similarity index 100% rename from apps/client/src/features/ws/wsMock.ts rename to apps/client/src/features/ws/lib/wsMock.ts diff --git a/apps/client/src/features/ws/FlowUiEventBridge.tsx b/apps/client/src/features/ws/ui/FlowUiEventBridge.tsx similarity index 100% rename from apps/client/src/features/ws/FlowUiEventBridge.tsx rename to apps/client/src/features/ws/ui/FlowUiEventBridge.tsx diff --git a/apps/client/src/features/ws/IsConnected.tsx b/apps/client/src/features/ws/ui/IsConnected.tsx similarity index 100% rename from apps/client/src/features/ws/IsConnected.tsx rename to apps/client/src/features/ws/ui/IsConnected.tsx diff --git a/apps/client/src/features/ws/WebSocketProvider.tsx b/apps/client/src/features/ws/ui/WebSocketProvider.tsx similarity index 100% rename from apps/client/src/features/ws/WebSocketProvider.tsx rename to apps/client/src/features/ws/ui/WebSocketProvider.tsx diff --git a/apps/client/src/pages/auth/index.tsx b/apps/client/src/pages/auth/ui/AuthPage.tsx similarity index 100% rename from apps/client/src/pages/auth/index.tsx rename to apps/client/src/pages/auth/ui/AuthPage.tsx diff --git a/apps/client/src/pages/code/index.tsx b/apps/client/src/pages/code/ui/CodePage.tsx similarity index 100% rename from apps/client/src/pages/code/index.tsx rename to apps/client/src/pages/code/ui/CodePage.tsx diff --git a/apps/client/src/pages/dashboard/index.tsx b/apps/client/src/pages/dashboard/index.tsx deleted file mode 100644 index ead3d26..0000000 --- a/apps/client/src/pages/dashboard/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export { - DashboardMainPanel, - type DashboardMainPanelContentView, -} from "./DashboardMainPanel"; diff --git a/apps/client/src/pages/dashboard/DashboardMainPanel.tsx b/apps/client/src/pages/dashboard/ui/DashboardMainPanel.tsx similarity index 100% rename from apps/client/src/pages/dashboard/DashboardMainPanel.tsx rename to apps/client/src/pages/dashboard/ui/DashboardMainPanel.tsx diff --git a/apps/client/src/pages/desktop-settings/index.tsx b/apps/client/src/pages/desktop-settings/ui/DesktopSettingsPage.tsx similarity index 100% rename from apps/client/src/pages/desktop-settings/index.tsx rename to apps/client/src/pages/desktop-settings/ui/DesktopSettingsPage.tsx diff --git a/apps/client/src/pages/devices/index.tsx b/apps/client/src/pages/devices/ui/DevicePage.tsx similarity index 100% rename from apps/client/src/pages/devices/index.tsx rename to apps/client/src/pages/devices/ui/DevicePage.tsx diff --git a/apps/client/src/pages/dynamic-dashboard/index.tsx b/apps/client/src/pages/dynamic-dashboard/index.tsx deleted file mode 100644 index 7ff288a..0000000 --- a/apps/client/src/pages/dynamic-dashboard/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { DynamicDashboardMainPanel } from "./DynamicDashboardMainPanel"; -export { NewDynamicDashboardPanel } from "./NewDynamicDashboardPanel"; diff --git a/apps/client/src/pages/dynamic-dashboard/DynamicDashboardMainPanel.tsx b/apps/client/src/pages/dynamic-dashboard/ui/DynamicDashboardMainPanel.tsx similarity index 100% rename from apps/client/src/pages/dynamic-dashboard/DynamicDashboardMainPanel.tsx rename to apps/client/src/pages/dynamic-dashboard/ui/DynamicDashboardMainPanel.tsx diff --git a/apps/client/src/pages/dynamic-dashboard/NewDynamicDashboardPanel.tsx b/apps/client/src/pages/dynamic-dashboard/ui/NewDynamicDashboardPanel.tsx similarity index 100% rename from apps/client/src/pages/dynamic-dashboard/NewDynamicDashboardPanel.tsx rename to apps/client/src/pages/dynamic-dashboard/ui/NewDynamicDashboardPanel.tsx diff --git a/apps/client/src/pages/flow/index.tsx b/apps/client/src/pages/flow/ui/FlowPage.tsx similarity index 100% rename from apps/client/src/pages/flow/index.tsx rename to apps/client/src/pages/flow/ui/FlowPage.tsx diff --git a/apps/client/src/pages/landing/index.tsx b/apps/client/src/pages/landing/ui/LandingPage.tsx similarity index 100% rename from apps/client/src/pages/landing/index.tsx rename to apps/client/src/pages/landing/ui/LandingPage.tsx diff --git a/apps/client/src/pages/map/index.tsx b/apps/client/src/pages/map/ui/MapPage.tsx similarity index 100% rename from apps/client/src/pages/map/index.tsx rename to apps/client/src/pages/map/ui/MapPage.tsx diff --git a/apps/client/src/pages/notfound/index.tsx b/apps/client/src/pages/notfound/ui/NotFound.tsx similarity index 100% rename from apps/client/src/pages/notfound/index.tsx rename to apps/client/src/pages/notfound/ui/NotFound.tsx diff --git a/apps/client/src/pages/recordings/index.tsx b/apps/client/src/pages/recordings/ui/RecordingsPage.tsx similarity index 100% rename from apps/client/src/pages/recordings/index.tsx rename to apps/client/src/pages/recordings/ui/RecordingsPage.tsx diff --git a/apps/client/src/pages/settings/account.tsx b/apps/client/src/pages/settings/ui/AccountSettingsPage.tsx similarity index 100% rename from apps/client/src/pages/settings/account.tsx rename to apps/client/src/pages/settings/ui/AccountSettingsPage.tsx diff --git a/apps/client/src/pages/settings/config.tsx b/apps/client/src/pages/settings/ui/ConfigSettingsPage.tsx similarity index 100% rename from apps/client/src/pages/settings/config.tsx rename to apps/client/src/pages/settings/ui/ConfigSettingsPage.tsx diff --git a/apps/client/src/pages/settings/integration.tsx b/apps/client/src/pages/settings/ui/IntegrationSettingsPage.tsx similarity index 100% rename from apps/client/src/pages/settings/integration.tsx rename to apps/client/src/pages/settings/ui/IntegrationSettingsPage.tsx diff --git a/apps/client/src/pages/settings/log.tsx b/apps/client/src/pages/settings/ui/LogSettingsPage.tsx similarity index 100% rename from apps/client/src/pages/settings/log.tsx rename to apps/client/src/pages/settings/ui/LogSettingsPage.tsx diff --git a/apps/client/src/pages/settings/networks.tsx b/apps/client/src/pages/settings/ui/NetworksSettingsPage.tsx similarity index 100% rename from apps/client/src/pages/settings/networks.tsx rename to apps/client/src/pages/settings/ui/NetworksSettingsPage.tsx diff --git a/apps/client/src/pages/settings/services.tsx b/apps/client/src/pages/settings/ui/ServicesSettingsPage.tsx similarity index 100% rename from apps/client/src/pages/settings/services.tsx rename to apps/client/src/pages/settings/ui/ServicesSettingsPage.tsx diff --git a/apps/client/src/pages/settings/index.tsx b/apps/client/src/pages/settings/ui/SettingsPage.tsx similarity index 100% rename from apps/client/src/pages/settings/index.tsx rename to apps/client/src/pages/settings/ui/SettingsPage.tsx diff --git a/apps/client/src/pages/settings/users.tsx b/apps/client/src/pages/settings/ui/UsersSettingsPage.tsx similarity index 100% rename from apps/client/src/pages/settings/users.tsx rename to apps/client/src/pages/settings/ui/UsersSettingsPage.tsx diff --git a/apps/client/src/pages/setup/index.tsx b/apps/client/src/pages/setup/ui/SetupPage.tsx similarity index 100% rename from apps/client/src/pages/setup/index.tsx rename to apps/client/src/pages/setup/ui/SetupPage.tsx diff --git a/apps/client/src/shared/demo.ts b/apps/client/src/shared/config/demo.ts similarity index 100% rename from apps/client/src/shared/demo.ts rename to apps/client/src/shared/config/demo.ts diff --git a/apps/client/src/shared/desktop.ts b/apps/client/src/shared/lib/desktop.ts similarity index 100% rename from apps/client/src/shared/desktop.ts rename to apps/client/src/shared/lib/desktop.ts diff --git a/apps/client/src/lib/electron.ts b/apps/client/src/shared/lib/electron.ts similarity index 100% rename from apps/client/src/lib/electron.ts rename to apps/client/src/shared/lib/electron.ts diff --git a/apps/client/src/lib/geometry-precision.ts b/apps/client/src/shared/lib/geometry-precision.ts similarity index 100% rename from apps/client/src/lib/geometry-precision.ts rename to apps/client/src/shared/lib/geometry-precision.ts diff --git a/apps/client/src/lib/geometry.ts b/apps/client/src/shared/lib/geometry.ts similarity index 100% rename from apps/client/src/lib/geometry.ts rename to apps/client/src/shared/lib/geometry.ts diff --git a/apps/client/src/hooks/use-mobile.ts b/apps/client/src/shared/lib/hooks/use-mobile.ts similarity index 100% rename from apps/client/src/hooks/use-mobile.ts rename to apps/client/src/shared/lib/hooks/use-mobile.ts diff --git a/apps/client/src/hooks/useDesktopSidecar.ts b/apps/client/src/shared/lib/hooks/useDesktopSidecar.ts similarity index 100% rename from apps/client/src/hooks/useDesktopSidecar.ts rename to apps/client/src/shared/lib/hooks/useDesktopSidecar.ts diff --git a/apps/client/src/hooks/usePreventBackNavigation.ts b/apps/client/src/shared/lib/hooks/usePreventBackNavigation.ts similarity index 100% rename from apps/client/src/hooks/usePreventBackNavigation.ts rename to apps/client/src/shared/lib/hooks/usePreventBackNavigation.ts diff --git a/apps/client/src/lib/jwt.ts b/apps/client/src/shared/lib/jwt.ts similarity index 100% rename from apps/client/src/lib/jwt.ts rename to apps/client/src/shared/lib/jwt.ts diff --git a/apps/client/src/lib/resetStores.ts b/apps/client/src/shared/lib/resetStores.ts similarity index 100% rename from apps/client/src/lib/resetStores.ts rename to apps/client/src/shared/lib/resetStores.ts diff --git a/apps/client/src/lib/storage.ts b/apps/client/src/shared/lib/storage.ts similarity index 100% rename from apps/client/src/lib/storage.ts rename to apps/client/src/shared/lib/storage.ts diff --git a/apps/client/src/lib/string.ts b/apps/client/src/shared/lib/string.ts similarity index 100% rename from apps/client/src/lib/string.ts rename to apps/client/src/shared/lib/string.ts diff --git a/apps/client/src/lib/supabase.ts b/apps/client/src/shared/lib/supabase.ts similarity index 100% rename from apps/client/src/lib/supabase.ts rename to apps/client/src/shared/lib/supabase.ts diff --git a/apps/client/src/lib/time.ts b/apps/client/src/shared/lib/time.ts similarity index 100% rename from apps/client/src/lib/time.ts rename to apps/client/src/shared/lib/time.ts diff --git a/apps/client/src/lib/utils.ts b/apps/client/src/shared/lib/utils.ts similarity index 100% rename from apps/client/src/lib/utils.ts rename to apps/client/src/shared/lib/utils.ts diff --git a/apps/client/src/components/ui/alert-dialog.tsx b/apps/client/src/shared/ui/alert-dialog.tsx similarity index 100% rename from apps/client/src/components/ui/alert-dialog.tsx rename to apps/client/src/shared/ui/alert-dialog.tsx diff --git a/apps/client/src/components/ui/alert.tsx b/apps/client/src/shared/ui/alert.tsx similarity index 100% rename from apps/client/src/components/ui/alert.tsx rename to apps/client/src/shared/ui/alert.tsx diff --git a/apps/client/src/components/ui/avatar.tsx b/apps/client/src/shared/ui/avatar.tsx similarity index 100% rename from apps/client/src/components/ui/avatar.tsx rename to apps/client/src/shared/ui/avatar.tsx diff --git a/apps/client/src/components/ui/badge.tsx b/apps/client/src/shared/ui/badge.tsx similarity index 100% rename from apps/client/src/components/ui/badge.tsx rename to apps/client/src/shared/ui/badge.tsx diff --git a/apps/client/src/components/ui/breadcrumb.tsx b/apps/client/src/shared/ui/breadcrumb.tsx similarity index 100% rename from apps/client/src/components/ui/breadcrumb.tsx rename to apps/client/src/shared/ui/breadcrumb.tsx diff --git a/apps/client/src/components/ui/button.tsx b/apps/client/src/shared/ui/button.tsx similarity index 100% rename from apps/client/src/components/ui/button.tsx rename to apps/client/src/shared/ui/button.tsx diff --git a/apps/client/src/components/ui/card.tsx b/apps/client/src/shared/ui/card.tsx similarity index 100% rename from apps/client/src/components/ui/card.tsx rename to apps/client/src/shared/ui/card.tsx diff --git a/apps/client/src/components/ui/command.tsx b/apps/client/src/shared/ui/command.tsx similarity index 100% rename from apps/client/src/components/ui/command.tsx rename to apps/client/src/shared/ui/command.tsx diff --git a/apps/client/src/components/ui/context-menu.tsx b/apps/client/src/shared/ui/context-menu.tsx similarity index 100% rename from apps/client/src/components/ui/context-menu.tsx rename to apps/client/src/shared/ui/context-menu.tsx diff --git a/apps/client/src/components/ui/dialog.tsx b/apps/client/src/shared/ui/dialog.tsx similarity index 100% rename from apps/client/src/components/ui/dialog.tsx rename to apps/client/src/shared/ui/dialog.tsx diff --git a/apps/client/src/components/ui/dropdown-menu.tsx b/apps/client/src/shared/ui/dropdown-menu.tsx similarity index 100% rename from apps/client/src/components/ui/dropdown-menu.tsx rename to apps/client/src/shared/ui/dropdown-menu.tsx diff --git a/apps/client/src/components/icon/Logo.tsx b/apps/client/src/shared/ui/icon/Logo.tsx similarity index 100% rename from apps/client/src/components/icon/Logo.tsx rename to apps/client/src/shared/ui/icon/Logo.tsx diff --git a/apps/client/src/components/ui/input.tsx b/apps/client/src/shared/ui/input.tsx similarity index 100% rename from apps/client/src/components/ui/input.tsx rename to apps/client/src/shared/ui/input.tsx diff --git a/apps/client/src/components/ui/label.tsx b/apps/client/src/shared/ui/label.tsx similarity index 100% rename from apps/client/src/components/ui/label.tsx rename to apps/client/src/shared/ui/label.tsx diff --git a/apps/client/src/components/ui/navigation-menu.tsx b/apps/client/src/shared/ui/navigation-menu.tsx similarity index 100% rename from apps/client/src/components/ui/navigation-menu.tsx rename to apps/client/src/shared/ui/navigation-menu.tsx diff --git a/apps/client/src/components/ui/popover.tsx b/apps/client/src/shared/ui/popover.tsx similarity index 100% rename from apps/client/src/components/ui/popover.tsx rename to apps/client/src/shared/ui/popover.tsx diff --git a/apps/client/src/components/ui/resizable.tsx b/apps/client/src/shared/ui/resizable.tsx similarity index 100% rename from apps/client/src/components/ui/resizable.tsx rename to apps/client/src/shared/ui/resizable.tsx diff --git a/apps/client/src/components/ui/scroll-area.tsx b/apps/client/src/shared/ui/scroll-area.tsx similarity index 100% rename from apps/client/src/components/ui/scroll-area.tsx rename to apps/client/src/shared/ui/scroll-area.tsx diff --git a/apps/client/src/components/ui/select.tsx b/apps/client/src/shared/ui/select.tsx similarity index 100% rename from apps/client/src/components/ui/select.tsx rename to apps/client/src/shared/ui/select.tsx diff --git a/apps/client/src/components/ui/separator.tsx b/apps/client/src/shared/ui/separator.tsx similarity index 100% rename from apps/client/src/components/ui/separator.tsx rename to apps/client/src/shared/ui/separator.tsx diff --git a/apps/client/src/components/ui/sheet.tsx b/apps/client/src/shared/ui/sheet.tsx similarity index 100% rename from apps/client/src/components/ui/sheet.tsx rename to apps/client/src/shared/ui/sheet.tsx diff --git a/apps/client/src/components/ui/sidebar.tsx b/apps/client/src/shared/ui/sidebar.tsx similarity index 100% rename from apps/client/src/components/ui/sidebar.tsx rename to apps/client/src/shared/ui/sidebar.tsx diff --git a/apps/client/src/components/ui/skeleton.tsx b/apps/client/src/shared/ui/skeleton.tsx similarity index 100% rename from apps/client/src/components/ui/skeleton.tsx rename to apps/client/src/shared/ui/skeleton.tsx diff --git a/apps/client/src/components/ui/sonner.tsx b/apps/client/src/shared/ui/sonner.tsx similarity index 100% rename from apps/client/src/components/ui/sonner.tsx rename to apps/client/src/shared/ui/sonner.tsx diff --git a/apps/client/src/components/ui/switch.tsx b/apps/client/src/shared/ui/switch.tsx similarity index 100% rename from apps/client/src/components/ui/switch.tsx rename to apps/client/src/shared/ui/switch.tsx diff --git a/apps/client/src/components/ui/table.tsx b/apps/client/src/shared/ui/table.tsx similarity index 100% rename from apps/client/src/components/ui/table.tsx rename to apps/client/src/shared/ui/table.tsx diff --git a/apps/client/src/components/ui/tabs.tsx b/apps/client/src/shared/ui/tabs.tsx similarity index 100% rename from apps/client/src/components/ui/tabs.tsx rename to apps/client/src/shared/ui/tabs.tsx diff --git a/apps/client/src/components/ui/textarea.tsx b/apps/client/src/shared/ui/textarea.tsx similarity index 100% rename from apps/client/src/components/ui/textarea.tsx rename to apps/client/src/shared/ui/textarea.tsx diff --git a/apps/client/src/components/ui/tooltip.tsx b/apps/client/src/shared/ui/tooltip.tsx similarity index 100% rename from apps/client/src/components/ui/tooltip.tsx rename to apps/client/src/shared/ui/tooltip.tsx diff --git a/apps/client/src/widgets/auth/AuthenticatedLayout.tsx b/apps/client/src/widgets/auth/ui/AuthenticatedLayout.tsx similarity index 100% rename from apps/client/src/widgets/auth/AuthenticatedLayout.tsx rename to apps/client/src/widgets/auth/ui/AuthenticatedLayout.tsx diff --git a/apps/client/src/widgets/auth/TopBarWrapper.tsx b/apps/client/src/widgets/auth/ui/TopBarWrapper.tsx similarity index 100% rename from apps/client/src/widgets/auth/TopBarWrapper.tsx rename to apps/client/src/widgets/auth/ui/TopBarWrapper.tsx diff --git a/apps/client/src/widgets/device-list/DeviceList.tsx b/apps/client/src/widgets/device-list/ui/DeviceList.tsx similarity index 100% rename from apps/client/src/widgets/device-list/DeviceList.tsx rename to apps/client/src/widgets/device-list/ui/DeviceList.tsx diff --git a/apps/client/src/widgets/entity-list/EntityList.tsx b/apps/client/src/widgets/entity-list/ui/EntityList.tsx similarity index 100% rename from apps/client/src/widgets/entity-list/EntityList.tsx rename to apps/client/src/widgets/entity-list/ui/EntityList.tsx diff --git a/apps/client/src/widgets/role-table/RoleList.tsx b/apps/client/src/widgets/role-table/ui/RoleList.tsx similarity index 100% rename from apps/client/src/widgets/role-table/RoleList.tsx rename to apps/client/src/widgets/role-table/ui/RoleList.tsx diff --git a/apps/client/src/widgets/user-table/UserList.tsx b/apps/client/src/widgets/user-table/ui/UserList.tsx similarity index 100% rename from apps/client/src/widgets/user-table/UserList.tsx rename to apps/client/src/widgets/user-table/ui/UserList.tsx From ac82ba81bc234180b8cc2aebc9820eec73294dd9 Mon Sep 17 00:00:00 2001 From: DipokalLab Date: Thu, 30 Apr 2026 16:38:29 +0900 Subject: [PATCH 07/10] refactor: update move other files --- apps/client/package.json | 6 +- apps/client/src/App.tsx | 49 +- .../src/app/pageWrapper/page-wrapper.tsx | 2 +- .../src/app/providers/SupabaseAuthContext.tsx | 2 +- .../src/entities/configurations/api/index.ts | 2 +- .../src/entities/configurations/index.ts | 4 + .../configurations/lib/codeService.ts | 2 +- .../entities/configurations/model/store.ts | 2 +- .../src/entities/custom-nodes/api/index.ts | 2 +- .../client/src/entities/custom-nodes/index.ts | 4 + .../src/entities/custom-nodes/model/store.ts | 2 +- .../src/entities/device-token/api/index.ts | 2 +- .../client/src/entities/device-token/index.ts | 3 + .../src/entities/device-token/model/store.ts | 2 +- apps/client/src/entities/device/api/index.ts | 2 +- apps/client/src/entities/device/index.ts | 3 + .../client/src/entities/device/model/store.ts | 2 +- .../client/src/entities/device/model/types.ts | 2 +- .../src/entities/dynamic-dashboard/index.ts | 4 + .../dynamic-dashboard/lib/layoutResolve.ts | 2 +- .../entities/dynamic-dashboard/model/store.ts | 4 +- apps/client/src/entities/entity/api/index.ts | 2 +- apps/client/src/entities/entity/index.ts | 3 + .../client/src/entities/entity/model/store.ts | 2 +- apps/client/src/entities/file/api/index.ts | 2 +- apps/client/src/entities/file/index.ts | 3 + apps/client/src/entities/file/model/store.ts | 2 +- apps/client/src/entities/flow/api/index.ts | 2 +- apps/client/src/entities/flow/index.ts | 3 + apps/client/src/entities/flow/model/store.ts | 6 +- apps/client/src/entities/ha/api/index.ts | 2 +- apps/client/src/entities/ha/index.ts | 3 + apps/client/src/entities/ha/model/store.ts | 2 +- .../src/entities/integrations/api/index.ts | 2 +- .../client/src/entities/integrations/index.ts | 3 + .../src/entities/integrations/model/store.ts | 2 +- apps/client/src/entities/log/api/index.ts | 2 +- apps/client/src/entities/log/index.ts | 2 + apps/client/src/entities/map/api/index.ts | 2 +- apps/client/src/entities/map/index.ts | 3 + apps/client/src/entities/map/model/store.ts | 2 +- .../src/entities/permission/api/index.ts | 2 +- apps/client/src/entities/permission/index.ts | 3 + .../src/entities/permission/model/store.ts | 2 +- .../src/entities/recording/api/index.ts | 2 +- apps/client/src/entities/recording/index.ts | 4 +- .../src/entities/recording/model/store.ts | 2 +- apps/client/src/entities/role/api/index.ts | 2 +- apps/client/src/entities/role/index.ts | 3 + apps/client/src/entities/role/model/store.ts | 2 +- apps/client/src/entities/role/model/types.ts | 2 +- apps/client/src/entities/stat/api/index.ts | 2 +- apps/client/src/entities/stat/index.ts | 3 + apps/client/src/entities/stat/model/store.ts | 2 +- apps/client/src/entities/tunnel/api/index.ts | 2 +- apps/client/src/entities/tunnel/index.ts | 3 + .../client/src/entities/tunnel/model/store.ts | 2 +- apps/client/src/entities/user/api/index.ts | 2 +- apps/client/src/entities/user/index.ts | 3 + apps/client/src/entities/user/model/store.ts | 2 +- apps/client/src/entities/user/model/types.ts | 2 +- .../src/features/account-switcher/index.ts | 1 + .../account-switcher/ui/AccountSwitcher.tsx | 12 +- apps/client/src/features/auth/api/index.ts | 2 +- apps/client/src/features/auth/index.ts | 5 + apps/client/src/features/auth/model/hook.ts | 4 +- .../src/features/auth/ui/AuthInterceptor.tsx | 6 +- .../auth/ui/DefaultAdminPasswordDialog.tsx | 10 +- .../client/src/features/auth/ui/LoginForm.tsx | 18 +- apps/client/src/features/code/index.ts | 3 + .../src/features/code/ui/CreateItemDialog.tsx | 6 +- .../src/features/code/ui/FileEditor.tsx | 4 +- apps/client/src/features/code/ui/FileTree.tsx | 14 +- .../src/features/configurations/index.ts | 3 + .../ui/ConfigurationActionButton.tsx | 20 +- .../configurations/ui/ConfigurationCreate.tsx | 16 +- .../ui/ConfigurationCreateButton.tsx | 2 +- apps/client/src/features/darkmode/index.ts | 1 + .../src/features/darkmode/ui/ModeToggle.tsx | 4 +- .../src/features/dashboard-swipe/index.ts | 2 + .../ui/DashboardSwipeHeader.tsx | 24 +- .../ui/DashboardSwipeLayout.tsx | 10 +- .../client/src/features/device-token/index.ts | 1 + .../device-token/ui/DeviceTokenManager.tsx | 10 +- apps/client/src/features/device/index.ts | 4 + .../features/device/ui/DeviceCreateButton.tsx | 12 +- .../features/device/ui/DeviceDeleteButton.tsx | 8 +- .../features/device/ui/DeviceKeyButton.tsx | 6 +- .../features/device/ui/DeviceUpdateButton.tsx | 14 +- .../src/features/dynamic-dashboard/index.ts | 5 + .../lib/events/dispatcher.test.ts | 2 +- .../lib/events/dispatcher.ts | 4 +- .../dynamic-dashboard/ui/GroupCanvas.tsx | 38 +- .../ui/panels/ButtonPanel.tsx | 8 +- .../dynamic-dashboard/ui/panels/FlowPanel.tsx | 14 +- .../dynamic-dashboard/ui/panels/MapPanel.tsx | 10 +- apps/client/src/features/entity/index.ts | 11 + .../features/entity/model/useEntitiesData.ts | 8 +- .../src/features/entity/ui/AllEntities.tsx | 4 +- .../features/entity/ui/AnalyzeMenuItem.tsx | 10 +- apps/client/src/features/entity/ui/Card.tsx | 14 +- .../features/entity/ui/EntityCreateButton.tsx | 20 +- .../features/entity/ui/EntityDeleteButton.tsx | 6 +- .../features/entity/ui/EntityUpdateButton.tsx | 20 +- .../features/entity/ui/SelectPlatforms.tsx | 2 +- .../src/features/entity/ui/SelectTypes.tsx | 2 +- .../features/entity/ui/StateHistorySheet.tsx | 14 +- apps/client/src/features/error/index.ts | 1 + .../src/features/error/ui/ErrorRender.tsx | 2 +- apps/client/src/features/flow-log/index.ts | 1 + .../src/features/flow-log/ui/FlowLog.tsx | 4 +- apps/client/src/features/flow/index.ts | 10 + .../flow/lib/flow-chat/buildSystemPrompt.ts | 6 +- .../flow/lib/flow-chat/executeToolCalls.ts | 2 +- apps/client/src/features/flow/lib/flowNode.ts | 2 +- .../client/src/features/flow/lib/flowUtils.ts | 4 +- apps/client/src/features/flow/model/types.ts | 2 +- .../src/features/flow/ui/AddCustomNode.tsx | 20 +- apps/client/src/features/flow/ui/Flow.tsx | 20 +- apps/client/src/features/flow/ui/Graph.tsx | 14 +- apps/client/src/features/flow/ui/Options.tsx | 14 +- apps/client/src/features/flow/ui/RunFlow.tsx | 8 +- .../features/flow/ui/SelectedItemActions.tsx | 2 +- .../src/features/flow/ui/nodes/ButtonNode.tsx | 2 +- .../src/features/flow/ui/nodes/CalcNode.tsx | 2 +- .../src/features/flow/ui/nodes/HttpNode.tsx | 2 +- .../features/flow/ui/nodes/IntervalNode.tsx | 2 +- .../src/features/flow/ui/nodes/LogicNode.tsx | 2 +- .../src/features/flow/ui/nodes/LoopNode.tsx | 2 +- .../src/features/flow/ui/nodes/MQTTNode.tsx | 2 +- .../src/features/flow/ui/nodes/NumberNode.tsx | 2 +- .../features/flow/ui/nodes/ProcessingNode.tsx | 2 +- .../src/features/flow/ui/nodes/TitleNode.tsx | 2 +- .../src/features/flow/ui/nodes/VarNode.tsx | 2 +- apps/client/src/features/footer/index.ts | 1 + apps/client/src/features/footer/ui/Footer.tsx | 12 +- apps/client/src/features/gps/index.ts | 1 + apps/client/src/features/ha/index.ts | 1 + .../client/src/features/ha/ui/HaDashboard.tsx | 4 +- .../src/features/ha/ui/HaEntitiesTable.tsx | 8 +- .../client/src/features/ha/ui/HaStatBlock.tsx | 4 +- apps/client/src/features/integration/index.ts | 3 + .../client/src/features/integration/ui/HA.tsx | 6 +- .../features/integration/ui/Integration.tsx | 8 +- .../src/features/integration/ui/ROS.tsx | 6 +- .../src/features/integration/ui/SDR.tsx | 6 +- apps/client/src/features/json/index.ts | 1 + apps/client/src/features/llm-chat/index.ts | 4 + .../src/features/llm-chat/model/store.ts | 4 +- .../src/features/llm-chat/model/types.ts | 2 +- .../src/features/llm-chat/ui/ChatInput.tsx | 2 +- .../src/features/llm-chat/ui/ChatMessage.tsx | 4 +- .../src/features/llm-chat/ui/ChatMessages.tsx | 4 +- .../src/features/llm-chat/ui/ChatPanel.tsx | 8 +- .../llm-chat/ui/ChatPanelContainer.tsx | 6 +- .../features/llm-chat/ui/ChatPanelMobile.tsx | 6 +- apps/client/src/features/log/index.ts | 1 + apps/client/src/features/log/ui/Logs.tsx | 14 +- apps/client/src/features/map-draw/index.ts | 8 + .../map-draw/ui/FeatureDetailsPanel.tsx | 14 +- .../map-draw/ui/FeatureDrawingPreview.tsx | 2 +- .../features/map-draw/ui/FeatureEditor.tsx | 4 +- .../features/map-draw/ui/FeatureRenderer.tsx | 4 +- .../src/features/map-draw/ui/LayerDialog.tsx | 12 +- .../src/features/map-draw/ui/LayerSidebar.tsx | 12 +- .../src/features/map-draw/ui/MapEvents.tsx | 2 +- .../src/features/map-draw/ui/MapToolbar.tsx | 4 +- apps/client/src/features/map-entity/index.ts | 3 + .../src/features/map-entity/model/store.ts | 2 +- .../map-entity/ui/EntityDetailsPanel.tsx | 22 +- .../src/features/map-entity/ui/render.tsx | 8 +- apps/client/src/features/map/index.ts | 1 + apps/client/src/features/map/ui/MapView.tsx | 26 +- apps/client/src/features/recording/index.ts | 6 +- .../recording/ui/AudioWaveformPlayer.tsx | 8 +- .../features/recording/ui/FrameTimeline.tsx | 4 +- .../recording/ui/PlaybackControls.tsx | 4 +- .../features/recording/ui/RecordingButton.tsx | 10 +- .../features/recording/ui/RecordingsList.tsx | 16 +- .../src/features/recording/ui/TimeRuler.tsx | 2 +- .../features/recording/ui/VideoControlBar.tsx | 6 +- .../recording/ui/VideoPlaybackDialog.tsx | 12 +- .../features/recording/ui/WaveformCanvas.tsx | 2 +- apps/client/src/features/role/index.ts | 2 + .../src/features/role/ui/RoleDialogs.tsx | 10 +- apps/client/src/features/role/ui/RoleForm.tsx | 20 +- apps/client/src/features/ros2/index.ts | 1 + .../src/features/ros2/ui/Ros2Dashboard.tsx | 4 +- apps/client/src/features/rtc/index.ts | 7 + apps/client/src/features/rtc/lib/rtc.ts | 2 +- .../src/features/rtc/lib/turnService.ts | 4 +- .../src/features/rtc/ui/AudioLevelBar.tsx | 2 +- .../src/features/rtc/ui/StreamReceiver.tsx | 4 +- .../src/features/rtc/ui/WebRTCProvider.tsx | 10 +- apps/client/src/features/sdr/index.ts | 3 + .../src/features/sdr/ui/SdrAudioPlayer.tsx | 2 +- .../src/features/sdr/ui/SdrDashboard.tsx | 12 +- apps/client/src/features/search/index.ts | 1 + .../src/features/search/ui/SearchForm.tsx | 4 +- .../src/features/server-resource/index.ts | 1 + .../server-resource/ui/ResourceUsage.tsx | 6 +- apps/client/src/features/setup/index.ts | 1 + .../src/features/setup/ui/SetupSteps.tsx | 8 +- apps/client/src/features/sidebar/index.ts | 2 + .../src/features/sidebar/ui/AppSidebar.tsx | 14 +- .../client/src/features/sidebar/ui/footer.tsx | 14 +- apps/client/src/features/stat/index.ts | 1 + .../client/src/features/stat/ui/StatBlock.tsx | 4 +- apps/client/src/features/topbar/index.ts | 1 + apps/client/src/features/user/index.ts | 4 + .../src/features/user/ui/UserRoleAssigner.tsx | 14 +- apps/client/src/features/user/ui/userAdd.tsx | 8 +- .../src/features/user/ui/userDelete.tsx | 6 +- apps/client/src/features/user/ui/userEdit.tsx | 8 +- apps/client/src/features/user/ui/userForm.tsx | 8 +- apps/client/src/features/ws/index.ts | 7 + .../ws/lib/adapters/toastFlowUiAdapter.ts | 2 +- .../src/features/ws/lib/flowUiEventRouter.ts | 2 +- apps/client/src/features/ws/lib/ws.ts | 2 +- .../src/features/ws/ui/FlowUiEventBridge.tsx | 6 +- .../src/features/ws/ui/WebSocketProvider.tsx | 6 +- apps/client/src/main.tsx | 6 +- apps/client/src/pages/auth/index.ts | 1 + apps/client/src/pages/code/index.ts | 1 + apps/client/src/pages/code/ui/CodePage.tsx | 16 +- apps/client/src/pages/dashboard/index.ts | 2 + .../pages/dashboard/ui/DashboardMainPanel.tsx | 8 +- .../src/pages/desktop-settings/index.ts | 2 + .../ui/DesktopSettingsPage.tsx | 12 +- apps/client/src/pages/devices/index.ts | 1 + .../src/pages/devices/ui/DevicePage.tsx | 16 +- .../src/pages/dynamic-dashboard/index.ts | 2 + .../ui/DynamicDashboardMainPanel.tsx | 6 +- .../ui/NewDynamicDashboardPanel.tsx | 4 +- apps/client/src/pages/flow/index.ts | 1 + apps/client/src/pages/flow/ui/FlowPage.tsx | 14 +- apps/client/src/pages/landing/index.ts | 1 + .../src/pages/landing/ui/LandingPage.tsx | 2 +- apps/client/src/pages/map/index.ts | 1 + apps/client/src/pages/map/ui/MapPage.tsx | 12 +- apps/client/src/pages/notfound/index.ts | 1 + apps/client/src/pages/recordings/index.ts | 1 + .../pages/recordings/ui/RecordingsPage.tsx | 6 +- apps/client/src/pages/settings/index.ts | 8 + .../pages/settings/ui/AccountSettingsPage.tsx | 24 +- .../pages/settings/ui/ConfigSettingsPage.tsx | 16 +- .../settings/ui/IntegrationSettingsPage.tsx | 8 +- .../src/pages/settings/ui/LogSettingsPage.tsx | 6 +- .../settings/ui/NetworksSettingsPage.tsx | 8 +- .../settings/ui/ServicesSettingsPage.tsx | 26 +- .../src/pages/settings/ui/SettingsPage.tsx | 6 +- .../pages/settings/ui/UsersSettingsPage.tsx | 12 +- apps/client/src/pages/setup/index.ts | 1 + apps/client/src/pages/setup/ui/SetupPage.tsx | 8 +- apps/client/src/shared/api/index.ts | 4 +- apps/client/src/shared/config/index.js | 0 apps/client/src/shared/config/index.ts | 1 + .../src/shared/lib/geometry-precision.ts | 2 +- apps/client/src/shared/lib/geometry.ts | 2 +- apps/client/src/shared/lib/hooks/index.js | 0 apps/client/src/shared/lib/hooks/index.ts | 3 + .../src/shared/lib/hooks/useDesktopSidecar.ts | 6 +- .../lib/hooks/usePreventBackNavigation.ts | 2 +- apps/client/src/shared/lib/resetStores.ts | 6 +- apps/client/src/shared/mock/index.js | 0 apps/client/src/shared/mock/index.ts | 1 + apps/client/src/shared/mock/mockAdapter.ts | 18 +- apps/client/src/shared/mock/mockData.ts | 24 +- apps/client/src/shared/ui/alert-dialog.tsx | 4 +- apps/client/src/shared/ui/alert.tsx | 2 +- apps/client/src/shared/ui/avatar.tsx | 2 +- apps/client/src/shared/ui/badge.tsx | 2 +- apps/client/src/shared/ui/breadcrumb.tsx | 2 +- apps/client/src/shared/ui/button.tsx | 2 +- apps/client/src/shared/ui/card.tsx | 2 +- apps/client/src/shared/ui/command.tsx | 4 +- apps/client/src/shared/ui/context-menu.tsx | 2 +- apps/client/src/shared/ui/dialog.tsx | 2 +- apps/client/src/shared/ui/dropdown-menu.tsx | 2 +- apps/client/src/shared/ui/icon/index.js | 0 apps/client/src/shared/ui/icon/index.ts | 1 + apps/client/src/shared/ui/input.tsx | 2 +- apps/client/src/shared/ui/label.tsx | 2 +- apps/client/src/shared/ui/navigation-menu.tsx | 2 +- apps/client/src/shared/ui/popover.tsx | 2 +- apps/client/src/shared/ui/resizable.tsx | 2 +- apps/client/src/shared/ui/scroll-area.tsx | 2 +- apps/client/src/shared/ui/select.tsx | 2 +- apps/client/src/shared/ui/separator.tsx | 2 +- apps/client/src/shared/ui/sheet.tsx | 2 +- apps/client/src/shared/ui/sidebar.tsx | 16 +- apps/client/src/shared/ui/skeleton.tsx | 2 +- apps/client/src/shared/ui/switch.tsx | 2 +- apps/client/src/shared/ui/table.tsx | 2 +- apps/client/src/shared/ui/tabs.tsx | 2 +- apps/client/src/shared/ui/textarea.tsx | 2 +- apps/client/src/shared/ui/tooltip.tsx | 2 +- apps/client/src/widgets/auth/index.ts | 2 + .../widgets/auth/ui/AuthenticatedLayout.tsx | 14 +- .../src/widgets/auth/ui/TopBarWrapper.tsx | 2 +- apps/client/src/widgets/device-list/index.ts | 1 + .../src/widgets/device-list/ui/DeviceList.tsx | 18 +- apps/client/src/widgets/entity-list/index.ts | 1 + .../src/widgets/entity-list/ui/EntityList.tsx | 18 +- apps/client/src/widgets/role-table/index.ts | 1 + .../src/widgets/role-table/ui/RoleList.tsx | 14 +- apps/client/src/widgets/user-table/index.ts | 1 + .../src/widgets/user-table/ui/UserList.tsx | 16 +- apps/client/steiger.config.ts | 15 + apps/client/tsconfig.app.tsbuildinfo | 2 +- package-lock.json | 1347 +++++++++++++++-- 311 files changed, 2203 insertions(+), 901 deletions(-) create mode 100644 apps/client/src/entities/configurations/index.ts create mode 100644 apps/client/src/entities/custom-nodes/index.ts create mode 100644 apps/client/src/entities/device-token/index.ts create mode 100644 apps/client/src/entities/device/index.ts create mode 100644 apps/client/src/entities/dynamic-dashboard/index.ts create mode 100644 apps/client/src/entities/entity/index.ts create mode 100644 apps/client/src/entities/file/index.ts create mode 100644 apps/client/src/entities/flow/index.ts create mode 100644 apps/client/src/entities/ha/index.ts create mode 100644 apps/client/src/entities/integrations/index.ts create mode 100644 apps/client/src/entities/log/index.ts create mode 100644 apps/client/src/entities/map/index.ts create mode 100644 apps/client/src/entities/permission/index.ts create mode 100644 apps/client/src/entities/role/index.ts create mode 100644 apps/client/src/entities/stat/index.ts create mode 100644 apps/client/src/entities/tunnel/index.ts create mode 100644 apps/client/src/entities/user/index.ts create mode 100644 apps/client/src/features/account-switcher/index.ts create mode 100644 apps/client/src/features/auth/index.ts create mode 100644 apps/client/src/features/code/index.ts create mode 100644 apps/client/src/features/configurations/index.ts create mode 100644 apps/client/src/features/darkmode/index.ts create mode 100644 apps/client/src/features/dashboard-swipe/index.ts create mode 100644 apps/client/src/features/device-token/index.ts create mode 100644 apps/client/src/features/device/index.ts create mode 100644 apps/client/src/features/dynamic-dashboard/index.ts create mode 100644 apps/client/src/features/entity/index.ts create mode 100644 apps/client/src/features/error/index.ts create mode 100644 apps/client/src/features/flow-log/index.ts create mode 100644 apps/client/src/features/flow/index.ts create mode 100644 apps/client/src/features/footer/index.ts create mode 100644 apps/client/src/features/gps/index.ts create mode 100644 apps/client/src/features/ha/index.ts create mode 100644 apps/client/src/features/integration/index.ts create mode 100644 apps/client/src/features/json/index.ts create mode 100644 apps/client/src/features/llm-chat/index.ts create mode 100644 apps/client/src/features/log/index.ts create mode 100644 apps/client/src/features/map-draw/index.ts create mode 100644 apps/client/src/features/map-entity/index.ts create mode 100644 apps/client/src/features/map/index.ts create mode 100644 apps/client/src/features/role/index.ts create mode 100644 apps/client/src/features/ros2/index.ts create mode 100644 apps/client/src/features/rtc/index.ts create mode 100644 apps/client/src/features/sdr/index.ts create mode 100644 apps/client/src/features/search/index.ts create mode 100644 apps/client/src/features/server-resource/index.ts create mode 100644 apps/client/src/features/setup/index.ts create mode 100644 apps/client/src/features/sidebar/index.ts create mode 100644 apps/client/src/features/stat/index.ts create mode 100644 apps/client/src/features/topbar/index.ts create mode 100644 apps/client/src/features/user/index.ts create mode 100644 apps/client/src/features/ws/index.ts create mode 100644 apps/client/src/pages/auth/index.ts create mode 100644 apps/client/src/pages/code/index.ts create mode 100644 apps/client/src/pages/dashboard/index.ts create mode 100644 apps/client/src/pages/desktop-settings/index.ts create mode 100644 apps/client/src/pages/devices/index.ts create mode 100644 apps/client/src/pages/dynamic-dashboard/index.ts create mode 100644 apps/client/src/pages/flow/index.ts create mode 100644 apps/client/src/pages/landing/index.ts create mode 100644 apps/client/src/pages/map/index.ts create mode 100644 apps/client/src/pages/notfound/index.ts create mode 100644 apps/client/src/pages/recordings/index.ts create mode 100644 apps/client/src/pages/settings/index.ts create mode 100644 apps/client/src/pages/setup/index.ts create mode 100644 apps/client/src/shared/config/index.js create mode 100644 apps/client/src/shared/config/index.ts create mode 100644 apps/client/src/shared/lib/hooks/index.js create mode 100644 apps/client/src/shared/lib/hooks/index.ts create mode 100644 apps/client/src/shared/mock/index.js create mode 100644 apps/client/src/shared/mock/index.ts create mode 100644 apps/client/src/shared/ui/icon/index.js create mode 100644 apps/client/src/shared/ui/icon/index.ts create mode 100644 apps/client/src/widgets/auth/index.ts create mode 100644 apps/client/src/widgets/device-list/index.ts create mode 100644 apps/client/src/widgets/entity-list/index.ts create mode 100644 apps/client/src/widgets/role-table/index.ts create mode 100644 apps/client/src/widgets/user-table/index.ts create mode 100644 apps/client/steiger.config.ts diff --git a/apps/client/package.json b/apps/client/package.json index 994378e..7874d6f 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -17,7 +17,6 @@ "test:watch": "vitest" }, "dependencies": { - "@vessel/capsule-client": "file:../../packages/capsule-client", "@codemirror/lang-json": "^6.0.2", "@emotion/react": "^11.14.0", "@monaco-editor/react": "^4.7.0", @@ -42,6 +41,7 @@ "@tauri-apps/api": "^2.2.0", "@tauri-apps/plugin-shell": "^2.3.4", "@uiw/react-codemirror": "^4.24.2", + "@vessel/capsule-client": "file:../../packages/capsule-client", "autoprefixer": "^10.4.21", "axios": "^1.11.0", "clsx": "^2.1.1", @@ -63,11 +63,13 @@ "zustand": "^5.0.5" }, "devDependencies": { - "vitest": "^3.0.0", "@eslint/js": "^9.21.0", + "@feature-sliced/steiger-plugin": "^0.5.7", "@types/d3": "^7.4.3", "@types/js-cookie": "^3.0.6", "@types/leaflet": "^1.9.20", + "steiger": "^0.5.11", + "vitest": "^3.0.0", "wait-on": "^8.0.3" } } diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index a88bf0a..692bf87 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -1,32 +1,33 @@ -import { AuthPage } from "./pages/auth"; +import { AuthPage } from "@/pages/auth"; import { createBrowserRouter, RouterProvider } from "react-router"; import { DashboardSwipeLayout, DashboardSwipeRoutePlaceholder, -} from "./features/dashboard-swipe/DashboardSwipeLayout"; -import { DevicePage } from "./pages/devices"; -import { FlowPage } from "./pages/flow"; -import { AuthInterceptor } from "./features/auth/AuthInterceptor"; -import { NotFound } from "./pages/notfound"; -import LandingPage from "./pages/landing"; -import { MapPage } from "./pages/map"; -import { SetupPage } from "./pages/setup"; -import { CodePage } from "./pages/code"; -import { AuthenticatedLayout } from "./widgets/auth/AuthenticatedLayout"; -import { TopBarWrapper } from "./widgets/auth/TopBarWrapper"; -import { useDesktopSidecar } from "./hooks/useDesktopSidecar"; -import { usePreventBackNavigation } from "./hooks/usePreventBackNavigation"; -import { SettingsPage } from "./pages/settings"; -import { AccountSettingsPage } from "./pages/settings/account"; -import { ServicesSettingsPage } from "./pages/settings/services"; -import { UsersSettingsPage } from "./pages/settings/users"; -import { NetworksSettingsPage } from "./pages/settings/networks"; -import { IntegrationSettingsPage } from "./pages/settings/integration"; -import { LogSettingsPage } from "./pages/settings/log"; -import { ConfigSettingsPage } from "./pages/settings/config"; -import { RecordingsPage } from "./pages/recordings"; -import { DesktopSettingsPage } from "./pages/desktop-settings"; +} from "@/features/dashboard-swipe"; +import { DevicePage } from "@/pages/devices"; +import { FlowPage } from "@/pages/flow"; +import { AuthInterceptor } from "@/features/auth"; +import { NotFound } from "@/pages/notfound"; +import LandingPage from "@/pages/landing"; +import { MapPage } from "@/pages/map"; +import { SetupPage } from "@/pages/setup"; +import { CodePage } from "@/pages/code"; +import { AuthenticatedLayout, TopBarWrapper } from "@/widgets/auth"; +import { useDesktopSidecar } from "@/shared/lib/hooks/useDesktopSidecar"; +import { usePreventBackNavigation } from "@/shared/lib/hooks/usePreventBackNavigation"; +import { + SettingsPage, + AccountSettingsPage, + ServicesSettingsPage, + UsersSettingsPage, + NetworksSettingsPage, + IntegrationSettingsPage, + LogSettingsPage, + ConfigSettingsPage, +} from "@/pages/settings"; +import { RecordingsPage } from "@/pages/recordings"; +import { DesktopSettingsPage } from "@/pages/desktop-settings"; const router = createBrowserRouter([ { diff --git a/apps/client/src/app/pageWrapper/page-wrapper.tsx b/apps/client/src/app/pageWrapper/page-wrapper.tsx index 9c7b269..e05706a 100644 --- a/apps/client/src/app/pageWrapper/page-wrapper.tsx +++ b/apps/client/src/app/pageWrapper/page-wrapper.tsx @@ -1,4 +1,4 @@ -import { isElectron } from "@/lib/electron"; +import { isElectron } from "@/shared/lib/electron"; import type { PropsWithChildren } from "react"; export function PageWrapper(props: PropsWithChildren) { diff --git a/apps/client/src/app/providers/SupabaseAuthContext.tsx b/apps/client/src/app/providers/SupabaseAuthContext.tsx index 177a97b..a137faa 100644 --- a/apps/client/src/app/providers/SupabaseAuthContext.tsx +++ b/apps/client/src/app/providers/SupabaseAuthContext.tsx @@ -7,7 +7,7 @@ import { type ReactNode, } from "react"; import type { Session } from "@supabase/supabase-js"; -import { supabase } from "@/lib/supabase"; +import { supabase } from "@/shared/lib/supabase"; const isTauri = (): boolean => { return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; diff --git a/apps/client/src/entities/configurations/api/index.ts b/apps/client/src/entities/configurations/api/index.ts index 0cd42c7..3e3ed21 100644 --- a/apps/client/src/entities/configurations/api/index.ts +++ b/apps/client/src/entities/configurations/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import type { SystemConfiguration, SystemConfigurationPayload } from "./types"; +import type { SystemConfiguration, SystemConfigurationPayload } from "../model/types"; export const getConfigs = () => apiClient.get("/configurations"); diff --git a/apps/client/src/entities/configurations/index.ts b/apps/client/src/entities/configurations/index.ts new file mode 100644 index 0000000..9788294 --- /dev/null +++ b/apps/client/src/entities/configurations/index.ts @@ -0,0 +1,4 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; +export * from "./lib/codeService"; diff --git a/apps/client/src/entities/configurations/lib/codeService.ts b/apps/client/src/entities/configurations/lib/codeService.ts index 9efe74c..d393287 100644 --- a/apps/client/src/entities/configurations/lib/codeService.ts +++ b/apps/client/src/entities/configurations/lib/codeService.ts @@ -1,4 +1,4 @@ -import type { SystemConfiguration } from "./types"; +import type { SystemConfiguration } from "../model/types"; export const CODE_SERVICE_CONFIG_KEY = "code_service_enabled"; diff --git a/apps/client/src/entities/configurations/model/store.ts b/apps/client/src/entities/configurations/model/store.ts index 7bdbc25..b32539d 100644 --- a/apps/client/src/entities/configurations/model/store.ts +++ b/apps/client/src/entities/configurations/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { SystemConfiguration, SystemConfigurationPayload } from "./types"; interface ConfigState { diff --git a/apps/client/src/entities/custom-nodes/api/index.ts b/apps/client/src/entities/custom-nodes/api/index.ts index db0d3df..ac7f11e 100644 --- a/apps/client/src/entities/custom-nodes/api/index.ts +++ b/apps/client/src/entities/custom-nodes/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { CustomNodeDynamicData, CustomNodeFromApi } from "./types"; +import { CustomNodeDynamicData, CustomNodeFromApi } from "../model/types"; export const getAllCustomNodes = async (): Promise => { const response = await apiClient.get("/custom-nodes"); diff --git a/apps/client/src/entities/custom-nodes/index.ts b/apps/client/src/entities/custom-nodes/index.ts new file mode 100644 index 0000000..38f8b68 --- /dev/null +++ b/apps/client/src/entities/custom-nodes/index.ts @@ -0,0 +1,4 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; +export * from "./lib/presets"; diff --git a/apps/client/src/entities/custom-nodes/model/store.ts b/apps/client/src/entities/custom-nodes/model/store.ts index 1a16de5..48934b8 100644 --- a/apps/client/src/entities/custom-nodes/model/store.ts +++ b/apps/client/src/entities/custom-nodes/model/store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { CustomNodeState } from "./types"; -import * as api from "./api"; +import * as api from "../api"; // const parseNodeData = (nodeFromApi: CustomNodeFromApi): CustomNodeFromApi => { // try { diff --git a/apps/client/src/entities/device-token/api/index.ts b/apps/client/src/entities/device-token/api/index.ts index e5037aa..ee2b40b 100644 --- a/apps/client/src/entities/device-token/api/index.ts +++ b/apps/client/src/entities/device-token/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import type { DeviceToken, IssuedTokenResponse } from "./types"; +import type { DeviceToken, IssuedTokenResponse } from "../model/types"; export const issueDeviceToken = (deviceId: number) => apiClient.post(`/devices/${deviceId}/token`); diff --git a/apps/client/src/entities/device-token/index.ts b/apps/client/src/entities/device-token/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/device-token/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/device-token/model/store.ts b/apps/client/src/entities/device-token/model/store.ts index 9a734ed..19c1e2f 100644 --- a/apps/client/src/entities/device-token/model/store.ts +++ b/apps/client/src/entities/device-token/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { DeviceToken } from "./types"; interface DeviceTokenState { diff --git a/apps/client/src/entities/device/api/index.ts b/apps/client/src/entities/device/api/index.ts index 56d0a3a..549def9 100644 --- a/apps/client/src/entities/device/api/index.ts +++ b/apps/client/src/entities/device/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import type { Device, DevicePayload, DeviceWithEntity } from "./types"; +import type { Device, DevicePayload, DeviceWithEntity } from "../model/types"; export const getDevices = () => apiClient.get("/devices"); export const getDeviceById = (pk_id: number) => diff --git a/apps/client/src/entities/device/index.ts b/apps/client/src/entities/device/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/device/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/device/model/store.ts b/apps/client/src/entities/device/model/store.ts index 069dc65..44b062f 100644 --- a/apps/client/src/entities/device/model/store.ts +++ b/apps/client/src/entities/device/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { Device, DevicePayload } from "./types"; interface DeviceState { diff --git a/apps/client/src/entities/device/model/types.ts b/apps/client/src/entities/device/model/types.ts index af8ba96..95cc9f7 100644 --- a/apps/client/src/entities/device/model/types.ts +++ b/apps/client/src/entities/device/model/types.ts @@ -1,4 +1,4 @@ -import { EntityAll } from "../entity/types"; +import { EntityAll } from "../../entity/model/types"; export interface Device { id: number; diff --git a/apps/client/src/entities/dynamic-dashboard/index.ts b/apps/client/src/entities/dynamic-dashboard/index.ts new file mode 100644 index 0000000..dc69921 --- /dev/null +++ b/apps/client/src/entities/dynamic-dashboard/index.ts @@ -0,0 +1,4 @@ +export * from "./api"; +export * from "./model/store"; +export * from "./lib/layoutResolve"; +export * from "./lib/interaction"; diff --git a/apps/client/src/entities/dynamic-dashboard/lib/layoutResolve.ts b/apps/client/src/entities/dynamic-dashboard/lib/layoutResolve.ts index 4ada425..e6fbd6b 100644 --- a/apps/client/src/entities/dynamic-dashboard/lib/layoutResolve.ts +++ b/apps/client/src/entities/dynamic-dashboard/lib/layoutResolve.ts @@ -1,4 +1,4 @@ -import type { DashboardGroup, DashboardItem } from "./store"; +import type { DashboardGroup, DashboardItem } from "../model/store"; /** Clamp top-left grid position to group bounds (matches store behavior). */ export function clampItemPosition( diff --git a/apps/client/src/entities/dynamic-dashboard/model/store.ts b/apps/client/src/entities/dynamic-dashboard/model/store.ts index 714835a..36f728f 100644 --- a/apps/client/src/entities/dynamic-dashboard/model/store.ts +++ b/apps/client/src/entities/dynamic-dashboard/model/store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; -import * as api from "./api"; -import { clampItemPosition, itemsCollide } from "./layoutResolve"; +import * as api from "../api"; +import { clampItemPosition, itemsCollide } from "../lib/layoutResolve"; export type DashboardItemType = | "entity-card" diff --git a/apps/client/src/entities/entity/api/index.ts b/apps/client/src/entities/entity/api/index.ts index 7fbdb0c..80f88f4 100644 --- a/apps/client/src/entities/entity/api/index.ts +++ b/apps/client/src/entities/entity/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import type { Entity, EntityAll, EntityPayload, State } from "./types"; +import type { Entity, EntityAll, EntityPayload, State } from "../model/types"; export const getEntities = () => apiClient.get("/entities"); export const getAllEntities = () => apiClient.get("/entities/all"); diff --git a/apps/client/src/entities/entity/index.ts b/apps/client/src/entities/entity/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/entity/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/entity/model/store.ts b/apps/client/src/entities/entity/model/store.ts index 64d3604..87d555c 100644 --- a/apps/client/src/entities/entity/model/store.ts +++ b/apps/client/src/entities/entity/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { Entity, EntityPayload } from "./types"; interface EntityState { diff --git a/apps/client/src/entities/file/api/index.ts b/apps/client/src/entities/file/api/index.ts index aea7f96..d49ace1 100644 --- a/apps/client/src/entities/file/api/index.ts +++ b/apps/client/src/entities/file/api/index.ts @@ -1,4 +1,4 @@ -import { DirEntry } from "./types"; +import { DirEntry } from "../model/types"; import { apiClient } from "@/shared/api"; export const getDirectoryListing = async ( diff --git a/apps/client/src/entities/file/index.ts b/apps/client/src/entities/file/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/file/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/file/model/store.ts b/apps/client/src/entities/file/model/store.ts index 1685d30..a3a774a 100644 --- a/apps/client/src/entities/file/model/store.ts +++ b/apps/client/src/entities/file/model/store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { IdeState } from "./types"; -import { getFileContent, updateFileContent } from "./api"; +import { getFileContent, updateFileContent } from "../api"; import { toast } from "sonner"; export interface FileTreeState { diff --git a/apps/client/src/entities/flow/api/index.ts b/apps/client/src/entities/flow/api/index.ts index 638cf58..bc7945a 100644 --- a/apps/client/src/entities/flow/api/index.ts +++ b/apps/client/src/entities/flow/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { Flow, FlowPayload, FlowVersion, FlowVersionPayload } from "./types"; +import { Flow, FlowPayload, FlowVersion, FlowVersionPayload } from "../model/types"; export const getFlows = async (): Promise => { const { data } = await apiClient.get("/flows"); diff --git a/apps/client/src/entities/flow/index.ts b/apps/client/src/entities/flow/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/flow/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/flow/model/store.ts b/apps/client/src/entities/flow/model/store.ts index 443bc19..3e25511 100644 --- a/apps/client/src/entities/flow/model/store.ts +++ b/apps/client/src/entities/flow/model/store.ts @@ -1,12 +1,12 @@ import { create } from "zustand"; -import { DataNodeType, Edge, Node } from "@/features/flow/flowTypes"; +import { DataNodeType, Edge, Node } from "@/features/flow"; import { getFlows, getFlowVersions, saveFlowVersion, createFlow, -} from "@/entities/flow/api"; -import { Flow } from "@/entities/flow/types"; +} from "@/entities/flow"; +import { Flow } from "@/entities/flow"; interface FlowState { flows: Flow[]; diff --git a/apps/client/src/entities/ha/api/index.ts b/apps/client/src/entities/ha/api/index.ts index ea7f3e4..bd36a0a 100644 --- a/apps/client/src/entities/ha/api/index.ts +++ b/apps/client/src/entities/ha/api/index.ts @@ -1,6 +1,6 @@ import { apiClient } from "@/shared/api"; -import { HaState } from "./types"; +import { HaState } from "../model/types"; export const fetchAllHaStates = async (): Promise => { try { diff --git a/apps/client/src/entities/ha/index.ts b/apps/client/src/entities/ha/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/ha/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/ha/model/store.ts b/apps/client/src/entities/ha/model/store.ts index 81e75cf..40fb893 100644 --- a/apps/client/src/entities/ha/model/store.ts +++ b/apps/client/src/entities/ha/model/store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { HaStateStore } from "./types"; -import { fetchAllHaStates } from "./api"; +import { fetchAllHaStates } from "../api"; export const useHaStore = create((set) => ({ states: [], diff --git a/apps/client/src/entities/integrations/api/index.ts b/apps/client/src/entities/integrations/api/index.ts index dd69116..536b10b 100644 --- a/apps/client/src/entities/integrations/api/index.ts +++ b/apps/client/src/entities/integrations/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import type { IntegrationStatus, IntegrationRegisterPayload } from "./types"; +import type { IntegrationStatus, IntegrationRegisterPayload } from "../model/types"; export const getIntegrationStatus = () => apiClient.get("/integrations/status"); diff --git a/apps/client/src/entities/integrations/index.ts b/apps/client/src/entities/integrations/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/integrations/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/integrations/model/store.ts b/apps/client/src/entities/integrations/model/store.ts index f45aeb9..9241bcf 100644 --- a/apps/client/src/entities/integrations/model/store.ts +++ b/apps/client/src/entities/integrations/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; interface IntegrationState { isHaConnected: boolean; diff --git a/apps/client/src/entities/log/api/index.ts b/apps/client/src/entities/log/api/index.ts index c23fe26..61d8d97 100644 --- a/apps/client/src/entities/log/api/index.ts +++ b/apps/client/src/entities/log/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { LogContentResponse, LogFileListResponse } from "./types"; +import { LogContentResponse, LogFileListResponse } from "../model/types"; export const getLatestLog = async (): Promise => { const { data } = await apiClient.get("/logs/latest"); diff --git a/apps/client/src/entities/log/index.ts b/apps/client/src/entities/log/index.ts new file mode 100644 index 0000000..81a3aef --- /dev/null +++ b/apps/client/src/entities/log/index.ts @@ -0,0 +1,2 @@ +export * from "./api"; +export * from "./model/types"; diff --git a/apps/client/src/entities/map/api/index.ts b/apps/client/src/entities/map/api/index.ts index ce78e0c..c99efa2 100644 --- a/apps/client/src/entities/map/api/index.ts +++ b/apps/client/src/entities/map/api/index.ts @@ -7,7 +7,7 @@ import { MapFeature, FeatureWithVertices, UpdateFeaturePayload, -} from "./types"; +} from "../model/types"; export const getAllLayers = async (): Promise => { const response = await apiClient.get("/map/layers"); diff --git a/apps/client/src/entities/map/index.ts b/apps/client/src/entities/map/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/map/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/map/model/store.ts b/apps/client/src/entities/map/model/store.ts index 609a211..34dd617 100644 --- a/apps/client/src/entities/map/model/store.ts +++ b/apps/client/src/entities/map/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as mapApi from "./api"; +import * as mapApi from "../api"; import { MapDataState, MapInteractionState, diff --git a/apps/client/src/entities/permission/api/index.ts b/apps/client/src/entities/permission/api/index.ts index c5574a6..f92b592 100644 --- a/apps/client/src/entities/permission/api/index.ts +++ b/apps/client/src/entities/permission/api/index.ts @@ -1,4 +1,4 @@ import { apiClient } from "@/shared/api"; -import { Permission } from "./types"; +import { Permission } from "../model/types"; export const getPermissions = () => apiClient.get("/permissions"); diff --git a/apps/client/src/entities/permission/index.ts b/apps/client/src/entities/permission/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/permission/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/permission/model/store.ts b/apps/client/src/entities/permission/model/store.ts index f841b83..2c47774 100644 --- a/apps/client/src/entities/permission/model/store.ts +++ b/apps/client/src/entities/permission/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import { getPermissions } from "./api"; +import { getPermissions } from "../api"; import { Permission } from "./types"; type PermissionState = { diff --git a/apps/client/src/entities/recording/api/index.ts b/apps/client/src/entities/recording/api/index.ts index ab91e6b..78895f1 100644 --- a/apps/client/src/entities/recording/api/index.ts +++ b/apps/client/src/entities/recording/api/index.ts @@ -5,7 +5,7 @@ import { StartRecordingResponse, ActiveRecordingInfo, TopicRecordingStatus, -} from "./types"; +} from "../model/types"; export const getRecordings = async (): Promise => { const { data } = await apiClient.get("/recordings"); diff --git a/apps/client/src/entities/recording/index.ts b/apps/client/src/entities/recording/index.ts index dc2dd3d..a0b7a18 100644 --- a/apps/client/src/entities/recording/index.ts +++ b/apps/client/src/entities/recording/index.ts @@ -1,3 +1,3 @@ -export * from "./types"; export * from "./api"; -export { useRecordingStore } from "./store"; +export * from "./model/types"; +export { useRecordingStore } from "./model/store"; diff --git a/apps/client/src/entities/recording/model/store.ts b/apps/client/src/entities/recording/model/store.ts index f4c6396..a956478 100644 --- a/apps/client/src/entities/recording/model/store.ts +++ b/apps/client/src/entities/recording/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { Recording, ActiveRecordingInfo } from "./types"; interface RecordingState { diff --git a/apps/client/src/entities/role/api/index.ts b/apps/client/src/entities/role/api/index.ts index 1935198..ad50273 100644 --- a/apps/client/src/entities/role/api/index.ts +++ b/apps/client/src/entities/role/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { Role, CreateRolePayload, UpdateRolePayload } from "./types"; +import { Role, CreateRolePayload, UpdateRolePayload } from "../model/types"; export const getRoles = () => apiClient.get("/roles"); export const createRole = (data: CreateRolePayload) => diff --git a/apps/client/src/entities/role/index.ts b/apps/client/src/entities/role/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/role/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/role/model/store.ts b/apps/client/src/entities/role/model/store.ts index 6d09c06..cfd2dcf 100644 --- a/apps/client/src/entities/role/model/store.ts +++ b/apps/client/src/entities/role/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import { getRoles, createRole, updateRole, deleteRole } from "./api"; +import { getRoles, createRole, updateRole, deleteRole } from "../api"; import { Role, CreateRolePayload, UpdateRolePayload } from "./types"; type RoleState = { diff --git a/apps/client/src/entities/role/model/types.ts b/apps/client/src/entities/role/model/types.ts index eb5c229..a9b6eaa 100644 --- a/apps/client/src/entities/role/model/types.ts +++ b/apps/client/src/entities/role/model/types.ts @@ -1,4 +1,4 @@ -import { Permission } from "@/entities/permission/types"; +import { Permission } from "@/entities/permission"; export type Role = { id: number; diff --git a/apps/client/src/entities/stat/api/index.ts b/apps/client/src/entities/stat/api/index.ts index 484f0d8..c6f3529 100644 --- a/apps/client/src/entities/stat/api/index.ts +++ b/apps/client/src/entities/stat/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { Stat } from "./types"; +import { Stat } from "../model/types"; export const getStats = async (): Promise => { const { data } = await apiClient.get("/stat"); diff --git a/apps/client/src/entities/stat/index.ts b/apps/client/src/entities/stat/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/stat/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/stat/model/store.ts b/apps/client/src/entities/stat/model/store.ts index 2034d0d..5c46593 100644 --- a/apps/client/src/entities/stat/model/store.ts +++ b/apps/client/src/entities/stat/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { Stat } from "./types"; interface StatState { diff --git a/apps/client/src/entities/tunnel/api/index.ts b/apps/client/src/entities/tunnel/api/index.ts index 246ea18..77dc2a2 100644 --- a/apps/client/src/entities/tunnel/api/index.ts +++ b/apps/client/src/entities/tunnel/api/index.ts @@ -3,7 +3,7 @@ import type { StartTunnelRequest, StartTunnelResponse, TunnelStatus, -} from "./types"; +} from "../model/types"; export const getStatus = () => apiClient.get("/tunnel/status"); diff --git a/apps/client/src/entities/tunnel/index.ts b/apps/client/src/entities/tunnel/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/tunnel/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/tunnel/model/store.ts b/apps/client/src/entities/tunnel/model/store.ts index a97bbef..7a8a695 100644 --- a/apps/client/src/entities/tunnel/model/store.ts +++ b/apps/client/src/entities/tunnel/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { TunnelStatus } from "./types"; type TunnelState = { diff --git a/apps/client/src/entities/user/api/index.ts b/apps/client/src/entities/user/api/index.ts index dd34023..46875d0 100644 --- a/apps/client/src/entities/user/api/index.ts +++ b/apps/client/src/entities/user/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { User, CreateUserPayload, UpdateUserPayload } from "./types"; +import { User, CreateUserPayload, UpdateUserPayload } from "../model/types"; export const getUsers = () => apiClient.get("/users"); export const createUser = (data: CreateUserPayload) => diff --git a/apps/client/src/entities/user/index.ts b/apps/client/src/entities/user/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/user/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/user/model/store.ts b/apps/client/src/entities/user/model/store.ts index d37440f..5a0e029 100644 --- a/apps/client/src/entities/user/model/store.ts +++ b/apps/client/src/entities/user/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import { getUsers, createUser, updateUser, deleteUser } from "./api"; +import { getUsers, createUser, updateUser, deleteUser } from "../api"; import { User, CreateUserPayload, UpdateUserPayload } from "./types"; type UserState = { diff --git a/apps/client/src/entities/user/model/types.ts b/apps/client/src/entities/user/model/types.ts index 6e41b64..5f9348a 100644 --- a/apps/client/src/entities/user/model/types.ts +++ b/apps/client/src/entities/user/model/types.ts @@ -1,4 +1,4 @@ -import { Role } from "../role/types"; +import { Role } from "../../role/model/types"; export type User = { id: number; diff --git a/apps/client/src/features/account-switcher/index.ts b/apps/client/src/features/account-switcher/index.ts new file mode 100644 index 0000000..0a020d4 --- /dev/null +++ b/apps/client/src/features/account-switcher/index.ts @@ -0,0 +1 @@ +export { AccountSwitcher } from "./ui/AccountSwitcher"; diff --git a/apps/client/src/features/account-switcher/ui/AccountSwitcher.tsx b/apps/client/src/features/account-switcher/ui/AccountSwitcher.tsx index 7ed91bc..fbc5bc5 100644 --- a/apps/client/src/features/account-switcher/ui/AccountSwitcher.tsx +++ b/apps/client/src/features/account-switcher/ui/AccountSwitcher.tsx @@ -8,16 +8,16 @@ import { DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, -} from "@/components/ui/sidebar"; -import { VesselLogo } from "@/components/icon/Logo"; -import { storage, type ServerConnection } from "@/lib/storage"; -import { hardNavigateToDashboard } from "@/lib/resetStores"; -import { isTauri } from "@/shared/desktop"; +} from "@/shared/ui/sidebar"; +import { VesselLogo } from "@/shared/ui/icon/Logo"; +import { storage, type ServerConnection } from "@/shared/lib/storage"; +import { hardNavigateToDashboard } from "@/shared/lib/resetStores"; +import { isTauri } from "@/shared/lib/desktop"; function displayName(server: ServerConnection): string { if (server.name) return server.name; diff --git a/apps/client/src/features/auth/api/index.ts b/apps/client/src/features/auth/api/index.ts index f151d8f..95ffa27 100644 --- a/apps/client/src/features/auth/api/index.ts +++ b/apps/client/src/features/auth/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { User } from "@/entities/user/types"; +import { User } from "@/entities/user"; export type AuthCredentials = { id: string; diff --git a/apps/client/src/features/auth/index.ts b/apps/client/src/features/auth/index.ts new file mode 100644 index 0000000..738be84 --- /dev/null +++ b/apps/client/src/features/auth/index.ts @@ -0,0 +1,5 @@ +export { LoginForm } from "./ui/LoginForm"; +export { AuthInterceptor } from "./ui/AuthInterceptor"; +export { DefaultAdminPasswordDialog } from "./ui/DefaultAdminPasswordDialog"; +export { useLogout } from "./model/hook"; +export * from "./api"; diff --git a/apps/client/src/features/auth/model/hook.ts b/apps/client/src/features/auth/model/hook.ts index 1b6df44..4aa4297 100644 --- a/apps/client/src/features/auth/model/hook.ts +++ b/apps/client/src/features/auth/model/hook.ts @@ -1,7 +1,7 @@ import { useCallback } from "react"; import { useNavigate } from "react-router"; -import { storage } from "@/lib/storage"; -import { hardNavigateToDashboard } from "@/lib/resetStores"; +import { storage } from "@/shared/lib/storage"; +import { hardNavigateToDashboard } from "@/shared/lib/resetStores"; export const useLogout = () => { const navigate = useNavigate(); diff --git a/apps/client/src/features/auth/ui/AuthInterceptor.tsx b/apps/client/src/features/auth/ui/AuthInterceptor.tsx index 0402de1..40b3392 100644 --- a/apps/client/src/features/auth/ui/AuthInterceptor.tsx +++ b/apps/client/src/features/auth/ui/AuthInterceptor.tsx @@ -1,8 +1,8 @@ import { useEffect } from "react"; import { Navigate, useLocation } from "react-router"; -import { parseJwt } from "@/lib/jwt"; -import { storage } from "@/lib/storage"; -import { isDemoMode } from "@/shared/demo"; +import { parseJwt } from "@/shared/lib/jwt"; +import { storage } from "@/shared/lib/storage"; +import { isDemoMode } from "@/shared/config/demo"; export function AuthInterceptor({ children }: { children: React.ReactNode }) { const location = useLocation(); diff --git a/apps/client/src/features/auth/ui/DefaultAdminPasswordDialog.tsx b/apps/client/src/features/auth/ui/DefaultAdminPasswordDialog.tsx index 25f7379..1dc2b5c 100644 --- a/apps/client/src/features/auth/ui/DefaultAdminPasswordDialog.tsx +++ b/apps/client/src/features/auth/ui/DefaultAdminPasswordDialog.tsx @@ -1,9 +1,9 @@ import { useState } from "react"; import { toast } from "sonner"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Dialog, DialogContent, @@ -11,12 +11,12 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { authenticateWithPassword, fetchAdminUser, updateUserPassword, -} from "./api"; +} from "../api"; type DefaultAdminPasswordDialogProps = { open: boolean; diff --git a/apps/client/src/features/auth/ui/LoginForm.tsx b/apps/client/src/features/auth/ui/LoginForm.tsx index d7741f3..75ba4cb 100644 --- a/apps/client/src/features/auth/ui/LoginForm.tsx +++ b/apps/client/src/features/auth/ui/LoginForm.tsx @@ -1,23 +1,23 @@ import { useEffect, useState } from "react"; import { toast } from "sonner"; -import { cn } from "@/lib/utils"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { cn } from "@/shared/lib/utils"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { useNavigate, useSearchParams } from "react-router"; import { ArrowRight, Loader2, Trash2 } from "lucide-react"; -import { DEMO_SERVER_URL, DEMO_TOKEN, isDemoMode } from "@/shared/demo"; +import { DEMO_SERVER_URL, DEMO_TOKEN, isDemoMode } from "@/shared/config/demo"; import { DefaultAdminPasswordDialog } from "./DefaultAdminPasswordDialog"; -import { authenticateWithPassword } from "./api"; -import { storage } from "@/lib/storage"; -import { parseJwt } from "@/lib/jwt"; +import { authenticateWithPassword } from "../api"; +import { storage } from "@/shared/lib/storage"; +import { parseJwt } from "@/shared/lib/jwt"; import { ensureSidecarRunning, getDesktopServerUrl, isTauri, openDesktopSettings, -} from "@/shared/desktop"; +} from "@/shared/lib/desktop"; const MAX_RECENT_URLS = 5; diff --git a/apps/client/src/features/code/index.ts b/apps/client/src/features/code/index.ts new file mode 100644 index 0000000..27d3f6c --- /dev/null +++ b/apps/client/src/features/code/index.ts @@ -0,0 +1,3 @@ +export { FileEditor } from "./ui/FileEditor"; +export { FileTree } from "./ui/FileTree"; +export { CreateItemDialog } from "./ui/CreateItemDialog"; diff --git a/apps/client/src/features/code/ui/CreateItemDialog.tsx b/apps/client/src/features/code/ui/CreateItemDialog.tsx index a1c118a..d3babcf 100644 --- a/apps/client/src/features/code/ui/CreateItemDialog.tsx +++ b/apps/client/src/features/code/ui/CreateItemDialog.tsx @@ -5,9 +5,9 @@ import { DialogHeader, DialogTitle, DialogFooter, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Button } from "@/shared/ui/button"; interface CreateItemDialogProps { isOpen: boolean; diff --git a/apps/client/src/features/code/ui/FileEditor.tsx b/apps/client/src/features/code/ui/FileEditor.tsx index 15cd4f7..cd737ab 100644 --- a/apps/client/src/features/code/ui/FileEditor.tsx +++ b/apps/client/src/features/code/ui/FileEditor.tsx @@ -1,5 +1,5 @@ -import { Button } from "@/components/ui/button"; -import { useIdeStore } from "@/entities/file/store"; +import { Button } from "@/shared/ui/button"; +import { useIdeStore } from "@/entities/file"; import Editor, { loader } from "@monaco-editor/react"; import { Loader2 } from "lucide-react"; import * as monaco from "monaco-editor"; diff --git a/apps/client/src/features/code/ui/FileTree.tsx b/apps/client/src/features/code/ui/FileTree.tsx index 51c43b9..4041c42 100644 --- a/apps/client/src/features/code/ui/FileTree.tsx +++ b/apps/client/src/features/code/ui/FileTree.tsx @@ -15,16 +15,16 @@ import { createNewFolder, renameEntry, deleteEntry, -} from "@/entities/file/api"; +} from "@/entities/file"; import { toast } from "sonner"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, -} from "@/components/ui/context-menu"; +} from "@/shared/ui/context-menu"; import { AlertDialog, AlertDialogAction, @@ -34,9 +34,9 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { useFileTreeStore, useIdeStore } from "@/entities/file/store"; -import { DirEntry } from "@/entities/file/types"; +} from "@/shared/ui/alert-dialog"; +import { useFileTreeStore, useIdeStore } from "@/entities/file"; +import { DirEntry } from "@/entities/file"; import { CreateItemDialog } from "./CreateItemDialog"; interface TreeNodeProps { diff --git a/apps/client/src/features/configurations/index.ts b/apps/client/src/features/configurations/index.ts new file mode 100644 index 0000000..b4aa917 --- /dev/null +++ b/apps/client/src/features/configurations/index.ts @@ -0,0 +1,3 @@ +export { ConfigurationActionButton } from "./ui/ConfigurationActionButton"; +export { ConfigurationCreate } from "./ui/ConfigurationCreate"; +export { ConfigurationCreateButton } from "./ui/ConfigurationCreateButton"; diff --git a/apps/client/src/features/configurations/ui/ConfigurationActionButton.tsx b/apps/client/src/features/configurations/ui/ConfigurationActionButton.tsx index 3161312..779a142 100644 --- a/apps/client/src/features/configurations/ui/ConfigurationActionButton.tsx +++ b/apps/client/src/features/configurations/ui/ConfigurationActionButton.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { MoreHorizontal, Pencil, Trash2 } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -9,13 +9,13 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, @@ -25,16 +25,16 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { Textarea } from "@/components/ui/textarea"; -import { useConfigStore } from "@/entities/configurations/store"; +} from "@/shared/ui/alert-dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Switch } from "@/shared/ui/switch"; +import { Textarea } from "@/shared/ui/textarea"; +import { useConfigStore } from "@/entities/configurations"; import { SystemConfiguration, SystemConfigurationPayload, -} from "@/entities/configurations/types"; +} from "@/entities/configurations"; interface Props { config: SystemConfiguration; diff --git a/apps/client/src/features/configurations/ui/ConfigurationCreate.tsx b/apps/client/src/features/configurations/ui/ConfigurationCreate.tsx index d57dd3e..b0e6e14 100644 --- a/apps/client/src/features/configurations/ui/ConfigurationCreate.tsx +++ b/apps/client/src/features/configurations/ui/ConfigurationCreate.tsx @@ -1,18 +1,18 @@ import { Controller, useForm } from "react-hook-form"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { Textarea } from "@/components/ui/textarea"; -import { useConfigStore } from "@/entities/configurations/store"; -import { SystemConfigurationPayload } from "@/entities/configurations/types"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Switch } from "@/shared/ui/switch"; +import { Textarea } from "@/shared/ui/textarea"; +import { useConfigStore } from "@/entities/configurations"; +import { SystemConfigurationPayload } from "@/entities/configurations"; export function ConfigurationCreate({ isOpen, diff --git a/apps/client/src/features/configurations/ui/ConfigurationCreateButton.tsx b/apps/client/src/features/configurations/ui/ConfigurationCreateButton.tsx index d43fc69..8d961e7 100644 --- a/apps/client/src/features/configurations/ui/ConfigurationCreateButton.tsx +++ b/apps/client/src/features/configurations/ui/ConfigurationCreateButton.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { ConfigurationCreate } from "./ConfigurationCreate"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { PlusCircle } from "lucide-react"; export function ConfigurationCreateButton() { diff --git a/apps/client/src/features/darkmode/index.ts b/apps/client/src/features/darkmode/index.ts new file mode 100644 index 0000000..f59aa1c --- /dev/null +++ b/apps/client/src/features/darkmode/index.ts @@ -0,0 +1 @@ +export { ModeToggle } from "./ui/ModeToggle"; diff --git a/apps/client/src/features/darkmode/ui/ModeToggle.tsx b/apps/client/src/features/darkmode/ui/ModeToggle.tsx index 181396c..8619622 100644 --- a/apps/client/src/features/darkmode/ui/ModeToggle.tsx +++ b/apps/client/src/features/darkmode/ui/ModeToggle.tsx @@ -1,12 +1,12 @@ import { Moon, Sun } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; import { useTheme } from "@/app/providers/theme-provider"; export function ModeToggle() { diff --git a/apps/client/src/features/dashboard-swipe/index.ts b/apps/client/src/features/dashboard-swipe/index.ts new file mode 100644 index 0000000..55ef2a7 --- /dev/null +++ b/apps/client/src/features/dashboard-swipe/index.ts @@ -0,0 +1,2 @@ +export { DashboardSwipeLayout, DashboardSwipeRoutePlaceholder } from "./ui/DashboardSwipeLayout"; +export { DashboardSwipeHeader } from "./ui/DashboardSwipeHeader"; diff --git a/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeHeader.tsx b/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeHeader.tsx index 47f2786..1be25ce 100644 --- a/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeHeader.tsx +++ b/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeHeader.tsx @@ -7,41 +7,41 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { Button } from "@/components/ui/button"; +} from "@/shared/ui/breadcrumb"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Separator } from "@/components/ui/separator"; -import { SidebarTrigger } from "@/components/ui/sidebar"; +} from "@/shared/ui/dropdown-menu"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Separator } from "@/shared/ui/separator"; +import { SidebarTrigger } from "@/shared/ui/sidebar"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; -import { useIntegrationStore } from "@/entities/integrations/store"; +} from "@/shared/ui/select"; +import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard"; +import { useIntegrationStore } from "@/entities/integrations"; import { MoreVertical, Pencil, Plus, Trash2 } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { diff --git a/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeLayout.tsx b/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeLayout.tsx index 712636a..2414256 100644 --- a/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeLayout.tsx +++ b/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeLayout.tsx @@ -1,10 +1,10 @@ import { Footer } from "@/features/footer"; import { AppSidebar } from "@/features/sidebar"; -import { WebRTCProvider } from "@/features/rtc/WebRTCProvider"; +import { WebRTCProvider } from "@/features/rtc"; import { SidebarInset, SidebarProvider, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { DashboardMainPanel, type DashboardMainPanelContentView, @@ -13,9 +13,9 @@ import { DynamicDashboardMainPanel, NewDynamicDashboardPanel, } from "@/pages/dynamic-dashboard"; -import type { DynamicDashboard } from "@/entities/dynamic-dashboard/store"; -import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; -import { useIntegrationStore } from "@/entities/integrations/store"; +import type { DynamicDashboard } from "@/entities/dynamic-dashboard"; +import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard"; +import { useIntegrationStore } from "@/entities/integrations"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Outlet, useLocation, useNavigate } from "react-router"; import { DashboardSwipeHeader } from "./DashboardSwipeHeader"; diff --git a/apps/client/src/features/device-token/index.ts b/apps/client/src/features/device-token/index.ts new file mode 100644 index 0000000..eaeb931 --- /dev/null +++ b/apps/client/src/features/device-token/index.ts @@ -0,0 +1 @@ +export { DeviceTokenManager } from "./ui/DeviceTokenManager"; diff --git a/apps/client/src/features/device-token/ui/DeviceTokenManager.tsx b/apps/client/src/features/device-token/ui/DeviceTokenManager.tsx index 6412ae9..2a6a772 100644 --- a/apps/client/src/features/device-token/ui/DeviceTokenManager.tsx +++ b/apps/client/src/features/device-token/ui/DeviceTokenManager.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { Copy, Check, Key, AlertTriangle, RefreshCw } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,7 +8,7 @@ import { DialogTitle, DialogDescription, DialogFooter, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { AlertDialog, AlertDialogAction, @@ -19,9 +19,9 @@ import { AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Badge } from "@/components/ui/badge"; -import { useDeviceTokenStore } from "@/entities/device-token/store"; +} from "@/shared/ui/alert-dialog"; +import { Badge } from "@/shared/ui/badge"; +import { useDeviceTokenStore } from "@/entities/device-token"; interface Props { deviceId: number; diff --git a/apps/client/src/features/device/index.ts b/apps/client/src/features/device/index.ts new file mode 100644 index 0000000..bf5ece4 --- /dev/null +++ b/apps/client/src/features/device/index.ts @@ -0,0 +1,4 @@ +export { DeviceCreateButton } from "./ui/DeviceCreateButton"; +export { DeviceDeleteButton } from "./ui/DeviceDeleteButton"; +export { DeviceKeyButton } from "./ui/DeviceKeyButton"; +export { DeviceUpdateButton } from "./ui/DeviceUpdateButton"; diff --git a/apps/client/src/features/device/ui/DeviceCreateButton.tsx b/apps/client/src/features/device/ui/DeviceCreateButton.tsx index 0cdc9b8..0cb65a6 100644 --- a/apps/client/src/features/device/ui/DeviceCreateButton.tsx +++ b/apps/client/src/features/device/ui/DeviceCreateButton.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,12 +8,12 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { PlusCircle } from "lucide-react"; -import { DevicePayload } from "@/entities/device/types"; -import { useDeviceStore } from "@/entities/device/store"; +import { DevicePayload } from "@/entities/device"; +import { useDeviceStore } from "@/entities/device"; export function DeviceCreateButton() { const [isOpen, setIsOpen] = useState(false); diff --git a/apps/client/src/features/device/ui/DeviceDeleteButton.tsx b/apps/client/src/features/device/ui/DeviceDeleteButton.tsx index a5d08ed..aa10fa2 100644 --- a/apps/client/src/features/device/ui/DeviceDeleteButton.tsx +++ b/apps/client/src/features/device/ui/DeviceDeleteButton.tsx @@ -9,11 +9,11 @@ import { AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; import { Trash2 } from "lucide-react"; -import { useDeviceStore } from "@/entities/device/store"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { Button } from "@/components/ui/button"; +import { useDeviceStore } from "@/entities/device"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; +import { Button } from "@/shared/ui/button"; interface Props { deviceId: number; diff --git a/apps/client/src/features/device/ui/DeviceKeyButton.tsx b/apps/client/src/features/device/ui/DeviceKeyButton.tsx index f30b549..d691329 100644 --- a/apps/client/src/features/device/ui/DeviceKeyButton.tsx +++ b/apps/client/src/features/device/ui/DeviceKeyButton.tsx @@ -6,10 +6,10 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { Key } from "lucide-react"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { DeviceTokenManager } from "@/features/device-token/DeviceTokenManager"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; +import { DeviceTokenManager } from "@/features/device-token"; interface Props { deviceId: number; diff --git a/apps/client/src/features/device/ui/DeviceUpdateButton.tsx b/apps/client/src/features/device/ui/DeviceUpdateButton.tsx index 38aac73..99c6c61 100644 --- a/apps/client/src/features/device/ui/DeviceUpdateButton.tsx +++ b/apps/client/src/features/device/ui/DeviceUpdateButton.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,13 +8,13 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Pencil } from "lucide-react"; -import { useDeviceStore } from "@/entities/device/store"; -import { Device, DevicePayload } from "@/entities/device/types"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { useDeviceStore } from "@/entities/device"; +import { Device, DevicePayload } from "@/entities/device"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; interface Props { device: Device; diff --git a/apps/client/src/features/dynamic-dashboard/index.ts b/apps/client/src/features/dynamic-dashboard/index.ts new file mode 100644 index 0000000..65505b9 --- /dev/null +++ b/apps/client/src/features/dynamic-dashboard/index.ts @@ -0,0 +1,5 @@ +export { GroupCanvas } from "./ui/GroupCanvas"; +export { FlowPanel } from "./ui/panels/FlowPanel"; +export { ButtonPanel } from "./ui/panels/ButtonPanel"; +export { MapPanel } from "./ui/panels/MapPanel"; +export * from "./lib/events/dispatcher"; diff --git a/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.test.ts b/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.test.ts index c1c9b44..f7ccf1e 100644 --- a/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.test.ts +++ b/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { createDashboardEventDispatcher } from "./dispatcher"; -import { DASHBOARD_COMPONENT_EVENT_VERSION } from "@/entities/dynamic-dashboard/interaction"; +import { DASHBOARD_COMPONENT_EVENT_VERSION } from "@/entities/dynamic-dashboard"; describe("createDashboardEventDispatcher", () => { const baseCtx = { diff --git a/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.ts b/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.ts index 35dd2f3..6041b01 100644 --- a/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.ts +++ b/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.ts @@ -3,8 +3,8 @@ import { type DashboardComponentEventPayload, type DashboardComponentAction, type DashboardComponentType, -} from "@/entities/dynamic-dashboard/interaction"; -import type { WebSocketMessage } from "@/features/ws/ws"; +} from "@/entities/dynamic-dashboard"; +import type { WebSocketMessage } from "@/features/ws"; export const DEFAULT_DASHBOARD_COOLDOWN_MS = 320; export const MIN_DASHBOARD_COOLDOWN_MS = 100; diff --git a/apps/client/src/features/dynamic-dashboard/ui/GroupCanvas.tsx b/apps/client/src/features/dynamic-dashboard/ui/GroupCanvas.tsx index 801753e..9d00dab 100644 --- a/apps/client/src/features/dynamic-dashboard/ui/GroupCanvas.tsx +++ b/apps/client/src/features/dynamic-dashboard/ui/GroupCanvas.tsx @@ -1,24 +1,24 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { PointerEvent as ReactPointerEvent } from "react"; import { toast } from "sonner"; -import { Button } from "@/components/ui/button"; -import { Card } from "@/components/ui/card"; -import { resolveItemPositionOrNull } from "@/entities/dynamic-dashboard/layoutResolve"; +import { Button } from "@/shared/ui/button"; +import { Card } from "@/shared/ui/card"; +import { resolveItemPositionOrNull } from "@/entities/dynamic-dashboard"; import { DashboardGroup, DashboardItem, useDynamicDashboardStore, -} from "@/entities/dynamic-dashboard/store"; -import { EntityAll } from "@/entities/entity/types"; -import { StreamState } from "@/features/entity/useEntitiesData"; -import { EntityCard } from "@/features/entity/Card"; -import { StreamReceiver } from "@/features/rtc/StreamReceiver"; +} from "@/entities/dynamic-dashboard"; +import { EntityAll } from "@/entities/entity"; +import { StreamState } from "@/features/entity"; +import { EntityCard } from "@/features/entity"; +import { StreamReceiver } from "@/features/rtc"; import { Dialog, DialogContent, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { DropdownMenu, DropdownMenuContent, @@ -27,19 +27,19 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dropdown-menu"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Plus, X } from "lucide-react"; -import { useSidebar } from "@/components/ui/sidebar"; -import { useFlowStore } from "@/entities/flow/store"; -import { useMapDataStore } from "@/entities/map/store"; +import { useSidebar } from "@/shared/ui/sidebar"; +import { useFlowStore } from "@/entities/flow"; +import { useMapDataStore } from "@/entities/map"; import { MapPanel } from "./panels/MapPanel"; import { FlowPanel } from "./panels/FlowPanel"; -import { useWebSocket } from "@/features/ws/WebSocketProvider"; -import { getFlowRunSessionId } from "@/features/ws/ws"; -import { createDashboardEventDispatcher } from "./events/dispatcher"; -import { isValidListenerId } from "@/entities/dynamic-dashboard/interaction"; +import { useWebSocket } from "@/features/ws"; +import { getFlowRunSessionId } from "@/features/ws"; +import { createDashboardEventDispatcher } from "../lib/events/dispatcher"; +import { isValidListenerId } from "@/entities/dynamic-dashboard"; const isMapItem = ( candidate: DashboardItem, diff --git a/apps/client/src/features/dynamic-dashboard/ui/panels/ButtonPanel.tsx b/apps/client/src/features/dynamic-dashboard/ui/panels/ButtonPanel.tsx index 081acbb..898128c 100644 --- a/apps/client/src/features/dynamic-dashboard/ui/panels/ButtonPanel.tsx +++ b/apps/client/src/features/dynamic-dashboard/ui/panels/ButtonPanel.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import type { DashboardItemDataMap } from "@/entities/dynamic-dashboard/store"; -import { isValidListenerId } from "@/entities/dynamic-dashboard/interaction"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import type { DashboardItemDataMap } from "@/entities/dynamic-dashboard"; +import { isValidListenerId } from "@/entities/dynamic-dashboard"; type ButtonPanelProps = { data?: DashboardItemDataMap["button"]; diff --git a/apps/client/src/features/dynamic-dashboard/ui/panels/FlowPanel.tsx b/apps/client/src/features/dynamic-dashboard/ui/panels/FlowPanel.tsx index b863637..f5c32f5 100644 --- a/apps/client/src/features/dynamic-dashboard/ui/panels/FlowPanel.tsx +++ b/apps/client/src/features/dynamic-dashboard/ui/panels/FlowPanel.tsx @@ -1,20 +1,20 @@ import { useEffect, useMemo, useState } from "react"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; +import { Badge } from "@/shared/ui/badge"; +import { Button } from "@/shared/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { useFlowStore } from "@/entities/flow/store"; +} from "@/shared/ui/select"; +import { useFlowStore } from "@/entities/flow"; import { useWebSocket, useWebSocketMessage, -} from "@/features/ws/WebSocketProvider"; -import { getFlowRunSessionId, WebSocketMessage } from "@/features/ws/ws"; -import { DashboardItemDataMap } from "@/entities/dynamic-dashboard/store"; +} from "@/features/ws"; +import { getFlowRunSessionId, WebSocketMessage } from "@/features/ws"; +import { DashboardItemDataMap } from "@/entities/dynamic-dashboard"; import { Play, Square } from "lucide-react"; type FlowPanelProps = { diff --git a/apps/client/src/features/dynamic-dashboard/ui/panels/MapPanel.tsx b/apps/client/src/features/dynamic-dashboard/ui/panels/MapPanel.tsx index 5008d2c..96642ac 100644 --- a/apps/client/src/features/dynamic-dashboard/ui/panels/MapPanel.tsx +++ b/apps/client/src/features/dynamic-dashboard/ui/panels/MapPanel.tsx @@ -1,17 +1,17 @@ import { useEffect, useMemo, useState } from "react"; import { MapContainer, TileLayer, useMap } from "react-leaflet"; import "leaflet/dist/leaflet.css"; -import { Badge } from "@/components/ui/badge"; +import { Badge } from "@/shared/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { useMapDataStore, useMapInteractionStore } from "@/entities/map/store"; -import { FeatureRenderer } from "@/features/map-draw/FeatureRenderer"; -import { DashboardItemDataMap } from "@/entities/dynamic-dashboard/store"; +} from "@/shared/ui/select"; +import { useMapDataStore, useMapInteractionStore } from "@/entities/map"; +import { FeatureRenderer } from "@/features/map-draw"; +import { DashboardItemDataMap } from "@/entities/dynamic-dashboard"; type MapPanelProps = { data?: DashboardItemDataMap["map"]; diff --git a/apps/client/src/features/entity/index.ts b/apps/client/src/features/entity/index.ts new file mode 100644 index 0000000..1a1b94d --- /dev/null +++ b/apps/client/src/features/entity/index.ts @@ -0,0 +1,11 @@ +export { AllEntities } from "./ui/AllEntities"; +export { AnalyzeMenuItem } from "./ui/AnalyzeMenuItem"; +export { EntityCard } from "./ui/Card"; +export { EntityCreateButton } from "./ui/EntityCreateButton"; +export { EntityDeleteButton } from "./ui/EntityDeleteButton"; +export { EntityUpdateButton } from "./ui/EntityUpdateButton"; +export { EntitySelectPlatforms } from "./ui/SelectPlatforms"; +export { EntitySelectTypes } from "./ui/SelectTypes"; +export { StateHistorySheet } from "./ui/StateHistorySheet"; +export { useEntitiesData } from "./model/useEntitiesData"; +export type { StreamState, ChangeStatePayload } from "./model/useEntitiesData"; diff --git a/apps/client/src/features/entity/model/useEntitiesData.ts b/apps/client/src/features/entity/model/useEntitiesData.ts index a72f9b8..54ecbe8 100644 --- a/apps/client/src/features/entity/model/useEntitiesData.ts +++ b/apps/client/src/features/entity/model/useEntitiesData.ts @@ -1,8 +1,8 @@ import { useCallback, useEffect, useState } from "react"; -import * as api from "@/entities/entity/api"; -import { EntityAll, State } from "@/entities/entity/types"; -import { useWebSocket, useWebSocketMessage } from "../ws/WebSocketProvider"; -import { WebSocketMessage } from "../ws/ws"; +import * as api from "@/entities/entity"; +import { EntityAll, State } from "@/entities/entity"; +import { useWebSocket, useWebSocketMessage } from "../../ws"; +import { WebSocketMessage } from "../../ws"; export type StreamState = { topic: string; diff --git a/apps/client/src/features/entity/ui/AllEntities.tsx b/apps/client/src/features/entity/ui/AllEntities.tsx index fe7c9fb..a279b65 100644 --- a/apps/client/src/features/entity/ui/AllEntities.tsx +++ b/apps/client/src/features/entity/ui/AllEntities.tsx @@ -1,10 +1,10 @@ import { Fragment } from "react"; -// import { EntityAll } from "@/entities/entity/types"; +// import { EntityAll } from "@/entities/entity"; import { EntityCard } from "./Card"; import { // StreamState, useEntitiesData, -} from "@/features/entity/useEntitiesData"; +} from "@/features/entity"; export function AllEntities() { const { entities, streamsState } = useEntitiesData(); diff --git a/apps/client/src/features/entity/ui/AnalyzeMenuItem.tsx b/apps/client/src/features/entity/ui/AnalyzeMenuItem.tsx index 48cf25f..817684f 100644 --- a/apps/client/src/features/entity/ui/AnalyzeMenuItem.tsx +++ b/apps/client/src/features/entity/ui/AnalyzeMenuItem.tsx @@ -1,10 +1,10 @@ import { useState } from "react"; import { Eye, Loader2 } from "lucide-react"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { useWebRTC } from "../rtc/WebRTCProvider"; -import { captureFrameFromStream } from "../rtc/captureFrame"; -import { useChatStore } from "../llm-chat/store"; -import { cn } from "@/lib/utils"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; +import { useWebRTC } from "../../rtc"; +import { captureFrameFromStream } from "../../rtc"; +import { useChatStore } from "../../llm-chat"; +import { cn } from "@/shared/lib/utils"; interface AnalyzeMenuItemProps { topic: string; diff --git a/apps/client/src/features/entity/ui/Card.tsx b/apps/client/src/features/entity/ui/Card.tsx index 64ec3e4..716244c 100644 --- a/apps/client/src/features/entity/ui/Card.tsx +++ b/apps/client/src/features/entity/ui/Card.tsx @@ -1,22 +1,22 @@ import { useState } from "react"; -import { EntityAll } from "@/entities/entity/types"; +import { EntityAll } from "@/entities/entity"; import { Card, CardHeader, CardDescription, CardTitle, CardFooter, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Button } from "@/components/ui/button"; +} from "@/shared/ui/dropdown-menu"; +import { Button } from "@/shared/ui/button"; import { MoreVertical } from "lucide-react"; -import { formatSimpleDateTime } from "@/lib/time"; -import { StreamReceiver } from "../rtc/StreamReceiver"; -import { RecordingMenuItem } from "../recording/RecordingButton"; +import { formatSimpleDateTime } from "@/shared/lib/time"; +import { StreamReceiver } from "../../rtc"; +import { RecordingMenuItem } from "../../recording"; import { AnalyzeMenuItem } from "./AnalyzeMenuItem"; import { StateHistorySheet } from "./StateHistorySheet"; import { useNavigate } from "react-router"; diff --git a/apps/client/src/features/entity/ui/EntityCreateButton.tsx b/apps/client/src/features/entity/ui/EntityCreateButton.tsx index 84a582c..b680ecf 100644 --- a/apps/client/src/features/entity/ui/EntityCreateButton.tsx +++ b/apps/client/src/features/entity/ui/EntityCreateButton.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -9,24 +9,24 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { AlertCircleIcon, PlusCircle } from "lucide-react"; -import { useDeviceStore } from "@/entities/device/store"; -import { useEntityStore } from "@/entities/entity/store"; -import { EntityPayload } from "@/entities/entity/types"; +import { useDeviceStore } from "@/entities/device"; +import { useEntityStore } from "@/entities/entity"; +import { EntityPayload } from "@/entities/entity"; import { Select, SelectContent, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { JsonCodeEditor } from "../json/JsonEditor"; +} from "@/shared/ui/select"; +import { JsonCodeEditor } from "../../json"; import { EntitySelectTypes } from "./SelectTypes"; import { EntitySelectPlatforms } from "./SelectPlatforms"; import { toast } from "sonner"; -import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; +import { Alert, AlertTitle, AlertDescription } from "@/shared/ui/alert"; export function EntityCreateButton() { const [isOpen, setIsOpen] = useState(false); diff --git a/apps/client/src/features/entity/ui/EntityDeleteButton.tsx b/apps/client/src/features/entity/ui/EntityDeleteButton.tsx index d647b9c..eea3399 100644 --- a/apps/client/src/features/entity/ui/EntityDeleteButton.tsx +++ b/apps/client/src/features/entity/ui/EntityDeleteButton.tsx @@ -9,10 +9,10 @@ import { AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; import { Trash2 } from "lucide-react"; -import { useEntityStore } from "@/entities/entity/store"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { useEntityStore } from "@/entities/entity"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; interface Props { entityId: number; diff --git a/apps/client/src/features/entity/ui/EntityUpdateButton.tsx b/apps/client/src/features/entity/ui/EntityUpdateButton.tsx index 77fc527..de363fc 100644 --- a/apps/client/src/features/entity/ui/EntityUpdateButton.tsx +++ b/apps/client/src/features/entity/ui/EntityUpdateButton.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useForm, Controller } from "react-hook-form"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,24 +8,24 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Select, SelectContent, SelectTrigger, SelectValue, -} from "@/components/ui/select"; +} from "@/shared/ui/select"; import { AlertCircleIcon, Pencil } from "lucide-react"; -import { useEntityStore } from "@/entities/entity/store"; -import { Entity, EntityPayload } from "@/entities/entity/types"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { JsonCodeEditor } from "../json/JsonEditor"; +import { useEntityStore } from "@/entities/entity"; +import { Entity, EntityPayload } from "@/entities/entity"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; +import { JsonCodeEditor } from "../../json"; import { EntitySelectTypes } from "./SelectTypes"; import { EntitySelectPlatforms } from "./SelectPlatforms"; import { toast } from "sonner"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Alert, AlertDescription, AlertTitle } from "@/shared/ui/alert"; interface Props { entity: Entity; diff --git a/apps/client/src/features/entity/ui/SelectPlatforms.tsx b/apps/client/src/features/entity/ui/SelectPlatforms.tsx index 8d28092..8baaba7 100644 --- a/apps/client/src/features/entity/ui/SelectPlatforms.tsx +++ b/apps/client/src/features/entity/ui/SelectPlatforms.tsx @@ -1,4 +1,4 @@ -import { SelectItem } from "@/components/ui/select"; +import { SelectItem } from "@/shared/ui/select"; export function EntitySelectPlatforms() { return ( diff --git a/apps/client/src/features/entity/ui/SelectTypes.tsx b/apps/client/src/features/entity/ui/SelectTypes.tsx index 98e9df2..b7b099f 100644 --- a/apps/client/src/features/entity/ui/SelectTypes.tsx +++ b/apps/client/src/features/entity/ui/SelectTypes.tsx @@ -1,4 +1,4 @@ -import { SelectItem } from "@/components/ui/select"; +import { SelectItem } from "@/shared/ui/select"; import { Baseline, Database, Locate, Play } from "lucide-react"; export function EntitySelectTypes() { diff --git a/apps/client/src/features/entity/ui/StateHistorySheet.tsx b/apps/client/src/features/entity/ui/StateHistorySheet.tsx index b644eed..98a2793 100644 --- a/apps/client/src/features/entity/ui/StateHistorySheet.tsx +++ b/apps/client/src/features/entity/ui/StateHistorySheet.tsx @@ -6,18 +6,18 @@ import { SheetDescription, SheetHeader, SheetTitle, -} from "@/components/ui/sheet"; -import { ScrollArea } from "@/components/ui/scroll-area"; +} from "@/shared/ui/sheet"; +import { ScrollArea } from "@/shared/ui/scroll-area"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/ui/tooltip"; -import { Skeleton } from "@/components/ui/skeleton"; -import { formatSimpleDateTime } from "@/lib/time"; -import * as api from "@/entities/entity/api"; -import type { State } from "@/entities/entity/types"; +} from "@/shared/ui/tooltip"; +import { Skeleton } from "@/shared/ui/skeleton"; +import { formatSimpleDateTime } from "@/shared/lib/time"; +import * as api from "@/entities/entity"; +import type { State } from "@/entities/entity"; type Props = { entityId: string; diff --git a/apps/client/src/features/error/index.ts b/apps/client/src/features/error/index.ts new file mode 100644 index 0000000..fe8046f --- /dev/null +++ b/apps/client/src/features/error/index.ts @@ -0,0 +1 @@ +export { ErrorRender } from "./ui/ErrorRender"; diff --git a/apps/client/src/features/error/ui/ErrorRender.tsx b/apps/client/src/features/error/ui/ErrorRender.tsx index 0ee0025..477ded5 100644 --- a/apps/client/src/features/error/ui/ErrorRender.tsx +++ b/apps/client/src/features/error/ui/ErrorRender.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; export function ErrorRender() { return ( diff --git a/apps/client/src/features/flow-log/index.ts b/apps/client/src/features/flow-log/index.ts new file mode 100644 index 0000000..6dd2a0a --- /dev/null +++ b/apps/client/src/features/flow-log/index.ts @@ -0,0 +1 @@ +export { FlowLog } from "./ui/FlowLog"; diff --git a/apps/client/src/features/flow-log/ui/FlowLog.tsx b/apps/client/src/features/flow-log/ui/FlowLog.tsx index 1625684..a5f0be2 100644 --- a/apps/client/src/features/flow-log/ui/FlowLog.tsx +++ b/apps/client/src/features/flow-log/ui/FlowLog.tsx @@ -1,7 +1,7 @@ import { useEffect, useState, useRef, useCallback } from "react"; import { X, ChevronDown, ChevronUp, Terminal } from "lucide-react"; -import { useWebSocketMessage } from "../ws/WebSocketProvider"; -import { WebSocketMessage } from "../ws/ws"; +import { useWebSocketMessage } from "../../ws"; +import { WebSocketMessage } from "../../ws"; export function FlowLog() { const [logMessages, setLogMessages] = useState([]); diff --git a/apps/client/src/features/flow/index.ts b/apps/client/src/features/flow/index.ts new file mode 100644 index 0000000..1f11a09 --- /dev/null +++ b/apps/client/src/features/flow/index.ts @@ -0,0 +1,10 @@ +export { default, FlowHeader, FlowSidebar } from "./ui/Flow"; +export { Graph } from "./ui/Graph"; +export { Options } from "./ui/Options"; +export { AddCustomNode } from "./ui/AddCustomNode"; +export { RunFlowButton } from "./ui/RunFlow"; +export { SelectedItemActions } from "./ui/SelectedItemActions"; +export * from "./model/types"; +export { DEFINITION_NODE } from "./lib/flowNode"; +export * from "./lib/flowUtils"; +export * from "./lib/flow-chat"; diff --git a/apps/client/src/features/flow/lib/flow-chat/buildSystemPrompt.ts b/apps/client/src/features/flow/lib/flow-chat/buildSystemPrompt.ts index 2b2560e..5eeb2fe 100644 --- a/apps/client/src/features/flow/lib/flow-chat/buildSystemPrompt.ts +++ b/apps/client/src/features/flow/lib/flow-chat/buildSystemPrompt.ts @@ -1,6 +1,6 @@ -import type { Node, Edge } from "@/features/flow/flowTypes"; -import type { Flow } from "@/entities/flow/types"; -import { DEFINITION_NODE } from "@/features/flow/flowNode"; +import type { Node, Edge } from "../../model/types"; +import type { Flow } from "@/entities/flow"; +import { DEFINITION_NODE } from "../flowNode"; export function buildFlowSystemPrompt( currentNodes: Node[], diff --git a/apps/client/src/features/flow/lib/flow-chat/executeToolCalls.ts b/apps/client/src/features/flow/lib/flow-chat/executeToolCalls.ts index 1cae51a..56427d1 100644 --- a/apps/client/src/features/flow/lib/flow-chat/executeToolCalls.ts +++ b/apps/client/src/features/flow/lib/flow-chat/executeToolCalls.ts @@ -1,5 +1,5 @@ import type { ToolCallResult } from "@vessel/capsule-client"; -import type { Node, Edge, DataNodeType } from "@/features/flow/flowTypes"; +import type { Node, Edge, DataNodeType } from "../../model/types"; export interface ToolExecutionResult { toolCallId: string; diff --git a/apps/client/src/features/flow/lib/flowNode.ts b/apps/client/src/features/flow/lib/flowNode.ts index f7ddc43..360abcb 100644 --- a/apps/client/src/features/flow/lib/flowNode.ts +++ b/apps/client/src/features/flow/lib/flowNode.ts @@ -1,4 +1,4 @@ -import { Node } from "./flowTypes"; +import { Node } from "../model/types"; export type DefaultValueType = { connectors: Node["connectors"]; diff --git a/apps/client/src/features/flow/lib/flowUtils.ts b/apps/client/src/features/flow/lib/flowUtils.ts index d03183f..b7fc1aa 100644 --- a/apps/client/src/features/flow/lib/flowUtils.ts +++ b/apps/client/src/features/flow/lib/flowUtils.ts @@ -1,6 +1,6 @@ -import { CustomNode, CustomNodeDynamicData } from "@/entities/custom-nodes/types"; +import { CustomNode, CustomNodeDynamicData } from "@/entities/custom-nodes"; import { DEFINITION_NODE } from "./flowNode"; -import { NodeTypes, Node, DataNodeTypeType } from "./flowTypes"; +import { NodeTypes, Node, DataNodeTypeType } from "../model/types"; export function getDefalutValue(type: NodeTypes, id: string) { const value = JSON.parse(JSON.stringify(DEFINITION_NODE[type])); diff --git a/apps/client/src/features/flow/model/types.ts b/apps/client/src/features/flow/model/types.ts index 92500c6..9201cef 100644 --- a/apps/client/src/features/flow/model/types.ts +++ b/apps/client/src/features/flow/model/types.ts @@ -1,4 +1,4 @@ -import { DEFINITION_NODE } from "./flowNode"; +import { DEFINITION_NODE } from "../lib/flowNode"; export type Connector = { id: string; diff --git a/apps/client/src/features/flow/ui/AddCustomNode.tsx b/apps/client/src/features/flow/ui/AddCustomNode.tsx index a4325be..0bde32c 100644 --- a/apps/client/src/features/flow/ui/AddCustomNode.tsx +++ b/apps/client/src/features/flow/ui/AddCustomNode.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,7 +8,7 @@ import { DialogTitle, DialogTrigger, DialogFooter, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { AlertDialog, AlertDialogAction, @@ -18,10 +18,10 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Badge } from "@/components/ui/badge"; +} from "@/shared/ui/alert-dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Badge } from "@/shared/ui/badge"; import { Blocks, Edit, @@ -30,12 +30,12 @@ import { Loader2, Download, } from "lucide-react"; -import { useCustomNodeStore } from "@/entities/custom-nodes/store"; +import { useCustomNodeStore } from "@/entities/custom-nodes"; import { CustomNode, CustomNodeDynamicData, CustomNodeFromApi, -} from "@/entities/custom-nodes/types"; +} from "@/entities/custom-nodes"; import { RHAI_PRESETS, getPresetCategories, @@ -43,9 +43,9 @@ import { presetToApiPayload, type RhaiPreset, type PresetCategory, -} from "@/entities/custom-nodes/presets"; +} from "@/entities/custom-nodes"; import { toast } from "sonner"; -import { JsonCodeEditor } from "../json/JsonEditor"; +import { JsonCodeEditor } from "../../json"; function CustomNodeForm({ onSubmit, diff --git a/apps/client/src/features/flow/ui/Flow.tsx b/apps/client/src/features/flow/ui/Flow.tsx index 438453d..b51e283 100644 --- a/apps/client/src/features/flow/ui/Flow.tsx +++ b/apps/client/src/features/flow/ui/Flow.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { Graph } from "./Graph"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, @@ -11,19 +11,19 @@ import { DialogTitle, DialogTrigger, DialogClose, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { MoreHorizontal, Plus, Trash2 } from "lucide-react"; import { File } from "lucide-react"; -import { deleteFlow } from "@/entities/flow/api"; -import { Flow } from "@/entities/flow/types"; +import { deleteFlow } from "@/entities/flow"; +import { Flow } from "@/entities/flow"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; import { AlertDialogHeader, AlertDialogFooter, @@ -33,10 +33,10 @@ import { AlertDialogContent, AlertDialogDescription, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { useFlowStore } from "@/entities/flow/store"; +} from "@/shared/ui/alert-dialog"; +import { useFlowStore } from "@/entities/flow"; import { RunFlowButton } from "./RunFlow"; -import { FlowLog } from "../flow-log/FlowLog"; +import { FlowLog } from "../../flow-log"; export default function FlowPage() { const { diff --git a/apps/client/src/features/flow/ui/Graph.tsx b/apps/client/src/features/flow/ui/Graph.tsx index 2d4a048..e197e93 100644 --- a/apps/client/src/features/flow/ui/Graph.tsx +++ b/apps/client/src/features/flow/ui/Graph.tsx @@ -1,7 +1,7 @@ import { useState, useRef, useEffect, useCallback, useMemo } from "react"; import * as d3 from "d3"; import { Plus, Minus, Lock, LockOpen, SquarePlus } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { GraphProps, Node, @@ -9,7 +9,7 @@ import { Connector, NodeRenderer, NodeTypes, -} from "./flowTypes"; +} from "../model/types"; // import { renderButtonNode } from "./nodes/ButtonNode"; import { renderTitleNode } from "./nodes/TitleNode"; @@ -23,21 +23,21 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; import { renderProcessingNode } from "./nodes/ProcessingNode"; -import { getCustomNode, getDefalutNode } from "./flowUtils"; +import { getCustomNode, getDefalutNode } from "../lib/flowUtils"; import { renderVarNode } from "./nodes/VarNode"; import { renderCalcNode } from "./nodes/CalcNode"; import { renderHttpNode } from "./nodes/HttpNode"; -import { formatConstantCase } from "@/lib/string"; +import { formatConstantCase } from "@/shared/lib/string"; import { renderLogicNode } from "./nodes/LogicNode"; import { renderIntervalNode } from "./nodes/IntervalNode"; import { renderMQTTNode } from "./nodes/MQTTNode"; import { renderButtonNode } from "./nodes/ButtonNode"; import { zoomIdentity } from "d3-zoom"; import { AddCustomNode } from "./AddCustomNode"; -import { useCustomNodeStore } from "@/entities/custom-nodes/store"; -import { useIntegrationStore } from "@/entities/integrations/store"; +import { useCustomNodeStore } from "@/entities/custom-nodes"; +import { useIntegrationStore } from "@/entities/integrations"; import { SelectedItemActions } from "./SelectedItemActions"; type NodeGroup = { diff --git a/apps/client/src/features/flow/ui/Options.tsx b/apps/client/src/features/flow/ui/Options.tsx index d5ad0f3..f8fd5ea 100644 --- a/apps/client/src/features/flow/ui/Options.tsx +++ b/apps/client/src/features/flow/ui/Options.tsx @@ -1,14 +1,14 @@ import { useState, useEffect, useCallback } from "react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; +} from "@/shared/ui/select"; import { Sheet, SheetContent, @@ -16,9 +16,9 @@ import { SheetFooter, SheetHeader, SheetTitle, -} from "@/components/ui/sheet"; -import { DataNodeType, DataNodeTypeType, Node } from "./flowTypes"; -import { useFlowStore } from "@/entities/flow/store"; +} from "@/shared/ui/sheet"; +import { DataNodeType, DataNodeTypeType, Node } from "../model/types"; +import { useFlowStore } from "@/entities/flow"; interface OptionsProps { open: boolean; diff --git a/apps/client/src/features/flow/ui/RunFlow.tsx b/apps/client/src/features/flow/ui/RunFlow.tsx index a9ceb03..ce7422e 100644 --- a/apps/client/src/features/flow/ui/RunFlow.tsx +++ b/apps/client/src/features/flow/ui/RunFlow.tsx @@ -1,10 +1,10 @@ -import { Button } from "@/components/ui/button"; -import { useFlowStore } from "@/entities/flow/store"; +import { Button } from "@/shared/ui/button"; +import { useFlowStore } from "@/entities/flow"; import { Play, Square } from "lucide-react"; import { toast } from "sonner"; -import { useWebSocket, useWebSocketMessage } from "@/features/ws/WebSocketProvider"; +import { useWebSocket, useWebSocketMessage } from "@/features/ws"; import { useCallback, useEffect, useState } from "react"; -import { getFlowRunSessionId, WebSocketMessage } from "@/features/ws/ws"; +import { getFlowRunSessionId, WebSocketMessage } from "@/features/ws"; export function RunFlowButton() { const { wsManager } = useWebSocket(); diff --git a/apps/client/src/features/flow/ui/SelectedItemActions.tsx b/apps/client/src/features/flow/ui/SelectedItemActions.tsx index 509319a..0dd5c4d 100644 --- a/apps/client/src/features/flow/ui/SelectedItemActions.tsx +++ b/apps/client/src/features/flow/ui/SelectedItemActions.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Trash2 } from "lucide-react"; type SelectedElement = { diff --git a/apps/client/src/features/flow/ui/nodes/ButtonNode.tsx b/apps/client/src/features/flow/ui/nodes/ButtonNode.tsx index f6826d9..7db9463 100644 --- a/apps/client/src/features/flow/ui/nodes/ButtonNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/ButtonNode.tsx @@ -1,4 +1,4 @@ -import { Node } from "../flowTypes"; +import { Node } from "../../model/types"; export function renderButtonNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/ui/nodes/CalcNode.tsx b/apps/client/src/features/flow/ui/nodes/CalcNode.tsx index 0d391a1..a9a432c 100644 --- a/apps/client/src/features/flow/ui/nodes/CalcNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/CalcNode.tsx @@ -1,4 +1,4 @@ -import { CalculationNodeType, Node } from "../flowTypes"; +import { CalculationNodeType, Node } from "../../model/types"; export function renderCalcNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/ui/nodes/HttpNode.tsx b/apps/client/src/features/flow/ui/nodes/HttpNode.tsx index e052daf..647f925 100644 --- a/apps/client/src/features/flow/ui/nodes/HttpNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/HttpNode.tsx @@ -1,4 +1,4 @@ -import { HTTPRequestNodeType, Node } from "../flowTypes"; +import { HTTPRequestNodeType, Node } from "../../model/types"; export function renderHttpNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/ui/nodes/IntervalNode.tsx b/apps/client/src/features/flow/ui/nodes/IntervalNode.tsx index 6d13048..baa8977 100644 --- a/apps/client/src/features/flow/ui/nodes/IntervalNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/IntervalNode.tsx @@ -1,4 +1,4 @@ -import { IntervalNodeType, Node } from "../flowTypes"; +import { IntervalNodeType, Node } from "../../model/types"; export function renderIntervalNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/ui/nodes/LogicNode.tsx b/apps/client/src/features/flow/ui/nodes/LogicNode.tsx index 4560b24..63ccffc 100644 --- a/apps/client/src/features/flow/ui/nodes/LogicNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/LogicNode.tsx @@ -1,4 +1,4 @@ -import { LogicOpetatorNodeType, Node } from "../flowTypes"; +import { LogicOpetatorNodeType, Node } from "../../model/types"; export function renderLogicNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/ui/nodes/LoopNode.tsx b/apps/client/src/features/flow/ui/nodes/LoopNode.tsx index 61dff9e..2edafe2 100644 --- a/apps/client/src/features/flow/ui/nodes/LoopNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/LoopNode.tsx @@ -1,4 +1,4 @@ -import { LoopNodeType, Node } from "../flowTypes"; +import { LoopNodeType, Node } from "../../model/types"; export function renderLoopNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/ui/nodes/MQTTNode.tsx b/apps/client/src/features/flow/ui/nodes/MQTTNode.tsx index d2d1e5b..512e9a5 100644 --- a/apps/client/src/features/flow/ui/nodes/MQTTNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/MQTTNode.tsx @@ -1,4 +1,4 @@ -import { MqttPublishNodeType, Node } from "../flowTypes"; +import { MqttPublishNodeType, Node } from "../../model/types"; function mqttLikeCenterLabel(d: Node): string { if (d.nodeType === "DASHBOARD_EVENT_LISTENER") { diff --git a/apps/client/src/features/flow/ui/nodes/NumberNode.tsx b/apps/client/src/features/flow/ui/nodes/NumberNode.tsx index fb839ea..b2d272c 100644 --- a/apps/client/src/features/flow/ui/nodes/NumberNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/NumberNode.tsx @@ -1,4 +1,4 @@ -import { Node, NumberNodeType } from "../flowTypes"; +import { Node, NumberNodeType } from "../../model/types"; export function renderNumberNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/ui/nodes/ProcessingNode.tsx b/apps/client/src/features/flow/ui/nodes/ProcessingNode.tsx index db82d8e..9b08294 100644 --- a/apps/client/src/features/flow/ui/nodes/ProcessingNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/ProcessingNode.tsx @@ -1,4 +1,4 @@ -import { Node } from "../flowTypes"; +import { Node } from "../../model/types"; export function renderProcessingNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/ui/nodes/TitleNode.tsx b/apps/client/src/features/flow/ui/nodes/TitleNode.tsx index 7db174c..3f43714 100644 --- a/apps/client/src/features/flow/ui/nodes/TitleNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/TitleNode.tsx @@ -1,4 +1,4 @@ -import { Node } from "../flowTypes"; +import { Node } from "../../model/types"; export function renderTitleNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/ui/nodes/VarNode.tsx b/apps/client/src/features/flow/ui/nodes/VarNode.tsx index a6d4446..a7c3cd7 100644 --- a/apps/client/src/features/flow/ui/nodes/VarNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/VarNode.tsx @@ -1,4 +1,4 @@ -import { Node } from "../flowTypes"; +import { Node } from "../../model/types"; export function renderVarNode( g: d3.Selection, diff --git a/apps/client/src/features/footer/index.ts b/apps/client/src/features/footer/index.ts new file mode 100644 index 0000000..079c661 --- /dev/null +++ b/apps/client/src/features/footer/index.ts @@ -0,0 +1 @@ +export { Footer } from "./ui/Footer"; diff --git a/apps/client/src/features/footer/ui/Footer.tsx b/apps/client/src/features/footer/ui/Footer.tsx index e9622ca..4f10034 100644 --- a/apps/client/src/features/footer/ui/Footer.tsx +++ b/apps/client/src/features/footer/ui/Footer.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from "react"; -import { useWebSocket, useWebSocketMessage } from "../ws/WebSocketProvider"; -import { WebSocketMessage } from "../ws/ws"; +import { useWebSocket, useWebSocketMessage } from "../../ws"; +import { WebSocketMessage } from "../../ws"; export function Footer() { const { wsManager } = useWebSocket(); @@ -50,10 +50,10 @@ export function Footer() { ); } -import { WebSocketStatusIndicator } from "../ws/IsConnected"; -import { parseJwt } from "@/lib/jwt"; -import { isDemoMode } from "@/shared/demo"; -import { storage } from "@/lib/storage"; +import { WebSocketStatusIndicator } from "../../ws"; +import { parseJwt } from "@/shared/lib/jwt"; +import { isDemoMode } from "@/shared/config/demo"; +import { storage } from "@/shared/lib/storage"; const TokenExpiration: React.FC = () => { const [timeLeft, setTimeLeft] = useState(""); diff --git a/apps/client/src/features/gps/index.ts b/apps/client/src/features/gps/index.ts new file mode 100644 index 0000000..86fd2b4 --- /dev/null +++ b/apps/client/src/features/gps/index.ts @@ -0,0 +1 @@ +export * from "./lib/parseGps"; diff --git a/apps/client/src/features/ha/index.ts b/apps/client/src/features/ha/index.ts new file mode 100644 index 0000000..6ca171f --- /dev/null +++ b/apps/client/src/features/ha/index.ts @@ -0,0 +1 @@ +export { HaDashboard } from "./ui/HaDashboard"; diff --git a/apps/client/src/features/ha/ui/HaDashboard.tsx b/apps/client/src/features/ha/ui/HaDashboard.tsx index e1452a1..b5bdfdb 100644 --- a/apps/client/src/features/ha/ui/HaDashboard.tsx +++ b/apps/client/src/features/ha/ui/HaDashboard.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from "react"; -import { useHaStore } from "@/entities/ha/store"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { useHaStore } from "@/entities/ha"; +import { Alert, AlertDescription, AlertTitle } from "@/shared/ui/alert"; import { Loader2, AlertTriangle } from "lucide-react"; import { HaStatBlock } from "./HaStatBlock"; import { HaEntitiesTable } from "./HaEntitiesTable"; diff --git a/apps/client/src/features/ha/ui/HaEntitiesTable.tsx b/apps/client/src/features/ha/ui/HaEntitiesTable.tsx index 8478bfd..b7ccfbd 100644 --- a/apps/client/src/features/ha/ui/HaEntitiesTable.tsx +++ b/apps/client/src/features/ha/ui/HaEntitiesTable.tsx @@ -4,7 +4,7 @@ import { CardHeader, CardTitle, CardDescription, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { Table, TableBody, @@ -12,9 +12,9 @@ import { TableHead, TableHeader, TableRow, -} from "@/components/ui/table"; -import { Badge } from "@/components/ui/badge"; -import { HaState } from "@/entities/ha/types"; +} from "@/shared/ui/table"; +import { Badge } from "@/shared/ui/badge"; +import { HaState } from "@/entities/ha"; interface HaEntitiesTableProps { states: HaState[]; diff --git a/apps/client/src/features/ha/ui/HaStatBlock.tsx b/apps/client/src/features/ha/ui/HaStatBlock.tsx index 305da91..5134f42 100644 --- a/apps/client/src/features/ha/ui/HaStatBlock.tsx +++ b/apps/client/src/features/ha/ui/HaStatBlock.tsx @@ -1,5 +1,5 @@ -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { HaState } from "@/entities/ha/types"; +import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card"; +import { HaState } from "@/entities/ha"; import { Lightbulb, Power, Router, Thermometer } from "lucide-react"; import React, { useMemo } from "react"; diff --git a/apps/client/src/features/integration/index.ts b/apps/client/src/features/integration/index.ts new file mode 100644 index 0000000..3e3b00a --- /dev/null +++ b/apps/client/src/features/integration/index.ts @@ -0,0 +1,3 @@ +export { Intergration } from "./ui/Integration"; +export * from "./model/types"; +export * from "./config/constants"; diff --git a/apps/client/src/features/integration/ui/HA.tsx b/apps/client/src/features/integration/ui/HA.tsx index 5aad2bb..7e0fee8 100644 --- a/apps/client/src/features/integration/ui/HA.tsx +++ b/apps/client/src/features/integration/ui/HA.tsx @@ -1,9 +1,9 @@ import React from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; -import { StepComponentProps } from "./types"; +import { StepComponentProps } from "../model/types"; export const HA_Step1_URL: React.FC = ({ value, diff --git a/apps/client/src/features/integration/ui/Integration.tsx b/apps/client/src/features/integration/ui/Integration.tsx index 74d07e3..79a438b 100644 --- a/apps/client/src/features/integration/ui/Integration.tsx +++ b/apps/client/src/features/integration/ui/Integration.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useMemo } from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,15 +8,15 @@ import { DialogTitle, DialogFooter, DialogDescription, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { Home, Bot, Radio, ArrowRight, CheckCircle, Loader2 } from "lucide-react"; -import { useIntegrationStore } from "@/entities/integrations/store"; +import { useIntegrationStore } from "@/entities/integrations"; import { StepComponentProps, FinalStepProps, IntegrationId, IntegrationWizardModalProps, -} from "./types"; +} from "../model/types"; import { HA_Step1_URL, HA_Step2_Token } from "./HA"; import { ROS2_Step1_Bridge, ROS2_Step2_Address } from "./ROS"; import { SDR_Step1_Info, SDR_Step2_Host, SDR_Step3_Port } from "./SDR"; diff --git a/apps/client/src/features/integration/ui/ROS.tsx b/apps/client/src/features/integration/ui/ROS.tsx index 5003ac7..779d3a6 100644 --- a/apps/client/src/features/integration/ui/ROS.tsx +++ b/apps/client/src/features/integration/ui/ROS.tsx @@ -1,9 +1,9 @@ import React from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; -import { StepComponentProps } from "./types"; +import { StepComponentProps } from "../model/types"; export const ROS2_Step1_Bridge: React.FC = () => (
diff --git a/apps/client/src/features/integration/ui/SDR.tsx b/apps/client/src/features/integration/ui/SDR.tsx index c223570..80d20c3 100644 --- a/apps/client/src/features/integration/ui/SDR.tsx +++ b/apps/client/src/features/integration/ui/SDR.tsx @@ -1,9 +1,9 @@ import React from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; -import { StepComponentProps } from "./types"; +import { StepComponentProps } from "../model/types"; export const SDR_Step1_Info: React.FC = () => (
diff --git a/apps/client/src/features/json/index.ts b/apps/client/src/features/json/index.ts new file mode 100644 index 0000000..fd9cf4b --- /dev/null +++ b/apps/client/src/features/json/index.ts @@ -0,0 +1 @@ +export { JsonCodeEditor } from "./ui/JsonEditor"; diff --git a/apps/client/src/features/llm-chat/index.ts b/apps/client/src/features/llm-chat/index.ts new file mode 100644 index 0000000..e3e2b21 --- /dev/null +++ b/apps/client/src/features/llm-chat/index.ts @@ -0,0 +1,4 @@ +export { ChatPanelContainer } from "./ui/ChatPanelContainer"; +export { ChatPanel, PANEL_WIDTH } from "./ui/ChatPanel"; +export { useChatStore } from "./model/store"; +export type { ChatMessage, ChatPanelState, FlowChatContext } from "./model/types"; diff --git a/apps/client/src/features/llm-chat/model/store.ts b/apps/client/src/features/llm-chat/model/store.ts index 9f1d420..f17c122 100644 --- a/apps/client/src/features/llm-chat/model/store.ts +++ b/apps/client/src/features/llm-chat/model/store.ts @@ -6,8 +6,8 @@ import { CapsuleRateLimitError, } from "@vessel/capsule-client"; import type { HistoryMessage, ToolCallResult } from "@vessel/capsule-client"; -import { supabase } from "@/lib/supabase"; -import { storage } from "@/lib/storage"; +import { supabase } from "@/shared/lib/supabase"; +import { storage } from "@/shared/lib/storage"; import type { ChatMessage, ChatPanelState } from "./types"; function generateId(): string { diff --git a/apps/client/src/features/llm-chat/model/types.ts b/apps/client/src/features/llm-chat/model/types.ts index 332dec0..c84e753 100644 --- a/apps/client/src/features/llm-chat/model/types.ts +++ b/apps/client/src/features/llm-chat/model/types.ts @@ -1,5 +1,5 @@ import type { Tool, ToolCallResult } from "@vessel/capsule-client"; -import type { ToolExecutionResult } from "@/features/flow/flow-chat"; +import type { ToolExecutionResult } from "@/features/flow"; export interface ChatMessageImage { /** Object URL for local preview (revoke after use) */ diff --git a/apps/client/src/features/llm-chat/ui/ChatInput.tsx b/apps/client/src/features/llm-chat/ui/ChatInput.tsx index da2695a..ad37629 100644 --- a/apps/client/src/features/llm-chat/ui/ChatInput.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatInput.tsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect, useMemo } from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { SendIcon, ImageIcon, XIcon } from "lucide-react"; interface ChatInputProps { diff --git a/apps/client/src/features/llm-chat/ui/ChatMessage.tsx b/apps/client/src/features/llm-chat/ui/ChatMessage.tsx index f6b4992..419802e 100644 --- a/apps/client/src/features/llm-chat/ui/ChatMessage.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatMessage.tsx @@ -1,6 +1,6 @@ -import { cn } from "@/lib/utils"; +import { cn } from "@/shared/lib/utils"; import { LoaderCircle } from "lucide-react"; -import type { ChatMessage as ChatMessageType } from "./types"; +import type { ChatMessage as ChatMessageType } from "../model/types"; interface ChatMessageProps { message: ChatMessageType; diff --git a/apps/client/src/features/llm-chat/ui/ChatMessages.tsx b/apps/client/src/features/llm-chat/ui/ChatMessages.tsx index 78b736d..5a6e9d0 100644 --- a/apps/client/src/features/llm-chat/ui/ChatMessages.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatMessages.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef } from "react"; -import { ScrollArea } from "@/components/ui/scroll-area"; +import { ScrollArea } from "@/shared/ui/scroll-area"; import { ChatMessage } from "./ChatMessage"; -import type { ChatMessage as ChatMessageType } from "./types"; +import type { ChatMessage as ChatMessageType } from "../model/types"; interface ChatMessagesProps { messages: ChatMessageType[]; diff --git a/apps/client/src/features/llm-chat/ui/ChatPanel.tsx b/apps/client/src/features/llm-chat/ui/ChatPanel.tsx index cc40479..4a71012 100644 --- a/apps/client/src/features/llm-chat/ui/ChatPanel.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatPanel.tsx @@ -1,8 +1,8 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { XIcon, TrashIcon } from "lucide-react"; -import { isElectron } from "@/lib/electron"; -import { cn } from "@/lib/utils"; -import { useChatStore } from "./store"; +import { isElectron } from "@/shared/lib/electron"; +import { cn } from "@/shared/lib/utils"; +import { useChatStore } from "../model/store"; import { ChatMessages } from "./ChatMessages"; import { ChatInput } from "./ChatInput"; diff --git a/apps/client/src/features/llm-chat/ui/ChatPanelContainer.tsx b/apps/client/src/features/llm-chat/ui/ChatPanelContainer.tsx index 3a03a4f..9bdc08e 100644 --- a/apps/client/src/features/llm-chat/ui/ChatPanelContainer.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatPanelContainer.tsx @@ -1,6 +1,6 @@ -import { useIsMobile } from "@/hooks/use-mobile"; -import { useChatStore } from "./store"; -import { useChatKeyboard } from "./useChatKeyboard"; +import { useIsMobile } from "@/shared/lib/hooks/use-mobile"; +import { useChatStore } from "../model/store"; +import { useChatKeyboard } from "../model/useChatKeyboard"; import { ChatPanel } from "./ChatPanel"; import { ChatPanelMobile } from "./ChatPanelMobile"; diff --git a/apps/client/src/features/llm-chat/ui/ChatPanelMobile.tsx b/apps/client/src/features/llm-chat/ui/ChatPanelMobile.tsx index b41db02..e1fb440 100644 --- a/apps/client/src/features/llm-chat/ui/ChatPanelMobile.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatPanelMobile.tsx @@ -1,12 +1,12 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Sheet, SheetContent, SheetHeader, SheetTitle, -} from "@/components/ui/sheet"; +} from "@/shared/ui/sheet"; import { TrashIcon, MessageSquareIcon } from "lucide-react"; -import { useChatStore } from "./store"; +import { useChatStore } from "../model/store"; import { ChatMessages } from "./ChatMessages"; import { ChatInput } from "./ChatInput"; diff --git a/apps/client/src/features/log/index.ts b/apps/client/src/features/log/index.ts new file mode 100644 index 0000000..dd175ac --- /dev/null +++ b/apps/client/src/features/log/index.ts @@ -0,0 +1 @@ +export { Logs } from "./ui/Logs"; diff --git a/apps/client/src/features/log/ui/Logs.tsx b/apps/client/src/features/log/ui/Logs.tsx index e7f64b1..3549a2d 100644 --- a/apps/client/src/features/log/ui/Logs.tsx +++ b/apps/client/src/features/log/ui/Logs.tsx @@ -2,18 +2,18 @@ import { getLatestLog, getLogFileList, getLogByFilename, -} from "@/entities/log/api"; +} from "@/entities/log"; import { useEffect, useState } from "react"; import { ResizablePanelGroup, ResizablePanel, ResizableHandle, -} from "@/components/ui/resizable"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Skeleton } from "@/components/ui/skeleton"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +} from "@/shared/ui/resizable"; +import { ScrollArea } from "@/shared/ui/scroll-area"; +import { Button } from "@/shared/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card"; +import { Skeleton } from "@/shared/ui/skeleton"; +import { Alert, AlertDescription, AlertTitle } from "@/shared/ui/alert"; import { Terminal } from "lucide-react"; export function Logs() { diff --git a/apps/client/src/features/map-draw/index.ts b/apps/client/src/features/map-draw/index.ts new file mode 100644 index 0000000..0204633 --- /dev/null +++ b/apps/client/src/features/map-draw/index.ts @@ -0,0 +1,8 @@ +export { FeatureDetailsPanel } from "./ui/FeatureDetailsPanel"; +export { DrawingPreview } from "./ui/FeatureDrawingPreview"; +export { FeatureEditor } from "./ui/FeatureEditor"; +export { FeatureRenderer } from "./ui/FeatureRenderer"; +export { LayerDialog } from "./ui/LayerDialog"; +export { LayerSidebar } from "./ui/LayerSidebar"; +export { MapEvents } from "./ui/MapEvents"; +export { MapToolbar } from "./ui/MapToolbar"; diff --git a/apps/client/src/features/map-draw/ui/FeatureDetailsPanel.tsx b/apps/client/src/features/map-draw/ui/FeatureDetailsPanel.tsx index 6858589..29660d7 100644 --- a/apps/client/src/features/map-draw/ui/FeatureDetailsPanel.tsx +++ b/apps/client/src/features/map-draw/ui/FeatureDetailsPanel.tsx @@ -12,15 +12,15 @@ import { Palette, Minus, } from "lucide-react"; -import { useMapDataStore, useMapInteractionStore } from "@/entities/map/store"; -import { Button } from "@/components/ui/button"; +import { useMapDataStore, useMapInteractionStore } from "@/entities/map"; +import { Button } from "@/shared/ui/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { AlertDialog, AlertDialogAction, @@ -30,10 +30,10 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { cn } from "@/lib/utils"; -import { MapVertex, UpdateFeaturePayload } from "@/entities/map/types"; -import { calculateFeatureGeometry } from "@/lib/geometry-precision"; +} from "@/shared/ui/alert-dialog"; +import { cn } from "@/shared/lib/utils"; +import { MapVertex, UpdateFeaturePayload } from "@/entities/map"; +import { calculateFeatureGeometry } from "@/shared/lib/geometry-precision"; const FeatureIcon = ({ type }: { type: string }) => { switch (type) { diff --git a/apps/client/src/features/map-draw/ui/FeatureDrawingPreview.tsx b/apps/client/src/features/map-draw/ui/FeatureDrawingPreview.tsx index 03a7bb3..1236750 100644 --- a/apps/client/src/features/map-draw/ui/FeatureDrawingPreview.tsx +++ b/apps/client/src/features/map-draw/ui/FeatureDrawingPreview.tsx @@ -1,4 +1,4 @@ -import { useMapInteractionStore } from "@/entities/map/store"; +import { useMapInteractionStore } from "@/entities/map"; import { Polyline, Polygon } from "react-leaflet"; export function DrawingPreview() { diff --git a/apps/client/src/features/map-draw/ui/FeatureEditor.tsx b/apps/client/src/features/map-draw/ui/FeatureEditor.tsx index 260f16a..bb31334 100644 --- a/apps/client/src/features/map-draw/ui/FeatureEditor.tsx +++ b/apps/client/src/features/map-draw/ui/FeatureEditor.tsx @@ -1,7 +1,7 @@ import { Marker } from "react-leaflet"; import L from "leaflet"; -import { useMapInteractionStore } from "@/entities/map/store"; -import { useMapDataStore } from "@/entities/map/store"; +import { useMapInteractionStore } from "@/entities/map"; +import { useMapDataStore } from "@/entities/map"; import { LatLng } from "leaflet"; const DraggableIcon = L.divIcon({ diff --git a/apps/client/src/features/map-draw/ui/FeatureRenderer.tsx b/apps/client/src/features/map-draw/ui/FeatureRenderer.tsx index 6e596da..d826de1 100644 --- a/apps/client/src/features/map-draw/ui/FeatureRenderer.tsx +++ b/apps/client/src/features/map-draw/ui/FeatureRenderer.tsx @@ -1,7 +1,7 @@ import { CircleMarker, Polygon, Polyline } from "react-leaflet"; import { LatLngExpression } from "leaflet"; -import { FeatureWithVertices } from "@/entities/map/types"; -import { useMapInteractionStore } from "@/entities/map/store"; +import { FeatureWithVertices } from "@/entities/map"; +import { useMapInteractionStore } from "@/entities/map"; import { useMemo } from "react"; interface FeatureRendererProps { diff --git a/apps/client/src/features/map-draw/ui/LayerDialog.tsx b/apps/client/src/features/map-draw/ui/LayerDialog.tsx index b6a92de..ddf66d6 100644 --- a/apps/client/src/features/map-draw/ui/LayerDialog.tsx +++ b/apps/client/src/features/map-draw/ui/LayerDialog.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { DialogHeader, DialogFooter, @@ -6,11 +6,11 @@ import { Dialog, DialogContent, DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { useMapDataStore } from "@/entities/map/store"; -import { MapLayer, LayerPayload } from "@/entities/map/types"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { useMapDataStore } from "@/entities/map"; +import { MapLayer, LayerPayload } from "@/entities/map"; import { useState, useEffect, useCallback } from "react"; interface LayerDialogProps { diff --git a/apps/client/src/features/map-draw/ui/LayerSidebar.tsx b/apps/client/src/features/map-draw/ui/LayerSidebar.tsx index 66885ea..e33b7f3 100644 --- a/apps/client/src/features/map-draw/ui/LayerSidebar.tsx +++ b/apps/client/src/features/map-draw/ui/LayerSidebar.tsx @@ -2,10 +2,10 @@ import { useState } from "react"; import { Plus, ChevronLeft, MoreHorizontal } from "lucide-react"; import { LayerDialog } from "./LayerDialog"; -import { useMapDataStore } from "@/entities/map/store"; -import { MapLayer } from "@/entities/map/types"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; +import { useMapDataStore } from "@/entities/map"; +import { MapLayer } from "@/entities/map"; +import { Button } from "@/shared/ui/button"; +import { cn } from "@/shared/lib/utils"; import { AlertDialog, AlertDialogAction, @@ -15,14 +15,14 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; type LayerDialogState = | { kind: "closed" } diff --git a/apps/client/src/features/map-draw/ui/MapEvents.tsx b/apps/client/src/features/map-draw/ui/MapEvents.tsx index 0e94681..f5f2625 100644 --- a/apps/client/src/features/map-draw/ui/MapEvents.tsx +++ b/apps/client/src/features/map-draw/ui/MapEvents.tsx @@ -1,4 +1,4 @@ -import { useMapInteractionStore, useMapDataStore } from "@/entities/map/store"; +import { useMapInteractionStore, useMapDataStore } from "@/entities/map"; import { useMapEvents } from "react-leaflet"; export function MapEvents() { diff --git a/apps/client/src/features/map-draw/ui/MapToolbar.tsx b/apps/client/src/features/map-draw/ui/MapToolbar.tsx index 69c9702..7ccc133 100644 --- a/apps/client/src/features/map-draw/ui/MapToolbar.tsx +++ b/apps/client/src/features/map-draw/ui/MapToolbar.tsx @@ -1,5 +1,5 @@ -import { Button } from "@/components/ui/button"; -import { useMapDataStore, useMapInteractionStore } from "@/entities/map/store"; +import { Button } from "@/shared/ui/button"; +import { useMapDataStore, useMapInteractionStore } from "@/entities/map"; import { MapPin, Milestone, Squircle, Check, X, Map, Satellite } from "lucide-react"; export function MapToolbar() { diff --git a/apps/client/src/features/map-entity/index.ts b/apps/client/src/features/map-entity/index.ts new file mode 100644 index 0000000..af1a0a1 --- /dev/null +++ b/apps/client/src/features/map-entity/index.ts @@ -0,0 +1,3 @@ +export { MapEntityRender } from "./ui/render"; +export { EntityDetailsPanel } from "./ui/EntityDetailsPanel"; +export { useMapEntityStore } from "./model/store"; diff --git a/apps/client/src/features/map-entity/model/store.ts b/apps/client/src/features/map-entity/model/store.ts index 0ac8057..d4365a6 100644 --- a/apps/client/src/features/map-entity/model/store.ts +++ b/apps/client/src/features/map-entity/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import type { EntityAll } from "@/entities/entity/types"; +import type { EntityAll } from "@/entities/entity"; interface MapEntityState { selectedEntity: EntityAll | null; diff --git a/apps/client/src/features/map-entity/ui/EntityDetailsPanel.tsx b/apps/client/src/features/map-entity/ui/EntityDetailsPanel.tsx index ec2705d..b1b7cac 100644 --- a/apps/client/src/features/map-entity/ui/EntityDetailsPanel.tsx +++ b/apps/client/src/features/map-entity/ui/EntityDetailsPanel.tsx @@ -1,22 +1,22 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { X, MapPin, TabletSmartphone, Server, Minus } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; -import { getDeviceById } from "@/entities/device/api"; -import { EntityAll } from "@/entities/entity/types"; -import { EntityCard } from "../entity/Card"; -import { useWebSocket, useWebSocketMessage } from "../ws/WebSocketProvider"; -import { WebSocketMessage } from "../ws/ws"; -import { ChangeStatePayload, StreamState } from "../entity/AllEntities"; -import * as api from "../../entities/entity/api"; -import { useMapEntityStore } from "./store"; -import { cn } from "@/lib/utils"; +import { getDeviceById } from "@/entities/device"; +import { EntityAll } from "@/entities/entity"; +import { EntityCard } from "../../entity"; +import { useWebSocket, useWebSocketMessage } from "../../ws"; +import { WebSocketMessage } from "../../ws"; +import { ChangeStatePayload, StreamState } from "../../entity"; +import * as api from "@/entities/entity"; +import { useMapEntityStore } from "../model/store"; +import { cn } from "@/shared/lib/utils"; interface EntityDetailsPanelProps { isCollapsed: boolean; diff --git a/apps/client/src/features/map-entity/ui/render.tsx b/apps/client/src/features/map-entity/ui/render.tsx index 8ccfebf..42f3422 100644 --- a/apps/client/src/features/map-entity/ui/render.tsx +++ b/apps/client/src/features/map-entity/ui/render.tsx @@ -1,11 +1,11 @@ -import { getAllEntitiesFilter } from "@/entities/entity/api"; -import { EntityAll } from "@/entities/entity/types"; +import { getAllEntitiesFilter } from "@/entities/entity"; +import { EntityAll } from "@/entities/entity"; import L from "leaflet"; import { MapPin } from "lucide-react"; import { useState, useEffect, createElement } from "react"; import { renderToStaticMarkup } from "react-dom/server"; -import { parseGpsState } from "../gps/parseGps"; -import { useMapEntityStore } from "./store"; +import { parseGpsState } from "../../gps"; +import { useMapEntityStore } from "../model/store"; import { Marker } from "react-leaflet"; const MARKER_W = 32; diff --git a/apps/client/src/features/map/index.ts b/apps/client/src/features/map/index.ts new file mode 100644 index 0000000..53f48b3 --- /dev/null +++ b/apps/client/src/features/map/index.ts @@ -0,0 +1 @@ +export { MapView } from "./ui/MapView"; diff --git a/apps/client/src/features/map/ui/MapView.tsx b/apps/client/src/features/map/ui/MapView.tsx index a45c638..250db75 100644 --- a/apps/client/src/features/map/ui/MapView.tsx +++ b/apps/client/src/features/map/ui/MapView.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from "react"; import { Plus, Minus } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { MapContainer, TileLayer, useMap } from "react-leaflet"; import "leaflet/dist/leaflet.css"; import L from "leaflet"; @@ -8,18 +8,18 @@ import L from "leaflet"; import icon from "leaflet/dist/images/marker-icon.png"; import iconShadow from "leaflet/dist/images/marker-shadow.png"; import "./style.css"; -import { useMapDataStore, useMapInteractionStore } from "@/entities/map/store"; -import { TILE_MAPS } from "@/entities/map/types"; - -import { MapEntityRender } from "../map-entity/render"; -import { FeatureDetailsPanel } from "../map-draw/FeatureDetailsPanel"; -import { DrawingPreview } from "../map-draw/FeatureDrawingPreview"; -import { FeatureEditor } from "../map-draw/FeatureEditor"; -import { FeatureRenderer } from "../map-draw/FeatureRenderer"; -import { MapEvents } from "../map-draw/MapEvents"; -import { EntityDetailsPanel } from "../map-entity/EntityDetailsPanel"; -import { useMapEntityStore } from "../map-entity/store"; -import { cn } from "@/lib/utils"; +import { useMapDataStore, useMapInteractionStore } from "@/entities/map"; +import { TILE_MAPS } from "@/entities/map"; + +import { MapEntityRender } from "../../map-entity"; +import { FeatureDetailsPanel } from "../../map-draw"; +import { DrawingPreview } from "../../map-draw"; +import { FeatureEditor } from "../../map-draw"; +import { FeatureRenderer } from "../../map-draw"; +import { MapEvents } from "../../map-draw"; +import { EntityDetailsPanel } from "../../map-entity"; +import { useMapEntityStore } from "../../map-entity"; +import { cn } from "@/shared/lib/utils"; import { MapLastViewTracker, getStoredMapView } from "./MapViewPersistence"; import { CurrentLocationMarker } from "./CurrentLocationMarker"; diff --git a/apps/client/src/features/recording/index.ts b/apps/client/src/features/recording/index.ts index b5b3595..7e9cf0f 100644 --- a/apps/client/src/features/recording/index.ts +++ b/apps/client/src/features/recording/index.ts @@ -1,3 +1,3 @@ -export { RecordingButton } from "./RecordingButton"; -export { RecordingsList } from "./RecordingsList"; -export { VideoPlaybackDialog } from "./VideoPlaybackDialog"; +export { RecordingButton, RecordingMenuItem } from "./ui/RecordingButton"; +export { RecordingsList } from "./ui/RecordingsList"; +export { VideoPlaybackDialog } from "./ui/VideoPlaybackDialog"; diff --git a/apps/client/src/features/recording/ui/AudioWaveformPlayer.tsx b/apps/client/src/features/recording/ui/AudioWaveformPlayer.tsx index 320e42f..cd14f2a 100644 --- a/apps/client/src/features/recording/ui/AudioWaveformPlayer.tsx +++ b/apps/client/src/features/recording/ui/AudioWaveformPlayer.tsx @@ -1,8 +1,8 @@ import { useRef, useState, useEffect, useCallback } from "react"; -import { cn } from "@/lib/utils"; -import { Skeleton } from "@/components/ui/skeleton"; -import { useMediaPlayback } from "../hooks/useMediaPlayback"; -import { useAudioWaveform } from "../hooks/useAudioWaveform"; +import { cn } from "@/shared/lib/utils"; +import { Skeleton } from "@/shared/ui/skeleton"; +import { useMediaPlayback } from "../model/useMediaPlayback"; +import { useAudioWaveform } from "../model/useAudioWaveform"; import { PlaybackControls } from "./PlaybackControls"; import { WaveformCanvas } from "./WaveformCanvas"; import { TimeRuler } from "./TimeRuler"; diff --git a/apps/client/src/features/recording/ui/FrameTimeline.tsx b/apps/client/src/features/recording/ui/FrameTimeline.tsx index 112c520..0544035 100644 --- a/apps/client/src/features/recording/ui/FrameTimeline.tsx +++ b/apps/client/src/features/recording/ui/FrameTimeline.tsx @@ -1,6 +1,6 @@ import { useRef, useState, useCallback } from "react"; -import { cn } from "@/lib/utils"; -import { Skeleton } from "@/components/ui/skeleton"; +import { cn } from "@/shared/lib/utils"; +import { Skeleton } from "@/shared/ui/skeleton"; interface Frame { timeMs: number; diff --git a/apps/client/src/features/recording/ui/PlaybackControls.tsx b/apps/client/src/features/recording/ui/PlaybackControls.tsx index 81265c7..9342726 100644 --- a/apps/client/src/features/recording/ui/PlaybackControls.tsx +++ b/apps/client/src/features/recording/ui/PlaybackControls.tsx @@ -1,6 +1,6 @@ import { Play, Pause, Square } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; +import { Button } from "@/shared/ui/button"; +import { cn } from "@/shared/lib/utils"; interface PlaybackControlsProps { isPlaying: boolean; diff --git a/apps/client/src/features/recording/ui/RecordingButton.tsx b/apps/client/src/features/recording/ui/RecordingButton.tsx index 842f48e..6ab7b7c 100644 --- a/apps/client/src/features/recording/ui/RecordingButton.tsx +++ b/apps/client/src/features/recording/ui/RecordingButton.tsx @@ -1,15 +1,15 @@ import { useEffect, useState } from "react"; import { Circle, Square, Loader2 } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { useRecordingStore } from "@/entities/recording/store"; -import { cn } from "@/lib/utils"; +import { Button } from "@/shared/ui/button"; +import { useRecordingStore } from "@/entities/recording"; +import { cn } from "@/shared/lib/utils"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/ui/tooltip"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/tooltip"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; interface RecordingButtonProps { topic: string; diff --git a/apps/client/src/features/recording/ui/RecordingsList.tsx b/apps/client/src/features/recording/ui/RecordingsList.tsx index 300dd25..c2c44fe 100644 --- a/apps/client/src/features/recording/ui/RecordingsList.tsx +++ b/apps/client/src/features/recording/ui/RecordingsList.tsx @@ -5,7 +5,7 @@ import { CardDescription, CardHeader, CardTitle, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { Table, TableBody, @@ -13,9 +13,9 @@ import { TableHead, TableHeader, TableRow, -} from "@/components/ui/table"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; +} from "@/shared/ui/table"; +import { Button } from "@/shared/ui/button"; +import { Badge } from "@/shared/ui/badge"; import { AlertDialog, AlertDialogAction, @@ -26,12 +26,12 @@ import { AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; import { Play, Trash2, Loader2, RefreshCw, Square } from "lucide-react"; -import { useRecordingStore } from "@/entities/recording/store"; +import { useRecordingStore } from "@/entities/recording"; import { VideoPlaybackDialog } from "./VideoPlaybackDialog"; -import { formatSimpleDateTime } from "@/lib/time"; -import type { Recording } from "@/entities/recording/types"; +import { formatSimpleDateTime } from "@/shared/lib/time"; +import type { Recording } from "@/entities/recording"; export function RecordingsList() { const { diff --git a/apps/client/src/features/recording/ui/TimeRuler.tsx b/apps/client/src/features/recording/ui/TimeRuler.tsx index c9e6ff0..0e6f190 100644 --- a/apps/client/src/features/recording/ui/TimeRuler.tsx +++ b/apps/client/src/features/recording/ui/TimeRuler.tsx @@ -1,5 +1,5 @@ import { useRef, useCallback, useMemo } from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/shared/lib/utils"; interface TimeRulerProps { durationMs: number; diff --git a/apps/client/src/features/recording/ui/VideoControlBar.tsx b/apps/client/src/features/recording/ui/VideoControlBar.tsx index 31ef2fc..41253b0 100644 --- a/apps/client/src/features/recording/ui/VideoControlBar.tsx +++ b/apps/client/src/features/recording/ui/VideoControlBar.tsx @@ -1,7 +1,7 @@ import { useRef, useState, useEffect, useCallback, RefObject } from "react"; -import { cn } from "@/lib/utils"; -import { useMediaPlayback } from "../hooks/useMediaPlayback"; -import { useVideoFrames } from "../hooks/useVideoFrames"; +import { cn } from "@/shared/lib/utils"; +import { useMediaPlayback } from "../model/useMediaPlayback"; +import { useVideoFrames } from "../model/useVideoFrames"; import { PlaybackControls } from "./PlaybackControls"; import { FrameTimeline } from "./FrameTimeline"; import { TimeRuler } from "./TimeRuler"; diff --git a/apps/client/src/features/recording/ui/VideoPlaybackDialog.tsx b/apps/client/src/features/recording/ui/VideoPlaybackDialog.tsx index 06d9f69..df31c7b 100644 --- a/apps/client/src/features/recording/ui/VideoPlaybackDialog.tsx +++ b/apps/client/src/features/recording/ui/VideoPlaybackDialog.tsx @@ -4,12 +4,12 @@ import { DialogContent, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; -import { useRecordingStore } from "@/entities/recording/store"; -import { getRecordingStreamUrl } from "@/entities/recording/api"; -import { storage } from "@/lib/storage"; -import { AudioWaveformPlayer } from "./components/AudioWaveformPlayer"; -import { VideoControlBar } from "./components/VideoControlBar"; +} from "@/shared/ui/dialog"; +import { useRecordingStore } from "@/entities/recording"; +import { getRecordingStreamUrl } from "@/entities/recording"; +import { storage } from "@/shared/lib/storage"; +import { AudioWaveformPlayer } from "./AudioWaveformPlayer"; +import { VideoControlBar } from "./VideoControlBar"; interface VideoPlaybackDialogProps { recordingId: number | null; diff --git a/apps/client/src/features/recording/ui/WaveformCanvas.tsx b/apps/client/src/features/recording/ui/WaveformCanvas.tsx index 5ee0483..4280dfe 100644 --- a/apps/client/src/features/recording/ui/WaveformCanvas.tsx +++ b/apps/client/src/features/recording/ui/WaveformCanvas.tsx @@ -1,6 +1,6 @@ import { useRef, useEffect, useCallback } from "react"; import * as d3 from "d3"; -import { cn } from "@/lib/utils"; +import { cn } from "@/shared/lib/utils"; interface WaveformCanvasProps { waveformData: number[]; diff --git a/apps/client/src/features/role/index.ts b/apps/client/src/features/role/index.ts new file mode 100644 index 0000000..275eb1c --- /dev/null +++ b/apps/client/src/features/role/index.ts @@ -0,0 +1,2 @@ +export * from "./ui/RoleDialogs"; +export { RoleForm } from "./ui/RoleForm"; diff --git a/apps/client/src/features/role/ui/RoleDialogs.tsx b/apps/client/src/features/role/ui/RoleDialogs.tsx index c78b42f..2ca3a94 100644 --- a/apps/client/src/features/role/ui/RoleDialogs.tsx +++ b/apps/client/src/features/role/ui/RoleDialogs.tsx @@ -4,17 +4,17 @@ import { DialogDescription, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { CreateRolePayload, Role, UpdateRolePayload, -} from "@/entities/role/types"; +} from "@/entities/role"; import { FC, useState } from "react"; import { RoleForm } from "./RoleForm"; -import { Button } from "@/components/ui/button"; -import { DialogFooter } from "@/components/ui/dialog"; -import { useRoleStore } from "@/entities/role/store"; +import { Button } from "@/shared/ui/button"; +import { DialogFooter } from "@/shared/ui/dialog"; +import { useRoleStore } from "@/entities/role"; // Add Role Dialog export const AddRoleDialog: FC<{ diff --git a/apps/client/src/features/role/ui/RoleForm.tsx b/apps/client/src/features/role/ui/RoleForm.tsx index dc14a11..bd28f01 100644 --- a/apps/client/src/features/role/ui/RoleForm.tsx +++ b/apps/client/src/features/role/ui/RoleForm.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Command, CommandEmpty, @@ -6,23 +6,23 @@ import { CommandInput, CommandItem, CommandList, -} from "@/components/ui/command"; -import { DialogFooter } from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/command"; +import { DialogFooter } from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Popover, PopoverContent, PopoverTrigger, -} from "@/components/ui/popover"; -import { usePermissionStore } from "@/entities/permission/store"; -import { Permission } from "@/entities/permission/types"; +} from "@/shared/ui/popover"; +import { usePermissionStore } from "@/entities/permission"; +import { Permission } from "@/entities/permission"; import { CreateRolePayload, Role, UpdateRolePayload, -} from "@/entities/role/types"; -import { cn } from "@/lib/utils"; +} from "@/entities/role"; +import { cn } from "@/shared/lib/utils"; import { Check, ChevronsUpDown } from "lucide-react"; import { FC, useEffect, useState } from "react"; diff --git a/apps/client/src/features/ros2/index.ts b/apps/client/src/features/ros2/index.ts new file mode 100644 index 0000000..e7df3ff --- /dev/null +++ b/apps/client/src/features/ros2/index.ts @@ -0,0 +1 @@ +export { Ros2Dashboard } from "./ui/Ros2Dashboard"; diff --git a/apps/client/src/features/ros2/ui/Ros2Dashboard.tsx b/apps/client/src/features/ros2/ui/Ros2Dashboard.tsx index 5417547..8bc829b 100644 --- a/apps/client/src/features/ros2/ui/Ros2Dashboard.tsx +++ b/apps/client/src/features/ros2/ui/Ros2Dashboard.tsx @@ -1,6 +1,6 @@ import { Link } from "react-router"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/shared/ui/card"; +import { Button } from "@/shared/ui/button"; export function Ros2Dashboard() { return ( diff --git a/apps/client/src/features/rtc/index.ts b/apps/client/src/features/rtc/index.ts new file mode 100644 index 0000000..6eec8dc --- /dev/null +++ b/apps/client/src/features/rtc/index.ts @@ -0,0 +1,7 @@ +export { AudioLevelBar } from "./ui/AudioLevelBar"; +export { StreamReceiver } from "./ui/StreamReceiver"; +export { WebRTCProvider, useWebRTC } from "./ui/WebRTCProvider"; +export { WebRTCManager } from "./lib/rtc"; +export type { WebRTCConfig } from "./lib/rtc"; +export { captureFrameFromStream } from "./lib/captureFrame"; +export * from "./lib/turnService"; diff --git a/apps/client/src/features/rtc/lib/rtc.ts b/apps/client/src/features/rtc/lib/rtc.ts index 3bb3868..76ac916 100644 --- a/apps/client/src/features/rtc/lib/rtc.ts +++ b/apps/client/src/features/rtc/lib/rtc.ts @@ -1,4 +1,4 @@ -import { WebSocketChannel, WebSocketMessage } from "../ws/ws"; +import { WebSocketChannel, WebSocketMessage } from "../../ws"; export interface WebRTCConfig { iceServers: RTCIceServer[]; diff --git a/apps/client/src/features/rtc/lib/turnService.ts b/apps/client/src/features/rtc/lib/turnService.ts index 607b97e..dd280a3 100644 --- a/apps/client/src/features/rtc/lib/turnService.ts +++ b/apps/client/src/features/rtc/lib/turnService.ts @@ -1,5 +1,5 @@ -import { supabase } from "@/lib/supabase"; -import { getConfigs, updateConfig } from "@/entities/configurations/api"; +import { supabase } from "@/shared/lib/supabase"; +import { getConfigs, updateConfig } from "@/entities/configurations"; export interface TurnUsage { egressBytes: number; diff --git a/apps/client/src/features/rtc/ui/AudioLevelBar.tsx b/apps/client/src/features/rtc/ui/AudioLevelBar.tsx index e1d9775..4361e74 100644 --- a/apps/client/src/features/rtc/ui/AudioLevelBar.tsx +++ b/apps/client/src/features/rtc/ui/AudioLevelBar.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/shared/lib/utils"; type AudioLevelBarProps = { stream: MediaStream | null; diff --git a/apps/client/src/features/rtc/ui/StreamReceiver.tsx b/apps/client/src/features/rtc/ui/StreamReceiver.tsx index 1532507..bcaf5c7 100644 --- a/apps/client/src/features/rtc/ui/StreamReceiver.tsx +++ b/apps/client/src/features/rtc/ui/StreamReceiver.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useMemo } from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { useWebRTC } from "./WebRTCProvider"; -import { WebRTCManager } from "./rtc"; +import { WebRTCManager } from "../lib/rtc"; import { AudioLevelBar } from "./AudioLevelBar"; type StreamReceiverProps = { diff --git a/apps/client/src/features/rtc/ui/WebRTCProvider.tsx b/apps/client/src/features/rtc/ui/WebRTCProvider.tsx index edea160..3bac19b 100644 --- a/apps/client/src/features/rtc/ui/WebRTCProvider.tsx +++ b/apps/client/src/features/rtc/ui/WebRTCProvider.tsx @@ -8,17 +8,17 @@ import { useMemo, } from "react"; import { toast } from "sonner"; -import { useWebSocket } from "../ws/WebSocketProvider"; -import { WebRTCManager } from "./rtc"; -import { isDemoMode } from "@/shared/demo"; -import { WebSocketChannel } from "../ws/ws"; +import { useWebSocket } from "../../ws"; +import { WebRTCManager } from "../lib/rtc"; +import { isDemoMode } from "@/shared/config/demo"; +import { WebSocketChannel } from "../../ws"; import { ensureIceServers, onCredentialChange, onTurnCredentialError, stopAutoRenewal, DEFAULT_ICE_SERVERS, -} from "./turnService"; +} from "../lib/turnService"; type StreamInfo = { stream: MediaStream; diff --git a/apps/client/src/features/sdr/index.ts b/apps/client/src/features/sdr/index.ts new file mode 100644 index 0000000..f3d8484 --- /dev/null +++ b/apps/client/src/features/sdr/index.ts @@ -0,0 +1,3 @@ +export { SdrDashboard } from "./ui/SdrDashboard"; +export { SdrAudioPlayer } from "./ui/SdrAudioPlayer"; +export * from "./api"; diff --git a/apps/client/src/features/sdr/ui/SdrAudioPlayer.tsx b/apps/client/src/features/sdr/ui/SdrAudioPlayer.tsx index 1b1feb8..012f46d 100644 --- a/apps/client/src/features/sdr/ui/SdrAudioPlayer.tsx +++ b/apps/client/src/features/sdr/ui/SdrAudioPlayer.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useCallback, useState } from "react"; -import { storage } from "@/lib/storage"; +import { storage } from "@/shared/lib/storage"; const AUDIO_SAMPLE_RATE = 48000; const BUFFER_SIZE = 4096; diff --git a/apps/client/src/features/sdr/ui/SdrDashboard.tsx b/apps/client/src/features/sdr/ui/SdrDashboard.tsx index 6569d1d..770bcfc 100644 --- a/apps/client/src/features/sdr/ui/SdrDashboard.tsx +++ b/apps/client/src/features/sdr/ui/SdrDashboard.tsx @@ -5,14 +5,14 @@ import { CardDescription, CardHeader, CardTitle, -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Badge } from "@/components/ui/badge"; +} from "@/shared/ui/card"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Badge } from "@/shared/ui/badge"; import { Radio, Play, Square, Loader2 } from "lucide-react"; import { SdrAudioPlayer } from "./SdrAudioPlayer"; -import * as sdrApi from "./api"; +import * as sdrApi from "../api"; export function SdrDashboard() { const [frequencyMhz, setFrequencyMhz] = useState("100.0"); diff --git a/apps/client/src/features/search/index.ts b/apps/client/src/features/search/index.ts new file mode 100644 index 0000000..46df3ce --- /dev/null +++ b/apps/client/src/features/search/index.ts @@ -0,0 +1 @@ +export { SearchForm } from "./ui/SearchForm"; diff --git a/apps/client/src/features/search/ui/SearchForm.tsx b/apps/client/src/features/search/ui/SearchForm.tsx index ad1a3a1..55e7d34 100644 --- a/apps/client/src/features/search/ui/SearchForm.tsx +++ b/apps/client/src/features/search/ui/SearchForm.tsx @@ -1,11 +1,11 @@ import { Search } from "lucide-react"; -import { Label } from "@/components/ui/label"; +import { Label } from "@/shared/ui/label"; import { SidebarGroup, SidebarGroupContent, SidebarInput, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; export function SearchForm({ ...props }: React.ComponentProps<"form">) { return ( diff --git a/apps/client/src/features/server-resource/index.ts b/apps/client/src/features/server-resource/index.ts new file mode 100644 index 0000000..226082f --- /dev/null +++ b/apps/client/src/features/server-resource/index.ts @@ -0,0 +1 @@ +export { default } from "./ui/ResourceUsage"; diff --git a/apps/client/src/features/server-resource/ui/ResourceUsage.tsx b/apps/client/src/features/server-resource/ui/ResourceUsage.tsx index 0fb702c..2f6a5cf 100644 --- a/apps/client/src/features/server-resource/ui/ResourceUsage.tsx +++ b/apps/client/src/features/server-resource/ui/ResourceUsage.tsx @@ -4,10 +4,10 @@ import { CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { useCallback, useEffect, useState } from "react"; -import { useWebSocket, useWebSocketMessage } from "../ws/WebSocketProvider"; -import { WebSocketMessage } from "../ws/ws"; +import { useWebSocket, useWebSocketMessage } from "../../ws"; +import { WebSocketMessage } from "../../ws"; export default function ResourceUsage() { const { wsManager } = useWebSocket(); diff --git a/apps/client/src/features/setup/index.ts b/apps/client/src/features/setup/index.ts new file mode 100644 index 0000000..c931948 --- /dev/null +++ b/apps/client/src/features/setup/index.ts @@ -0,0 +1 @@ +export * from "./ui/SetupSteps"; diff --git a/apps/client/src/features/setup/ui/SetupSteps.tsx b/apps/client/src/features/setup/ui/SetupSteps.tsx index ff77edc..74c4f31 100644 --- a/apps/client/src/features/setup/ui/SetupSteps.tsx +++ b/apps/client/src/features/setup/ui/SetupSteps.tsx @@ -1,7 +1,7 @@ -import { getDevices } from "@/entities/device/api"; -import { getAllEntities } from "@/entities/entity/api"; -import { getFlows } from "@/entities/flow/api"; -import { getIntegrationStatus } from "@/entities/integrations/api"; +import { getDevices } from "@/entities/device"; +import { getAllEntities } from "@/entities/entity"; +import { getFlows } from "@/entities/flow"; +import { getIntegrationStatus } from "@/entities/integrations"; export type SetupStep = { id: string; diff --git a/apps/client/src/features/sidebar/index.ts b/apps/client/src/features/sidebar/index.ts new file mode 100644 index 0000000..7fd0469 --- /dev/null +++ b/apps/client/src/features/sidebar/index.ts @@ -0,0 +1,2 @@ +export { AppSidebar } from "./ui/AppSidebar"; +export { NavFooter } from "./ui/footer"; diff --git a/apps/client/src/features/sidebar/ui/AppSidebar.tsx b/apps/client/src/features/sidebar/ui/AppSidebar.tsx index a4da1aa..798331f 100644 --- a/apps/client/src/features/sidebar/ui/AppSidebar.tsx +++ b/apps/client/src/features/sidebar/ui/AppSidebar.tsx @@ -13,8 +13,8 @@ import { SidebarMenuSubButton, SidebarMenuSubItem, SidebarRail, -} from "@/components/ui/sidebar"; -import { AccountSwitcher } from "../account-switcher"; +} from "@/shared/ui/sidebar"; +import { AccountSwitcher } from "../../account-switcher"; import { useLocation, useNavigate } from "react-router"; import { LayoutDashboard, @@ -29,11 +29,11 @@ import { Video, } from "lucide-react"; import { NavFooter } from "./footer"; -import { isElectron } from "@/lib/electron"; -import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; -import { useIntegrationStore } from "@/entities/integrations/store"; -import { useConfigStore } from "@/entities/configurations/store"; -import { getCodeServiceEnabled } from "@/entities/configurations/codeService"; +import { isElectron } from "@/shared/lib/electron"; +import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard"; +import { useIntegrationStore } from "@/entities/integrations"; +import { useConfigStore } from "@/entities/configurations"; +import { getCodeServiceEnabled } from "@/entities/configurations"; import { ComponentProps, useCallback, diff --git a/apps/client/src/features/sidebar/ui/footer.tsx b/apps/client/src/features/sidebar/ui/footer.tsx index 01aadb4..7de47db 100644 --- a/apps/client/src/features/sidebar/ui/footer.tsx +++ b/apps/client/src/features/sidebar/ui/footer.tsx @@ -6,7 +6,7 @@ import { User, } from "lucide-react"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Avatar, AvatarFallback, AvatarImage } from "@/shared/ui/avatar"; import { DropdownMenu, DropdownMenuContent, @@ -15,18 +15,18 @@ import { DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar, -} from "@/components/ui/sidebar"; -import { useLogout } from "../auth/hook"; +} from "@/shared/ui/sidebar"; +import { useLogout } from "../../auth"; import { useEffect, useState } from "react"; -import { parseJwt } from "@/lib/jwt"; -import { isDemoMode } from "@/shared/demo"; -import { storage } from "@/lib/storage"; +import { parseJwt } from "@/shared/lib/jwt"; +import { isDemoMode } from "@/shared/config/demo"; +import { storage } from "@/shared/lib/storage"; const openExternal = (url: string) => { import("@tauri-apps/plugin-shell") diff --git a/apps/client/src/features/stat/index.ts b/apps/client/src/features/stat/index.ts new file mode 100644 index 0000000..0aadd51 --- /dev/null +++ b/apps/client/src/features/stat/index.ts @@ -0,0 +1 @@ +export { default } from "./ui/StatBlock"; diff --git a/apps/client/src/features/stat/ui/StatBlock.tsx b/apps/client/src/features/stat/ui/StatBlock.tsx index 67a2e73..921a287 100644 --- a/apps/client/src/features/stat/ui/StatBlock.tsx +++ b/apps/client/src/features/stat/ui/StatBlock.tsx @@ -4,8 +4,8 @@ import { CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card"; -import { useStatStore } from "@/entities/stat/store"; +} from "@/shared/ui/card"; +import { useStatStore } from "@/entities/stat"; import { useEffect } from "react"; export default function StatBlock() { diff --git a/apps/client/src/features/topbar/index.ts b/apps/client/src/features/topbar/index.ts new file mode 100644 index 0000000..a04dade --- /dev/null +++ b/apps/client/src/features/topbar/index.ts @@ -0,0 +1 @@ +export { TopBar } from "./ui/TopBar"; diff --git a/apps/client/src/features/user/index.ts b/apps/client/src/features/user/index.ts new file mode 100644 index 0000000..f417e4a --- /dev/null +++ b/apps/client/src/features/user/index.ts @@ -0,0 +1,4 @@ +export { AddUserDialog } from "./ui/userAdd"; +export { DeleteUserDialog } from "./ui/userDelete"; +export { EditUserDialog } from "./ui/userEdit"; +export { UserRoleAssigner } from "./ui/UserRoleAssigner"; diff --git a/apps/client/src/features/user/ui/UserRoleAssigner.tsx b/apps/client/src/features/user/ui/UserRoleAssigner.tsx index a05369f..990deda 100644 --- a/apps/client/src/features/user/ui/UserRoleAssigner.tsx +++ b/apps/client/src/features/user/ui/UserRoleAssigner.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Command, CommandEmpty, @@ -6,16 +6,16 @@ import { CommandInput, CommandItem, CommandList, -} from "@/components/ui/command"; +} from "@/shared/ui/command"; import { Popover, PopoverContent, PopoverTrigger, -} from "@/components/ui/popover"; -import { useRoleStore } from "@/entities/role/store"; -import { Role } from "@/entities/role/types"; -import { assignRoleToUser, revokeRoleFromUser } from "@/entities/user/api"; -import { cn } from "@/lib/utils"; +} from "@/shared/ui/popover"; +import { useRoleStore } from "@/entities/role"; +import { Role } from "@/entities/role"; +import { assignRoleToUser, revokeRoleFromUser } from "@/entities/user"; +import { cn } from "@/shared/lib/utils"; import { Check, ChevronsUpDown } from "lucide-react"; import { FC, useEffect, useState } from "react"; import { toast } from "sonner"; diff --git a/apps/client/src/features/user/ui/userAdd.tsx b/apps/client/src/features/user/ui/userAdd.tsx index a34548d..c5e9eec 100644 --- a/apps/client/src/features/user/ui/userAdd.tsx +++ b/apps/client/src/features/user/ui/userAdd.tsx @@ -1,16 +1,16 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; -import { CreateUserPayload, UpdateUserPayload } from "@/entities/user/types"; +} from "@/shared/ui/dialog"; +import { CreateUserPayload, UpdateUserPayload } from "@/entities/user"; import { PlusCircle } from "lucide-react"; import { FC, useState } from "react"; import { UserForm } from "./userForm"; -import { useUserStore } from "@/entities/user/store"; +import { useUserStore } from "@/entities/user"; export const AddUser: FC = () => { const [isOpen, setIsOpen] = useState(false); diff --git a/apps/client/src/features/user/ui/userDelete.tsx b/apps/client/src/features/user/ui/userDelete.tsx index fadbd4c..f8321cf 100644 --- a/apps/client/src/features/user/ui/userDelete.tsx +++ b/apps/client/src/features/user/ui/userDelete.tsx @@ -7,9 +7,9 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { useUserStore } from "@/entities/user/store"; +} from "@/shared/ui/alert-dialog"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; +import { useUserStore } from "@/entities/user"; import { Trash2 } from "lucide-react"; import { FC, useState } from "react"; diff --git a/apps/client/src/features/user/ui/userEdit.tsx b/apps/client/src/features/user/ui/userEdit.tsx index 310c3cd..878ba53 100644 --- a/apps/client/src/features/user/ui/userEdit.tsx +++ b/apps/client/src/features/user/ui/userEdit.tsx @@ -4,17 +4,17 @@ import { DialogDescription, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; -import { useUserStore } from "@/entities/user/store"; +} from "@/shared/ui/dialog"; +import { useUserStore } from "@/entities/user"; import { User, UpdateUserPayload, CreateUserPayload, -} from "@/entities/user/types"; +} from "@/entities/user"; import { Edit } from "lucide-react"; import { FC, useState } from "react"; import { UserForm } from "./userForm"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; interface EditUserProps { user: User; diff --git a/apps/client/src/features/user/ui/userForm.tsx b/apps/client/src/features/user/ui/userForm.tsx index 950ef27..794f323 100644 --- a/apps/client/src/features/user/ui/userForm.tsx +++ b/apps/client/src/features/user/ui/userForm.tsx @@ -1,11 +1,11 @@ -import { Button } from "@/components/ui/button"; -import { DialogFooter } from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; +import { Button } from "@/shared/ui/button"; +import { DialogFooter } from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; import { User, CreateUserPayload, UpdateUserPayload, -} from "@/entities/user/types"; +} from "@/entities/user"; import { Label } from "@radix-ui/react-label"; import { FC, useState } from "react"; import { UserRoleAssigner } from "./UserRoleAssigner"; diff --git a/apps/client/src/features/ws/index.ts b/apps/client/src/features/ws/index.ts new file mode 100644 index 0000000..a8e90e7 --- /dev/null +++ b/apps/client/src/features/ws/index.ts @@ -0,0 +1,7 @@ +export { WebSocketProvider, useWebSocket, useWebSocketMessage } from "./ui/WebSocketProvider"; +export { FlowUiEventBridge } from "./ui/FlowUiEventBridge"; +export { WebSocketStatusIndicator } from "./ui/IsConnected"; +export * from "./lib/ws"; +export { MockWebSocketChannel } from "./lib/wsMock"; +export * from "./lib/flowUiEventRouter"; +export { toastFlowUiAdapter } from "./lib/adapters/toastFlowUiAdapter"; diff --git a/apps/client/src/features/ws/lib/adapters/toastFlowUiAdapter.ts b/apps/client/src/features/ws/lib/adapters/toastFlowUiAdapter.ts index 5e1e576..24098fc 100644 --- a/apps/client/src/features/ws/lib/adapters/toastFlowUiAdapter.ts +++ b/apps/client/src/features/ws/lib/adapters/toastFlowUiAdapter.ts @@ -1,5 +1,5 @@ import { toast } from "sonner"; -import type { FlowUiEventPayload, FlowUiEventToastData } from "@/features/ws/ws"; +import type { FlowUiEventPayload, FlowUiEventToastData } from "@/features/ws"; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; diff --git a/apps/client/src/features/ws/lib/flowUiEventRouter.ts b/apps/client/src/features/ws/lib/flowUiEventRouter.ts index 79dba36..2df655e 100644 --- a/apps/client/src/features/ws/lib/flowUiEventRouter.ts +++ b/apps/client/src/features/ws/lib/flowUiEventRouter.ts @@ -1,4 +1,4 @@ -import type { FlowUiEventPayload } from "@/features/ws/ws"; +import type { FlowUiEventPayload } from "./ws"; export type FlowUiEventAdapter = (payload: FlowUiEventPayload) => void; diff --git a/apps/client/src/features/ws/lib/ws.ts b/apps/client/src/features/ws/lib/ws.ts index ac1c6b8..1bc340f 100644 --- a/apps/client/src/features/ws/lib/ws.ts +++ b/apps/client/src/features/ws/lib/ws.ts @@ -1,4 +1,4 @@ -import type { DashboardComponentEventPayload } from "@/entities/dynamic-dashboard/interaction"; +import type { DashboardComponentEventPayload } from "@/entities/dynamic-dashboard"; /** Flow-driven UI events (toast, etc.); client should filter by `target.session_id`. */ export type FlowUiEventPayload = { diff --git a/apps/client/src/features/ws/ui/FlowUiEventBridge.tsx b/apps/client/src/features/ws/ui/FlowUiEventBridge.tsx index e319be5..9cc8d3f 100644 --- a/apps/client/src/features/ws/ui/FlowUiEventBridge.tsx +++ b/apps/client/src/features/ws/ui/FlowUiEventBridge.tsx @@ -4,9 +4,9 @@ import { getFlowRunSessionId, type FlowUiEventPayload, type WebSocketMessage, -} from "./ws"; -import { createFlowUiEventRouter, mergeFlowUiAdapters } from "./flowUiEventRouter"; -import { toastFlowUiAdapter } from "./flowUiAdapters/toastFlowUiAdapter"; +} from "../lib/ws"; +import { createFlowUiEventRouter, mergeFlowUiAdapters } from "../lib/flowUiEventRouter"; +import { toastFlowUiAdapter } from "../lib/adapters/toastFlowUiAdapter"; /** Subscribes once app-wide: routes `flow_ui_event` → adapters for this tab's session only. */ export function FlowUiEventBridge() { diff --git a/apps/client/src/features/ws/ui/WebSocketProvider.tsx b/apps/client/src/features/ws/ui/WebSocketProvider.tsx index 594814a..06b94c1 100644 --- a/apps/client/src/features/ws/ui/WebSocketProvider.tsx +++ b/apps/client/src/features/ws/ui/WebSocketProvider.tsx @@ -7,9 +7,9 @@ import React, { ReactNode, useRef, } from "react"; -import { WebSocketChannel, WebSocketMessage } from "../ws/ws"; -import { isDemoMode } from "@/shared/demo"; -import { MockWebSocketChannel } from "./wsMock"; +import { WebSocketChannel, WebSocketMessage } from "../lib/ws"; +import { isDemoMode } from "@/shared/config/demo"; +import { MockWebSocketChannel } from "../lib/wsMock"; type WebSocketManager = WebSocketChannel | MockWebSocketChannel; diff --git a/apps/client/src/main.tsx b/apps/client/src/main.tsx index d3b3637..dec923a 100644 --- a/apps/client/src/main.tsx +++ b/apps/client/src/main.tsx @@ -4,9 +4,9 @@ import "./index.css"; import "./font.css"; import App from "./App.tsx"; -import { ThemeProvider } from "./app/providers/theme-provider.tsx"; -import { Toaster } from "./components/ui/sonner.tsx"; -import { SupabaseAuthProvider } from "./contexts/SupabaseAuthContext.tsx"; +import { ThemeProvider } from "@/app/providers/theme-provider.tsx"; +import { Toaster } from "@/shared/ui/sonner.tsx"; +import { SupabaseAuthProvider } from "@/app/providers/SupabaseAuthContext.tsx"; createRoot(document.getElementById("root")!).render( diff --git a/apps/client/src/pages/auth/index.ts b/apps/client/src/pages/auth/index.ts new file mode 100644 index 0000000..ae0b13f --- /dev/null +++ b/apps/client/src/pages/auth/index.ts @@ -0,0 +1 @@ +export { AuthPage } from "./ui/AuthPage"; diff --git a/apps/client/src/pages/code/index.ts b/apps/client/src/pages/code/index.ts new file mode 100644 index 0000000..e4bc00c --- /dev/null +++ b/apps/client/src/pages/code/index.ts @@ -0,0 +1 @@ +export { CodePage } from "./ui/CodePage"; diff --git a/apps/client/src/pages/code/ui/CodePage.tsx b/apps/client/src/pages/code/ui/CodePage.tsx index 6c0617b..48a4941 100644 --- a/apps/client/src/pages/code/ui/CodePage.tsx +++ b/apps/client/src/pages/code/ui/CodePage.tsx @@ -7,23 +7,23 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; +} from "@/shared/ui/breadcrumb"; import { ResizablePanelGroup, ResizablePanel, ResizableHandle, -} from "@/components/ui/resizable"; -import { Separator } from "@/components/ui/separator"; +} from "@/shared/ui/resizable"; +import { Separator } from "@/shared/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; -import { FileEditor } from "@/features/code/FileEditor"; -import { FileTree } from "@/features/code/FileTree"; +} from "@/shared/ui/sidebar"; +import { FileEditor } from "@/features/code"; +import { FileTree } from "@/features/code"; import { AppSidebar } from "@/features/sidebar"; -import { useConfigStore } from "@/entities/configurations/store"; -import { getCodeServiceEnabled } from "@/entities/configurations/codeService"; +import { useConfigStore } from "@/entities/configurations"; +import { getCodeServiceEnabled } from "@/entities/configurations"; export function CodePage() { const { configurations, fetchConfigs, isLoading } = useConfigStore(); diff --git a/apps/client/src/pages/dashboard/index.ts b/apps/client/src/pages/dashboard/index.ts new file mode 100644 index 0000000..77c7bc0 --- /dev/null +++ b/apps/client/src/pages/dashboard/index.ts @@ -0,0 +1,2 @@ +export { DashboardMainPanel } from "./ui/DashboardMainPanel"; +export type { DashboardMainPanelContentView } from "./ui/DashboardMainPanel"; diff --git a/apps/client/src/pages/dashboard/ui/DashboardMainPanel.tsx b/apps/client/src/pages/dashboard/ui/DashboardMainPanel.tsx index 63ce609..08eff78 100644 --- a/apps/client/src/pages/dashboard/ui/DashboardMainPanel.tsx +++ b/apps/client/src/pages/dashboard/ui/DashboardMainPanel.tsx @@ -1,9 +1,9 @@ -import { AllEntities } from "@/features/entity/AllEntities"; +import { AllEntities } from "@/features/entity"; import StatBlock from "@/features/stat"; -import ResourceUsage from "@/features/server-resource/resourceUsage"; +import ResourceUsage from "@/features/server-resource"; import { HaDashboard } from "@/features/ha"; -import { SdrDashboard } from "@/features/sdr/SdrDashboard"; -import { Ros2Dashboard } from "@/features/ros2/Ros2Dashboard"; +import { SdrDashboard } from "@/features/sdr"; +import { Ros2Dashboard } from "@/features/ros2"; export type DashboardMainPanelContentView = "main" | "ha" | "ros2" | "sdr"; diff --git a/apps/client/src/pages/desktop-settings/index.ts b/apps/client/src/pages/desktop-settings/index.ts new file mode 100644 index 0000000..4847007 --- /dev/null +++ b/apps/client/src/pages/desktop-settings/index.ts @@ -0,0 +1,2 @@ +export { DesktopSettingsPage } from "./ui/DesktopSettingsPage"; +export { default } from "./ui/DesktopSettingsPage"; diff --git a/apps/client/src/pages/desktop-settings/ui/DesktopSettingsPage.tsx b/apps/client/src/pages/desktop-settings/ui/DesktopSettingsPage.tsx index 90ce098..0daa22f 100644 --- a/apps/client/src/pages/desktop-settings/ui/DesktopSettingsPage.tsx +++ b/apps/client/src/pages/desktop-settings/ui/DesktopSettingsPage.tsx @@ -1,21 +1,21 @@ import React, { useEffect, useMemo, useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { storage } from "@/lib/storage"; +} from "@/shared/ui/select"; +import { storage } from "@/shared/lib/storage"; import { getServerAddress, type ServerAddress, updateServerAddress, -} from "@/shared/desktop"; +} from "@/shared/lib/desktop"; const HOST_PRESETS = [ { value: "0.0.0.0", label: "0.0.0.0 (LAN / external access)" }, diff --git a/apps/client/src/pages/devices/index.ts b/apps/client/src/pages/devices/index.ts new file mode 100644 index 0000000..00a82ae --- /dev/null +++ b/apps/client/src/pages/devices/index.ts @@ -0,0 +1 @@ +export { DevicePage } from "./ui/DevicePage"; diff --git a/apps/client/src/pages/devices/ui/DevicePage.tsx b/apps/client/src/pages/devices/ui/DevicePage.tsx index fd3edcf..ab39a5f 100644 --- a/apps/client/src/pages/devices/ui/DevicePage.tsx +++ b/apps/client/src/pages/devices/ui/DevicePage.tsx @@ -7,22 +7,22 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { Separator } from "@/components/ui/separator"; +} from "@/shared/ui/breadcrumb"; +import { Separator } from "@/shared/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { AppSidebar } from "@/features/sidebar"; import { INTEGRATION_DEVICE_ID, INTEGRATION_ENTITY_ID, -} from "@/features/integration/constants"; -import { useDeviceStore } from "@/entities/device/store"; -import { useEntityStore } from "@/entities/entity/store"; -import { DeviceList } from "@/widgets/device-list/DeviceList"; -import { EntityList } from "@/widgets/entity-list/EntityList"; +} from "@/features/integration"; +import { useDeviceStore } from "@/entities/device"; +import { useEntityStore } from "@/entities/entity"; +import { DeviceList } from "@/widgets/device-list"; +import { EntityList } from "@/widgets/entity-list"; export function DevicePage() { const [searchParams, setSearchParams] = useSearchParams(); diff --git a/apps/client/src/pages/dynamic-dashboard/index.ts b/apps/client/src/pages/dynamic-dashboard/index.ts new file mode 100644 index 0000000..da72659 --- /dev/null +++ b/apps/client/src/pages/dynamic-dashboard/index.ts @@ -0,0 +1,2 @@ +export { DynamicDashboardMainPanel } from "./ui/DynamicDashboardMainPanel"; +export { NewDynamicDashboardPanel } from "./ui/NewDynamicDashboardPanel"; diff --git a/apps/client/src/pages/dynamic-dashboard/ui/DynamicDashboardMainPanel.tsx b/apps/client/src/pages/dynamic-dashboard/ui/DynamicDashboardMainPanel.tsx index 04fff5f..0669a0f 100644 --- a/apps/client/src/pages/dynamic-dashboard/ui/DynamicDashboardMainPanel.tsx +++ b/apps/client/src/pages/dynamic-dashboard/ui/DynamicDashboardMainPanel.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; -import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; -import { GroupCanvas } from "@/features/dynamic-dashboard/GroupCanvas"; -import { useEntitiesData } from "@/features/entity/useEntitiesData"; +import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard"; +import { GroupCanvas } from "@/features/dynamic-dashboard"; +import { useEntitiesData } from "@/features/entity"; type DynamicDashboardMainPanelProps = { /** Which dashboard this column renders (store order / swipe order). */ diff --git a/apps/client/src/pages/dynamic-dashboard/ui/NewDynamicDashboardPanel.tsx b/apps/client/src/pages/dynamic-dashboard/ui/NewDynamicDashboardPanel.tsx index 8fcd436..f23192a 100644 --- a/apps/client/src/pages/dynamic-dashboard/ui/NewDynamicDashboardPanel.tsx +++ b/apps/client/src/pages/dynamic-dashboard/ui/NewDynamicDashboardPanel.tsx @@ -1,5 +1,5 @@ -import { Button } from "@/components/ui/button"; -import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; +import { Button } from "@/shared/ui/button"; +import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard"; import { Plus } from "lucide-react"; import { useNavigate } from "react-router"; diff --git a/apps/client/src/pages/flow/index.ts b/apps/client/src/pages/flow/index.ts new file mode 100644 index 0000000..206a52f --- /dev/null +++ b/apps/client/src/pages/flow/index.ts @@ -0,0 +1 @@ +export { FlowPage } from "./ui/FlowPage"; diff --git a/apps/client/src/pages/flow/ui/FlowPage.tsx b/apps/client/src/pages/flow/ui/FlowPage.tsx index b0a8d9a..c2d5336 100644 --- a/apps/client/src/pages/flow/ui/FlowPage.tsx +++ b/apps/client/src/pages/flow/ui/FlowPage.tsx @@ -6,23 +6,23 @@ import { BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage, -} from "@/components/ui/breadcrumb"; -import { Separator } from "@/components/ui/separator"; +} from "@/shared/ui/breadcrumb"; +import { Separator } from "@/shared/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; -import Flow, { FlowHeader, FlowSidebar } from "@/features/flow/Flow"; +} from "@/shared/ui/sidebar"; +import Flow, { FlowHeader, FlowSidebar } from "@/features/flow"; import { AppSidebar } from "@/features/sidebar"; import { useBeforeUnload, useBlocker } from "react-router"; -import { useFlowStore } from "@/entities/flow/store"; +import { useFlowStore } from "@/entities/flow"; import { useChatStore } from "@/features/llm-chat"; import { buildFlowSystemPrompt, FLOW_TOOLS, executeFlowToolCalls, -} from "@/features/flow/flow-chat"; +} from "@/features/flow"; import { AlertDialog, AlertDialogAction, @@ -32,7 +32,7 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; const UNSAVED_FLOW_MESSAGE = "You have unsaved changes in this flow. Leave without saving?"; diff --git a/apps/client/src/pages/landing/index.ts b/apps/client/src/pages/landing/index.ts new file mode 100644 index 0000000..084e042 --- /dev/null +++ b/apps/client/src/pages/landing/index.ts @@ -0,0 +1 @@ +export { default, Navbar } from "./ui/LandingPage"; diff --git a/apps/client/src/pages/landing/ui/LandingPage.tsx b/apps/client/src/pages/landing/ui/LandingPage.tsx index eb84c4f..65b3e20 100644 --- a/apps/client/src/pages/landing/ui/LandingPage.tsx +++ b/apps/client/src/pages/landing/ui/LandingPage.tsx @@ -4,7 +4,7 @@ import { NavigationMenuLink, NavigationMenuList, navigationMenuTriggerStyle, -} from "@/components/ui/navigation-menu"; +} from "@/shared/ui/navigation-menu"; import { Link, useNavigate } from "react-router"; import { useEffect } from "react"; diff --git a/apps/client/src/pages/map/index.ts b/apps/client/src/pages/map/index.ts new file mode 100644 index 0000000..8826d0c --- /dev/null +++ b/apps/client/src/pages/map/index.ts @@ -0,0 +1 @@ +export { MapPage, MapLayout } from "./ui/MapPage"; diff --git a/apps/client/src/pages/map/ui/MapPage.tsx b/apps/client/src/pages/map/ui/MapPage.tsx index 4dc2ae5..a0122b8 100644 --- a/apps/client/src/pages/map/ui/MapPage.tsx +++ b/apps/client/src/pages/map/ui/MapPage.tsx @@ -5,18 +5,18 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { Separator } from "@/components/ui/separator"; +} from "@/shared/ui/breadcrumb"; +import { Separator } from "@/shared/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, useSidebar, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { MapView } from "@/features/map"; -import { LayerSidebar } from "@/features/map-draw/LayerSidebar"; -import { MapToolbar } from "@/features/map-draw/MapToolbar"; -import { WebRTCProvider } from "@/features/rtc/WebRTCProvider"; +import { LayerSidebar } from "@/features/map-draw"; +import { MapToolbar } from "@/features/map-draw"; +import { WebRTCProvider } from "@/features/rtc"; import { AppSidebar } from "@/features/sidebar"; export function MapLayout() { diff --git a/apps/client/src/pages/notfound/index.ts b/apps/client/src/pages/notfound/index.ts new file mode 100644 index 0000000..077a72d --- /dev/null +++ b/apps/client/src/pages/notfound/index.ts @@ -0,0 +1 @@ +export { NotFound } from "./ui/NotFound"; diff --git a/apps/client/src/pages/recordings/index.ts b/apps/client/src/pages/recordings/index.ts new file mode 100644 index 0000000..703010f --- /dev/null +++ b/apps/client/src/pages/recordings/index.ts @@ -0,0 +1 @@ +export { RecordingsPage } from "./ui/RecordingsPage"; diff --git a/apps/client/src/pages/recordings/ui/RecordingsPage.tsx b/apps/client/src/pages/recordings/ui/RecordingsPage.tsx index 36da96b..c69ef19 100644 --- a/apps/client/src/pages/recordings/ui/RecordingsPage.tsx +++ b/apps/client/src/pages/recordings/ui/RecordingsPage.tsx @@ -5,13 +5,13 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { Separator } from "@/components/ui/separator"; +} from "@/shared/ui/breadcrumb"; +import { Separator } from "@/shared/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { RecordingsList } from "@/features/recording"; import { AppSidebar } from "@/features/sidebar"; diff --git a/apps/client/src/pages/settings/index.ts b/apps/client/src/pages/settings/index.ts new file mode 100644 index 0000000..566278c --- /dev/null +++ b/apps/client/src/pages/settings/index.ts @@ -0,0 +1,8 @@ +export { SettingsPage } from "./ui/SettingsPage"; +export { AccountSettingsPage } from "./ui/AccountSettingsPage"; +export { ConfigSettingsPage } from "./ui/ConfigSettingsPage"; +export { IntegrationSettingsPage } from "./ui/IntegrationSettingsPage"; +export { LogSettingsPage } from "./ui/LogSettingsPage"; +export { NetworksSettingsPage } from "./ui/NetworksSettingsPage"; +export { ServicesSettingsPage } from "./ui/ServicesSettingsPage"; +export { UsersSettingsPage } from "./ui/UsersSettingsPage"; diff --git a/apps/client/src/pages/settings/ui/AccountSettingsPage.tsx b/apps/client/src/pages/settings/ui/AccountSettingsPage.tsx index 66d3b6f..87e5906 100644 --- a/apps/client/src/pages/settings/ui/AccountSettingsPage.tsx +++ b/apps/client/src/pages/settings/ui/AccountSettingsPage.tsx @@ -8,26 +8,26 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; +} from "@/shared/ui/breadcrumb"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { AppSidebar } from "@/features/sidebar"; -import { Separator } from "@/components/ui/separator"; +import { Separator } from "@/shared/ui/separator"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, -} from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { useSupabaseAuth } from "@/contexts/SupabaseAuthContext"; +} from "@/shared/ui/card"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Button } from "@/shared/ui/button"; +import { Badge } from "@/shared/ui/badge"; +import { useSupabaseAuth } from "@/app/providers/SupabaseAuthContext"; import { fetchTurnCredentials, clearTurnCache, @@ -37,9 +37,9 @@ import { notifyListeners, type TurnCredentialsResponse, type TurnUsage, -} from "@/features/rtc/turnService"; -import { useChatStore } from "@/features/llm-chat/store"; -import { storage } from "@/lib/storage"; +} from "@/features/rtc"; +import { useChatStore } from "@/features/llm-chat"; +import { storage } from "@/shared/lib/storage"; function formatTurnQuotaUsage(usage: TurnUsage | null): string | null { if (!usage?.quotaBytes) return null; diff --git a/apps/client/src/pages/settings/ui/ConfigSettingsPage.tsx b/apps/client/src/pages/settings/ui/ConfigSettingsPage.tsx index 4145cec..b9d9497 100644 --- a/apps/client/src/pages/settings/ui/ConfigSettingsPage.tsx +++ b/apps/client/src/pages/settings/ui/ConfigSettingsPage.tsx @@ -7,12 +7,12 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; +} from "@/shared/ui/breadcrumb"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { AppSidebar } from "@/features/sidebar"; import { Table, @@ -21,12 +21,12 @@ import { TableHead, TableHeader, TableRow, -} from "@/components/ui/table"; -import { Badge } from "@/components/ui/badge"; -import { ConfigurationCreateButton } from "@/features/configurations/ConfigurationCreateButton"; -import { ConfigurationActionButton } from "@/features/configurations/ConfigurationActionButton"; -import { Separator } from "@/components/ui/separator"; -import { useConfigStore } from "@/entities/configurations/store"; +} from "@/shared/ui/table"; +import { Badge } from "@/shared/ui/badge"; +import { ConfigurationCreateButton } from "@/features/configurations"; +import { ConfigurationActionButton } from "@/features/configurations"; +import { Separator } from "@/shared/ui/separator"; +import { useConfigStore } from "@/entities/configurations"; export function ConfigSettingsPage() { const { configurations, fetchConfigs, isLoading, error } = useConfigStore(); diff --git a/apps/client/src/pages/settings/ui/IntegrationSettingsPage.tsx b/apps/client/src/pages/settings/ui/IntegrationSettingsPage.tsx index 329eb21..0bf1853 100644 --- a/apps/client/src/pages/settings/ui/IntegrationSettingsPage.tsx +++ b/apps/client/src/pages/settings/ui/IntegrationSettingsPage.tsx @@ -7,16 +7,16 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { Separator } from "@/components/ui/separator"; +} from "@/shared/ui/breadcrumb"; +import { Separator } from "@/shared/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { AppSidebar } from "@/features/sidebar"; -import { Intergration } from "@/features/integration/Integration"; +import { Intergration } from "@/features/integration"; export function IntegrationSettingsPage(): React.ReactElement { return ( diff --git a/apps/client/src/pages/settings/ui/LogSettingsPage.tsx b/apps/client/src/pages/settings/ui/LogSettingsPage.tsx index 0bada1f..c0c125a 100644 --- a/apps/client/src/pages/settings/ui/LogSettingsPage.tsx +++ b/apps/client/src/pages/settings/ui/LogSettingsPage.tsx @@ -6,13 +6,13 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { Separator } from "@/components/ui/separator"; +} from "@/shared/ui/breadcrumb"; +import { Separator } from "@/shared/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { Logs } from "@/features/log"; import { AppSidebar } from "@/features/sidebar"; diff --git a/apps/client/src/pages/settings/ui/NetworksSettingsPage.tsx b/apps/client/src/pages/settings/ui/NetworksSettingsPage.tsx index 700086f..135fdcf 100644 --- a/apps/client/src/pages/settings/ui/NetworksSettingsPage.tsx +++ b/apps/client/src/pages/settings/ui/NetworksSettingsPage.tsx @@ -7,15 +7,15 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; +} from "@/shared/ui/breadcrumb"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { AppSidebar } from "@/features/sidebar"; -import { Separator } from "@/components/ui/separator"; -import { Badge } from "@/components/ui/badge"; +import { Separator } from "@/shared/ui/separator"; +import { Badge } from "@/shared/ui/badge"; import { Globe, Radio, Bluetooth, Zap } from "lucide-react"; function useOnlineStatus() { diff --git a/apps/client/src/pages/settings/ui/ServicesSettingsPage.tsx b/apps/client/src/pages/settings/ui/ServicesSettingsPage.tsx index 47651df..7a0d072 100644 --- a/apps/client/src/pages/settings/ui/ServicesSettingsPage.tsx +++ b/apps/client/src/pages/settings/ui/ServicesSettingsPage.tsx @@ -8,33 +8,33 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; +} from "@/shared/ui/breadcrumb"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { AppSidebar } from "@/features/sidebar"; -import { Separator } from "@/components/ui/separator"; +import { Separator } from "@/shared/ui/separator"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, -} from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { useTunnelStore } from "@/entities/tunnel/store"; -import { useSupabaseAuth } from "@/contexts/SupabaseAuthContext"; -import { useConfigStore } from "@/entities/configurations/store"; +} from "@/shared/ui/card"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Switch } from "@/shared/ui/switch"; +import { Button } from "@/shared/ui/button"; +import { Badge } from "@/shared/ui/badge"; +import { useTunnelStore } from "@/entities/tunnel"; +import { useSupabaseAuth } from "@/app/providers/SupabaseAuthContext"; +import { useConfigStore } from "@/entities/configurations"; import { CODE_SERVICE_CONFIG_KEY, getCodeServiceEnabled, -} from "@/entities/configurations/codeService"; +} from "@/entities/configurations"; export function ServicesSettingsPage() { const { status, isLoading, error, refresh, start, stop } = useTunnelStore(); diff --git a/apps/client/src/pages/settings/ui/SettingsPage.tsx b/apps/client/src/pages/settings/ui/SettingsPage.tsx index f3292ed..94de1c2 100644 --- a/apps/client/src/pages/settings/ui/SettingsPage.tsx +++ b/apps/client/src/pages/settings/ui/SettingsPage.tsx @@ -6,14 +6,14 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; +} from "@/shared/ui/breadcrumb"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { AppSidebar } from "@/features/sidebar"; -import { Separator } from "@/components/ui/separator"; +import { Separator } from "@/shared/ui/separator"; import { Blocks, ChevronRight, diff --git a/apps/client/src/pages/settings/ui/UsersSettingsPage.tsx b/apps/client/src/pages/settings/ui/UsersSettingsPage.tsx index 33e5da0..38cf22a 100644 --- a/apps/client/src/pages/settings/ui/UsersSettingsPage.tsx +++ b/apps/client/src/pages/settings/ui/UsersSettingsPage.tsx @@ -6,17 +6,17 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { Separator } from "@/components/ui/separator"; +} from "@/shared/ui/breadcrumb"; +import { Separator } from "@/shared/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +} from "@/shared/ui/sidebar"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/shared/ui/tabs"; import { AppSidebar } from "@/features/sidebar"; -import { RoleTable } from "@/widgets/role-table/RoleList"; -import { UserTable } from "@/widgets/user-table/UserList"; +import { RoleTable } from "@/widgets/role-table"; +import { UserTable } from "@/widgets/user-table"; export function UsersSettingsPage() { return ( diff --git a/apps/client/src/pages/setup/index.ts b/apps/client/src/pages/setup/index.ts new file mode 100644 index 0000000..95ab236 --- /dev/null +++ b/apps/client/src/pages/setup/index.ts @@ -0,0 +1 @@ +export { SetupPage } from "./ui/SetupPage"; diff --git a/apps/client/src/pages/setup/ui/SetupPage.tsx b/apps/client/src/pages/setup/ui/SetupPage.tsx index 3bffdb0..4d92bc6 100644 --- a/apps/client/src/pages/setup/ui/SetupPage.tsx +++ b/apps/client/src/pages/setup/ui/SetupPage.tsx @@ -6,14 +6,14 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { Button } from "@/components/ui/button"; -import { Separator } from "@/components/ui/separator"; +} from "@/shared/ui/breadcrumb"; +import { Button } from "@/shared/ui/button"; +import { Separator } from "@/shared/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { AppSidebar } from "@/features/sidebar"; import { ArrowRight, Check, Circle, Loader2 } from "lucide-react"; import { initialSetupSteps, SetupStep } from "@/features/setup"; diff --git a/apps/client/src/shared/api/index.ts b/apps/client/src/shared/api/index.ts index 226a1aa..9f82ed0 100644 --- a/apps/client/src/shared/api/index.ts +++ b/apps/client/src/shared/api/index.ts @@ -1,7 +1,7 @@ import axios from "axios"; -import { isDemoMode } from "../demo"; +import { isDemoMode } from "../config/demo"; import { createMockAdapter } from "../mock/mockAdapter"; -import { storage } from "@/lib/storage"; +import { storage } from "@/shared/lib/storage"; export const apiClient = axios.create({ headers: { diff --git a/apps/client/src/shared/config/index.js b/apps/client/src/shared/config/index.js new file mode 100644 index 0000000..e69de29 diff --git a/apps/client/src/shared/config/index.ts b/apps/client/src/shared/config/index.ts new file mode 100644 index 0000000..d084538 --- /dev/null +++ b/apps/client/src/shared/config/index.ts @@ -0,0 +1 @@ +export * from "./demo"; diff --git a/apps/client/src/shared/lib/geometry-precision.ts b/apps/client/src/shared/lib/geometry-precision.ts index 49368bb..5eefbc1 100644 --- a/apps/client/src/shared/lib/geometry-precision.ts +++ b/apps/client/src/shared/lib/geometry-precision.ts @@ -1,4 +1,4 @@ -import { FeatureWithVertices } from "@/entities/map/types"; +import { FeatureWithVertices } from "@/entities/map"; interface GeometryProperties { location?: string; diff --git a/apps/client/src/shared/lib/geometry.ts b/apps/client/src/shared/lib/geometry.ts index f0369e6..3a69d6e 100644 --- a/apps/client/src/shared/lib/geometry.ts +++ b/apps/client/src/shared/lib/geometry.ts @@ -1,4 +1,4 @@ -import { FeatureWithVertices } from "@/entities/map/types"; +import { FeatureWithVertices } from "@/entities/map"; interface GeometryProperties { location?: string; diff --git a/apps/client/src/shared/lib/hooks/index.js b/apps/client/src/shared/lib/hooks/index.js new file mode 100644 index 0000000..e69de29 diff --git a/apps/client/src/shared/lib/hooks/index.ts b/apps/client/src/shared/lib/hooks/index.ts new file mode 100644 index 0000000..9927385 --- /dev/null +++ b/apps/client/src/shared/lib/hooks/index.ts @@ -0,0 +1,3 @@ +export { useIsMobile } from "./use-mobile"; +export { useDesktopSidecar } from "./useDesktopSidecar"; +export { usePreventBackNavigation } from "./usePreventBackNavigation"; diff --git a/apps/client/src/shared/lib/hooks/useDesktopSidecar.ts b/apps/client/src/shared/lib/hooks/useDesktopSidecar.ts index d1d1ac0..fff5646 100644 --- a/apps/client/src/shared/lib/hooks/useDesktopSidecar.ts +++ b/apps/client/src/shared/lib/hooks/useDesktopSidecar.ts @@ -1,8 +1,8 @@ import { useEffect } from "react"; -import { isDemoMode } from "@/shared/demo"; -import { ensureSidecarRunning, getDesktopServerUrl, isTauri } from "@/shared/desktop"; -import { storage } from "@/lib/storage"; +import { isDemoMode } from "@/shared/config/demo"; +import { ensureSidecarRunning, getDesktopServerUrl, isTauri } from "@/shared/lib/desktop"; +import { storage } from "@/shared/lib/storage"; export const useDesktopSidecar = () => { useEffect(() => { diff --git a/apps/client/src/shared/lib/hooks/usePreventBackNavigation.ts b/apps/client/src/shared/lib/hooks/usePreventBackNavigation.ts index 03ae7a8..1a4a1a4 100644 --- a/apps/client/src/shared/lib/hooks/usePreventBackNavigation.ts +++ b/apps/client/src/shared/lib/hooks/usePreventBackNavigation.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; -import { isTauri } from "@/shared/desktop"; +import { isTauri } from "@/shared/lib/desktop"; const isBackspaceAllowed = (event: KeyboardEvent) => { const path = event.composedPath(); diff --git a/apps/client/src/shared/lib/resetStores.ts b/apps/client/src/shared/lib/resetStores.ts index a2855bf..2099c73 100644 --- a/apps/client/src/shared/lib/resetStores.ts +++ b/apps/client/src/shared/lib/resetStores.ts @@ -1,6 +1,6 @@ -import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; -import { useIntegrationStore } from "@/entities/integrations/store"; -import { useConfigStore } from "@/entities/configurations/store"; +import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard"; +import { useIntegrationStore } from "@/entities/integrations"; +import { useConfigStore } from "@/entities/configurations"; /** * Wipe in-memory zustand caches that are scoped to the active server. diff --git a/apps/client/src/shared/mock/index.js b/apps/client/src/shared/mock/index.js new file mode 100644 index 0000000..e69de29 diff --git a/apps/client/src/shared/mock/index.ts b/apps/client/src/shared/mock/index.ts new file mode 100644 index 0000000..0df0159 --- /dev/null +++ b/apps/client/src/shared/mock/index.ts @@ -0,0 +1 @@ +export { createMockAdapter } from "./mockAdapter"; diff --git a/apps/client/src/shared/mock/mockAdapter.ts b/apps/client/src/shared/mock/mockAdapter.ts index 93150a6..714fa77 100644 --- a/apps/client/src/shared/mock/mockAdapter.ts +++ b/apps/client/src/shared/mock/mockAdapter.ts @@ -7,18 +7,18 @@ import { FeatureWithVertices, MapFeature, MapLayer, -} from "@/entities/map/types"; +} from "@/entities/map"; import { SystemConfiguration, SystemConfigurationPayload, -} from "@/entities/configurations/types"; -import { CODE_SERVICE_CONFIG_KEY } from "@/entities/configurations/codeService"; -import { Device, DevicePayload } from "@/entities/device/types"; -import { EntityAll, EntityPayload } from "@/entities/entity/types"; -import { Role, CreateRolePayload } from "@/entities/role/types"; -import { User, CreateUserPayload, UpdateUserPayload } from "@/entities/user/types"; -import { Flow, FlowPayload } from "@/entities/flow/types"; -import { CustomNodeFromApi } from "@/entities/custom-nodes/types"; +} from "@/entities/configurations"; +import { CODE_SERVICE_CONFIG_KEY } from "@/entities/configurations"; +import { Device, DevicePayload } from "@/entities/device"; +import { EntityAll, EntityPayload } from "@/entities/entity"; +import { Role, CreateRolePayload } from "@/entities/role"; +import { User, CreateUserPayload, UpdateUserPayload } from "@/entities/user"; +import { Flow, FlowPayload } from "@/entities/flow"; +import { CustomNodeFromApi } from "@/entities/custom-nodes"; import { createMockDb, MockDatabase } from "./mockData"; const clone = (data: T): T => JSON.parse(JSON.stringify(data)); diff --git a/apps/client/src/shared/mock/mockData.ts b/apps/client/src/shared/mock/mockData.ts index a66cdfa..c3ed7c9 100644 --- a/apps/client/src/shared/mock/mockData.ts +++ b/apps/client/src/shared/mock/mockData.ts @@ -1,15 +1,15 @@ -import { Device } from "@/entities/device/types"; -import { EntityAll } from "@/entities/entity/types"; -import { Flow, FlowVersion } from "@/entities/flow/types"; -import { HaState } from "@/entities/ha/types"; -import { MapLayer, FeatureWithVertices } from "@/entities/map/types"; -import { Permission } from "@/entities/permission/types"; -import { Role } from "@/entities/role/types"; -import { DeviceToken } from "@/entities/device-token/types"; -import { Stat } from "@/entities/stat/types"; -import { SystemConfiguration } from "@/entities/configurations/types"; -import { User } from "@/entities/user/types"; -import { CustomNode } from "@/entities/custom-nodes/types"; +import { Device } from "@/entities/device"; +import { EntityAll } from "@/entities/entity"; +import { Flow, FlowVersion } from "@/entities/flow"; +import { HaState } from "@/entities/ha"; +import { MapLayer, FeatureWithVertices } from "@/entities/map"; +import { Permission } from "@/entities/permission"; +import { Role } from "@/entities/role"; +import { DeviceToken } from "@/entities/device-token"; +import { Stat } from "@/entities/stat"; +import { SystemConfiguration } from "@/entities/configurations"; +import { User } from "@/entities/user"; +import { CustomNode } from "@/entities/custom-nodes"; export type MockDatabase = { stat: Stat; diff --git a/apps/client/src/shared/ui/alert-dialog.tsx b/apps/client/src/shared/ui/alert-dialog.tsx index 9c4d276..7e4cf53 100644 --- a/apps/client/src/shared/ui/alert-dialog.tsx +++ b/apps/client/src/shared/ui/alert-dialog.tsx @@ -1,8 +1,8 @@ import * as React from "react"; import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; -import { cn } from "@/lib/utils"; -import { buttonVariants } from "@/components/ui/button"; +import { cn } from "@/shared/lib/utils"; +import { buttonVariants } from "@/shared/ui/button"; function AlertDialog({ ...props diff --git a/apps/client/src/shared/ui/alert.tsx b/apps/client/src/shared/ui/alert.tsx index 21d45e2..b14e9bd 100644 --- a/apps/client/src/shared/ui/alert.tsx +++ b/apps/client/src/shared/ui/alert.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "@/shared/lib/utils"; const alertVariants = cva( "relative w-full border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", diff --git a/apps/client/src/shared/ui/avatar.tsx b/apps/client/src/shared/ui/avatar.tsx index b7224f0..8a6dbf4 100644 --- a/apps/client/src/shared/ui/avatar.tsx +++ b/apps/client/src/shared/ui/avatar.tsx @@ -1,7 +1,7 @@ import * as React from "react" import * as AvatarPrimitive from "@radix-ui/react-avatar" -import { cn } from "@/lib/utils" +import { cn } from "@/shared/lib/utils" function Avatar({ className, diff --git a/apps/client/src/shared/ui/badge.tsx b/apps/client/src/shared/ui/badge.tsx index 7d511ce..e5703f8 100644 --- a/apps/client/src/shared/ui/badge.tsx +++ b/apps/client/src/shared/ui/badge.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "@/shared/lib/utils"; const badgeVariants = cva( "inline-flex items-center justify-center border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", diff --git a/apps/client/src/shared/ui/breadcrumb.tsx b/apps/client/src/shared/ui/breadcrumb.tsx index eb88f32..5642c14 100644 --- a/apps/client/src/shared/ui/breadcrumb.tsx +++ b/apps/client/src/shared/ui/breadcrumb.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { ChevronRight, MoreHorizontal } from "lucide-react" -import { cn } from "@/lib/utils" +import { cn } from "@/shared/lib/utils" function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { return
)} -
-

- Personal C2 platform
for Physical AI -

+
+
- - + + + + + +
diff --git a/package-lock.json b/package-lock.json index 7c5fe38..a66d6ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122,6 +122,7 @@ "@react-three/postprocessing": "^3.0.4", "@supabase/supabase-js": "^2.90.1", "@theatre/core": "^0.7.2", + "gsap": "^3.15.0", "lucide-react": "^0.564.0", "maath": "^0.10.8", "next-themes": "^0.4.6", @@ -11171,6 +11172,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/gsap": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz", + "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==", + "license": "Standard 'no charge' license: https://gsap.com/standard-license." + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",