ui: add jump and sync for backend logs

This commit is contained in:
nick-delirium 2024-10-29 17:14:57 +01:00
parent 36edfd8413
commit fc63e4d83d
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
2 changed files with 51 additions and 42 deletions

View file

@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import { Segmented } from 'antd'; import { Segmented } from 'antd';
import React from 'react'; import React from 'react';
import { VList, VListHandle } from 'virtua'; import { VList, VListHandle } from 'virtua';
import { PlayerContext } from "App/components/Session/playerContext";
import { processLog, UnifiedLog } from './utils'; import { processLog, UnifiedLog } from './utils';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore'; import { useStore } from 'App/mstore';
@ -32,6 +33,7 @@ async function fetchLogs(
const logsResp = await fetch(json.url) const logsResp = await fetch(json.url)
if (logsResp.ok) { if (logsResp.ok) {
const logJson = await logsResp.json() const logJson = await logsResp.json()
if (logJson.length === 0) return []
return processLog(logJson) return processLog(logJson)
} else { } else {
throw new Error('Failed to fetch logs') throw new Error('Failed to fetch logs')
@ -59,16 +61,7 @@ function BackendLogsPanel() {
enabled: tab !== null, enabled: tab !== null,
retry: 3, retry: 3,
}); });
console.log(isError, isPending, isSuccess)
const [filter, setFilter] = React.useState(''); const [filter, setFilter] = React.useState('');
const _list = React.useRef<VListHandle>(null);
const activeIndex = 1;
React.useEffect(() => {
if (_list.current) {
_list.current.scrollToIndex(activeIndex);
}
}, [activeIndex]);
const onFilterChange = (e: React.ChangeEvent<HTMLInputElement>) => { const onFilterChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFilter(e.target.value); setFilter(e.target.value);
@ -122,40 +115,44 @@ function BackendLogsPanel() {
/> />
) : null} ) : null}
{isSuccess ? ( {isSuccess ? (
<> <LogsTable data={data} />
<TableHeader size={data.length} />
<VList ref={_list} count={testLogs.length}>
{data.map((log, index) => (
<LogRow key={index} log={log} />
))}
</VList>
</>
) : null} ) : null}
</BottomBlock.Content> </BottomBlock.Content>
</BottomBlock> </BottomBlock>
); );
} }
const testLogs = [ const LogsTable = observer(({ data }: { data: UnifiedLog[] }) => {
{ const { store, player } = React.useContext(PlayerContext);
key: 1, const time = store.get().time;
timestamp: '2021-09-01 12:00:00', const sessionStart = store.get().sessionStart;
status: 'INFO', const _list = React.useRef<VListHandle>(null);
content: 'This is a test log', const activeIndex = React.useMemo(() => {
}, const currTs = time + sessionStart;
{ const index = data.findIndex(
key: 2, (log) => log.timestamp !== 'N/A' ? new Date(log.timestamp).getTime() >= currTs : false
timestamp: '2021-09-01 12:00:00', );
status: 'WARN', return index === -1 ? data.length - 1 : index;
content: 'This is a test log', }, [time, data.length]);
}, React.useEffect(() => {
{ if (_list.current) {
key: 3, _list.current.scrollToIndex(activeIndex);
timestamp: '2021-09-01 12:00:00', }
status: 'ERROR', }, [activeIndex]);
content:
'This is a test log that is very long and should be truncated to fit in the table cell and it will be displayed later in a separate thing when clicked on a row because its so long you never gonna give htem up or alskjhaskfjhqwfhwekfqwfjkqlwhfkjqhflqkwjhefqwklfehqwlkfjhqwlkjfhqwe \n kjhdafskjfhlqkwjhfwelefkhwqlkqehfkqlwehfkqwhefkqhwefkjqwhf', const onJump = (ts: number) => {
}, player.jump(ts - sessionStart);
]; }
return (
<>
<TableHeader size={data.length} />
<VList ref={_list} count={data.length}>
{data.map((log, index) => (
<LogRow key={index} isActive={index === activeIndex} log={log} onJump={onJump} />
))}
</VList>
</>
)
});
export default observer(BackendLogsPanel); export default observer(BackendLogsPanel);

View file

@ -5,6 +5,7 @@ import { Button } from 'antd';
import cn from 'classnames'; import cn from 'classnames';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import { getDateFromString } from 'App/date'; import { getDateFromString } from 'App/date';
import JumpButton from 'App/components/shared/DevTools/JumpButton';
export function TableHeader({ size }: { size: number }) { export function TableHeader({ size }: { size: number }) {
return ( return (
@ -28,8 +29,12 @@ export function TableHeader({ size }: { size: number }) {
export function LogRow({ export function LogRow({
log, log,
onJump,
isActive,
}: { }: {
log: { timestamp: string; status: string; content: string }; log: { timestamp: string; status: string; content: string };
onJump: (ts: number) => void;
isActive?: boolean;
}) { }) {
const [isExpanded, setIsExpanded] = React.useState(false); const [isExpanded, setIsExpanded] = React.useState(false);
const bg = (status: string) => { const bg = (status: string) => {
@ -54,13 +59,16 @@ export function LogRow({
return 'border-l border-l-4 border-gray-lighter'; return 'border-l border-l-4 border-gray-lighter';
}; };
return ( return (
<div className={'code-font'}> <div
className={'code-font relative group'}
>
<JumpButton onClick={() => onJump(new Date(log.timestamp).getTime())} />
<div <div
className={cn( className={cn(
'text-sm grid items-center py-2 px-4', 'text-sm grid items-center py-2 px-4',
'cursor-pointer border-b border-b-gray-light last:border-b-0', 'cursor-pointer border-b border-b-gray-light last:border-b-0',
border(log.status), border(log.status),
bg(log.status) isActive ? 'bg-gray-lightest' : bg(log.status)
)} )}
style={{ style={{
gridTemplateColumns: 'repeat(14, minmax(0, 1fr))', gridTemplateColumns: 'repeat(14, minmax(0, 1fr))',
@ -90,10 +98,14 @@ export function LogRow({
</div> </div>
</div> </div>
{isExpanded ? ( {isExpanded ? (
<div className={'rounded bg-gray-lightest px-4 py-2 relative mx-4 my-2'}> <div
className={'rounded bg-gray-lightest px-4 py-2 relative mx-4 my-2'}
>
{log.content.split('\n').map((line, index) => ( {log.content.split('\n').map((line, index) => (
<div key={index} className={'flex items-start gap-2'}> <div key={index} className={'flex items-start gap-2'}>
<div className={'border-r border-r-gray-light pr-2 select-none'}>{index}</div> <div className={'border-r border-r-gray-light pr-2 select-none'}>
{index}
</div>
<div className={'whitespace-pre-wrap'}>{line}</div> <div className={'whitespace-pre-wrap'}>{line}</div>
</div> </div>
))} ))}