diff --git a/package.json b/package.json index b124bad..d6a6703 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "odo", - "version": "0.10.0", + "version": "0.10.1", "description": "ODO — OpenClaw Dashboard Orchestrator", "main": "main.js", "scripts": { diff --git a/postcss.config.js b/postcss.config.cjs similarity index 77% rename from postcss.config.js rename to postcss.config.cjs index 2aa7205..12a703d 100644 --- a/postcss.config.js +++ b/postcss.config.cjs @@ -1,4 +1,4 @@ -export default { +module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, diff --git a/preload.js b/preload.js index 871edb1..f46176e 100644 --- a/preload.js +++ b/preload.js @@ -4,7 +4,7 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('odo', { platform: process.platform, - version: '0.10.0', + version: '0.10.1', isElectron: true, gateway: { diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 258b7a0..a6d2a46 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -2,11 +2,27 @@ import { useState, useEffect, useCallback } from 'react'; type GatewayStatus = 'unknown' | 'checking' | 'running' | 'stopped' | 'error'; +interface StatsData { + totalTokens?: number; + totalCost?: number; + activeAgents?: number; + totalRequests?: number; + [key: string]: unknown; +} + +type StatsState = + | { status: 'idle' } + | { status: 'loading' } + | { status: 'loaded'; data: StatsData } + | { status: 'error'; message: string }; + function Dashboard() { const [gwStatus, setGwStatus] = useState('unknown'); const [gwOutput, setGwOutput] = useState(''); const [actionInProgress, setActionInProgress] = useState(false); + const [stats, setStats] = useState({ status: 'idle' }); + const isElectron = typeof window !== 'undefined' && window.odo?.isElectron; const checkStatus = useCallback(async () => { @@ -38,6 +54,27 @@ function Dashboard() { return () => clearInterval(interval); }, [checkStatus]); + const fetchStats = useCallback(async () => { + if (!isElectron) { + setStats({ status: 'error', message: 'Not running in Electron' }); + return; + } + setStats({ status: 'loading' }); + try { + const data = (await window.odo.ocm.api('GET', '/api/stats')) as StatsData; + setStats({ status: 'loaded', data }); + } catch (err) { + setStats({ status: 'error', message: String(err) }); + } + }, [isElectron]); + + useEffect(() => { + fetchStats(); + // Refresh stats every 30 seconds + const interval = setInterval(fetchStats, 30000); + return () => clearInterval(interval); + }, [fetchStats]); + const handleAction = async (action: 'start' | 'stop' | 'restart') => { if (!isElectron) return; setActionInProgress(true); @@ -136,20 +173,76 @@ function Dashboard() {

- {/* Stats Summary (placeholder) */} + {/* Stats Summary */}
-

- Stats Summary -

-

- System statistics and metrics will be available in a future update. -

+
+

+ Stats Summary +

+ +
+ + {stats.status === 'idle' && ( +

Initializing...

+ )} + + {stats.status === 'loading' && ( +
+ {[1, 2, 3].map((i) => ( +
+
+
+
+ ))} +
+ )} + + {stats.status === 'error' && ( +

+ Unable to load stats — OCM server may not be running. +

+ )} + + {stats.status === 'loaded' && ( +
+
+

Total Tokens

+

+ {typeof stats.data.totalTokens === 'number' + ? stats.data.totalTokens.toLocaleString() + : '—'} +

+
+
+

Total Cost

+

+ {typeof stats.data.totalCost === 'number' + ? `$${stats.data.totalCost.toFixed(4)}` + : '—'} +

+
+
+

Active Agents

+

+ {typeof stats.data.activeAgents === 'number' + ? stats.data.activeAgents + : '—'} +

+
+
+ )}
{/* Footer */} diff --git a/tailwind.config.js b/tailwind.config.cjs similarity index 95% rename from tailwind.config.js rename to tailwind.config.cjs index c4dd1fb..d215652 100644 --- a/tailwind.config.js +++ b/tailwind.config.cjs @@ -1,5 +1,5 @@ /** @type {import('tailwindcss').Config} */ -export default { +module.exports = { content: ['./src/**/*.{html,js,ts,jsx,tsx}'], theme: { extend: {