chore: auto-commit uncommitted changes
This commit is contained in:
parent
9c449e51a0
commit
6ed96a4514
|
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{
|
||||
"session_percent": 8,
|
||||
"timestamp": "2026-02-11T05:05:37.770936Z",
|
||||
"weekly_percent": 42
|
||||
},
|
||||
{
|
||||
"session_percent": 9,
|
||||
"timestamp": "2026-02-11T05:19:36.479005Z",
|
||||
"weekly_percent": 43
|
||||
}
|
||||
]
|
||||
|
|
@ -2,15 +2,15 @@
|
|||
"items": {
|
||||
"claude": {
|
||||
"key": "claude",
|
||||
"value": "42% used · 11:00 PM",
|
||||
"value": "42% used · 12:00 AM",
|
||||
"type": "info",
|
||||
"updated_at": "2026-02-10T23:00:08.709873416-05:00"
|
||||
"updated_at": "2026-02-11T00:00:09.222923015-05:00"
|
||||
},
|
||||
"claude-usage": {
|
||||
"key": "claude-usage",
|
||||
"value": "📊 Weekly: 42% used (58% remaining)",
|
||||
"value": "📊 Weekly: 43% used (57% remaining)",
|
||||
"type": "info",
|
||||
"updated_at": "2026-02-10T22:19:41.18380063-05:00"
|
||||
"updated_at": "2026-02-11T00:19:41.954497545-05:00"
|
||||
},
|
||||
"claude_weekly": {
|
||||
"key": "claude_weekly",
|
||||
|
|
|
|||
98
index.html
98
index.html
|
|
@ -853,23 +853,24 @@
|
|||
if (!container) return;
|
||||
|
||||
let weeklyPct = 10;
|
||||
let lastCheck = '';
|
||||
let resetTime = null;
|
||||
let lastUpdated = null;
|
||||
let history = [];
|
||||
|
||||
if (claudeStatus) {
|
||||
const m = claudeStatus.value.match(/(\d+)%/);
|
||||
if (m) weeklyPct = parseInt(m[1]);
|
||||
const tm = claudeStatus.value.match(/checked\s+(.+)/);
|
||||
if (tm) lastCheck = tm[1];
|
||||
}
|
||||
|
||||
// Try to get reset time from usage data
|
||||
// Get usage data + history from API
|
||||
try {
|
||||
const res = await fetch('/api/claude-usage');
|
||||
if (res.ok) {
|
||||
const d = await res.json();
|
||||
if (d.weekly_resets) resetTime = new Date(d.weekly_resets);
|
||||
if (d.weekly_percent !== undefined) weeklyPct = d.weekly_percent;
|
||||
if (d.last_updated) lastUpdated = d.last_updated;
|
||||
if (d.history) history = d.history;
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
|
|
@ -877,60 +878,97 @@
|
|||
if (weeklyPct > 70) pctClass = 'warning';
|
||||
if (weeklyPct > 85) pctClass = 'error';
|
||||
|
||||
// Chart: 7 days, 4 blocks per day (6h each) = 28 blocks
|
||||
const W = 160, H = 36, PAD = 2;
|
||||
const BLOCKS = 28;
|
||||
const blockW = (W - PAD*2) / BLOCKS;
|
||||
|
||||
// Figure out week start (reset - 7 days)
|
||||
const now = new Date();
|
||||
const weekMs = 7 * 86400000;
|
||||
|
||||
// Week start = reset time - 7 days
|
||||
let weekStart;
|
||||
if (resetTime) {
|
||||
weekStart = new Date(resetTime.getTime() - 7 * 86400000);
|
||||
weekStart = new Date(resetTime.getTime() - weekMs);
|
||||
} else {
|
||||
// Guess: week resets Friday, so started last Friday
|
||||
const d = new Date(now);
|
||||
d.setDate(d.getDate() - ((d.getDay() + 2) % 7)); // last Friday
|
||||
d.setHours(0,0,0,0);
|
||||
d.setDate(d.getDate() - ((d.getDay() + 2) % 7));
|
||||
d.setHours(14,0,0,0);
|
||||
weekStart = d;
|
||||
}
|
||||
const weekEnd = new Date(weekStart.getTime() + weekMs);
|
||||
|
||||
const weekMs = 7 * 86400000;
|
||||
const elapsed = Math.min(now - weekStart, weekMs);
|
||||
const nowBlock = Math.min(Math.floor((elapsed / weekMs) * BLOCKS), BLOCKS);
|
||||
|
||||
// Budget line: 0% → 100% over full week (dashed gray)
|
||||
const bx0 = PAD, by0 = H - PAD;
|
||||
const bx1 = PAD + BLOCKS * blockW, by1 = PAD;
|
||||
|
||||
// Actual usage line: linear from 0 to weeklyPct, ending at nowBlock
|
||||
const ax1 = PAD + nowBlock * blockW;
|
||||
const ay1 = H - PAD - ((weeklyPct / 100) * (H - PAD*2));
|
||||
// Map time to X coordinate
|
||||
const timeToX = (t) => {
|
||||
const frac = Math.max(0, Math.min(1, (t - weekStart) / weekMs));
|
||||
return PAD + frac * (W - PAD*2);
|
||||
};
|
||||
const pctToY = (p) => H - PAD - (p / 100) * (H - PAD*2);
|
||||
|
||||
// Day separators
|
||||
let dayLines = '';
|
||||
for (let d = 1; d < 7; d++) {
|
||||
const x = PAD + d * 4 * blockW;
|
||||
const x = timeToX(new Date(weekStart.getTime() + d * 86400000));
|
||||
dayLines += `<line x1="${x}" y1="0" x2="${x}" y2="${H}" stroke="#E5E2DE" stroke-width="0.5"/>`;
|
||||
}
|
||||
|
||||
// "Now" marker
|
||||
const nowX = PAD + nowBlock * blockW;
|
||||
// Budget line (dashed diagonal)
|
||||
const bx0 = PAD, by0 = H - PAD;
|
||||
const bx1 = W - PAD, by1 = PAD;
|
||||
|
||||
const color = pctClass === 'ok' ? '#059669' : pctClass === 'warning' ? '#B45309' : '#DC2626';
|
||||
|
||||
// Build polyline from history points (filter to current week)
|
||||
const weekStartMs = weekStart.getTime();
|
||||
const points = history
|
||||
.filter(h => new Date(h.timestamp).getTime() >= weekStartMs)
|
||||
.map(h => ({ t: new Date(h.timestamp), p: h.weekly_percent }))
|
||||
.sort((a, b) => a.t - b.t);
|
||||
|
||||
let pathD = '';
|
||||
let lastPoint = null;
|
||||
if (points.length > 0) {
|
||||
// Start from week start at 0%
|
||||
pathD = `M${PAD},${H - PAD}`;
|
||||
// First history point
|
||||
pathD += ` L${timeToX(points[0].t)},${pctToY(points[0].p)}`;
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
pathD += ` L${timeToX(points[i].t)},${pctToY(points[i].p)}`;
|
||||
}
|
||||
lastPoint = points[points.length - 1];
|
||||
}
|
||||
|
||||
// Current position dot
|
||||
const nowX = timeToX(now);
|
||||
const nowY = pctToY(weeklyPct);
|
||||
if (pathD) {
|
||||
pathD += ` L${nowX},${nowY}`;
|
||||
} else {
|
||||
// No history: draw single line from 0 to current
|
||||
pathD = `M${PAD},${H - PAD} L${nowX},${nowY}`;
|
||||
}
|
||||
|
||||
const svg = `<svg class="claude-chart-svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
|
||||
${dayLines}
|
||||
<line x1="${bx0}" y1="${by0}" x2="${bx1}" y2="${by1}" stroke="#E5E2DE" stroke-width="1" stroke-dasharray="3,2"/>
|
||||
<line x1="${PAD}" y1="${H - PAD}" x2="${ax1}" y2="${ay1}" stroke="${color}" stroke-width="2" stroke-linecap="round"/>
|
||||
<circle cx="${ax1}" cy="${ay1}" r="2.5" fill="${color}"/>
|
||||
<path d="${pathD}" stroke="${color}" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="${nowX}" cy="${nowY}" r="2.5" fill="${color}"/>
|
||||
</svg>`;
|
||||
|
||||
// Issue 2: last-pull timestamp
|
||||
let updatedLabel = '';
|
||||
if (lastUpdated) {
|
||||
const updatedDate = new Date(lastUpdated);
|
||||
const diffMs = now - updatedDate;
|
||||
const diffMin = Math.floor(diffMs / 60000);
|
||||
const diffHr = Math.floor(diffMs / 3600000);
|
||||
if (diffMs < 60000) updatedLabel = 'just now';
|
||||
else if (diffMin < 60) updatedLabel = diffMin + 'm ago';
|
||||
else if (diffHr < 24) updatedLabel = diffHr + 'h ago';
|
||||
else updatedLabel = updatedDate.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<span class="claude-chart-label">Claude</span>
|
||||
<span class="claude-chart-pct ${pctClass}">${weeklyPct}%</span>
|
||||
${svg}
|
||||
${lastCheck ? `<span class="claude-chart-label">${lastCheck}</span>` : ''}
|
||||
${updatedLabel ? `<span class="claude-chart-label">${updatedLabel}</span>` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
james-dashboard
BIN
james-dashboard
Binary file not shown.
Binary file not shown.
73
server.go
73
server.go
|
|
@ -849,7 +849,7 @@ func getAgents(gatewayIP string, gatewayPort string, gatewayToken string) []Agen
|
|||
// Include token for auto-authentication
|
||||
if agentConfig.ID == "main" {
|
||||
agent.Default = true
|
||||
agent.URL = baseURL + "/chat?token=" + gatewayToken
|
||||
agent.URL = baseURL + "/chat?session=agent:main:main&token=" + gatewayToken
|
||||
} else {
|
||||
// Control UI reads session param on /chat route
|
||||
agent.URL = baseURL + "/chat?session=agent:" + agentConfig.ID + ":main&token=" + gatewayToken
|
||||
|
|
@ -1339,6 +1339,77 @@ func main() {
|
|||
}
|
||||
})
|
||||
|
||||
// ========== CLAUDE USAGE API ==========
|
||||
claudeUsageFile := "/home/johan/clawd/memory/claude-usage.json"
|
||||
claudeHistoryFile := filepath.Join(*dir, "data", "claude-usage-history.json")
|
||||
|
||||
mux.HandleFunc("/api/claude-usage", func(w http.ResponseWriter, r *http.Request) {
|
||||
cors(w)
|
||||
if r.Method == "OPTIONS" {
|
||||
return
|
||||
}
|
||||
if r.Method != "GET" {
|
||||
http.Error(w, `{"error": "method not allowed"}`, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Read current usage
|
||||
result := make(map[string]interface{})
|
||||
if data, err := os.ReadFile(claudeUsageFile); err == nil {
|
||||
json.Unmarshal(data, &result)
|
||||
}
|
||||
|
||||
// Read history
|
||||
var history []map[string]interface{}
|
||||
if data, err := os.ReadFile(claudeHistoryFile); err == nil {
|
||||
json.Unmarshal(data, &history)
|
||||
}
|
||||
result["history"] = history
|
||||
|
||||
json.NewEncoder(w).Encode(result)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/api/claude-usage/record", func(w http.ResponseWriter, r *http.Request) {
|
||||
cors(w)
|
||||
if r.Method == "OPTIONS" {
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, `{"error": "method not allowed"}`, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var entry map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&entry); err != nil {
|
||||
http.Error(w, `{"error": "invalid JSON"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Read existing history
|
||||
var history []map[string]interface{}
|
||||
if data, err := os.ReadFile(claudeHistoryFile); err == nil {
|
||||
json.Unmarshal(data, &history)
|
||||
}
|
||||
|
||||
// Add timestamp if missing
|
||||
if _, ok := entry["timestamp"]; !ok {
|
||||
entry["timestamp"] = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
history = append(history, entry)
|
||||
|
||||
// Keep last 2000 entries
|
||||
if len(history) > 2000 {
|
||||
history = history[len(history)-2000:]
|
||||
}
|
||||
|
||||
data, _ := json.MarshalIndent(history, "", " ")
|
||||
os.WriteFile(claudeHistoryFile, data, 0644)
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "recorded"})
|
||||
})
|
||||
|
||||
// ========== AGENTS API ==========
|
||||
mux.HandleFunc("/api/agents", func(w http.ResponseWriter, r *http.Request) {
|
||||
cors(w)
|
||||
|
|
|
|||
Loading…
Reference in New Issue