feat(ops): upload node logs and task traffic [CI]
This commit is contained in:
1 parent
e82b165eb0
commit
6cf6b23585
25 files changed
+714
-13
No files matched your search
@@ -0,0 +1,46 @@
|
||||
import type { NodeRuntimeLogsDto } from '@shared/api/types'
|
||||
import { fmtTime } from '@shared/format'
|
||||
import { useQuery } from '@shared/hooks'
|
||||
import { Chip, Empty, ErrorNotice, Spinner } from '@shared/ui'
|
||||
import { api } from '@shared/api/client'
|
||||
|
||||
export function RuntimeLogs({ nodeId }: { nodeId: string }) {
|
||||
const logs = useQuery(
|
||||
() => api.get<NodeRuntimeLogsDto>(`/api/admin/nodes/${encodeURIComponent(nodeId)}/logs`, { limit: 100 }),
|
||||
[nodeId],
|
||||
5000,
|
||||
)
|
||||
|
||||
return (
|
||||
<section className="card">
|
||||
<div className="card-title row">
|
||||
运行日志
|
||||
<span className="right muted">最近 {logs.data?.items.length ?? 0} 条</span>
|
||||
</div>
|
||||
<ErrorNotice error={logs.error} />
|
||||
{logs.data?.dropped ? <p className="faint">节点或后端缓冲区累计丢弃 {logs.data.dropped} 条较早日志。</p> : null}
|
||||
{logs.loading && !logs.data ? <Spinner /> : null}
|
||||
{!logs.loading && (logs.data?.items.length ?? 0) === 0 ? <Empty>还没有收到运行日志</Empty> : null}
|
||||
{(logs.data?.items.length ?? 0) > 0 ? (
|
||||
<div className="runtime-logs">
|
||||
{logs.data?.items.map((entry) => (
|
||||
<div className="runtime-log" key={entry.id}>
|
||||
<time className="muted num">{fmtTime(entry.at)}</time>
|
||||
<Chip tone={entry.level === 'error' ? 'bad' : entry.level === 'warn' ? 'warn' : ''}>{entry.level}</Chip>
|
||||
<div className="runtime-log-message">
|
||||
<div>{entry.message}</div>
|
||||
{Object.keys(entry.fields).length ? (
|
||||
<div className="runtime-log-fields">
|
||||
{Object.entries(entry.fields).map(([key, value]) => (
|
||||
<span key={key}><b>{key}</b>={value}</span>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -17,6 +17,54 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.runtime-logs {
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
max-height: 520px;
|
||||
overflow: auto;
|
||||
border-radius: var(--r-card);
|
||||
background: var(--app-outline-soft);
|
||||
}
|
||||
|
||||
.runtime-log {
|
||||
display: grid;
|
||||
grid-template-columns: 150px auto minmax(0, 1fr);
|
||||
align-items: start;
|
||||
gap: 10px;
|
||||
padding: 9px 10px;
|
||||
background: var(--app-card);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.runtime-log-message {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.runtime-log-fields {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 10px;
|
||||
margin-top: 4px;
|
||||
color: var(--app-ink-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.runtime-log-fields b {
|
||||
color: var(--app-ink-faint);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.runtime-log {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.runtime-log-message {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.side .brand {
|
||||
padding: 8px 14px 16px;
|
||||
font-size: 18px;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useQuery } from '@shared/hooks'
|
||||
import { Chip, Confirm, Dialog, Empty, ErrorNotice, Field, Spinner, useAction } from '@shared/ui'
|
||||
import { AgentStatus } from './Agents'
|
||||
import { IPV6_STATES } from '@shared/ipVersions'
|
||||
import { RuntimeLogs } from '../RuntimeLogs'
|
||||
|
||||
function EditDialog({ agent, onClose, onSaved }: { agent: AgentDto; onClose: () => void; onSaved: () => void }) {
|
||||
const { busy, run } = useAction()
|
||||
@@ -358,6 +359,7 @@ export function AgentDetail() {
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
<RuntimeLogs nodeId={a.id} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useQuery } from '@shared/hooks'
|
||||
import { NodeTag, RuntimeStatusChip, useNodeInfos } from '@shared/nodes'
|
||||
import { IPV6_STATES } from '@shared/ipVersions'
|
||||
import { Chip, Dot, Empty, ErrorNotice, Spinner } from '@shared/ui'
|
||||
import { RuntimeLogs } from '../RuntimeLogs'
|
||||
|
||||
export function ProbeDetail() {
|
||||
const { id = '' } = useParams()
|
||||
@@ -111,6 +112,7 @@ export function ProbeDetail() {
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
<RuntimeLogs nodeId={p.id} />
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
Reference in new issue
Block a user