From 7c107471cf1c3a0ce2a50d2d3f8b0257d6750a2b Mon Sep 17 00:00:00 2001
From: dtzp555
Date: Fri, 20 Mar 2026 08:07:10 +1000
Subject: [PATCH] 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
---
package.json | 2 +-
postcss.config.js => postcss.config.cjs | 2 +-
preload.js | 2 +-
src/pages/Dashboard.tsx | 109 ++++++++++++++++++++--
tailwind.config.js => tailwind.config.cjs | 2 +-
5 files changed, 105 insertions(+), 12 deletions(-)
rename postcss.config.js => postcss.config.cjs (77%)
rename tailwind.config.js => tailwind.config.cjs (95%)
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: {