fix: add date filtering to usage/real endpoint and export

This commit is contained in:
2026-03-04 15:36:44 -08:00
parent 2a0788ee04
commit 65b1b42a79
12 changed files with 3833 additions and 1149 deletions

View File

@@ -4,6 +4,7 @@ const fs = require('fs');
const http = require('http');
const sqlite3 = require('sqlite3').verbose();
const { WebSocketServer } = require('ws');
const { setupGiteaRoutes } = require('./gitea-routes.js');
const PORT = process.env.PORT || 8395;
const DB_PATH = process.env.DB_PATH || path.join(__dirname, 'data', 'tasks.db');
@@ -937,6 +938,9 @@ wss.on('connection', (socket) => {
socket.send(JSON.stringify({ type: 'connected', payload: { ok: true } }));
});
// Setup Gitea integration
setupGiteaRoutes(app, renderPage);
server.listen(PORT, '0.0.0.0', () => {
console.log(`openclaw-taskboard listening on ${PORT}`);
});
@@ -946,8 +950,13 @@ const REAL_SESSIONS_DIR = process.env.SESSIONS_DIR || '/app/agents';
const SWARM_TASKS_FILE = process.env.SWARM_TASKS_FILE || '/app/swarm/active-tasks.json';
// GET /api/usage/real - Aggregate usage from session files
// GET /api/usage/real - Aggregate usage from session files with date filtering
app.get('/api/usage/real', async (req, res) => {
try {
const { from, to } = req.query;
const fromDate = from ? new Date(from) : null;
const toDate = to ? new Date(to) : null;
const usageByAgent = {};
const usageByModel = {};
let totalInput = 0, totalOutput = 0, totalCost = 0;
@@ -975,6 +984,14 @@ app.get('/api/usage/real', async (req, res) => {
if (!line.trim()) continue;
try {
const msg = JSON.parse(line);
// Date filtering
if (fromDate || toDate) {
const msgDate = new Date(msg.timestamp);
if (fromDate && msgDate < fromDate) continue;
if (toDate && msgDate > toDate) continue;
}
if (msg.message?.usage) {
const u = msg.message.usage;
agentInput += u.input || 0;
@@ -1014,6 +1031,7 @@ app.get('/api/usage/real', async (req, res) => {
total: totalInput + totalOutput,
cost: totalCost
},
filters: { from, to },
lastUpdated: new Date().toISOString()
});
} catch (err) {
@@ -1022,7 +1040,79 @@ app.get('/api/usage/real', async (req, res) => {
}
});
// GET /api/swarm/tasks - Get swarm task registry
// GET /api/usage/export/real - Export real usage data
app.get('/api/usage/export/real', (req, res) => {
const { format = 'json', from, to } = req.query;
try {
const fromDate = from ? new Date(from) : null;
const toDate = to ? new Date(to) : null;
const usageData = [];
if (!fs.existsSync(REAL_SESSIONS_DIR)) {
return res.status(404).json({ error: 'sessions_dir_not_found' });
}
const agents = fs.readdirSync(REAL_SESSIONS_DIR).filter(d => {
return fs.statSync(path.join(REAL_SESSIONS_DIR, d)).isDirectory();
});
for (const agent of agents) {
const sessionsDir = path.join(REAL_SESSIONS_DIR, agent, 'sessions');
if (!fs.existsSync(sessionsDir)) continue;
const sessions = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl'));
for (const sessionFile of sessions) {
const filePath = path.join(sessionsDir, sessionFile);
const lines = fs.readFileSync(filePath, 'utf8').split('\n');
for (const line of lines) {
if (!line.trim()) continue;
try {
const msg = JSON.parse(line);
if (fromDate || toDate) {
const msgDate = new Date(msg.timestamp);
if (fromDate && msgDate < fromDate) continue;
if (toDate && msgDate > toDate) continue;
}
if (msg.message?.usage) {
usageData.push({
timestamp: msg.timestamp,
agent,
model: msg.message.model || 'unknown',
provider: msg.message.provider || 'unknown',
input: msg.message.usage.input || 0,
output: msg.message.usage.output || 0,
total: (msg.message.usage.input || 0) + (msg.message.usage.output || 0),
cost: msg.message.usage.cost?.total || 0
});
}
} catch {}
}
}
}
if (format === 'csv') {
const csv = [
'timestamp,agent,model,provider,input,output,total,cost',
...usageData.map(r => `${r.timestamp},${r.agent},${r.model},${r.provider},${r.input},${r.output},${r.total},${r.cost}`)
].join('\n');
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename="usage-export.csv"');
res.send(csv);
} else {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Disposition', 'attachment; filename="usage-export.json"');
res.json(usageData);
}
} catch (err) {
console.error('Error exporting usage:', err);
res.status(500).json({ error: 'failed_to_export_usage' });
}
});
app.get('/api/swarm/tasks', (req, res) => {
try {
if (fs.existsSync(SWARM_TASKS_FILE)) {