mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
feat(events): live domain event stream log panel (#2653)
Co-authored-by: sanil-23 <sanil@vezures.xyz> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
sanil-23
Claude
parent
a211cac9fd
commit
c5c93c502e
@@ -138,6 +138,22 @@ const developerItems = [
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'event-log',
|
||||
titleKey: 'settings.developerMenu.eventLog.title',
|
||||
descriptionKey: 'settings.developerMenu.eventLog.desc',
|
||||
route: 'event-log',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 6h16M4 10h16M4 14h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'tool-policy-diagnostics',
|
||||
titleKey: 'devOptions.diagnostics',
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useT } from '../../../lib/i18n/I18nContext';
|
||||
import { getCoreHttpBaseUrl, getCoreRpcToken } from '../../../services/coreRpcClient';
|
||||
import SettingsHeader from '../components/SettingsHeader';
|
||||
import { useSettingsNavigation } from '../hooks/useSettingsNavigation';
|
||||
|
||||
interface EventEntry {
|
||||
id: number;
|
||||
domain: string;
|
||||
event: string;
|
||||
agent: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
const DOMAIN_BADGE_KEYS: Record<string, string> = {
|
||||
tool: 'settings.developerMenu.eventLog.badge.tool',
|
||||
agent: 'settings.developerMenu.eventLog.badge.agent',
|
||||
system: 'settings.developerMenu.eventLog.badge.info',
|
||||
memory: 'settings.developerMenu.eventLog.badge.mem',
|
||||
channel: 'settings.developerMenu.eventLog.badge.chan',
|
||||
cron: 'settings.developerMenu.eventLog.badge.cron',
|
||||
webhook: 'settings.developerMenu.eventLog.badge.hook',
|
||||
approval: 'settings.developerMenu.eventLog.badge.warn',
|
||||
skill: 'settings.developerMenu.eventLog.badge.skill',
|
||||
composio: 'settings.developerMenu.eventLog.badge.comp',
|
||||
mcp_client: 'settings.developerMenu.eventLog.badge.mcp',
|
||||
};
|
||||
|
||||
const DOMAIN_BADGE_COLORS: Record<string, { bg: string; text: string }> = {
|
||||
tool: { bg: 'bg-blue-500/20', text: 'text-blue-400' },
|
||||
agent: { bg: 'bg-green-500/20', text: 'text-green-400' },
|
||||
system: { bg: 'bg-slate-500/20', text: 'text-slate-400' },
|
||||
memory: { bg: 'bg-purple-500/20', text: 'text-purple-400' },
|
||||
channel: { bg: 'bg-cyan-500/20', text: 'text-cyan-400' },
|
||||
cron: { bg: 'bg-orange-500/20', text: 'text-orange-400' },
|
||||
webhook: { bg: 'bg-indigo-500/20', text: 'text-indigo-400' },
|
||||
approval: { bg: 'bg-amber-500/20', text: 'text-amber-400' },
|
||||
skill: { bg: 'bg-teal-500/20', text: 'text-teal-400' },
|
||||
composio: { bg: 'bg-pink-500/20', text: 'text-pink-400' },
|
||||
mcp_client: { bg: 'bg-violet-500/20', text: 'text-violet-400' },
|
||||
};
|
||||
|
||||
const MAX_ENTRIES = 200;
|
||||
const RECONNECT_DELAY_MS = 3000;
|
||||
|
||||
const EventLogPanel = () => {
|
||||
const { t } = useT();
|
||||
const { navigateBack, breadcrumbs } = useSettingsNavigation();
|
||||
const [entries, setEntries] = useState<EventEntry[]>([]);
|
||||
const [isLive, setIsLive] = useState(false);
|
||||
const [filterType, setFilterType] = useState<string>('');
|
||||
const [filterText, setFilterText] = useState('');
|
||||
const [autoScroll, setAutoScroll] = useState(true);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const idRef = useRef(0);
|
||||
const controllerRef = useRef<AbortController | null>(null);
|
||||
const reconnectRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const unmountedRef = useRef(false);
|
||||
const maxEntriesRef = useRef(MAX_ENTRIES);
|
||||
const newEntriesRef = useRef<'top' | 'bottom'>('top');
|
||||
|
||||
const connectRef = useRef<(() => Promise<void>) | null>(null);
|
||||
|
||||
const connect = async () => {
|
||||
if (unmountedRef.current) return;
|
||||
try {
|
||||
const [baseUrl, token] = await Promise.all([getCoreHttpBaseUrl(), getCoreRpcToken()]);
|
||||
if (!token) {
|
||||
setIsLive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `${baseUrl}/events/domain`;
|
||||
const controller = new AbortController();
|
||||
controllerRef.current = controller;
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok || !response.body) {
|
||||
setIsLive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLive(true);
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('event:')) {
|
||||
const eventType = line.slice(6).trim();
|
||||
if (eventType === 'config') {
|
||||
// Next data: line is config — handled below
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (line.startsWith('data:')) {
|
||||
const jsonStr = line.slice(5).trim();
|
||||
if (!jsonStr) continue;
|
||||
try {
|
||||
const data = JSON.parse(jsonStr);
|
||||
// Config message from server
|
||||
if (data.max_entries !== undefined) {
|
||||
maxEntriesRef.current = data.max_entries;
|
||||
if (data.new_entries === 'top' || data.new_entries === 'bottom') {
|
||||
newEntriesRef.current = data.new_entries;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const entry: EventEntry = {
|
||||
id: ++idRef.current,
|
||||
domain: data.domain || 'unknown',
|
||||
event: data.event || '',
|
||||
agent: data.agent || '',
|
||||
timestamp: data.timestamp || '',
|
||||
};
|
||||
setEntries(prev => {
|
||||
const next = newEntriesRef.current === 'top' ? [entry, ...prev] : [...prev, entry];
|
||||
return next.length > maxEntriesRef.current
|
||||
? newEntriesRef.current === 'top'
|
||||
? next.slice(0, maxEntriesRef.current)
|
||||
: next.slice(-maxEntriesRef.current)
|
||||
: next;
|
||||
});
|
||||
} catch {
|
||||
// skip malformed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setIsLive(false);
|
||||
} catch {
|
||||
setIsLive(false);
|
||||
} finally {
|
||||
controllerRef.current = null;
|
||||
// Auto-reconnect unless unmounted
|
||||
if (!unmountedRef.current) {
|
||||
reconnectRef.current = setTimeout(() => void connectRef.current?.(), RECONNECT_DELAY_MS);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
connectRef.current = connect;
|
||||
|
||||
useEffect(() => {
|
||||
unmountedRef.current = false;
|
||||
void connectRef.current?.();
|
||||
return () => {
|
||||
unmountedRef.current = true;
|
||||
controllerRef.current?.abort();
|
||||
controllerRef.current = null;
|
||||
if (reconnectRef.current) {
|
||||
clearTimeout(reconnectRef.current);
|
||||
reconnectRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoScroll && containerRef.current) {
|
||||
const el = containerRef.current;
|
||||
el.scrollTop = newEntriesRef.current === 'top' ? 0 : el.scrollHeight;
|
||||
}
|
||||
}, [entries, autoScroll]);
|
||||
|
||||
const handleScroll = () => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
const atAnchor =
|
||||
newEntriesRef.current === 'top'
|
||||
? el.scrollTop < 10
|
||||
: el.scrollHeight - el.scrollTop - el.clientHeight < 10;
|
||||
setAutoScroll(atAnchor);
|
||||
};
|
||||
|
||||
const filteredEntries = entries.filter(e => {
|
||||
if (filterType && e.domain !== filterType) return false;
|
||||
if (filterText) {
|
||||
const q = filterText.toLowerCase();
|
||||
if (!e.event.toLowerCase().includes(q) && !e.agent.toLowerCase().includes(q)) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const exportLog = () => {
|
||||
const blob = new Blob([filteredEntries.map(e => JSON.stringify(e)).join('\n')], {
|
||||
type: 'application/x-ndjson',
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `event-log-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.ndjson`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const domains = [...new Set(entries.map(e => e.domain))].sort();
|
||||
|
||||
return (
|
||||
<div data-testid="event-log-panel">
|
||||
<SettingsHeader
|
||||
title={t('settings.developerMenu.eventLog.title')}
|
||||
showBackButton={true}
|
||||
onBack={navigateBack}
|
||||
breadcrumbs={breadcrumbs}
|
||||
/>
|
||||
|
||||
<div className="p-4 space-y-4">
|
||||
{/* Status bar */}
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs">
|
||||
<select
|
||||
className="rounded-lg border border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 px-3 py-1.5 font-medium text-stone-700 dark:text-neutral-200"
|
||||
value={filterType}
|
||||
onChange={e => setFilterType(e.target.value)}>
|
||||
<option value="">{t('settings.developerMenu.eventLog.allTypes')}</option>
|
||||
{domains.map(d => (
|
||||
<option key={d} value={d}>
|
||||
{d}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
className="rounded-lg border border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 px-3 py-1.5 font-medium text-stone-700 dark:text-neutral-200 w-40"
|
||||
placeholder={t('settings.developerMenu.eventLog.filterAgent')}
|
||||
value={filterText}
|
||||
onChange={e => setFilterText(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={exportLog}
|
||||
disabled={filteredEntries.length === 0}
|
||||
className="rounded-lg border border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 px-3 py-1.5 font-medium text-stone-700 dark:text-neutral-200 hover:bg-stone-100 dark:hover:bg-neutral-800 disabled:opacity-50">
|
||||
{t('settings.developerMenu.eventLog.download')}
|
||||
</button>
|
||||
<span className="text-stone-500 dark:text-neutral-400">
|
||||
{filteredEntries.length} {t('settings.developerMenu.eventLog.events')} ·{' '}
|
||||
<span
|
||||
className={
|
||||
isLive ? 'text-sage-600 dark:text-sage-300' : 'text-stone-400 dark:text-neutral-500'
|
||||
}>
|
||||
{isLive
|
||||
? t('settings.developerMenu.eventLog.live')
|
||||
: t('settings.developerMenu.eventLog.disconnected')}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Jump to latest */}
|
||||
{!autoScroll && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setAutoScroll(true);
|
||||
const el = containerRef.current;
|
||||
if (el) {
|
||||
el.scrollTop = newEntriesRef.current === 'top' ? 0 : el.scrollHeight;
|
||||
}
|
||||
}}
|
||||
className="text-xs rounded-lg border border-primary-300 dark:border-primary-500/40 bg-primary-50 dark:bg-primary-500/10 px-3 py-1 text-primary-700 dark:text-primary-300">
|
||||
{t('settings.developerMenu.eventLog.jumpToLatest')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Event stream */}
|
||||
<section className="space-y-1">
|
||||
<div
|
||||
ref={containerRef}
|
||||
onScroll={handleScroll}
|
||||
className="max-h-[60vh] overflow-y-auto space-y-1">
|
||||
{filteredEntries.length === 0 && (
|
||||
<p className="text-xs text-stone-400 dark:text-neutral-500 py-4 text-center">
|
||||
{isLive
|
||||
? t('settings.developerMenu.eventLog.waiting')
|
||||
: t('settings.developerMenu.eventLog.notConnected')}
|
||||
</p>
|
||||
)}
|
||||
{filteredEntries.map(entry => {
|
||||
const colors = DOMAIN_BADGE_COLORS[entry.domain] || {
|
||||
bg: 'bg-stone-500/20',
|
||||
text: 'text-stone-400',
|
||||
};
|
||||
return (
|
||||
<div
|
||||
key={entry.id}
|
||||
className="rounded-xl border border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 px-3 py-2 flex items-start gap-2">
|
||||
<span className="text-[10px] text-stone-400 dark:text-neutral-500 font-mono shrink-0 pt-0.5">
|
||||
{entry.timestamp}
|
||||
</span>
|
||||
<span
|
||||
className={`rounded-full ${colors.bg} px-2 py-0.5 text-[10px] ${colors.text} shrink-0`}>
|
||||
{DOMAIN_BADGE_KEYS[entry.domain]
|
||||
? t(DOMAIN_BADGE_KEYS[entry.domain])
|
||||
: entry.domain.toUpperCase()}
|
||||
</span>
|
||||
{entry.agent && (
|
||||
<span className="text-[10px] text-stone-500 dark:text-neutral-400 shrink-0 font-mono">
|
||||
{entry.agent}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-xs text-stone-900 dark:text-neutral-100 truncate">
|
||||
{entry.event}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventLogPanel;
|
||||
@@ -0,0 +1,180 @@
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { renderWithProviders } from '../../../../test/test-utils';
|
||||
import EventLogPanel from '../EventLogPanel';
|
||||
|
||||
vi.mock('../../../../services/coreRpcClient', () => ({
|
||||
getCoreHttpBaseUrl: vi.fn().mockResolvedValue('http://localhost:9999'),
|
||||
getCoreRpcToken: vi.fn().mockResolvedValue('test-token'),
|
||||
}));
|
||||
|
||||
vi.mock('../../hooks/useSettingsNavigation', () => ({
|
||||
useSettingsNavigation: () => ({ navigateBack: vi.fn(), breadcrumbs: [] }),
|
||||
}));
|
||||
|
||||
vi.mock('../../../../lib/i18n/I18nContext', () => ({ useT: () => ({ t: (k: string) => k }) }));
|
||||
|
||||
function mockFetchSSE(events: Array<{ domain: string; event: string }>) {
|
||||
const lines = events.map(e => `data:${JSON.stringify({ ...e, timestamp: '12:00:00' })}\n`);
|
||||
const body = lines.join('');
|
||||
const encoder = new TextEncoder();
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(encoder.encode(body));
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
global.fetch = vi.fn().mockResolvedValue({ ok: true, body: stream });
|
||||
}
|
||||
|
||||
describe('EventLogPanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders the panel with header and filter controls', () => {
|
||||
global.fetch = vi.fn().mockResolvedValue({ ok: false, body: null });
|
||||
renderWithProviders(<EventLogPanel />);
|
||||
expect(screen.getByTestId('event-log-panel')).toBeTruthy();
|
||||
expect(screen.getByText('settings.developerMenu.eventLog.allTypes')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows waiting message when connected but no events', async () => {
|
||||
const stream = new ReadableStream({
|
||||
start() {
|
||||
// never enqueue — stays open
|
||||
},
|
||||
});
|
||||
global.fetch = vi.fn().mockResolvedValue({ ok: true, body: stream });
|
||||
renderWithProviders(<EventLogPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('settings.developerMenu.eventLog.waiting')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders events from SSE stream with domain badges', async () => {
|
||||
mockFetchSSE([
|
||||
{ domain: 'tool', event: 'ToolExecuted' },
|
||||
{ domain: 'agent', event: 'AgentStarted' },
|
||||
]);
|
||||
renderWithProviders(<EventLogPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('ToolExecuted')).toBeTruthy();
|
||||
expect(screen.getByText('AgentStarted')).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(screen.getByText('settings.developerMenu.eventLog.badge.tool')).toBeTruthy();
|
||||
expect(screen.getByText('settings.developerMenu.eventLog.badge.agent')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows disconnected state when fetch fails', async () => {
|
||||
global.fetch = vi.fn().mockRejectedValue(new Error('network'));
|
||||
renderWithProviders(<EventLogPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('settings.developerMenu.eventLog.disconnected')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('filters events by domain type', async () => {
|
||||
mockFetchSSE([
|
||||
{ domain: 'tool', event: 'ToolA' },
|
||||
{ domain: 'agent', event: 'AgentB' },
|
||||
]);
|
||||
const { container } = renderWithProviders(<EventLogPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('ToolA')).toBeTruthy();
|
||||
});
|
||||
|
||||
const select = container.querySelector('select')!;
|
||||
fireEvent.change(select, { target: { value: 'tool' } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('AgentB')).toBeNull();
|
||||
});
|
||||
expect(screen.getByText('ToolA')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows not connected when token is missing', async () => {
|
||||
const { getCoreRpcToken } = await import('../../../../services/coreRpcClient');
|
||||
vi.mocked(getCoreRpcToken).mockResolvedValueOnce(null as unknown as string);
|
||||
global.fetch = vi.fn();
|
||||
renderWithProviders(<EventLogPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('settings.developerMenu.eventLog.notConnected')).toBeTruthy();
|
||||
});
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('exports filtered events as ndjson', async () => {
|
||||
mockFetchSSE([{ domain: 'tool', event: 'ToolExport' }]);
|
||||
renderWithProviders(<EventLogPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('ToolExport')).toBeTruthy();
|
||||
});
|
||||
|
||||
const createObjectURL = vi.fn().mockReturnValue('blob:test');
|
||||
const revokeObjectURL = vi.fn();
|
||||
global.URL.createObjectURL = createObjectURL;
|
||||
global.URL.revokeObjectURL = revokeObjectURL;
|
||||
|
||||
const downloadBtn = screen.getByText('settings.developerMenu.eventLog.download');
|
||||
fireEvent.click(downloadBtn);
|
||||
|
||||
expect(createObjectURL).toHaveBeenCalled();
|
||||
expect(revokeObjectURL).toHaveBeenCalledWith('blob:test');
|
||||
});
|
||||
|
||||
it('filters events by text input', async () => {
|
||||
mockFetchSSE([
|
||||
{ domain: 'tool', event: 'ToolMatch' },
|
||||
{ domain: 'tool', event: 'Other' },
|
||||
]);
|
||||
renderWithProviders(<EventLogPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('ToolMatch')).toBeTruthy();
|
||||
});
|
||||
|
||||
const input = screen.getByPlaceholderText('settings.developerMenu.eventLog.filterAgent');
|
||||
fireEvent.change(input, { target: { value: 'Match' } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Other')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders unknown domain as uppercase text', async () => {
|
||||
mockFetchSSE([{ domain: 'custom_thing', event: 'Evt' }]);
|
||||
renderWithProviders(<EventLogPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('CUSTOM_THING')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles scroll and shows jump-to-latest button', async () => {
|
||||
mockFetchSSE([{ domain: 'tool', event: 'ScrollTest' }]);
|
||||
const { container } = renderWithProviders(<EventLogPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('ScrollTest')).toBeTruthy();
|
||||
});
|
||||
|
||||
const scrollDiv = container.querySelector('.max-h-\\[60vh\\]')!;
|
||||
Object.defineProperty(scrollDiv, 'scrollTop', { value: 100, writable: true });
|
||||
fireEvent.scroll(scrollDiv);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('settings.developerMenu.eventLog.jumpToLatest')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByText('settings.developerMenu.eventLog.jumpToLatest'));
|
||||
});
|
||||
});
|
||||
@@ -225,6 +225,29 @@ const ar5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'خطافات الويب',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'فحص تسجيلات خطافات الويب وقت التشغيل وسجلات الطلبات الملتقطة',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'الذكاء',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'مساحة عمل الذاكرة، ومحرك اللاوعي، والأحلام، والإعدادات',
|
||||
|
||||
@@ -231,6 +231,29 @@ const bn5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'ওয়েবহুক',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'রানটাইম ওয়েবহুক রেজিস্ট্রেশন এবং ধরা পড়া রিকোয়েস্ট লগ পরিদর্শন করুন',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'ইন্টেলিজেন্স',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'মেমরি ওয়ার্কস্পেস, সাবকনশাস ইঞ্জিন, ড্রিমস এবং সেটিংস',
|
||||
|
||||
@@ -239,6 +239,29 @@ const de5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'Webhooks',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'Prüfe Laufzeit-Webhook-Registrierungen und erfasste Anforderungsprotokolle',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'Intelligenz',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'Gedächtnisarbeitsbereich, Unterbewusstseinsmotor, Träume und Einstellungen',
|
||||
|
||||
@@ -232,6 +232,29 @@ const en5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'Webhooks',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'Inspect runtime webhook registrations and captured request logs',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'Intelligence',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'Memory workspace, subconscious engine, dreams, and settings',
|
||||
|
||||
@@ -234,6 +234,29 @@ const es5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'Ganchos web',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'Inspecciona registros de webhooks en tiempo de ejecución y solicitudes capturadas',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'Inteligencia',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'Espacio de trabajo de memoria, motor subconsciente, sueños y ajustes',
|
||||
|
||||
@@ -236,6 +236,29 @@ const fr5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'Webhooks',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
"Inspecter les enregistrements de webhooks d'exécution et les journaux de requêtes capturés",
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'Intelligence',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'Espace mémoire, moteur subconscient, rêves et paramètres',
|
||||
|
||||
@@ -231,6 +231,29 @@ const hi5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'वेबहुक्स',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'रनटाइम वेबहुक रजिस्ट्रेशन और कैप्चर किए गए अनुरोध लॉग देखें',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'इंटेलिजेंस',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'मेमोरी वर्कस्पेस, सबकॉन्शस इंजन, ड्रीम्स, और सेटिंग्स',
|
||||
|
||||
@@ -231,6 +231,29 @@ const id5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'Webhook',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'Periksa pendaftaran webhook runtime dan log permintaan yang tertangkap',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'Kecerdasan',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'Ruang kerja memori, mesin bawah sadar, mimpi, dan pengaturan',
|
||||
|
||||
@@ -234,6 +234,29 @@ const it5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'Webhook',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'Ispeziona registrazioni webhook di runtime e log delle richieste catturate',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'Intelligenza',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'Area di lavoro memoria, motore subconscio, sogni e impostazioni',
|
||||
|
||||
@@ -562,6 +562,29 @@ const ko5: TranslationMap = {
|
||||
'settings.developerMenu.composio.desc': '라우팅 모드, 통합 트리거 및 트리거 기록 아카이브.',
|
||||
'settings.developerMenu.webhooks.title': '웹훅',
|
||||
'settings.developerMenu.webhooks.desc': '런타임 웹훅 등록 및 캡처된 요청 로그 검사',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': '인텔리전스',
|
||||
'settings.developerMenu.intelligence.desc': '메모리 작업 공간, 잠재의식 엔진, 드림 및 설정',
|
||||
'settings.developerMenu.notificationRouting.title': '알림 라우팅',
|
||||
|
||||
@@ -235,6 +235,29 @@ const pt5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'Webhooks',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'Inspecione registros de webhooks em tempo de execução e logs de solicitações capturadas',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'Inteligência',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'Espaço de trabalho de memória, motor subconsciente, sonhos e configurações',
|
||||
|
||||
@@ -232,6 +232,29 @@ const ru5: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'Вебхуки',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'Проверяйте регистрации runtime-вебхуков и журналы захваченных запросов',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'Интеллект',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'Рабочая область памяти, подсознательный движок, сны и настройки',
|
||||
|
||||
@@ -218,6 +218,29 @@ const zhCN5: TranslationMap = {
|
||||
'settings.developerMenu.localModelDebug.desc': 'Ollama 配置、资源下载、模型测试和诊断',
|
||||
'settings.developerMenu.webhooks.title': 'Webhook',
|
||||
'settings.developerMenu.webhooks.desc': '检查运行时 Webhook 注册和捕获的请求日志',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': '智能',
|
||||
'settings.developerMenu.intelligence.desc': '记忆工作区、潜意识引擎、梦境和设置',
|
||||
'settings.developerMenu.notificationRouting.title': '通知路由',
|
||||
|
||||
@@ -3262,6 +3262,29 @@ const en: TranslationMap = {
|
||||
'settings.developerMenu.webhooks.title': 'Webhooks',
|
||||
'settings.developerMenu.webhooks.desc':
|
||||
'Inspect runtime webhook registrations and captured request logs',
|
||||
'settings.developerMenu.eventLog.title': 'Event Log',
|
||||
'settings.developerMenu.eventLog.desc':
|
||||
'Live colour-coded stream of all agent, tool, and system events',
|
||||
'settings.developerMenu.eventLog.allTypes': 'All types',
|
||||
'settings.developerMenu.eventLog.filterAgent': 'Filter...',
|
||||
'settings.developerMenu.eventLog.download': 'Download',
|
||||
'settings.developerMenu.eventLog.events': 'events',
|
||||
'settings.developerMenu.eventLog.live': 'Live',
|
||||
'settings.developerMenu.eventLog.disconnected': 'Disconnected',
|
||||
'settings.developerMenu.eventLog.waiting': 'Waiting for events...',
|
||||
'settings.developerMenu.eventLog.notConnected': 'Not connected to core',
|
||||
'settings.developerMenu.eventLog.jumpToLatest': 'Jump to latest',
|
||||
'settings.developerMenu.eventLog.badge.tool': 'TOOL',
|
||||
'settings.developerMenu.eventLog.badge.agent': 'AGENT',
|
||||
'settings.developerMenu.eventLog.badge.info': 'INFO',
|
||||
'settings.developerMenu.eventLog.badge.mem': 'MEM',
|
||||
'settings.developerMenu.eventLog.badge.chan': 'CHAN',
|
||||
'settings.developerMenu.eventLog.badge.cron': 'CRON',
|
||||
'settings.developerMenu.eventLog.badge.hook': 'HOOK',
|
||||
'settings.developerMenu.eventLog.badge.warn': 'WARN',
|
||||
'settings.developerMenu.eventLog.badge.skill': 'SKILL',
|
||||
'settings.developerMenu.eventLog.badge.comp': 'COMP',
|
||||
'settings.developerMenu.eventLog.badge.mcp': 'MCP',
|
||||
'settings.developerMenu.intelligence.title': 'Intelligence',
|
||||
'settings.developerMenu.intelligence.desc':
|
||||
'Memory workspace, subconscious engine, dreams, and settings',
|
||||
|
||||
@@ -20,6 +20,7 @@ import DeveloperOptionsPanel from '../components/settings/panels/DeveloperOption
|
||||
import DevicesComingSoonPanel from '../components/settings/panels/DevicesComingSoonPanel';
|
||||
import DevWorkflowPanel from '../components/settings/panels/DevWorkflowPanel';
|
||||
import EmbeddingsPanel from '../components/settings/panels/EmbeddingsPanel';
|
||||
import EventLogPanel from '../components/settings/panels/EventLogPanel';
|
||||
import HeartbeatPanel from '../components/settings/panels/HeartbeatPanel';
|
||||
import LedgerUsagePanel from '../components/settings/panels/LedgerUsagePanel';
|
||||
import LocalModelDebugPanel from '../components/settings/panels/LocalModelDebugPanel';
|
||||
@@ -467,6 +468,7 @@ const Settings = () => {
|
||||
<Route path="voice-debug" element={wrapSettingsPage(<VoiceDebugPanel />)} />
|
||||
<Route path="local-model-debug" element={wrapSettingsPage(<LocalModelDebugPanel />)} />
|
||||
<Route path="webhooks-debug" element={wrapSettingsPage(<WebhooksDebugPanel />)} />
|
||||
<Route path="event-log" element={wrapSettingsPage(<EventLogPanel />)} />
|
||||
<Route
|
||||
path="model-health"
|
||||
element={wrapSettingsPage(<ModelHealthPanel />, { maxWidthClass: 'max-w-4xl' })}
|
||||
|
||||
@@ -728,6 +728,104 @@ impl DomainEvent {
|
||||
| Self::McpSetupSecretRequested { .. } => "mcp_client",
|
||||
}
|
||||
}
|
||||
|
||||
/// Stable variant name without payload (avoids Debug format coupling).
|
||||
pub fn variant_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::AgentTurnStarted { .. } => "AgentTurnStarted",
|
||||
Self::AgentTurnCompleted { .. } => "AgentTurnCompleted",
|
||||
Self::AgentError { .. } => "AgentError",
|
||||
Self::SubagentSpawned { .. } => "SubagentSpawned",
|
||||
Self::SubagentCompleted { .. } => "SubagentCompleted",
|
||||
Self::SubagentFailed { .. } => "SubagentFailed",
|
||||
Self::MemoryStored { .. } => "MemoryStored",
|
||||
Self::MemoryRecalled { .. } => "MemoryRecalled",
|
||||
Self::MemorySyncRequested { .. } => "MemorySyncRequested",
|
||||
Self::MemorySyncStageChanged { .. } => "MemorySyncStageChanged",
|
||||
Self::MemoryIngestionStarted { .. } => "MemoryIngestionStarted",
|
||||
Self::MemoryIngestionCompleted { .. } => "MemoryIngestionCompleted",
|
||||
Self::DocumentCanonicalized { .. } => "DocumentCanonicalized",
|
||||
Self::CacheRebuilt { .. } => "CacheRebuilt",
|
||||
Self::ChannelInboundMessage { .. } => "ChannelInboundMessage",
|
||||
Self::ChannelMessageReceived { .. } => "ChannelMessageReceived",
|
||||
Self::ChannelMessageProcessed { .. } => "ChannelMessageProcessed",
|
||||
Self::ChannelReactionReceived { .. } => "ChannelReactionReceived",
|
||||
Self::ChannelReactionSent { .. } => "ChannelReactionSent",
|
||||
Self::ChannelConnected { .. } => "ChannelConnected",
|
||||
Self::ChannelDisconnected { .. } => "ChannelDisconnected",
|
||||
Self::CronJobTriggered { .. } => "CronJobTriggered",
|
||||
Self::CronJobCompleted { .. } => "CronJobCompleted",
|
||||
Self::CronDeliveryRequested { .. } => "CronDeliveryRequested",
|
||||
Self::ProactiveMessageRequested { .. } => "ProactiveMessageRequested",
|
||||
Self::SkillLoaded { .. } => "SkillLoaded",
|
||||
Self::SkillStopped { .. } => "SkillStopped",
|
||||
Self::SkillStartFailed { .. } => "SkillStartFailed",
|
||||
Self::SkillExecuted { .. } => "SkillExecuted",
|
||||
Self::ToolExecutionStarted { .. } => "ToolExecutionStarted",
|
||||
Self::ToolExecutionCompleted { .. } => "ToolExecutionCompleted",
|
||||
Self::WebhookIncomingRequest { .. } => "WebhookIncomingRequest",
|
||||
Self::WebhookReceived { .. } => "WebhookReceived",
|
||||
Self::WebhookRegistered { .. } => "WebhookRegistered",
|
||||
Self::WebhookUnregistered { .. } => "WebhookUnregistered",
|
||||
Self::WebhookProcessed { .. } => "WebhookProcessed",
|
||||
Self::ComposioTriggerReceived { .. } => "ComposioTriggerReceived",
|
||||
Self::ComposioConnectionCreated { .. } => "ComposioConnectionCreated",
|
||||
Self::ComposioConnectionDeleted { .. } => "ComposioConnectionDeleted",
|
||||
Self::ComposioActionExecuted { .. } => "ComposioActionExecuted",
|
||||
Self::ComposioConfigChanged { .. } => "ComposioConfigChanged",
|
||||
Self::TriggerEvaluated { .. } => "TriggerEvaluated",
|
||||
Self::TriggerEscalated { .. } => "TriggerEscalated",
|
||||
Self::TriggerEscalationFailed { .. } => "TriggerEscalationFailed",
|
||||
Self::TreeSummarizerHourCompleted { .. } => "TreeSummarizerHourCompleted",
|
||||
Self::TreeSummarizerPropagated { .. } => "TreeSummarizerPropagated",
|
||||
Self::TreeSummarizerRebuildCompleted { .. } => "TreeSummarizerRebuildCompleted",
|
||||
Self::NotificationIngested { .. } => "NotificationIngested",
|
||||
Self::NotificationTriaged { .. } => "NotificationTriaged",
|
||||
Self::DevicePaired { .. } => "DevicePaired",
|
||||
Self::DeviceRevoked { .. } => "DeviceRevoked",
|
||||
Self::DevicePeerOnline { .. } => "DevicePeerOnline",
|
||||
Self::DevicePeerOffline { .. } => "DevicePeerOffline",
|
||||
Self::DeviceTunnelFrame { .. } => "DeviceTunnelFrame",
|
||||
Self::DeviceTunnelRegistered { .. } => "DeviceTunnelRegistered",
|
||||
Self::CompanionSessionStarted { .. } => "CompanionSessionStarted",
|
||||
Self::CompanionStateChanged { .. } => "CompanionStateChanged",
|
||||
Self::CompanionSessionEnded { .. } => "CompanionSessionEnded",
|
||||
Self::SystemStartup { .. } => "SystemStartup",
|
||||
Self::SystemShutdown { .. } => "SystemShutdown",
|
||||
Self::SystemRestartRequested { .. } => "SystemRestartRequested",
|
||||
Self::SystemShutdownRequested { .. } => "SystemShutdownRequested",
|
||||
Self::AutonomyConfigChanged => "AutonomyConfigChanged",
|
||||
Self::HealthChanged { .. } => "HealthChanged",
|
||||
Self::HealthRestarted { .. } => "HealthRestarted",
|
||||
Self::SessionExpired { .. } => "SessionExpired",
|
||||
Self::ApprovalRequested { .. } => "ApprovalRequested",
|
||||
Self::ApprovalDecided { .. } => "ApprovalDecided",
|
||||
Self::McpServerInstalled { .. } => "McpServerInstalled",
|
||||
Self::McpServerConnected { .. } => "McpServerConnected",
|
||||
Self::McpServerDisconnected { .. } => "McpServerDisconnected",
|
||||
Self::McpClientToolExecuted { .. } => "McpClientToolExecuted",
|
||||
Self::McpSetupSecretRequested { .. } => "McpSetupSecretRequested",
|
||||
Self::EmbeddingModelUnhealthy { .. } => "EmbeddingModelUnhealthy",
|
||||
}
|
||||
}
|
||||
|
||||
/// Best-effort agent/session hint for display (not all events carry one).
|
||||
pub fn agent_hint(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::AgentTurnStarted { session_id, .. }
|
||||
| Self::AgentTurnCompleted { session_id, .. }
|
||||
| Self::AgentError { session_id, .. } => Some(session_id.as_str()),
|
||||
Self::SubagentSpawned { agent_id, .. }
|
||||
| Self::SubagentCompleted { agent_id, .. }
|
||||
| Self::SubagentFailed { agent_id, .. } => Some(agent_id.as_str()),
|
||||
Self::ChannelMessageReceived { channel, .. }
|
||||
| Self::ChannelConnected { channel, .. }
|
||||
| Self::ChannelDisconnected { channel, .. } => Some(channel.as_str()),
|
||||
Self::ToolExecutionStarted { tool_name, .. }
|
||||
| Self::ToolExecutionCompleted { tool_name, .. } => Some(tool_name.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -862,6 +862,7 @@ pub fn build_core_http_router(socketio_enabled: bool) -> Router {
|
||||
.route("/schema", get(schema_handler))
|
||||
.route("/events", get(events_handler))
|
||||
.route("/events/webhooks", get(webhook_events_handler))
|
||||
.route("/events/domain", get(domain_events_handler))
|
||||
.route("/rpc", post(rpc_handler))
|
||||
.route("/ws/dictation", get(dictation_ws_handler))
|
||||
.route("/auth", get(desktop_auth_handler))
|
||||
@@ -1186,6 +1187,101 @@ async fn webhook_events_handler() -> Response {
|
||||
.into_response()
|
||||
}
|
||||
|
||||
/// SSE endpoint streaming DomainEvent bus events for the live event log panel.
|
||||
///
|
||||
/// Requires bearer auth. Streams all domain events as JSON with event type
|
||||
/// set to the domain name (agent, tool, memory, etc.).
|
||||
async fn domain_events_handler(headers: axum::http::HeaderMap) -> Response {
|
||||
let bearer = headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| v.strip_prefix("Bearer "))
|
||||
.map(str::trim)
|
||||
.filter(|s| !s.is_empty());
|
||||
let bearer_ok = bearer
|
||||
.map(crate::core::auth::verify_bearer_token)
|
||||
.unwrap_or(false);
|
||||
|
||||
if !bearer_ok {
|
||||
log::warn!("[events/domain] reject subscribe: missing or invalid bearer token");
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({
|
||||
"ok": false,
|
||||
"error": "unauthorized",
|
||||
"message": "Bearer token required for domain event stream"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
|
||||
// Read dashboard config for event stream settings.
|
||||
let es_cfg = crate::openhuman::config::rpc::load_config_with_timeout()
|
||||
.await
|
||||
.map(|c| c.dashboard.event_stream)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !es_cfg.enabled {
|
||||
return (
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(json!({ "ok": false, "error": "event stream disabled by config" })),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
|
||||
let bus = match crate::core::event_bus::global() {
|
||||
Some(bus) => bus,
|
||||
None => {
|
||||
log::warn!("[events/domain] event bus not initialized");
|
||||
return (
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(json!({ "ok": false, "error": "event bus not initialized" })),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
|
||||
log::debug!("[events/domain] client connected, streaming domain events");
|
||||
|
||||
// Send config as first SSE event so frontend can apply settings.
|
||||
let config_event = Event::default().event("config").data(
|
||||
serde_json::to_string(&json!({
|
||||
"max_entries": es_cfg.max_entries,
|
||||
"new_entries": es_cfg.new_entries,
|
||||
}))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
let rx = bus.raw_receiver();
|
||||
let event_stream = tokio_stream::wrappers::BroadcastStream::new(rx).filter_map(
|
||||
|item| -> Option<Result<Event, std::convert::Infallible>> {
|
||||
let event = match item {
|
||||
Ok(ev) => ev,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let domain = event.domain().to_string();
|
||||
let event_name = event.variant_name();
|
||||
let agent = event.agent_hint().unwrap_or("").to_string();
|
||||
let data = json!({
|
||||
"domain": domain,
|
||||
"event": event_name,
|
||||
"agent": agent,
|
||||
"timestamp": chrono::Utc::now().format("%H:%M:%S").to_string(),
|
||||
});
|
||||
let data_str = serde_json::to_string(&data).ok()?;
|
||||
Some(Ok(Event::default().event(domain).data(data_str)))
|
||||
},
|
||||
);
|
||||
|
||||
let config_stream =
|
||||
futures::stream::once(async move { Ok::<_, std::convert::Infallible>(config_event) });
|
||||
let stream = config_stream.chain(event_stream);
|
||||
|
||||
Sse::new(stream)
|
||||
.keep_alive(KeepAlive::new().interval(std::time::Duration::from_secs(5)))
|
||||
.into_response()
|
||||
}
|
||||
|
||||
/// Handler for the root endpoint, returning server information and available endpoints.
|
||||
async fn root_handler() -> impl IntoResponse {
|
||||
let api_server = match crate::openhuman::config::Config::load_or_init().await {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Dashboard configuration (event stream, model health, future panels).
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -16,6 +18,7 @@ fn default_diagram_viewer_refresh_interval_seconds() -> u64 {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(default)]
|
||||
pub struct DashboardConfig {
|
||||
#[serde(default)]
|
||||
pub event_stream: EventStreamConfig,
|
||||
#[serde(default)]
|
||||
pub model_health: ModelHealthConfig,
|
||||
@@ -36,17 +39,36 @@ impl Default for DashboardConfig {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(default)]
|
||||
pub struct EventStreamConfig {
|
||||
#[serde(default = "default_es_enabled")]
|
||||
/// Whether the live event stream endpoint is enabled.
|
||||
#[serde(default = "default_enabled")]
|
||||
pub enabled: bool,
|
||||
|
||||
/// Maximum number of entries the frontend should retain.
|
||||
#[serde(default = "default_max_entries")]
|
||||
pub max_entries: usize,
|
||||
|
||||
/// Where new entries appear: "top" (newest first) or "bottom" (oldest first).
|
||||
#[serde(default = "default_new_entries")]
|
||||
pub new_entries: String,
|
||||
}
|
||||
|
||||
fn default_es_enabled() -> bool {
|
||||
fn default_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
fn default_max_entries() -> usize {
|
||||
200
|
||||
}
|
||||
fn default_new_entries() -> String {
|
||||
"top".to_string()
|
||||
}
|
||||
|
||||
impl Default for EventStreamConfig {
|
||||
fn default() -> Self {
|
||||
Self { enabled: true }
|
||||
Self {
|
||||
enabled: true,
|
||||
max_entries: 200,
|
||||
new_entries: "top".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +134,36 @@ impl Default for DiagramViewerConfig {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn defaults_match_issue_spec() {
|
||||
let cfg = DashboardConfig::default();
|
||||
assert!(cfg.event_stream.enabled);
|
||||
assert_eq!(cfg.event_stream.max_entries, 200);
|
||||
assert_eq!(cfg.event_stream.new_entries, "top");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_from_empty_json() {
|
||||
let cfg: DashboardConfig = serde_json::from_value(serde_json::json!({})).unwrap();
|
||||
assert!(cfg.event_stream.enabled);
|
||||
assert_eq!(cfg.event_stream.max_entries, 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_custom_values() {
|
||||
let cfg: DashboardConfig = serde_json::from_value(serde_json::json!({
|
||||
"event_stream": {
|
||||
"enabled": false,
|
||||
"max_entries": 500,
|
||||
"new_entries": "bottom"
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
assert!(!cfg.event_stream.enabled);
|
||||
assert_eq!(cfg.event_stream.max_entries, 500);
|
||||
assert_eq!(cfg.event_stream.new_entries, "bottom");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dashboard_config_defaults_enable_local_diagram_viewer() {
|
||||
let config = DashboardConfig::default();
|
||||
|
||||
@@ -87,10 +87,10 @@ pub struct Config {
|
||||
pub temperature_unsupported_models: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub observability: ObservabilityConfig,
|
||||
pub dashboard: DashboardConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub dashboard: DashboardConfig,
|
||||
pub observability: ObservabilityConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub autonomy: AutonomyConfig,
|
||||
|
||||
Reference in New Issue
Block a user