generated from CubeCraft-Creations/Tracehound
Merge commit '5793617be3154877fa10e4f46b7608de86fa54ca' into dev
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
# RemoteRig Frontend Environment Variables
|
||||||
|
# Copy this file to .env and fill in your values
|
||||||
|
|
||||||
|
# Backend API URL (default: /api proxied through Vite dev server)
|
||||||
|
VITE_API_URL=http://localhost:8080/api
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
.vite
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "default",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"css": "src/index.css",
|
||||||
|
"baseColor": "slate",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/utils",
|
||||||
|
"hooks": "@/hooks",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"types": "@/types"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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'
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['dist'] },
|
||||||
|
{
|
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>RemoteRig — Camera Monitoring Dashboard</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body class="bg-rig-dark-900 text-rig-dark-100">
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+3860
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "remoterig-dashboard",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lucide-react": "^0.469.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.17.0",
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.16",
|
||||||
|
"globals": "^15.13.0",
|
||||||
|
"postcss": "^8.4.49",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"typescript": "~5.7.2",
|
||||||
|
"typescript-eslint": "^8.18.0",
|
||||||
|
"vite": "^6.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
import { Camera } from 'lucide-react'
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-rig-dark-900">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="border-b border-rig-dark-700 bg-rig-dark-800/50 backdrop-blur-sm">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Camera className="h-7 w-7 text-rig-accent" />
|
||||||
|
<h1 className="text-xl font-bold tracking-tight text-rig-dark-50">
|
||||||
|
RemoteRig
|
||||||
|
</h1>
|
||||||
|
<span className="ml-2 rounded-full bg-rig-accent/10 px-2.5 py-0.5 text-xs font-medium text-rig-accent">
|
||||||
|
Dashboard
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-rig-dark-600 bg-rig-dark-800/30 py-24 text-center">
|
||||||
|
<Camera className="mb-4 h-12 w-12 text-rig-dark-500" />
|
||||||
|
<h2 className="text-lg font-semibold text-rig-dark-200">
|
||||||
|
Dashboard Coming Soon
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 max-w-sm text-sm text-rig-dark-400">
|
||||||
|
Camera monitoring and remote control interface — under construction.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="border-t border-rig-dark-700 bg-rig-dark-800/30">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 py-3 sm:px-6 lg:px-8">
|
||||||
|
<p className="text-center text-xs text-rig-dark-500">
|
||||||
|
RemoteRig v0.1.0 — Multi-Camera Remote Monitoring System
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { useCameraStatus } from './useCameraStatus'
|
||||||
|
export { useSystemHealth } from './useSystemHealth'
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import type { Camera } from '../types'
|
||||||
|
|
||||||
|
const MOCK_CAMERAS: Camera[] = [
|
||||||
|
{ id: 'cam-1', name: 'Front View', status: 'online', position: 'North', fps: 30, resolution: '1920x1080', streamUrl: '/api/cameras/cam-1/stream', recording: true },
|
||||||
|
{ id: 'cam-2', name: 'Side View', status: 'online', position: 'East', fps: 30, resolution: '1920x1080', streamUrl: '/api/cameras/cam-2/stream', recording: false },
|
||||||
|
{ id: 'cam-3', name: 'Top View', status: 'connecting', position: 'Center', fps: 15, streamUrl: '/api/cameras/cam-3/stream', recording: false },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function useCameraStatus() {
|
||||||
|
const [cameras, setCameras] = useState<Camera[]>(MOCK_CAMERAS)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setCameras((prev) =>
|
||||||
|
prev.map((c) => ({ ...c })),
|
||||||
|
)
|
||||||
|
}, 5000)
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return { cameras }
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import type { SystemHealth } from '../types'
|
||||||
|
|
||||||
|
const MOCK_HEALTH: SystemHealth = {
|
||||||
|
cpuUsage: 23,
|
||||||
|
memoryUsage: 45,
|
||||||
|
gpuUsage: 12,
|
||||||
|
temperature: 58,
|
||||||
|
uptime: '2d 14h 32m',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSystemHealth() {
|
||||||
|
const [health, setHealth] = useState<SystemHealth>(MOCK_HEALTH)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setHealth((prev) => ({
|
||||||
|
...prev,
|
||||||
|
cpuUsage: Math.min(100, Math.max(0, prev.cpuUsage + Math.round((Math.random() - 0.5) * 10))),
|
||||||
|
memoryUsage: Math.min(100, Math.max(0, prev.memoryUsage + Math.round((Math.random() - 0.5) * 4))),
|
||||||
|
temperature: Math.min(95, Math.max(30, prev.temperature + Math.round((Math.random() - 0.5) * 3))),
|
||||||
|
}))
|
||||||
|
}, 3000)
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return { health }
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* Dashboard-specific resets */
|
||||||
|
html {
|
||||||
|
@apply antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply bg-rig-dark-900 text-rig-dark-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar styling */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
@apply bg-rig-dark-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-rig-dark-600 rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
@apply bg-rig-dark-500;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import './index.css'
|
||||||
|
import App from './App'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
const API_BASE = import.meta.env.VITE_API_URL || '/api'
|
||||||
|
|
||||||
|
async function request<T>(endpoint: string, options?: RequestInit): Promise<T> {
|
||||||
|
const response = await fetch(`${API_BASE}${endpoint}`, {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`API error: ${response.status} ${response.statusText}`)
|
||||||
|
}
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const api = {
|
||||||
|
getCameras: () => request<[]>('/cameras'),
|
||||||
|
getCameraStatus: (id: string) => request<[]>(`/cameras/${id}/status`),
|
||||||
|
getSystemHealth: () => request<[]>('/system/health'),
|
||||||
|
toggleRecording: (cameraId: string) =>
|
||||||
|
request<[]>(`/cameras/${cameraId}/recording`, { method: 'POST' }),
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// RemoteRig TypeScript types
|
||||||
|
|
||||||
|
export interface Camera {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
streamUrl: string
|
||||||
|
status: 'online' | 'offline' | 'error' | 'connecting'
|
||||||
|
position?: string
|
||||||
|
fps: number
|
||||||
|
resolution?: string
|
||||||
|
recording: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CameraFeed {
|
||||||
|
cameraId: string
|
||||||
|
thumbnailUrl?: string
|
||||||
|
frameRate: number
|
||||||
|
bitrate?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SystemHealth {
|
||||||
|
cpuUsage: number
|
||||||
|
memoryUsage: number
|
||||||
|
gpuUsage: number
|
||||||
|
temperature: number
|
||||||
|
uptime: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StreamConfig {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
fps: number
|
||||||
|
codec: string
|
||||||
|
bitrate: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export function formatUptime(seconds: number): string {
|
||||||
|
const days = Math.floor(seconds / 86400)
|
||||||
|
const hours = Math.floor((seconds % 86400) / 3600)
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
const parts: string[] = []
|
||||||
|
if (days > 0) parts.push(`${days}d`)
|
||||||
|
if (hours > 0) parts.push(`${hours}h`)
|
||||||
|
parts.push(`${minutes}m`)
|
||||||
|
return parts.join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function statusColor(status: string): string {
|
||||||
|
switch (status) {
|
||||||
|
case 'online':
|
||||||
|
return 'bg-rig-success'
|
||||||
|
case 'offline':
|
||||||
|
return 'bg-rig-danger'
|
||||||
|
case 'error':
|
||||||
|
return 'bg-rig-danger'
|
||||||
|
case 'connecting':
|
||||||
|
return 'bg-rig-warning'
|
||||||
|
default:
|
||||||
|
return 'bg-rig-dark-500'
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{js,ts,jsx,tsx}",
|
||||||
|
],
|
||||||
|
darkMode: 'class',
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
// Dark dashboard palette
|
||||||
|
'rig-dark': {
|
||||||
|
50: '#f3f4f6',
|
||||||
|
100: '#e5e7eb',
|
||||||
|
200: '#d1d5db',
|
||||||
|
300: '#9ca3af',
|
||||||
|
400: '#6b7280',
|
||||||
|
500: '#4b5563',
|
||||||
|
600: '#374151',
|
||||||
|
700: '#1f2937',
|
||||||
|
800: '#111827',
|
||||||
|
900: '#030712',
|
||||||
|
},
|
||||||
|
'rig-accent': {
|
||||||
|
DEFAULT: '#22d3ee',
|
||||||
|
light: '#67e8f9',
|
||||||
|
dark: '#06b6d4',
|
||||||
|
},
|
||||||
|
'rig-danger': '#ef4444',
|
||||||
|
'rig-success': '#22c55e',
|
||||||
|
'rig-warning': '#eab308',
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||||
|
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8080',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user