mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-14 22:09:37 +08:00
fix logs
This commit is contained in:
698
webui/public/log-codes.json
Normal file
698
webui/public/log-codes.json
Normal file
@@ -0,0 +1,698 @@
|
||||
{
|
||||
"version": 1,
|
||||
"generated_at": "Tue Mar 3 02:06:08 2026 UTC",
|
||||
"items": [
|
||||
{
|
||||
"code": 1,
|
||||
"text": "Message rejected by allowlist"
|
||||
},
|
||||
{
|
||||
"code": 2,
|
||||
"text": "Duplicate inbound message skipped"
|
||||
},
|
||||
{
|
||||
"code": 3,
|
||||
"text": "Duplicate inbound content skipped"
|
||||
},
|
||||
{
|
||||
"code": 4,
|
||||
"text": "Initializing channel manager"
|
||||
},
|
||||
{
|
||||
"code": 5,
|
||||
"text": "Attempting to initialize Telegram channel"
|
||||
},
|
||||
{
|
||||
"code": 6,
|
||||
"text": "Telegram token is empty, skipping"
|
||||
},
|
||||
{
|
||||
"code": 7,
|
||||
"text": "Failed to initialize Telegram channel"
|
||||
},
|
||||
{
|
||||
"code": 8,
|
||||
"text": "Telegram channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 9,
|
||||
"text": "WhatsApp bridge URL is empty, skipping"
|
||||
},
|
||||
{
|
||||
"code": 10,
|
||||
"text": "Failed to initialize WhatsApp channel"
|
||||
},
|
||||
{
|
||||
"code": 11,
|
||||
"text": "WhatsApp channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 12,
|
||||
"text": "Failed to initialize Feishu channel"
|
||||
},
|
||||
{
|
||||
"code": 13,
|
||||
"text": "Feishu channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 14,
|
||||
"text": "Discord token is empty, skipping"
|
||||
},
|
||||
{
|
||||
"code": 15,
|
||||
"text": "Failed to initialize Discord channel"
|
||||
},
|
||||
{
|
||||
"code": 16,
|
||||
"text": "Discord channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 17,
|
||||
"text": "Failed to initialize MaixCam channel"
|
||||
},
|
||||
{
|
||||
"code": 18,
|
||||
"text": "MaixCam channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 19,
|
||||
"text": "Failed to initialize QQ channel"
|
||||
},
|
||||
{
|
||||
"code": 20,
|
||||
"text": "QQ channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 21,
|
||||
"text": "DingTalk Client ID is empty, skipping"
|
||||
},
|
||||
{
|
||||
"code": 22,
|
||||
"text": "Failed to initialize DingTalk channel"
|
||||
},
|
||||
{
|
||||
"code": 23,
|
||||
"text": "DingTalk channel enabled successfully"
|
||||
},
|
||||
{
|
||||
"code": 24,
|
||||
"text": "Channel initialization completed"
|
||||
},
|
||||
{
|
||||
"code": 25,
|
||||
"text": "No channels enabled"
|
||||
},
|
||||
{
|
||||
"code": 26,
|
||||
"text": "Starting all channels"
|
||||
},
|
||||
{
|
||||
"code": 27,
|
||||
"text": "Starting channel"
|
||||
},
|
||||
{
|
||||
"code": 28,
|
||||
"text": "Failed to start channel"
|
||||
},
|
||||
{
|
||||
"code": 29,
|
||||
"text": "All channels started"
|
||||
},
|
||||
{
|
||||
"code": 30,
|
||||
"text": "Stopping all channels"
|
||||
},
|
||||
{
|
||||
"code": 31,
|
||||
"text": "Stopping channel"
|
||||
},
|
||||
{
|
||||
"code": 32,
|
||||
"text": "Error stopping channel"
|
||||
},
|
||||
{
|
||||
"code": 33,
|
||||
"text": "All channels stopped"
|
||||
},
|
||||
{
|
||||
"code": 34,
|
||||
"text": "Restarting channel"
|
||||
},
|
||||
{
|
||||
"code": 35,
|
||||
"text": "Outbound dispatcher started"
|
||||
},
|
||||
{
|
||||
"code": 36,
|
||||
"text": "Outbound dispatcher stopped"
|
||||
},
|
||||
{
|
||||
"code": 37,
|
||||
"text": "Outbound dispatcher stopped (bus closed)"
|
||||
},
|
||||
{
|
||||
"code": 38,
|
||||
"text": "Duplicate outbound message skipped"
|
||||
},
|
||||
{
|
||||
"code": 39,
|
||||
"text": "Unknown channel for outbound message"
|
||||
},
|
||||
{
|
||||
"code": 40,
|
||||
"text": "Channel does not support outbound action"
|
||||
},
|
||||
{
|
||||
"code": 41,
|
||||
"text": "Outbound rate limiter canceled"
|
||||
},
|
||||
{
|
||||
"code": 42,
|
||||
"text": "Error sending message to channel"
|
||||
},
|
||||
{
|
||||
"code": 43,
|
||||
"text": "Feishu channel started (websocket mode)"
|
||||
},
|
||||
{
|
||||
"code": 44,
|
||||
"text": "Feishu channel stopped"
|
||||
},
|
||||
{
|
||||
"code": 45,
|
||||
"text": "create sheet from markdown table failed"
|
||||
},
|
||||
{
|
||||
"code": 46,
|
||||
"text": "Feishu message sent"
|
||||
},
|
||||
{
|
||||
"code": 47,
|
||||
"text": "Feishu message rejected by chat allowlist"
|
||||
},
|
||||
{
|
||||
"code": 48,
|
||||
"text": "Ignoring group message without mention/command"
|
||||
},
|
||||
{
|
||||
"code": 49,
|
||||
"text": "Feishu message received"
|
||||
},
|
||||
{
|
||||
"code": 50,
|
||||
"text": "download inbound image failed"
|
||||
},
|
||||
{
|
||||
"code": 51,
|
||||
"text": "download inbound file failed"
|
||||
},
|
||||
{
|
||||
"code": 52,
|
||||
"text": "set sheet permission failed"
|
||||
},
|
||||
{
|
||||
"code": 53,
|
||||
"text": "Upload failed"
|
||||
},
|
||||
{
|
||||
"code": 54,
|
||||
"text": "Starting Telegram bot (polling mode)"
|
||||
},
|
||||
{
|
||||
"code": 55,
|
||||
"text": "Telegram bot connected"
|
||||
},
|
||||
{
|
||||
"code": 56,
|
||||
"text": "Updates channel closed unexpectedly, attempting to restart polling..."
|
||||
},
|
||||
{
|
||||
"code": 57,
|
||||
"text": "Failed to restart updates polling"
|
||||
},
|
||||
{
|
||||
"code": 58,
|
||||
"text": "Updates polling restarted successfully"
|
||||
},
|
||||
{
|
||||
"code": 59,
|
||||
"text": "Stopping Telegram bot"
|
||||
},
|
||||
{
|
||||
"code": 60,
|
||||
"text": "Timeout waiting for telegram message handlers to stop"
|
||||
},
|
||||
{
|
||||
"code": 61,
|
||||
"text": "Recovered panic in telegram message handler"
|
||||
},
|
||||
{
|
||||
"code": 62,
|
||||
"text": "Callback query received"
|
||||
},
|
||||
{
|
||||
"code": 63,
|
||||
"text": "HTML parse failed, fallback to plain text"
|
||||
},
|
||||
{
|
||||
"code": 64,
|
||||
"text": "Ignoring bot-originated message"
|
||||
},
|
||||
{
|
||||
"code": 65,
|
||||
"text": "Telegram message rejected by chat allowlist"
|
||||
},
|
||||
{
|
||||
"code": 66,
|
||||
"text": "Telegram message received"
|
||||
},
|
||||
{
|
||||
"code": 67,
|
||||
"text": "Telegram message rejected by allowlist"
|
||||
},
|
||||
{
|
||||
"code": 68,
|
||||
"text": "Failed to fetch job details"
|
||||
},
|
||||
{
|
||||
"code": 69,
|
||||
"text": "Starting Discord bot"
|
||||
},
|
||||
{
|
||||
"code": 70,
|
||||
"text": "Discord bot connected"
|
||||
},
|
||||
{
|
||||
"code": 71,
|
||||
"text": "Stopping Discord bot"
|
||||
},
|
||||
{
|
||||
"code": 72,
|
||||
"text": "Received message"
|
||||
},
|
||||
{
|
||||
"code": 73,
|
||||
"text": "Failed to create media directory"
|
||||
},
|
||||
{
|
||||
"code": 74,
|
||||
"text": "Failed to download attachment"
|
||||
},
|
||||
{
|
||||
"code": 75,
|
||||
"text": "Attachment download returned non-200"
|
||||
},
|
||||
{
|
||||
"code": 76,
|
||||
"text": "Failed to create local attachment file"
|
||||
},
|
||||
{
|
||||
"code": 77,
|
||||
"text": "Failed to write local attachment file"
|
||||
},
|
||||
{
|
||||
"code": 78,
|
||||
"text": "Attachment downloaded successfully"
|
||||
},
|
||||
{
|
||||
"code": 79,
|
||||
"text": "Starting MaixCam channel server"
|
||||
},
|
||||
{
|
||||
"code": 80,
|
||||
"text": "MaixCam server listening"
|
||||
},
|
||||
{
|
||||
"code": 81,
|
||||
"text": "Starting connection acceptor"
|
||||
},
|
||||
{
|
||||
"code": 82,
|
||||
"text": "Stopping connection acceptor"
|
||||
},
|
||||
{
|
||||
"code": 83,
|
||||
"text": "Failed to accept connection"
|
||||
},
|
||||
{
|
||||
"code": 84,
|
||||
"text": "New connection from MaixCam device"
|
||||
},
|
||||
{
|
||||
"code": 85,
|
||||
"text": "Handling MaixCam connection"
|
||||
},
|
||||
{
|
||||
"code": 86,
|
||||
"text": "Connection closed"
|
||||
},
|
||||
{
|
||||
"code": 87,
|
||||
"text": "Failed to decode message"
|
||||
},
|
||||
{
|
||||
"code": 88,
|
||||
"text": "Received heartbeat"
|
||||
},
|
||||
{
|
||||
"code": 89,
|
||||
"text": "Unknown message type"
|
||||
},
|
||||
{
|
||||
"code": 90,
|
||||
"text": "Person detected event"
|
||||
},
|
||||
{
|
||||
"code": 91,
|
||||
"text": "Status update from MaixCam"
|
||||
},
|
||||
{
|
||||
"code": 92,
|
||||
"text": "Stopping MaixCam channel"
|
||||
},
|
||||
{
|
||||
"code": 93,
|
||||
"text": "MaixCam channel stopped"
|
||||
},
|
||||
{
|
||||
"code": 94,
|
||||
"text": "No MaixCam devices connected"
|
||||
},
|
||||
{
|
||||
"code": 95,
|
||||
"text": "Failed to send to client"
|
||||
},
|
||||
{
|
||||
"code": 96,
|
||||
"text": "load recent logs failed"
|
||||
},
|
||||
{
|
||||
"code": 97,
|
||||
"text": "Log stream error:"
|
||||
},
|
||||
{
|
||||
"code": 98,
|
||||
"text": "Agent initialized"
|
||||
},
|
||||
{
|
||||
"code": 99,
|
||||
"text": "Starting QQ bot (WebSocket mode)"
|
||||
},
|
||||
{
|
||||
"code": 100,
|
||||
"text": "Got WebSocket info"
|
||||
},
|
||||
{
|
||||
"code": 101,
|
||||
"text": "QQ bot started successfully"
|
||||
},
|
||||
{
|
||||
"code": 102,
|
||||
"text": "Stopping QQ bot"
|
||||
},
|
||||
{
|
||||
"code": 103,
|
||||
"text": "Failed to send C2C message"
|
||||
},
|
||||
{
|
||||
"code": 104,
|
||||
"text": "Received message with no sender ID"
|
||||
},
|
||||
{
|
||||
"code": 105,
|
||||
"text": "Received empty message, ignoring"
|
||||
},
|
||||
{
|
||||
"code": 106,
|
||||
"text": "Received C2C message"
|
||||
},
|
||||
{
|
||||
"code": 107,
|
||||
"text": "Received group message with no sender ID"
|
||||
},
|
||||
{
|
||||
"code": 108,
|
||||
"text": "Received empty group message, ignoring"
|
||||
},
|
||||
{
|
||||
"code": 109,
|
||||
"text": "Received group AT message"
|
||||
},
|
||||
{
|
||||
"code": 110,
|
||||
"text": "Startup compaction check completed"
|
||||
},
|
||||
{
|
||||
"code": 111,
|
||||
"text": "Bootstrap init model call failed"
|
||||
},
|
||||
{
|
||||
"code": 112,
|
||||
"text": "Bootstrap init marker write failed"
|
||||
},
|
||||
{
|
||||
"code": 113,
|
||||
"text": "Bootstrap file cleanup failed"
|
||||
},
|
||||
{
|
||||
"code": 114,
|
||||
"text": "Bootstrap init model call completed"
|
||||
},
|
||||
{
|
||||
"code": 115,
|
||||
"text": "Starting DingTalk channel (Stream Mode)"
|
||||
},
|
||||
{
|
||||
"code": 116,
|
||||
"text": "DingTalk channel started (Stream Mode)"
|
||||
},
|
||||
{
|
||||
"code": 117,
|
||||
"text": "Stopping DingTalk channel"
|
||||
},
|
||||
{
|
||||
"code": 118,
|
||||
"text": "DingTalk channel stopped"
|
||||
},
|
||||
{
|
||||
"code": 119,
|
||||
"text": "DingTalk outbound message"
|
||||
},
|
||||
{
|
||||
"code": 120,
|
||||
"text": "DingTalk inbound message"
|
||||
},
|
||||
{
|
||||
"code": 121,
|
||||
"text": "Starting WhatsApp channel"
|
||||
},
|
||||
{
|
||||
"code": 122,
|
||||
"text": "WhatsApp channel connected"
|
||||
},
|
||||
{
|
||||
"code": 123,
|
||||
"text": "Stopping WhatsApp channel"
|
||||
},
|
||||
{
|
||||
"code": 124,
|
||||
"text": "Error closing WhatsApp connection"
|
||||
},
|
||||
{
|
||||
"code": 125,
|
||||
"text": "WhatsApp connection closed"
|
||||
},
|
||||
{
|
||||
"code": 126,
|
||||
"text": "WhatsApp read error"
|
||||
},
|
||||
{
|
||||
"code": 127,
|
||||
"text": "Failed to unmarshal WhatsApp message"
|
||||
},
|
||||
{
|
||||
"code": 128,
|
||||
"text": "WhatsApp message received"
|
||||
},
|
||||
{
|
||||
"code": 129,
|
||||
"text": "PublishInbound on closed channel recovered"
|
||||
},
|
||||
{
|
||||
"code": 130,
|
||||
"text": "PublishInbound timeout (queue full)"
|
||||
},
|
||||
{
|
||||
"code": 131,
|
||||
"text": "PublishOutbound on closed channel recovered"
|
||||
},
|
||||
{
|
||||
"code": 132,
|
||||
"text": "PublishOutbound timeout (queue full)"
|
||||
},
|
||||
{
|
||||
"code": 133,
|
||||
"text": "HTTP chat request"
|
||||
},
|
||||
{
|
||||
"code": 134,
|
||||
"text": "Sentinel started"
|
||||
},
|
||||
{
|
||||
"code": 135,
|
||||
"text": "Sentinel stopped"
|
||||
},
|
||||
{
|
||||
"code": 136,
|
||||
"text": "Attempting auto-heal for channel"
|
||||
},
|
||||
{
|
||||
"code": 137,
|
||||
"text": "Auto-heal restart failed"
|
||||
},
|
||||
{
|
||||
"code": 138,
|
||||
"text": "Auto-heal successful"
|
||||
},
|
||||
{
|
||||
"code": 139,
|
||||
"text": "Starting HTTP server"
|
||||
},
|
||||
{
|
||||
"code": 140,
|
||||
"text": "Server ready for reverse proxying"
|
||||
},
|
||||
{
|
||||
"code": 141,
|
||||
"text": "HTTP server failed"
|
||||
},
|
||||
{
|
||||
"code": 142,
|
||||
"text": "Stopping HTTP server"
|
||||
},
|
||||
{
|
||||
"code": 143,
|
||||
"text": "System prompt built"
|
||||
},
|
||||
{
|
||||
"code": 144,
|
||||
"text": "System prompt preview"
|
||||
},
|
||||
{
|
||||
"code": 145,
|
||||
"text": "Failed to clean up old log files:"
|
||||
},
|
||||
{
|
||||
"code": 146,
|
||||
"text": "File logging enabled:"
|
||||
},
|
||||
{
|
||||
"code": 147,
|
||||
"text": "File logging disabled"
|
||||
},
|
||||
{
|
||||
"code": 148,
|
||||
"text": "Failed to write file log:"
|
||||
},
|
||||
{
|
||||
"code": 149,
|
||||
"text": "Session-sharded dispatcher enabled"
|
||||
},
|
||||
{
|
||||
"code": 150,
|
||||
"text": "LLM fallback provider switched"
|
||||
},
|
||||
{
|
||||
"code": 151,
|
||||
"text": "LLM iteration"
|
||||
},
|
||||
{
|
||||
"code": 152,
|
||||
"text": "LLM request"
|
||||
},
|
||||
{
|
||||
"code": 153,
|
||||
"text": "Full LLM request"
|
||||
},
|
||||
{
|
||||
"code": 154,
|
||||
"text": "Purged orphan tool outputs after provider pairing error"
|
||||
},
|
||||
{
|
||||
"code": 155,
|
||||
"text": "LLM call failed"
|
||||
},
|
||||
{
|
||||
"code": 156,
|
||||
"text": "LLM response without tool calls (direct answer)"
|
||||
},
|
||||
{
|
||||
"code": 157,
|
||||
"text": "LLM requested tool calls"
|
||||
},
|
||||
{
|
||||
"code": 158,
|
||||
"text": "append daily summary log failed"
|
||||
},
|
||||
{
|
||||
"code": 159,
|
||||
"text": "Processing system message"
|
||||
},
|
||||
{
|
||||
"code": 160,
|
||||
"text": "Purged orphan tool outputs after provider pairing error (system)"
|
||||
},
|
||||
{
|
||||
"code": 161,
|
||||
"text": "Heartbeat session reset after repeated provider pairing error"
|
||||
},
|
||||
{
|
||||
"code": 162,
|
||||
"text": "LLM call failed in system message"
|
||||
},
|
||||
{
|
||||
"code": 163,
|
||||
"text": "System message processing completed"
|
||||
},
|
||||
{
|
||||
"code": 164,
|
||||
"text": "Tool execution started"
|
||||
},
|
||||
{
|
||||
"code": 165,
|
||||
"text": "Tool not found"
|
||||
},
|
||||
{
|
||||
"code": 166,
|
||||
"text": "Tool execution failed"
|
||||
},
|
||||
{
|
||||
"code": 167,
|
||||
"text": "Tool execution completed"
|
||||
},
|
||||
{
|
||||
"code": 168,
|
||||
"text": "Channel task stopped"
|
||||
},
|
||||
{
|
||||
"code": 169,
|
||||
"text": "Channel task failed"
|
||||
},
|
||||
{
|
||||
"code": 170,
|
||||
"text": "Sentinel alert"
|
||||
},
|
||||
{
|
||||
"code": 171,
|
||||
"text": "Processing inbound message"
|
||||
},
|
||||
{
|
||||
"code": 172,
|
||||
"text": "Tool call invoked"
|
||||
},
|
||||
{
|
||||
"code": 173,
|
||||
"text": "Response generated"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import Memory from './pages/Memory';
|
||||
import TaskAudit from './pages/TaskAudit';
|
||||
import EKG from './pages/EKG';
|
||||
import Tasks from './pages/Tasks';
|
||||
import LogCodes from './pages/LogCodes';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
@@ -25,6 +26,7 @@ export default function App() {
|
||||
<Route index element={<Dashboard />} />
|
||||
<Route path="chat" element={<Chat />} />
|
||||
<Route path="logs" element={<Logs />} />
|
||||
<Route path="log-codes" element={<LogCodes />} />
|
||||
<Route path="skills" element={<Skills />} />
|
||||
<Route path="config" element={<Config />} />
|
||||
<Route path="cron" element={<Cron />} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { LayoutDashboard, MessageSquare, Settings, Clock, Server, Terminal, Zap, FolderOpen, ClipboardList, ListTodo, BrainCircuit } from 'lucide-react';
|
||||
import { LayoutDashboard, MessageSquare, Settings, Clock, Server, Terminal, Zap, FolderOpen, ClipboardList, ListTodo, BrainCircuit, Hash } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppContext } from '../context/AppContext';
|
||||
import NavItem from './NavItem';
|
||||
@@ -15,6 +15,7 @@ const Sidebar: React.FC = () => {
|
||||
{ icon: <LayoutDashboard className="w-5 h-5" />, label: t('dashboard'), to: '/' },
|
||||
{ icon: <MessageSquare className="w-5 h-5" />, label: t('chat'), to: '/chat' },
|
||||
{ icon: <Terminal className="w-5 h-5" />, label: t('logs'), to: '/logs' },
|
||||
{ icon: <Hash className="w-5 h-5" />, label: t('logCodes'), to: '/log-codes' },
|
||||
{ icon: <Zap className="w-5 h-5" />, label: t('skills'), to: '/skills' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ const resources = {
|
||||
cronJobs: 'Cron Jobs',
|
||||
nodes: 'Nodes',
|
||||
logs: 'Real-time Logs',
|
||||
logCodes: 'Log Codes',
|
||||
skills: 'Skills',
|
||||
memory: 'Memory',
|
||||
taskAudit: 'Task Audit',
|
||||
@@ -195,6 +196,7 @@ const resources = {
|
||||
cronJobs: '定时任务',
|
||||
nodes: '节点',
|
||||
logs: '实时日志',
|
||||
logCodes: '日志编号',
|
||||
skills: '技能管理',
|
||||
memory: '记忆文件',
|
||||
taskAudit: '任务审计',
|
||||
|
||||
@@ -64,7 +64,7 @@ const Chat: React.FC = () => {
|
||||
const ur = await fetch(`/webui/api/upload${q}`, { method: 'POST', body: fd });
|
||||
const uj = await ur.json(); media = uj.path || '';
|
||||
} catch (e) {
|
||||
console.error('Upload failed', e);
|
||||
console.error('L0053', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ const Cron: React.FC = () => {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch job details', e);
|
||||
console.error('L0068', e);
|
||||
}
|
||||
} else {
|
||||
setEditingCron(null);
|
||||
|
||||
81
webui/src/pages/LogCodes.tsx
Normal file
81
webui/src/pages/LogCodes.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppContext } from '../context/AppContext';
|
||||
|
||||
type CodeItem = {
|
||||
code: number;
|
||||
text: string;
|
||||
};
|
||||
|
||||
const LogCodes: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { q } = useAppContext();
|
||||
const [items, setItems] = useState<CodeItem[]>([]);
|
||||
const [kw, setKw] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
try {
|
||||
const paths = [`/webui/log-codes.json${q}`, '/log-codes.json'];
|
||||
for (const p of paths) {
|
||||
const r = await fetch(p);
|
||||
if (!r.ok) continue;
|
||||
const j = await r.json();
|
||||
if (Array.isArray(j?.items)) {
|
||||
setItems(j.items);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
setItems([]);
|
||||
}
|
||||
};
|
||||
load();
|
||||
}, [q]);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const k = kw.trim().toLowerCase();
|
||||
if (!k) return items;
|
||||
return items.filter((it) => String(it.code).includes(k) || it.text.toLowerCase().includes(k));
|
||||
}, [items, kw]);
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-7xl mx-auto space-y-6">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{t('logCodes')}</h1>
|
||||
<input
|
||||
value={kw}
|
||||
onChange={(e) => setKw(e.target.value)}
|
||||
placeholder="Search code/text"
|
||||
className="w-72 bg-zinc-900 border border-zinc-800 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-zinc-950 border border-zinc-800 rounded-2xl overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-zinc-900/90 border-b border-zinc-800">
|
||||
<tr className="text-zinc-400">
|
||||
<th className="text-left p-3 font-medium w-40">Code</th>
|
||||
<th className="text-left p-3 font-medium">Template</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map((it) => (
|
||||
<tr key={it.code} className="border-b border-zinc-900 hover:bg-zinc-900/40">
|
||||
<td className="p-3 font-mono text-indigo-300">{it.code}</td>
|
||||
<td className="p-3 text-zinc-200 break-all">{it.text}</td>
|
||||
</tr>
|
||||
))}
|
||||
{filtered.length === 0 && (
|
||||
<tr>
|
||||
<td className="p-6 text-zinc-500" colSpan={2}>No codes</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogCodes;
|
||||
@@ -8,6 +8,7 @@ const Logs: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { q } = useAppContext();
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
const [codeMap, setCodeMap] = useState<Record<number, string>>({});
|
||||
const [isStreaming, setIsStreaming] = useState(true);
|
||||
const [showRaw, setShowRaw] = useState(false);
|
||||
const logEndRef = useRef<HTMLDivElement>(null);
|
||||
@@ -22,7 +23,7 @@ const Logs: React.FC = () => {
|
||||
setLogs(j.logs.map(normalizeLog));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('load recent logs failed', e);
|
||||
console.error('L0096', e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,11 +60,38 @@ const Logs: React.FC = () => {
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.name !== 'AbortError') {
|
||||
console.error('Log stream error:', e);
|
||||
console.error('L0097', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadCodeMap = async () => {
|
||||
try {
|
||||
const paths = [`/webui/log-codes.json${q}`, '/log-codes.json'];
|
||||
for (const p of paths) {
|
||||
const r = await fetch(p);
|
||||
if (!r.ok) continue;
|
||||
const j = await r.json();
|
||||
if (Array.isArray(j?.items)) {
|
||||
const m: Record<number, string> = {};
|
||||
j.items.forEach((it: any) => {
|
||||
if (typeof it?.code === 'number' && typeof it?.text === 'string') {
|
||||
m[it.code] = it.text;
|
||||
}
|
||||
});
|
||||
setCodeMap(m);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
setCodeMap({});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadCodeMap();
|
||||
}, [q]);
|
||||
|
||||
useEffect(() => {
|
||||
loadRecent();
|
||||
if (isStreaming) {
|
||||
@@ -84,13 +112,29 @@ const Logs: React.FC = () => {
|
||||
const clearLogs = () => setLogs([]);
|
||||
|
||||
const normalizeLog = (v: any): LogEntry => ({
|
||||
time: typeof v?.time === 'string' && v.time ? v.time : new Date().toISOString(),
|
||||
time: typeof v?.time === 'string' && v.time ? v.time : (typeof v?.timestamp === 'string' && v.timestamp ? v.timestamp : new Date().toISOString()),
|
||||
level: typeof v?.level === 'string' && v.level ? v.level : 'INFO',
|
||||
msg: typeof v?.msg === 'string' ? v.msg : JSON.stringify(v),
|
||||
code: typeof v?.code === 'number' ? v.code : undefined,
|
||||
msg: typeof v?.msg === 'string' ? v.msg : (typeof v?.message === 'string' ? v.message : JSON.stringify(v)),
|
||||
__raw: JSON.stringify(v),
|
||||
...v,
|
||||
});
|
||||
|
||||
const toCode = (v: any): number | undefined => {
|
||||
if (typeof v === 'number' && Number.isFinite(v) && v > 0) return v;
|
||||
if (typeof v === 'string') {
|
||||
if (/^L\d{4}$/.test(v)) return Number(v.slice(1));
|
||||
const n = Number(v);
|
||||
if (Number.isFinite(n) && n > 0) return n;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
const decode = (v: any) => {
|
||||
const c = toCode(v);
|
||||
if (!c) return v;
|
||||
return codeMap[c] || v;
|
||||
};
|
||||
|
||||
const formatTime = (raw: string) => {
|
||||
try {
|
||||
if (!raw) return '--:--:--';
|
||||
@@ -189,16 +233,19 @@ const Logs: React.FC = () => {
|
||||
<tbody>
|
||||
{logs.map((log, i) => {
|
||||
const lvl = (log.level || 'INFO').toUpperCase();
|
||||
const errText = (log as any).error || (lvl === 'ERROR' ? log.msg : '');
|
||||
const message = lvl === 'ERROR' ? ((log as any).message || log.msg || '') : ((log as any).message || log.msg || '');
|
||||
const rawCode = (log as any).code ?? (log as any).message ?? log.msg ?? '';
|
||||
const message = String(decode(rawCode) || '');
|
||||
const errRaw = (log as any).message || (log as any).error || (lvl === 'ERROR' ? rawCode : '');
|
||||
const errText = String(decode(errRaw) || '');
|
||||
const caller = (log as any).caller || (log as any).source || '';
|
||||
const code = toCode(rawCode);
|
||||
return (
|
||||
<tr key={i} className="border-b border-zinc-900 hover:bg-zinc-900/40 align-top">
|
||||
<td className="p-2 text-zinc-500 whitespace-nowrap">{formatTime(log.time)}</td>
|
||||
<td className={`p-2 font-semibold whitespace-nowrap ${getLevelColor(lvl)}`}>{lvl}</td>
|
||||
<td className="p-2 text-zinc-200 break-all">{message}</td>
|
||||
<td className="p-2 text-red-300 break-all">{errText}</td>
|
||||
<td className="p-2 text-zinc-500 break-all">{caller}</td>
|
||||
<td className="p-2 text-zinc-500 break-all">{code ? `${code} | ${caller}` : caller}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -25,7 +25,9 @@ export type Lang = 'en' | 'zh';
|
||||
export type LogEntry = {
|
||||
time: string;
|
||||
level: string;
|
||||
code?: number;
|
||||
msg: string;
|
||||
message?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user