@hasna/logs 0.3.26 → 0.3.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +33 -10
  2. package/dashboard/dist/assets/index-C0wZYq1m.js +53 -0
  3. package/dashboard/dist/assets/index-DGNrK5qb.css +1 -0
  4. package/dashboard/dist/index.html +14 -0
  5. package/dist/cli/index.js +8511 -177
  6. package/dist/count-bmj4r2zb.js +10 -0
  7. package/dist/{diagnose-e0w5rwbc.js → diagnose-3q5cy9ra.js} +2 -2
  8. package/dist/{export-c3eqjste.js → export-cngdb9fh.js} +1 -1
  9. package/dist/{http-zm3ph78w.js → http-r0xc3d2s.js} +79 -8
  10. package/dist/index-931pbyn5.js +141 -0
  11. package/dist/index-b5c72f1p.js +7 -0
  12. package/dist/{index-gc0zvs88.js → index-bnr19y0h.js} +596 -37
  13. package/dist/{index-7w7v7hnr.js → index-by1pdzbr.js} +14 -5
  14. package/dist/{index-3dr7d80h.js → index-e1930v9b.js} +12 -8
  15. package/dist/{index-eh9bkbpa.js → index-e72k53yq.js} +10 -2
  16. package/dist/{index-edn08m6f.js → index-gcd14q2f.js} +9 -6
  17. package/dist/index-hq6kzaah.js +26 -0
  18. package/dist/index-j34f36wy.js +5672 -0
  19. package/dist/index-p4dbdzx4.js +1849 -0
  20. package/dist/{index-5qznfyah.js → index-q27bgpr1.js} +1086 -1646
  21. package/dist/index-t3x838zw.js +2583 -0
  22. package/dist/{index-ww5ggfv3.js → index-zkb3z95a.js} +12 -9
  23. package/dist/index.js +2982 -22
  24. package/dist/{jobs-ypmmc2ma.js → jobs-hsgyhfvm.js} +2 -1
  25. package/dist/mcp/index.js +1473 -4286
  26. package/dist/{query-7jwj05er.js → query-c5a43zx3.js} +3 -2
  27. package/dist/server/index.js +2944 -417
  28. package/dist/storage.js +50 -0
  29. package/package.json +27 -8
  30. package/biome.json +0 -13
  31. package/bun.lock +0 -376
  32. package/dashboard/README.md +0 -73
  33. package/dashboard/bun.lock +0 -526
  34. package/dashboard/eslint.config.js +0 -23
  35. package/dashboard/index.html +0 -13
  36. package/dashboard/package.json +0 -32
  37. package/dashboard/src/App.css +0 -184
  38. package/dashboard/src/App.tsx +0 -49
  39. package/dashboard/src/api.ts +0 -33
  40. package/dashboard/src/assets/hero.png +0 -0
  41. package/dashboard/src/assets/react.svg +0 -1
  42. package/dashboard/src/assets/vite.svg +0 -1
  43. package/dashboard/src/index.css +0 -111
  44. package/dashboard/src/main.tsx +0 -10
  45. package/dashboard/src/pages/Alerts.tsx +0 -69
  46. package/dashboard/src/pages/Issues.tsx +0 -50
  47. package/dashboard/src/pages/Perf.tsx +0 -75
  48. package/dashboard/src/pages/Projects.tsx +0 -67
  49. package/dashboard/src/pages/Summary.tsx +0 -67
  50. package/dashboard/src/pages/Tail.tsx +0 -65
  51. package/dashboard/tsconfig.app.json +0 -28
  52. package/dashboard/tsconfig.json +0 -7
  53. package/dashboard/tsconfig.node.json +0 -26
  54. package/dashboard/vite.config.ts +0 -14
  55. package/dist/count-x3n7qg3c.js +0 -9
  56. package/dist/index-997bkzr2.js +0 -15
  57. package/dist/index-pen6t0yc.js +0 -10794
  58. package/sdk/package.json +0 -27
  59. package/sdk/src/index.ts +0 -143
  60. package/sdk/src/types.ts +0 -56
  61. package/src/cli/entrypoints.test.ts +0 -63
  62. package/src/cli/index.ts +0 -471
  63. package/src/db/index.test.ts +0 -33
  64. package/src/db/index.ts +0 -189
  65. package/src/db/migrations/001_alert_rules.ts +0 -21
  66. package/src/db/migrations/002_issues.ts +0 -21
  67. package/src/db/migrations/003_retention.ts +0 -15
  68. package/src/db/migrations/004_page_auth.ts +0 -13
  69. package/src/db/pg-migrations.ts +0 -167
  70. package/src/index.ts +0 -1
  71. package/src/lib/alerts.test.ts +0 -67
  72. package/src/lib/alerts.ts +0 -117
  73. package/src/lib/browser-script.test.ts +0 -35
  74. package/src/lib/browser-script.ts +0 -31
  75. package/src/lib/compare.test.ts +0 -52
  76. package/src/lib/compare.ts +0 -85
  77. package/src/lib/count.test.ts +0 -44
  78. package/src/lib/count.ts +0 -55
  79. package/src/lib/diagnose.test.ts +0 -55
  80. package/src/lib/diagnose.ts +0 -91
  81. package/src/lib/export.test.ts +0 -66
  82. package/src/lib/export.ts +0 -65
  83. package/src/lib/github.ts +0 -38
  84. package/src/lib/health.test.ts +0 -48
  85. package/src/lib/health.ts +0 -51
  86. package/src/lib/ingest.test.ts +0 -57
  87. package/src/lib/ingest.ts +0 -78
  88. package/src/lib/issues.test.ts +0 -79
  89. package/src/lib/issues.ts +0 -70
  90. package/src/lib/jobs.test.ts +0 -69
  91. package/src/lib/jobs.ts +0 -63
  92. package/src/lib/lighthouse.ts +0 -65
  93. package/src/lib/package-meta.test.ts +0 -43
  94. package/src/lib/package-meta.ts +0 -80
  95. package/src/lib/page-auth.test.ts +0 -54
  96. package/src/lib/page-auth.ts +0 -48
  97. package/src/lib/parse-time.test.ts +0 -37
  98. package/src/lib/parse-time.ts +0 -14
  99. package/src/lib/perf.test.ts +0 -45
  100. package/src/lib/perf.ts +0 -46
  101. package/src/lib/projects.test.ts +0 -73
  102. package/src/lib/projects.ts +0 -69
  103. package/src/lib/query.test.ts +0 -104
  104. package/src/lib/query.ts +0 -84
  105. package/src/lib/retention.test.ts +0 -42
  106. package/src/lib/retention.ts +0 -62
  107. package/src/lib/rotate.test.ts +0 -37
  108. package/src/lib/rotate.ts +0 -27
  109. package/src/lib/scanner.ts +0 -131
  110. package/src/lib/scheduler.ts +0 -63
  111. package/src/lib/session-context.ts +0 -28
  112. package/src/lib/summarize.test.ts +0 -38
  113. package/src/lib/summarize.ts +0 -23
  114. package/src/mcp/http.test.ts +0 -92
  115. package/src/mcp/http.ts +0 -135
  116. package/src/mcp/index.test.ts +0 -27
  117. package/src/mcp/index.ts +0 -444
  118. package/src/server/index.ts +0 -61
  119. package/src/server/routes/alerts.ts +0 -32
  120. package/src/server/routes/issues.ts +0 -43
  121. package/src/server/routes/jobs.ts +0 -32
  122. package/src/server/routes/logs.ts +0 -113
  123. package/src/server/routes/perf.ts +0 -23
  124. package/src/server/routes/projects.ts +0 -67
  125. package/src/server/routes/stream.ts +0 -43
  126. package/src/server/server.test.ts +0 -194
  127. package/src/types/index.ts +0 -119
  128. package/tsconfig.json +0 -22
  129. /package/dashboard/{public → dist}/favicon.svg +0 -0
  130. /package/dashboard/{public → dist}/icons.svg +0 -0
@@ -1,75 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
3
- import { get, type Project, type PerfSnapshot } from '../api'
4
-
5
- function ScoreBadge({ score }: { score: number | null }) {
6
- const color = score === null ? '#64748b' : score >= 90 ? '#4ade80' : score >= 50 ? '#fbbf24' : '#f87171'
7
- return <span style={{ color, fontWeight: 700, fontSize: 18 }}>{score !== null ? Math.round(score) : '—'}</span>
8
- }
9
-
10
- export function Perf() {
11
- const [projects, setProjects] = useState<Project[]>([])
12
- const [selected, setSelected] = useState<string>('')
13
- const [trend, setTrend] = useState<PerfSnapshot[]>([])
14
-
15
- useEffect(() => {
16
- get<Project[]>('/projects').then(p => { setProjects(p); if (p[0]) setSelected(p[0].id) }).catch(() => {})
17
- }, [])
18
-
19
- useEffect(() => {
20
- if (!selected) return
21
- get<PerfSnapshot[]>(`/perf/trend?project_id=${selected}&limit=30`).then(setTrend).catch(() => {})
22
- }, [selected])
23
-
24
- const chartData = [...trend].reverse().map(s => ({
25
- time: s.timestamp.slice(5, 16).replace('T', ' '),
26
- score: s.score !== null ? Math.round(s.score) : null,
27
- lcp: s.lcp !== null ? Math.round(s.lcp) : null,
28
- fcp: s.fcp !== null ? Math.round(s.fcp) : null,
29
- }))
30
-
31
- return (
32
- <div>
33
- <div style={{ display: 'flex', gap: 12, marginBottom: 16, alignItems: 'center' }}>
34
- <h2 style={{ margin: 0, color: '#38bdf8' }}>Performance</h2>
35
- <select value={selected} onChange={e => setSelected(e.target.value)} style={{ background: '#1e293b', border: '1px solid #334155', color: '#e2e8f0', padding: '4px 8px', borderRadius: 4 }}>
36
- {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
37
- </select>
38
- </div>
39
-
40
- {trend.length > 0 && (
41
- <div style={{ display: 'flex', gap: 16, marginBottom: 20, flexWrap: 'wrap' }}>
42
- {[
43
- { label: 'Score', value: trend[0]?.score ?? null },
44
- { label: 'LCP (ms)', value: trend[0]?.lcp !== null && trend[0]?.lcp !== undefined ? Math.round(trend[0].lcp) : null },
45
- { label: 'FCP (ms)', value: trend[0]?.fcp !== null && trend[0]?.fcp !== undefined ? Math.round(trend[0].fcp) : null },
46
- { label: 'CLS', value: trend[0]?.cls !== null && trend[0]?.cls !== undefined ? trend[0].cls.toFixed(3) : null },
47
- ].map(stat => (
48
- <div key={stat.label} style={{ background: '#1e293b', borderRadius: 8, padding: '12px 20px', textAlign: 'center', minWidth: 100 }}>
49
- <ScoreBadge score={Number(stat.value)} />
50
- <div style={{ color: '#64748b', fontSize: 12, marginTop: 4 }}>{stat.label}</div>
51
- </div>
52
- ))}
53
- </div>
54
- )}
55
-
56
- {chartData.length > 1 ? (
57
- <div style={{ background: '#1e293b', borderRadius: 8, padding: 20 }}>
58
- <div style={{ color: '#94a3b8', marginBottom: 12, fontSize: 13 }}>Performance Score Over Time</div>
59
- <ResponsiveContainer width="100%" height={250}>
60
- <LineChart data={chartData}>
61
- <XAxis dataKey="time" stroke="#64748b" tick={{ fill: '#94a3b8', fontSize: 11 }} />
62
- <YAxis domain={[0, 100]} stroke="#64748b" tick={{ fill: '#94a3b8', fontSize: 12 }} />
63
- <Tooltip contentStyle={{ background: '#0f172a', border: '1px solid #334155', color: '#e2e8f0' }} />
64
- <Line type="monotone" dataKey="score" stroke="#38bdf8" strokeWidth={2} dot={false} />
65
- </LineChart>
66
- </ResponsiveContainer>
67
- </div>
68
- ) : (
69
- <div style={{ color: '#64748b', padding: 40, textAlign: 'center', background: '#1e293b', borderRadius: 8 }}>
70
- {projects.length === 0 ? 'No projects yet. Register one via CLI or MCP.' : 'No performance data yet. Run a scan job to collect metrics.'}
71
- </div>
72
- )}
73
- </div>
74
- )
75
- }
@@ -1,67 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import { get, post, type Project } from '../api'
3
-
4
- export function Projects() {
5
- const [projects, setProjects] = useState<Project[]>([])
6
- const [form, setForm] = useState({ name: '', github_repo: '', base_url: '' })
7
- const [pageForm, setPageForm] = useState({ project_id: '', url: '', name: '' })
8
-
9
- const load = () => get<Project[]>('/projects').then(setProjects).catch(() => {})
10
- useEffect(() => { load() }, [])
11
-
12
- const create = async () => {
13
- if (!form.name) return
14
- await post('/projects', form)
15
- setForm({ name: '', github_repo: '', base_url: '' })
16
- load()
17
- }
18
-
19
- const addPage = async () => {
20
- if (!pageForm.project_id || !pageForm.url) return
21
- await post(`/projects/${pageForm.project_id}/pages`, { url: pageForm.url, name: pageForm.name })
22
- setPageForm(f => ({ ...f, url: '', name: '' }))
23
- }
24
-
25
- return (
26
- <div>
27
- <h2 style={{ color: '#38bdf8', marginBottom: 16 }}>Projects</h2>
28
-
29
- <div style={{ background: '#1e293b', borderRadius: 8, padding: 16, marginBottom: 16 }}>
30
- <div style={{ color: '#94a3b8', fontSize: 13, marginBottom: 10 }}>Register Project</div>
31
- <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
32
- <input placeholder="Name *" value={form.name} onChange={e => setForm(f => ({ ...f, name: e.target.value }))} style={{ background: '#0f172a', border: '1px solid #334155', color: '#e2e8f0', padding: '4px 8px', borderRadius: 4, fontFamily: 'monospace' }} />
33
- <input placeholder="GitHub repo" value={form.github_repo} onChange={e => setForm(f => ({ ...f, github_repo: e.target.value }))} style={{ background: '#0f172a', border: '1px solid #334155', color: '#e2e8f0', padding: '4px 8px', borderRadius: 4, width: 220, fontFamily: 'monospace' }} />
34
- <input placeholder="Base URL" value={form.base_url} onChange={e => setForm(f => ({ ...f, base_url: e.target.value }))} style={{ background: '#0f172a', border: '1px solid #334155', color: '#e2e8f0', padding: '4px 8px', borderRadius: 4, width: 200, fontFamily: 'monospace' }} />
35
- <button onClick={create} style={{ background: '#1e40af', color: '#e2e8f0', border: 'none', padding: '4px 16px', borderRadius: 4, cursor: 'pointer' }}>+ Create</button>
36
- </div>
37
- </div>
38
-
39
- <div style={{ background: '#1e293b', borderRadius: 8, padding: 16, marginBottom: 20 }}>
40
- <div style={{ color: '#94a3b8', fontSize: 13, marginBottom: 10 }}>Register Page</div>
41
- <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
42
- <select value={pageForm.project_id} onChange={e => setPageForm(f => ({ ...f, project_id: e.target.value }))} style={{ background: '#0f172a', border: '1px solid #334155', color: '#e2e8f0', padding: '4px 8px', borderRadius: 4 }}>
43
- <option value="">Select project</option>
44
- {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
45
- </select>
46
- <input placeholder="URL *" value={pageForm.url} onChange={e => setPageForm(f => ({ ...f, url: e.target.value }))} style={{ background: '#0f172a', border: '1px solid #334155', color: '#e2e8f0', padding: '4px 8px', borderRadius: 4, width: 250, fontFamily: 'monospace' }} />
47
- <input placeholder="Name" value={pageForm.name} onChange={e => setPageForm(f => ({ ...f, name: e.target.value }))} style={{ background: '#0f172a', border: '1px solid #334155', color: '#e2e8f0', padding: '4px 8px', borderRadius: 4, fontFamily: 'monospace' }} />
48
- <button onClick={addPage} style={{ background: '#1e40af', color: '#e2e8f0', border: 'none', padding: '4px 16px', borderRadius: 4, cursor: 'pointer' }}>+ Add Page</button>
49
- </div>
50
- </div>
51
-
52
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
53
- {projects.length === 0 && <div style={{ color: '#64748b', padding: 20, textAlign: 'center' }}>No projects yet.</div>}
54
- {projects.map(p => (
55
- <div key={p.id} style={{ background: '#1e293b', borderRadius: 8, padding: '12px 16px' }}>
56
- <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
57
- <span style={{ color: '#e2e8f0', fontWeight: 600 }}>{p.name}</span>
58
- <span style={{ color: '#475569', fontSize: 11 }}>{p.id}</span>
59
- {p.base_url && <a href={p.base_url} target="_blank" rel="noreferrer" style={{ color: '#38bdf8', fontSize: 12 }}>{p.base_url}</a>}
60
- {p.github_repo && <span style={{ color: '#7dd3fc', fontSize: 12 }}>⎋ {p.github_repo}</span>}
61
- </div>
62
- </div>
63
- ))}
64
- </div>
65
- </div>
66
- )
67
- }
@@ -1,67 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend } from 'recharts'
3
- import { get, type LogSummary, type Health } from '../api'
4
-
5
- export function Summary() {
6
- const [summary, setSummary] = useState<LogSummary[]>([])
7
- const [health, setHealth] = useState<Health | null>(null)
8
-
9
- useEffect(() => {
10
- get<LogSummary[]>('/logs/summary?since=' + new Date(Date.now() - 24 * 3600 * 1000).toISOString()).then(setSummary).catch(() => {})
11
- fetch('/health').then(r => r.json()).then(setHealth).catch(() => {})
12
- }, [])
13
-
14
- // Group by service for chart
15
- const byService = summary.reduce<Record<string, { service: string; error: number; warn: number; fatal: number }>>((acc, s) => {
16
- const svc = s.service ?? 'unknown'
17
- if (!acc[svc]) acc[svc] = { service: svc, error: 0, warn: 0, fatal: 0 }
18
- if (s.level === 'error') acc[svc]!.error += s.count
19
- if (s.level === 'warn') acc[svc]!.warn += s.count
20
- if (s.level === 'fatal') acc[svc]!.fatal += s.count
21
- return acc
22
- }, {})
23
- const chartData = Object.values(byService).sort((a, b) => (b.error + b.fatal) - (a.error + a.fatal))
24
-
25
- return (
26
- <div>
27
- <h2 style={{ color: '#38bdf8', marginBottom: 16 }}>Summary (last 24h)</h2>
28
-
29
- {health && (
30
- <div style={{ display: 'flex', gap: 16, marginBottom: 24, flexWrap: 'wrap' }}>
31
- {[
32
- { label: 'Total Logs', value: health.total_logs.toLocaleString(), color: '#38bdf8' },
33
- { label: 'Projects', value: health.projects, color: '#7dd3fc' },
34
- { label: 'Open Issues', value: health.open_issues, color: health.open_issues > 0 ? '#f87171' : '#4ade80' },
35
- { label: 'Errors', value: (health.logs_by_level['error'] ?? 0) + (health.logs_by_level['fatal'] ?? 0), color: '#f87171' },
36
- { label: 'Warnings', value: health.logs_by_level['warn'] ?? 0, color: '#fbbf24' },
37
- { label: 'Uptime', value: `${Math.floor(health.uptime_seconds / 60)}m`, color: '#4ade80' },
38
- ].map(stat => (
39
- <div key={stat.label} style={{ background: '#1e293b', borderRadius: 8, padding: '12px 20px', minWidth: 120, textAlign: 'center' }}>
40
- <div style={{ color: stat.color, fontSize: 24, fontWeight: 700 }}>{stat.value}</div>
41
- <div style={{ color: '#64748b', fontSize: 12, marginTop: 4 }}>{stat.label}</div>
42
- </div>
43
- ))}
44
- </div>
45
- )}
46
-
47
- {chartData.length > 0 ? (
48
- <div style={{ background: '#1e293b', borderRadius: 8, padding: 20 }}>
49
- <div style={{ color: '#94a3b8', marginBottom: 12, fontSize: 13 }}>Errors & Warnings by Service</div>
50
- <ResponsiveContainer width="100%" height={300}>
51
- <BarChart data={chartData}>
52
- <XAxis dataKey="service" stroke="#64748b" tick={{ fill: '#94a3b8', fontSize: 12 }} />
53
- <YAxis stroke="#64748b" tick={{ fill: '#94a3b8', fontSize: 12 }} />
54
- <Tooltip contentStyle={{ background: '#0f172a', border: '1px solid #334155', color: '#e2e8f0' }} />
55
- <Legend wrapperStyle={{ color: '#94a3b8' }} />
56
- <Bar dataKey="fatal" fill="#c084fc" stackId="a" />
57
- <Bar dataKey="error" fill="#f87171" stackId="a" />
58
- <Bar dataKey="warn" fill="#fbbf24" stackId="a" />
59
- </BarChart>
60
- </ResponsiveContainer>
61
- </div>
62
- ) : (
63
- <div style={{ color: '#64748b', padding: 40, textAlign: 'center', background: '#1e293b', borderRadius: 8 }}>No errors or warnings in the last 24h 🎉</div>
64
- )}
65
- </div>
66
- )
67
- }
@@ -1,65 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react'
2
- import type { LogRow } from '../api'
3
-
4
- const LEVEL_COLOR: Record<string, string> = {
5
- debug: '#64748b', info: '#22d3ee', warn: '#fbbf24', error: '#f87171', fatal: '#c084fc'
6
- }
7
-
8
- export function Tail() {
9
- const [logs, setLogs] = useState<LogRow[]>([])
10
- const [paused, setPaused] = useState(false)
11
- const [filter, setFilter] = useState('')
12
- const bottomRef = useRef<HTMLDivElement>(null)
13
- const esRef = useRef<EventSource | null>(null)
14
-
15
- useEffect(() => {
16
- const es = new EventSource('/api/logs/stream')
17
- esRef.current = es
18
- es.onmessage = (e) => {
19
- if (paused) return
20
- try {
21
- const log = JSON.parse(e.data) as LogRow
22
- setLogs(prev => [...prev.slice(-499), log])
23
- } catch {}
24
- }
25
- return () => es.close()
26
- }, [paused])
27
-
28
- useEffect(() => {
29
- if (!paused) bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
30
- }, [logs, paused])
31
-
32
- const filtered = filter
33
- ? logs.filter(l => l.message.toLowerCase().includes(filter.toLowerCase()) || (l.service ?? '').includes(filter))
34
- : logs
35
-
36
- return (
37
- <div>
38
- <div style={{ display: 'flex', gap: 12, marginBottom: 12, alignItems: 'center' }}>
39
- <h2 style={{ margin: 0, color: '#38bdf8' }}>Live Tail</h2>
40
- <input
41
- placeholder="Filter..."
42
- value={filter}
43
- onChange={e => setFilter(e.target.value)}
44
- style={{ background: '#1e293b', border: '1px solid #334155', color: '#e2e8f0', padding: '4px 8px', borderRadius: 4, fontFamily: 'monospace' }}
45
- />
46
- <button onClick={() => setPaused(p => !p)} style={{ background: paused ? '#22d3ee' : '#334155', color: paused ? '#0f172a' : '#e2e8f0', border: 'none', padding: '4px 12px', borderRadius: 4, cursor: 'pointer' }}>
47
- {paused ? '▶ Resume' : '⏸ Pause'}
48
- </button>
49
- <button onClick={() => setLogs([])} style={{ background: '#334155', color: '#e2e8f0', border: 'none', padding: '4px 12px', borderRadius: 4, cursor: 'pointer' }}>Clear</button>
50
- <span style={{ color: '#64748b', fontSize: 12 }}>{filtered.length} logs</span>
51
- </div>
52
- <div style={{ background: '#020617', borderRadius: 8, padding: 12, height: 'calc(100vh - 160px)', overflowY: 'auto', fontSize: 13 }}>
53
- {filtered.map(log => (
54
- <div key={log.id} style={{ display: 'flex', gap: 12, marginBottom: 2, lineHeight: 1.5 }}>
55
- <span style={{ color: '#475569', minWidth: 200 }}>{log.timestamp.slice(0, 19).replace('T', ' ')}</span>
56
- <span style={{ color: LEVEL_COLOR[log.level] ?? '#e2e8f0', minWidth: 50, fontWeight: 700 }}>{log.level.toUpperCase()}</span>
57
- <span style={{ color: '#7dd3fc', minWidth: 100 }}>{log.service ?? '-'}</span>
58
- <span style={{ color: '#e2e8f0', wordBreak: 'break-all' }}>{log.message}</span>
59
- </div>
60
- ))}
61
- <div ref={bottomRef} />
62
- </div>
63
- </div>
64
- )
65
- }
@@ -1,28 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
- "target": "ES2023",
5
- "useDefineForClassFields": true,
6
- "lib": ["ES2023", "DOM", "DOM.Iterable"],
7
- "module": "ESNext",
8
- "types": ["vite/client"],
9
- "skipLibCheck": true,
10
-
11
- /* Bundler mode */
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "moduleDetection": "force",
16
- "noEmit": true,
17
- "jsx": "react-jsx",
18
-
19
- /* Linting */
20
- "strict": true,
21
- "noUnusedLocals": true,
22
- "noUnusedParameters": true,
23
- "erasableSyntaxOnly": true,
24
- "noFallthroughCasesInSwitch": true,
25
- "noUncheckedSideEffectImports": true
26
- },
27
- "include": ["src"]
28
- }
@@ -1,7 +0,0 @@
1
- {
2
- "files": [],
3
- "references": [
4
- { "path": "./tsconfig.app.json" },
5
- { "path": "./tsconfig.node.json" }
6
- ]
7
- }
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
- "target": "ES2023",
5
- "lib": ["ES2023"],
6
- "module": "ESNext",
7
- "types": ["node"],
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "verbatimModuleSyntax": true,
14
- "moduleDetection": "force",
15
- "noEmit": true,
16
-
17
- /* Linting */
18
- "strict": true,
19
- "noUnusedLocals": true,
20
- "noUnusedParameters": true,
21
- "erasableSyntaxOnly": true,
22
- "noFallthroughCasesInSwitch": true,
23
- "noUncheckedSideEffectImports": true
24
- },
25
- "include": ["vite.config.ts"]
26
- }
@@ -1,14 +0,0 @@
1
- import { defineConfig } from 'vite'
2
- import react from '@vitejs/plugin-react'
3
-
4
- export default defineConfig({
5
- plugins: [react()],
6
- base: '/dashboard/',
7
- build: { outDir: 'dist' },
8
- server: {
9
- proxy: {
10
- '/api': 'http://localhost:3460',
11
- '/health': 'http://localhost:3460',
12
- }
13
- }
14
- })
@@ -1,9 +0,0 @@
1
- // @bun
2
- import {
3
- countLogs
4
- } from "./index-edn08m6f.js";
5
- import"./index-997bkzr2.js";
6
- import"./index-re3ntm60.js";
7
- export {
8
- countLogs
9
- };
@@ -1,15 +0,0 @@
1
- // @bun
2
- // src/lib/parse-time.ts
3
- function parseTime(val) {
4
- if (!val)
5
- return;
6
- const m = val.match(/^(\d+(?:\.\d+)?)(m|h|d|w)$/);
7
- if (!m)
8
- return val;
9
- const n = parseFloat(m[1]);
10
- const unit = m[2];
11
- const ms = n * { m: 60, h: 3600, d: 86400, w: 604800 }[unit] * 1000;
12
- return new Date(Date.now() - ms).toISOString();
13
- }
14
-
15
- export { parseTime };