generated from CubeCraft-Creations/Tracehound
CUB-194: scaffold Vite + React + TypeScript + Tailwind frontend
- Initialize Vite project with React + TypeScript + Tailwind CSS - Dark theme dashboard design (rig-dark palette, rig-accent colors) - Minimal App with RemoteRig header + 'Dashboard coming soon' placeholder - Directory structure: components, hooks, services, types, utils - TypeScript types for Camera, CameraFeed, SystemHealth, StreamConfig - Custom hooks: useCameraStatus, useSystemHealth (with mock data) - API service layer with proxy config for /api → localhost:8080 - eslint, postcss, autoprefixer configured - lucide-react icons integrated (Camera icon) - .env.example, .gitignore, tsconfig basepaths configured - Build + type-check + lint verified clean
This commit is contained in:
+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" />
|
||||
Reference in New Issue
Block a user