From 8284b1dd11ccdac7046bf25eeea5de429f7ed6ff Mon Sep 17 00:00:00 2001 From: Toba Ojo Date: Thu, 20 Nov 2025 19:09:43 +0000 Subject: [PATCH] first commit --- .gitignore | 25 + .prettierrc | 3 + eslint.config.js | 23 + index.html | 13 + package.json | 44 + public/MAV.svg | 18 + public/vite.svg | 1 + src/App.css | 0 src/App.tsx | 9 + src/app/config/queryClient.ts | 10 + src/app/config/wsconfig.ts | 5 + src/app/providers/AppProviders.tsx | 7 + src/app/providers/QueryProviders.tsx | 7 + src/assets/react.svg | 1 + .../dashboard/components/CameraStatus.tsx | 0 .../dashboard/components/DashboardGrid.tsx | 11 + .../dashboard/components/SystemStatusCard.tsx | 26 + src/hooks/useInfoWebSocket.ts | 17 + src/index.css | 7 + src/main.tsx | 22 + src/realtime/WebSocketManager.ts | 57 + src/routeTree.gen.ts | 131 + src/routes/__root.tsx | 19 + src/routes/about.tsx | 9 + src/routes/baywatch.tsx | 9 + src/routes/index.tsx | 15 + src/routes/output.tsx | 9 + src/routes/settings.tsx | 9 + src/types/types.ts | 12 + src/ui/Card.tsx | 22 + src/ui/CardHeader.tsx | 21 + src/ui/Footer.tsx | 12 + src/ui/Header.tsx | 34 + tsconfig.app.json | 28 + tsconfig.json | 7 + tsconfig.node.json | 26 + vite.config.ts | 16 + yarn.lock | 2358 +++++++++++++++++ 38 files changed, 3043 insertions(+) create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 package.json create mode 100644 public/MAV.svg create mode 100644 public/vite.svg create mode 100644 src/App.css create mode 100644 src/App.tsx create mode 100644 src/app/config/queryClient.ts create mode 100644 src/app/config/wsconfig.ts create mode 100644 src/app/providers/AppProviders.tsx create mode 100644 src/app/providers/QueryProviders.tsx create mode 100644 src/assets/react.svg create mode 100644 src/features/dashboard/components/CameraStatus.tsx create mode 100644 src/features/dashboard/components/DashboardGrid.tsx create mode 100644 src/features/dashboard/components/SystemStatusCard.tsx create mode 100644 src/hooks/useInfoWebSocket.ts create mode 100644 src/index.css create mode 100644 src/main.tsx create mode 100644 src/realtime/WebSocketManager.ts create mode 100644 src/routeTree.gen.ts create mode 100644 src/routes/__root.tsx create mode 100644 src/routes/about.tsx create mode 100644 src/routes/baywatch.tsx create mode 100644 src/routes/index.tsx create mode 100644 src/routes/output.tsx create mode 100644 src/routes/settings.tsx create mode 100644 src/types/types.ts create mode 100644 src/ui/Card.tsx create mode 100644 src/ui/CardHeader.tsx create mode 100644 src/ui/Footer.tsx create mode 100644 src/ui/Header.tsx create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1cac559 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.env \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..963354f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "printWidth": 120 +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..5e6b472 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/index.html b/index.html new file mode 100644 index 0000000..fd0bb43 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + MAV | BayiQ + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..cb4c39e --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "bayiq-ui", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^7.1.0", + "@fortawesome/free-brands-svg-icons": "^7.1.0", + "@fortawesome/free-regular-svg-icons": "^7.1.0", + "@fortawesome/free-solid-svg-icons": "^7.1.0", + "@fortawesome/react-fontawesome": "^3.1.0", + "@tanstack/react-query": "^5.90.10", + "@tanstack/react-router": "^1.136.18", + "@tanstack/react-router-devtools": "^1.136.18", + "clsx": "^2.1.1", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tailwindcss/vite": "^4.1.17", + "@tanstack/router-plugin": "^1.136.18", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.22", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.17", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } +} diff --git a/public/MAV.svg b/public/MAV.svg new file mode 100644 index 0000000..a2cce6c --- /dev/null +++ b/public/MAV.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..e69de29 diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..f44d745 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,9 @@ +function App() { + return ( + <> +

hello world

+ + ); +} + +export default App; diff --git a/src/app/config/queryClient.ts b/src/app/config/queryClient.ts new file mode 100644 index 0000000..ee9f895 --- /dev/null +++ b/src/app/config/queryClient.ts @@ -0,0 +1,10 @@ +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60_000, + refetchOnWindowFocus: false, + }, + }, +}); diff --git a/src/app/config/wsconfig.ts b/src/app/config/wsconfig.ts new file mode 100644 index 0000000..74c8421 --- /dev/null +++ b/src/app/config/wsconfig.ts @@ -0,0 +1,5 @@ +export const wsConfig = { + infoBar: "ws://100.115.148.59/websocket-infobar", +}; + +export type SocketKey = keyof typeof wsConfig; diff --git a/src/app/providers/AppProviders.tsx b/src/app/providers/AppProviders.tsx new file mode 100644 index 0000000..a07b51a --- /dev/null +++ b/src/app/providers/AppProviders.tsx @@ -0,0 +1,7 @@ +import { QueryClientProvider } from "@tanstack/react-query"; +import { queryClient } from "../config/queryClient"; +import type { PropsWithChildren } from "react"; + +export const AppProviders = ({ children }: PropsWithChildren) => { + return {children}; +}; diff --git a/src/app/providers/QueryProviders.tsx b/src/app/providers/QueryProviders.tsx new file mode 100644 index 0000000..2cf5ca0 --- /dev/null +++ b/src/app/providers/QueryProviders.tsx @@ -0,0 +1,7 @@ +import type { PropsWithChildren } from "react"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { queryClient } from "../config/queryClient"; + +export function QueryProvider({ children }: PropsWithChildren) { + return {children}; +} diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/features/dashboard/components/CameraStatus.tsx b/src/features/dashboard/components/CameraStatus.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/features/dashboard/components/DashboardGrid.tsx b/src/features/dashboard/components/DashboardGrid.tsx new file mode 100644 index 0000000..8cf481c --- /dev/null +++ b/src/features/dashboard/components/DashboardGrid.tsx @@ -0,0 +1,11 @@ +import SystemStatusCard from "./SystemStatusCard"; + +const DashboardGrid = () => { + return ( +
+ +
+ ); +}; + +export default DashboardGrid; diff --git a/src/features/dashboard/components/SystemStatusCard.tsx b/src/features/dashboard/components/SystemStatusCard.tsx new file mode 100644 index 0000000..b887e7e --- /dev/null +++ b/src/features/dashboard/components/SystemStatusCard.tsx @@ -0,0 +1,26 @@ +import { useInfoSocket } from "../../../hooks/useInfoWebSocket"; +import Card from "../../../ui/Card"; +import CardHeader from "../../../ui/CardHeader"; + +const SystemStatusCard = () => { + const { stats } = useInfoSocket(); + + return ( + + + {stats ? ( + <> +
UTC: {stats["system-clock-utc"]}
+ Local: {stats["system-clock-local"]} + CPU: {stats["memory-cpu-status"]} + Threads: {stats["thread-count"]} + + ) : ( + Loading system status… + )} +
+
+ ); +}; + +export default SystemStatusCard; diff --git a/src/hooks/useInfoWebSocket.ts b/src/hooks/useInfoWebSocket.ts new file mode 100644 index 0000000..106af41 --- /dev/null +++ b/src/hooks/useInfoWebSocket.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from "react"; +import { wsManager } from "../realtime/WebSocketManager"; +import type { InfoBarData } from "../types/types"; + +export function useInfoSocket() { + const [stats, setStats] = useState(null); + + useEffect(() => { + const unsubscribe = wsManager.subscribe("infoBar", (msg: InfoBarData) => { + setStats(msg); + }); + + return unsubscribe; + }, []); + + return { stats }; +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..9951853 --- /dev/null +++ b/src/index.css @@ -0,0 +1,7 @@ +@import "tailwindcss"; + +body { + background: #1e2a38; + color: #fff; + font-family: Arial, Helvetica, sans-serif; +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..d0ee7d9 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { RouterProvider, createRouter } from "@tanstack/react-router"; +import { routeTree } from "./routeTree.gen"; // generated by plugin +import { AppProviders } from "./app/providers/AppProviders"; +import "./index.css"; + +const router = createRouter({ routeTree }); + +declare module "@tanstack/react-router" { + interface Register { + router: typeof router; + } +} + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + + + , +); diff --git a/src/realtime/WebSocketManager.ts b/src/realtime/WebSocketManager.ts new file mode 100644 index 0000000..36273a3 --- /dev/null +++ b/src/realtime/WebSocketManager.ts @@ -0,0 +1,57 @@ +import { wsConfig, type SocketKey } from "../app/config/wsconfig"; + +type Listener = (msg: any) => void; + +class WebSocketManager { + private sockets = new Map(); + private listeners = new Map>(); + + private ensureSocket(key: SocketKey): WebSocket { + if (!this.sockets.has(key)) { + const url = wsConfig[key]; + const ws = new WebSocket(url); + this.sockets.set(key, ws); + + ws.onmessage = async (event) => { + const text = await event.data.text(); + const data = JSON.parse(text); + + const ls = this.listeners.get(key); + + ls?.forEach((cb) => cb(data)); + }; + + ws.onclose = () => { + // optional: handle reconnect logic here later + }; + } + return this.sockets.get(key)!; + } + + subscribe(key: SocketKey, listener: Listener) { + if (!this.listeners.has(key)) { + this.listeners.set(key, new Set()); + } + this.listeners.get(key)!.add(listener); + + // Ensure socket is created and listening + this.ensureSocket(key); + + // Return unsubscribe function + return () => { + const set = this.listeners.get(key); + if (!set) return; + set.delete(listener); + }; + } + + send(key: SocketKey, payload: any) { + const socket = this.ensureSocket(key); + if (socket.readyState === WebSocket.OPEN) { + socket.send(JSON.stringify(payload)); + } + // if not open, you might buffer / retry etc. + } +} + +export const wsManager = new WebSocketManager(); diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts new file mode 100644 index 0000000..2ce97f6 --- /dev/null +++ b/src/routeTree.gen.ts @@ -0,0 +1,131 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as SettingsRouteImport } from './routes/settings' +import { Route as OutputRouteImport } from './routes/output' +import { Route as BaywatchRouteImport } from './routes/baywatch' +import { Route as AboutRouteImport } from './routes/about' +import { Route as IndexRouteImport } from './routes/index' + +const SettingsRoute = SettingsRouteImport.update({ + id: '/settings', + path: '/settings', + getParentRoute: () => rootRouteImport, +} as any) +const OutputRoute = OutputRouteImport.update({ + id: '/output', + path: '/output', + getParentRoute: () => rootRouteImport, +} as any) +const BaywatchRoute = BaywatchRouteImport.update({ + id: '/baywatch', + path: '/baywatch', + getParentRoute: () => rootRouteImport, +} as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/about': typeof AboutRoute + '/baywatch': typeof BaywatchRoute + '/output': typeof OutputRoute + '/settings': typeof SettingsRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/about': typeof AboutRoute + '/baywatch': typeof BaywatchRoute + '/output': typeof OutputRoute + '/settings': typeof SettingsRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/about': typeof AboutRoute + '/baywatch': typeof BaywatchRoute + '/output': typeof OutputRoute + '/settings': typeof SettingsRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/about' | '/baywatch' | '/output' | '/settings' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/about' | '/baywatch' | '/output' | '/settings' + id: '__root__' | '/' | '/about' | '/baywatch' | '/output' | '/settings' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + AboutRoute: typeof AboutRoute + BaywatchRoute: typeof BaywatchRoute + OutputRoute: typeof OutputRoute + SettingsRoute: typeof SettingsRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/settings': { + id: '/settings' + path: '/settings' + fullPath: '/settings' + preLoaderRoute: typeof SettingsRouteImport + parentRoute: typeof rootRouteImport + } + '/output': { + id: '/output' + path: '/output' + fullPath: '/output' + preLoaderRoute: typeof OutputRouteImport + parentRoute: typeof rootRouteImport + } + '/baywatch': { + id: '/baywatch' + path: '/baywatch' + fullPath: '/baywatch' + preLoaderRoute: typeof BaywatchRouteImport + parentRoute: typeof rootRouteImport + } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + AboutRoute: AboutRoute, + BaywatchRoute: BaywatchRoute, + OutputRoute: OutputRoute, + SettingsRoute: SettingsRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx new file mode 100644 index 0000000..6621494 --- /dev/null +++ b/src/routes/__root.tsx @@ -0,0 +1,19 @@ +import { createRootRoute, Outlet } from "@tanstack/react-router"; +import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; +import Header from "../ui/Header"; +import Footer from "../ui/Footer"; + +const RootLayout = () => ( + <> +
+
+ +
+