mirror of
https://github.com/dtzp555-max/odo.git
synced 2026-07-19 09:44:37 +00:00
feat: Dashboard stats from OCM API, fix module warnings (v0.10.1)
- Rename postcss.config.js/tailwind.config.js to .cjs to fix ESM warning - Replace Stats Summary placeholder with real OCM /api/stats data - Handle loading, error, and offline states gracefully - Bump version to 0.10.1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "odo",
|
"name": "odo",
|
||||||
"version": "0.10.0",
|
"version": "0.10.1",
|
||||||
"description": "ODO — OpenClaw Dashboard Orchestrator",
|
"description": "ODO — OpenClaw Dashboard Orchestrator",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
+1
-1
@@ -4,7 +4,7 @@ const { contextBridge, ipcRenderer } = require('electron');
|
|||||||
|
|
||||||
contextBridge.exposeInMainWorld('odo', {
|
contextBridge.exposeInMainWorld('odo', {
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
version: '0.10.0',
|
version: '0.10.1',
|
||||||
isElectron: true,
|
isElectron: true,
|
||||||
|
|
||||||
gateway: {
|
gateway: {
|
||||||
|
|||||||
+101
-8
@@ -2,11 +2,27 @@ import { useState, useEffect, useCallback } from 'react';
|
|||||||
|
|
||||||
type GatewayStatus = 'unknown' | 'checking' | 'running' | 'stopped' | 'error';
|
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() {
|
function Dashboard() {
|
||||||
const [gwStatus, setGwStatus] = useState<GatewayStatus>('unknown');
|
const [gwStatus, setGwStatus] = useState<GatewayStatus>('unknown');
|
||||||
const [gwOutput, setGwOutput] = useState('');
|
const [gwOutput, setGwOutput] = useState('');
|
||||||
const [actionInProgress, setActionInProgress] = useState(false);
|
const [actionInProgress, setActionInProgress] = useState(false);
|
||||||
|
|
||||||
|
const [stats, setStats] = useState<StatsState>({ status: 'idle' });
|
||||||
|
|
||||||
const isElectron = typeof window !== 'undefined' && window.odo?.isElectron;
|
const isElectron = typeof window !== 'undefined' && window.odo?.isElectron;
|
||||||
|
|
||||||
const checkStatus = useCallback(async () => {
|
const checkStatus = useCallback(async () => {
|
||||||
@@ -38,6 +54,27 @@ function Dashboard() {
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [checkStatus]);
|
}, [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') => {
|
const handleAction = async (action: 'start' | 'stop' | 'restart') => {
|
||||||
if (!isElectron) return;
|
if (!isElectron) return;
|
||||||
setActionInProgress(true);
|
setActionInProgress(true);
|
||||||
@@ -136,20 +173,76 @@ function Dashboard() {
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Stats Summary (placeholder) */}
|
{/* Stats Summary */}
|
||||||
<section className="bg-odo-surface border border-odo-border rounded-lg p-6 lg:col-span-2">
|
<section className="bg-odo-surface border border-odo-border rounded-lg p-6 lg:col-span-2">
|
||||||
<h2 className="text-lg font-semibold text-white mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
Stats Summary
|
<h2 className="text-lg font-semibold text-white">
|
||||||
</h2>
|
Stats Summary
|
||||||
<p className="text-odo-text-dim text-sm">
|
</h2>
|
||||||
System statistics and metrics will be available in a future update.
|
<button
|
||||||
</p>
|
onClick={fetchStats}
|
||||||
|
disabled={stats.status === 'loading'}
|
||||||
|
className="px-3 py-1 bg-odo-border hover:bg-gray-600 disabled:opacity-50 text-white rounded text-xs font-medium transition-colors"
|
||||||
|
>
|
||||||
|
{stats.status === 'loading' ? 'Loading...' : 'Refresh'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{stats.status === 'idle' && (
|
||||||
|
<p className="text-odo-text-dim text-sm">Initializing...</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{stats.status === 'loading' && (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
{[1, 2, 3].map((i) => (
|
||||||
|
<div key={i} className="bg-odo-bg border border-odo-border rounded-lg p-4 animate-pulse">
|
||||||
|
<div className="h-4 bg-odo-border rounded w-24 mb-2" />
|
||||||
|
<div className="h-6 bg-odo-border rounded w-16" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{stats.status === 'error' && (
|
||||||
|
<p className="text-odo-text-dim text-sm">
|
||||||
|
Unable to load stats — OCM server may not be running.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{stats.status === 'loaded' && (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
<div className="bg-odo-bg border border-odo-border rounded-lg p-4">
|
||||||
|
<p className="text-odo-text-dim text-xs uppercase tracking-wide mb-1">Total Tokens</p>
|
||||||
|
<p className="text-2xl font-bold text-white">
|
||||||
|
{typeof stats.data.totalTokens === 'number'
|
||||||
|
? stats.data.totalTokens.toLocaleString()
|
||||||
|
: '—'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-odo-bg border border-odo-border rounded-lg p-4">
|
||||||
|
<p className="text-odo-text-dim text-xs uppercase tracking-wide mb-1">Total Cost</p>
|
||||||
|
<p className="text-2xl font-bold text-white">
|
||||||
|
{typeof stats.data.totalCost === 'number'
|
||||||
|
? `$${stats.data.totalCost.toFixed(4)}`
|
||||||
|
: '—'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-odo-bg border border-odo-border rounded-lg p-4">
|
||||||
|
<p className="text-odo-text-dim text-xs uppercase tracking-wide mb-1">Active Agents</p>
|
||||||
|
<p className="text-2xl font-bold text-white">
|
||||||
|
{typeof stats.data.activeAgents === 'number'
|
||||||
|
? stats.data.activeAgents
|
||||||
|
: '—'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="mt-8 text-center text-odo-text-dim text-xs">
|
<footer className="mt-8 text-center text-odo-text-dim text-xs">
|
||||||
ODO v{window.odo?.version ?? '0.10.0'} · OpenClaw Dashboard
|
ODO v{window.odo?.version ?? '0.10.1'} · OpenClaw Dashboard
|
||||||
Orchestrator
|
Orchestrator
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
module.exports = {
|
||||||
content: ['./src/**/*.{html,js,ts,jsx,tsx}'],
|
content: ['./src/**/*.{html,js,ts,jsx,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
Reference in New Issue
Block a user