@polderlabs/bizar 4.5.2 → 4.7.1
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.
- package/bizar-dash/dist/assets/main-DHZmbnxQ.js +361 -0
- package/bizar-dash/dist/assets/main-DHZmbnxQ.js.map +1 -0
- package/bizar-dash/dist/assets/main-DX_Jh8Wc.css +1 -0
- package/bizar-dash/dist/assets/{mobile-lbH6szyX.js → mobile-BK8-ythT.js} +18 -18
- package/bizar-dash/dist/assets/mobile-BK8-ythT.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js → mobile-Chvf9u_B.js} +1 -1
- package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js.map → mobile-Chvf9u_B.js.map} +1 -1
- package/bizar-dash/dist/index.html +3 -3
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/bizar-dash/skills/publishing/SKILL.md +146 -0
- package/bizar-dash/src/server/api.mjs +8 -0
- package/bizar-dash/src/server/backup-store.mjs +525 -0
- package/bizar-dash/src/server/digest-store.mjs +558 -0
- package/bizar-dash/src/server/lib/rate-limit.mjs +122 -0
- package/bizar-dash/src/server/logger.mjs +71 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +45 -6
- package/bizar-dash/src/server/metrics.mjs +193 -0
- package/bizar-dash/src/server/routes/backup.mjs +112 -0
- package/bizar-dash/src/server/routes/chat.mjs +20 -3
- package/bizar-dash/src/server/routes/digests.mjs +82 -0
- package/bizar-dash/src/server/routes/lightrag.mjs +3 -2
- package/bizar-dash/src/server/routes/memory.mjs +5 -4
- package/bizar-dash/src/server/routes/misc.mjs +2 -1
- package/bizar-dash/src/server/routes/overview.mjs +2 -1
- package/bizar-dash/src/server/routes-v2/events.mjs +14 -0
- package/bizar-dash/src/server/schedules-runner.mjs +126 -0
- package/bizar-dash/src/server/server.mjs +79 -0
- package/bizar-dash/src/web/App.tsx +8 -1
- package/bizar-dash/src/web/components/BackupRestore.tsx +330 -0
- package/bizar-dash/src/web/components/SearchModal.tsx +6 -3
- package/bizar-dash/src/web/components/VirtualList.tsx +53 -0
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +17 -11
- package/bizar-dash/src/web/components/chat/Composer.tsx +2 -0
- package/bizar-dash/src/web/hooks/useI18n.ts +13 -0
- package/bizar-dash/src/web/lib/i18n.ts +25 -0
- package/bizar-dash/src/web/locales/en.json +52 -0
- package/bizar-dash/src/web/main.tsx +5 -0
- package/bizar-dash/src/web/styles/main.css +70 -8
- package/bizar-dash/src/web/views/Activity.tsx +35 -18
- package/bizar-dash/src/web/views/Agents.tsx +57 -42
- package/bizar-dash/src/web/views/Artifacts.tsx +38 -25
- package/bizar-dash/src/web/views/Chat.tsx +8 -0
- package/bizar-dash/src/web/views/History.tsx +94 -76
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +21 -4
- package/bizar-dash/src/web/views/Mods.tsx +30 -17
- package/bizar-dash/src/web/views/Overview.tsx +19 -9
- package/bizar-dash/src/web/views/Providers.tsx +16 -16
- package/bizar-dash/src/web/views/Schedules.tsx +33 -15
- package/bizar-dash/src/web/views/Settings.tsx +97 -1745
- package/bizar-dash/src/web/views/Skills.tsx +4 -1
- package/bizar-dash/src/web/views/Tasks.tsx +11 -2
- package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +8 -2
- package/bizar-dash/src/web/views/memory/LightragPanel.tsx +3 -0
- package/bizar-dash/src/web/views/memory/ObsidianPanel.tsx +12 -4
- package/bizar-dash/src/web/views/memory/SemanticSearchPanel.tsx +3 -0
- package/bizar-dash/src/web/views/settings/ActivitySection.tsx +205 -0
- package/bizar-dash/src/web/views/settings/AgentSection.tsx +294 -0
- package/bizar-dash/src/web/views/settings/AuthSection.tsx +159 -0
- package/bizar-dash/src/web/views/settings/BackupSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/GeneralSection.tsx +105 -0
- package/bizar-dash/src/web/views/settings/HeadroomSection.tsx +39 -0
- package/bizar-dash/src/web/views/settings/MemorySection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/NetworkSection.tsx +87 -0
- package/bizar-dash/src/web/views/settings/NotificationsSection.tsx +34 -0
- package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/SkillsSection.tsx +16 -0
- package/bizar-dash/src/web/views/settings/SystemLlmSection.tsx +81 -0
- package/bizar-dash/src/web/views/settings/ThemeSection.tsx +168 -0
- package/bizar-dash/src/web/views/settings/UpdatesSection.tsx +256 -0
- package/bizar-dash/tests/a11y.test.tsx +206 -0
- package/bizar-dash/tests/backup-restore.test.mjs +217 -0
- package/bizar-dash/tests/backup-restore.test.tsx +123 -0
- package/bizar-dash/tests/backup-store.test.mjs +300 -0
- package/bizar-dash/tests/cli-bugfixes.test.mjs +4 -4
- package/bizar-dash/tests/cli-error-visibility.test.mjs +153 -0
- package/bizar-dash/tests/cli-refactor.test.mjs +184 -0
- package/bizar-dash/tests/components/Button.test.tsx +41 -0
- package/bizar-dash/tests/components/Card.test.tsx +42 -0
- package/bizar-dash/tests/components/Modal.test.tsx +104 -0
- package/bizar-dash/tests/components/Spinner.test.tsx +32 -0
- package/bizar-dash/tests/components/StatusBadge.test.tsx +35 -0
- package/bizar-dash/tests/components/Toast.test.tsx +108 -0
- package/bizar-dash/tests/digest-generation.test.mjs +191 -0
- package/bizar-dash/tests/digest-store.test.mjs +264 -0
- package/bizar-dash/tests/hooks/useModal.test.tsx +84 -0
- package/bizar-dash/tests/hooks/useToast.test.tsx +50 -0
- package/bizar-dash/tests/lib/i18n.test.ts +46 -0
- package/bizar-dash/tests/lib/utils.test.ts +194 -0
- package/bizar-dash/tests/logger.test.mjs +207 -0
- package/bizar-dash/tests/metrics.test.mjs +183 -0
- package/bizar-dash/tests/rate-limit.test.mjs +298 -0
- package/bizar-dash/tests/server-bugfixes.test.mjs +2 -2
- package/bizar-dash/tests/setup.ts +7 -0
- package/bizar-dash/vitest.config.ts +13 -0
- package/cli/artifact-cli.mjs +605 -0
- package/cli/artifact-render.mjs +621 -0
- package/cli/artifact-server.mjs +847 -0
- package/cli/artifact.mjs +38 -2096
- package/cli/bg.mjs +5 -13
- package/cli/bin.mjs +221 -1350
- package/cli/commands/artifact.mjs +20 -0
- package/cli/commands/dash.mjs +160 -0
- package/cli/commands/headroom.mjs +204 -0
- package/cli/commands/install.mjs +169 -0
- package/cli/commands/memory.mjs +25 -0
- package/cli/commands/minimax.mjs +285 -0
- package/cli/commands/mod.mjs +185 -0
- package/cli/commands/service.mjs +65 -0
- package/cli/commands/usage.mjs +109 -0
- package/cli/commands/util.mjs +459 -0
- package/cli/digest.mjs +149 -0
- package/cli/doctor.mjs +1 -13
- package/cli/provision.mjs +2 -13
- package/cli/service-controller.mjs +1 -11
- package/cli/service.mjs +1 -11
- package/cli/utils.mjs +41 -1
- package/package.json +6 -1
- package/bizar-dash/dist/assets/main-B4OfGAwz.js +0 -361
- package/bizar-dash/dist/assets/main-B4OfGAwz.js.map +0 -1
- package/bizar-dash/dist/assets/main-DAlLdW8I.css +0 -1
- package/bizar-dash/dist/assets/mobile-lbH6szyX.js.map +0 -1
|
@@ -191,13 +191,16 @@ function SkillsInner({ snapshot, refreshSnapshot }: Props) {
|
|
|
191
191
|
</div>
|
|
192
192
|
<div className="view-actions">
|
|
193
193
|
<div className="search-input">
|
|
194
|
-
<Search size={14} />
|
|
194
|
+
<Search size={14} aria-hidden />
|
|
195
|
+
<label htmlFor="skills-search" className="sr-only">Search skills</label>
|
|
195
196
|
<input
|
|
197
|
+
id="skills-search"
|
|
196
198
|
className="input"
|
|
197
199
|
type="text"
|
|
198
200
|
placeholder="Search skills…"
|
|
199
201
|
value={searchQ}
|
|
200
202
|
onChange={(e) => setSearchQ(e.target.value)}
|
|
203
|
+
aria-label="Search skills"
|
|
201
204
|
/>
|
|
202
205
|
{searchQ && (
|
|
203
206
|
<button
|
|
@@ -85,6 +85,7 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
|
|
|
85
85
|
const [priorityFilter, setPriorityFilter] = useState<string>('');
|
|
86
86
|
const [showBacklog, setShowBacklog] = useState(false);
|
|
87
87
|
const [tick, setTick] = useState(0); // for live timer re-render
|
|
88
|
+
const [statusAnnouncement, setStatusAnnouncement] = useState('');
|
|
88
89
|
|
|
89
90
|
const reload = async () => {
|
|
90
91
|
try {
|
|
@@ -146,6 +147,7 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
|
|
|
146
147
|
setTasks((cur) =>
|
|
147
148
|
cur.map((x) => (x.id === taskId ? { ...x, status: newStatus } : x)),
|
|
148
149
|
);
|
|
150
|
+
setStatusAnnouncement(`Task ${t.title} moved to ${newStatus}.`);
|
|
149
151
|
try {
|
|
150
152
|
await api.patch(`/tasks/${encodeURIComponent(taskId)}/status`, {
|
|
151
153
|
status: newStatus,
|
|
@@ -155,6 +157,7 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
|
|
|
155
157
|
setTasks((cur) =>
|
|
156
158
|
cur.map((x) => (x.id === taskId ? { ...x, status: prev } : x)),
|
|
157
159
|
);
|
|
160
|
+
setStatusAnnouncement(`Failed to move task ${t.title}: ${(err as Error).message}`);
|
|
158
161
|
toast.error(`Move failed: ${(err as Error).message}`);
|
|
159
162
|
}
|
|
160
163
|
};
|
|
@@ -217,6 +220,10 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
|
|
|
217
220
|
|
|
218
221
|
return (
|
|
219
222
|
<div className="view view-tasks">
|
|
223
|
+
{/* Hidden live region for screen-reader announcements of status changes. */}
|
|
224
|
+
<div className="sr-only" role="status" aria-live="polite">
|
|
225
|
+
{statusAnnouncement}
|
|
226
|
+
</div>
|
|
220
227
|
<header className="view-header">
|
|
221
228
|
<div className="view-header-text">
|
|
222
229
|
<h2 className="view-title">
|
|
@@ -232,13 +239,14 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
|
|
|
232
239
|
<div className="tasks-toolbar">
|
|
233
240
|
<div className="tasks-toolbar-group">
|
|
234
241
|
<div className="search-input" style={{ width: 200 }}>
|
|
235
|
-
<Search size={12} />
|
|
242
|
+
<Search size={12} aria-hidden />
|
|
236
243
|
<input
|
|
237
244
|
className="input"
|
|
238
245
|
type="text"
|
|
239
246
|
placeholder="Search…"
|
|
240
247
|
value={filter}
|
|
241
248
|
onChange={(e) => setFilter(e.target.value)}
|
|
249
|
+
aria-label="Search tasks"
|
|
242
250
|
/>
|
|
243
251
|
{filter && (
|
|
244
252
|
<button
|
|
@@ -256,8 +264,9 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
|
|
|
256
264
|
<div className="tasks-toolbar-divider" />
|
|
257
265
|
|
|
258
266
|
<div className="tasks-toolbar-group">
|
|
259
|
-
<
|
|
267
|
+
<label className="tasks-toolbar-label" htmlFor="tasks-priority-filter">Priority</label>
|
|
260
268
|
<select
|
|
269
|
+
id="tasks-priority-filter"
|
|
261
270
|
className="select select-sm"
|
|
262
271
|
value={priorityFilter}
|
|
263
272
|
onChange={(e) => setPriorityFilter(e.target.value)}
|
|
@@ -274,8 +274,14 @@ function Row({
|
|
|
274
274
|
if (inline) {
|
|
275
275
|
return (
|
|
276
276
|
<div className="memory-config-row-inline">
|
|
277
|
-
<div className="memory-config-row-label">{label}</div>
|
|
278
|
-
<div
|
|
277
|
+
<div className="memory-config-row-label" id={`cfg-${label.replace(/\s+/g, '-').toLowerCase()}`}>{label}</div>
|
|
278
|
+
<div
|
|
279
|
+
className="memory-config-row-control"
|
|
280
|
+
role="group"
|
|
281
|
+
aria-labelledby={`cfg-${label.replace(/\s+/g, '-').toLowerCase()}`}
|
|
282
|
+
>
|
|
283
|
+
{children}
|
|
284
|
+
</div>
|
|
279
285
|
</div>
|
|
280
286
|
);
|
|
281
287
|
}
|
|
@@ -216,7 +216,9 @@ export function LightragPanel({ refreshKey }: Props) {
|
|
|
216
216
|
</CardTitle>
|
|
217
217
|
<CardMeta>Send a natural-language query to the LightRAG index.</CardMeta>
|
|
218
218
|
<div className="memory-search-row">
|
|
219
|
+
<label htmlFor="lightrag-query" className="sr-only">Search LightRAG</label>
|
|
219
220
|
<input
|
|
221
|
+
id="lightrag-query"
|
|
220
222
|
type="text"
|
|
221
223
|
className="input"
|
|
222
224
|
placeholder="e.g. how does the memory service write notes?"
|
|
@@ -226,6 +228,7 @@ export function LightragPanel({ refreshKey }: Props) {
|
|
|
226
228
|
if (e.key === 'Enter') onQuery();
|
|
227
229
|
}}
|
|
228
230
|
disabled={busy === 'query'}
|
|
231
|
+
aria-label="Search LightRAG"
|
|
229
232
|
/>
|
|
230
233
|
<Button variant="primary" onClick={onQuery} disabled={busy === 'query' || !query.trim()}>
|
|
231
234
|
{busy === 'query' ? <Loader2 size={12} className="memory-spin" /> : <SearchIcon size={12} />}
|
|
@@ -222,13 +222,16 @@ export function ObsidianPanel({ refreshKey }: Props) {
|
|
|
222
222
|
<div className="memory-obsidian-list-col">
|
|
223
223
|
<div className="memory-list-toolbar">
|
|
224
224
|
<div className="search-input">
|
|
225
|
-
<SearchIcon size={14} />
|
|
225
|
+
<SearchIcon size={14} aria-hidden />
|
|
226
|
+
<label htmlFor="obsidian-search" className="sr-only">Search notes</label>
|
|
226
227
|
<input
|
|
228
|
+
id="obsidian-search"
|
|
227
229
|
type="text"
|
|
228
230
|
className="input"
|
|
229
231
|
placeholder="Search notes…"
|
|
230
232
|
value={searchQ}
|
|
231
233
|
onChange={(e) => setSearchQ(e.target.value)}
|
|
234
|
+
aria-label="Search notes"
|
|
232
235
|
/>
|
|
233
236
|
{searchQ && (
|
|
234
237
|
<button
|
|
@@ -519,8 +522,9 @@ function NoteEditor({
|
|
|
519
522
|
|
|
520
523
|
return (
|
|
521
524
|
<div className="memory-editor-modal">
|
|
522
|
-
<label className="field-label">Path</label>
|
|
525
|
+
<label className="field-label" htmlFor="obsidian-note-path">Path</label>
|
|
523
526
|
<input
|
|
527
|
+
id="obsidian-note-path"
|
|
524
528
|
type="text"
|
|
525
529
|
className="input mono"
|
|
526
530
|
value={relPath}
|
|
@@ -528,19 +532,23 @@ function NoteEditor({
|
|
|
528
532
|
disabled={mode === 'edit'}
|
|
529
533
|
placeholder="notes/example.md"
|
|
530
534
|
/>
|
|
531
|
-
<label className="field-label" style={{ marginTop: 12 }}>Frontmatter (YAML)</label>
|
|
535
|
+
<label className="field-label" htmlFor="obsidian-note-frontmatter" style={{ marginTop: 12 }}>Frontmatter (YAML)</label>
|
|
532
536
|
<textarea
|
|
537
|
+
id="obsidian-note-frontmatter"
|
|
533
538
|
className="memory-editor-textarea mono text-sm"
|
|
534
539
|
rows={6}
|
|
535
540
|
value={frontmatterYaml}
|
|
536
541
|
onChange={(e) => setFrontmatterYaml(e.target.value)}
|
|
542
|
+
aria-label="Frontmatter YAML"
|
|
537
543
|
/>
|
|
538
|
-
<label className="field-label" style={{ marginTop: 12 }}>Body (Markdown)</label>
|
|
544
|
+
<label className="field-label" htmlFor="obsidian-note-body" style={{ marginTop: 12 }}>Body (Markdown)</label>
|
|
539
545
|
<textarea
|
|
546
|
+
id="obsidian-note-body"
|
|
540
547
|
className="memory-editor-textarea mono text-sm"
|
|
541
548
|
rows={14}
|
|
542
549
|
value={body}
|
|
543
550
|
onChange={(e) => setBody(e.target.value)}
|
|
551
|
+
aria-label="Body markdown"
|
|
544
552
|
/>
|
|
545
553
|
<div className="modal-footer-actions" style={{ marginTop: 12 }}>
|
|
546
554
|
<Button variant="ghost" onClick={onCancel} disabled={busy}>Cancel</Button>
|
|
@@ -77,7 +77,9 @@ export function SemanticSearchPanel({ refreshKey }: Props) {
|
|
|
77
77
|
</CardTitle>
|
|
78
78
|
<CardMeta>Query both LightRAG (graph-based) and the Obsidian vault in one shot.</CardMeta>
|
|
79
79
|
<div className="memory-search-row">
|
|
80
|
+
<label htmlFor="semantic-search-input" className="sr-only">Semantic search</label>
|
|
80
81
|
<input
|
|
82
|
+
id="semantic-search-input"
|
|
81
83
|
type="text"
|
|
82
84
|
className="input"
|
|
83
85
|
placeholder="e.g. how are notes indexed?"
|
|
@@ -87,6 +89,7 @@ export function SemanticSearchPanel({ refreshKey }: Props) {
|
|
|
87
89
|
if (e.key === 'Enter') onSearch(q);
|
|
88
90
|
}}
|
|
89
91
|
disabled={loading}
|
|
92
|
+
aria-label="Semantic search query"
|
|
90
93
|
/>
|
|
91
94
|
<Button variant="primary" onClick={() => onSearch(q)} disabled={loading || !q.trim()}>
|
|
92
95
|
{loading ? <Loader2 size={12} className="memory-spin" /> : <SearchIcon size={12} />}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// src/web/views/settings/ActivitySection.tsx
|
|
2
|
+
import React, { useEffect, useState, useCallback } from 'react';
|
|
3
|
+
import { Activity, Search, Eye, RefreshCw, Info } from 'lucide-react';
|
|
4
|
+
import { Card, CardTitle, CardMeta } from '../../components/Card';
|
|
5
|
+
import { Button } from '../../components/Button';
|
|
6
|
+
import { useToast } from '../../components/Toast';
|
|
7
|
+
import { api } from '../../lib/api';
|
|
8
|
+
import { cn } from '../../lib/utils';
|
|
9
|
+
import type { Settings } from '../../lib/types';
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
about: Settings['about'];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function ActivityLogCard() {
|
|
16
|
+
const toast = useToast();
|
|
17
|
+
const [items, setItems] = useState<Array<{ kind?: string; ts?: string; slug?: string; message?: string }>>([]);
|
|
18
|
+
const [hidden, setHidden] = useState<Set<string>>(new Set());
|
|
19
|
+
const [loading, setLoading] = useState(true);
|
|
20
|
+
const [filter, setFilter] = useState('');
|
|
21
|
+
const [showHidden, setShowHidden] = useState(true);
|
|
22
|
+
|
|
23
|
+
const refresh = useCallback(async () => {
|
|
24
|
+
setLoading(true);
|
|
25
|
+
try {
|
|
26
|
+
const [log, hid] = await Promise.all([
|
|
27
|
+
api.get<{ items: Array<{ kind?: string; ts?: string; slug?: string; message?: string }> }>('/activity'),
|
|
28
|
+
api.get<{ hidden: string[] }>('/activity/hidden'),
|
|
29
|
+
]);
|
|
30
|
+
setItems(Array.isArray(log.items) ? log.items : []);
|
|
31
|
+
setHidden(new Set(hid.hidden || []));
|
|
32
|
+
} catch (err) {
|
|
33
|
+
toast.error(`Load failed: ${(err as Error).message}`);
|
|
34
|
+
} finally {
|
|
35
|
+
setLoading(false);
|
|
36
|
+
}
|
|
37
|
+
}, [toast]);
|
|
38
|
+
|
|
39
|
+
useEffect(() => { refresh(); }, [refresh]);
|
|
40
|
+
|
|
41
|
+
const keyOf = (it: { kind?: string; ts?: string; slug?: string }, idx: number) => {
|
|
42
|
+
const k = `${it.kind || ''}|${it.ts || ''}|${it.slug || ''}|${idx}`;
|
|
43
|
+
let h = 0;
|
|
44
|
+
for (let i = 0; i < k.length; i++) h = ((h << 5) - h + k.charCodeAt(i)) | 0;
|
|
45
|
+
return Math.abs(h).toString(16).padStart(8, '0').slice(0, 16);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const onRestoreAll = async () => {
|
|
49
|
+
try {
|
|
50
|
+
await api.del('/activity/hide');
|
|
51
|
+
setHidden(new Set());
|
|
52
|
+
toast.success('All hidden activity restored.');
|
|
53
|
+
} catch (err) {
|
|
54
|
+
toast.error(`Restore failed: ${(err as Error).message}`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const onRestoreOne = async (key: string) => {
|
|
59
|
+
try {
|
|
60
|
+
await api.del(`/activity/hide/${encodeURIComponent(key)}`);
|
|
61
|
+
const next = new Set(hidden);
|
|
62
|
+
next.delete(key);
|
|
63
|
+
setHidden(next);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
toast.error(`Restore failed: ${(err as Error).message}`);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const filtered = items.filter((it, idx) => {
|
|
70
|
+
const k = keyOf(it, idx);
|
|
71
|
+
if (!showHidden && hidden.has(k)) return false;
|
|
72
|
+
if (!filter) return true;
|
|
73
|
+
const q = filter.toLowerCase();
|
|
74
|
+
return [it.kind, it.slug, it.message].some((v) => typeof v === 'string' && v.toLowerCase().includes(q));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Card id="settings-activity-log" data-section="activity-log">
|
|
79
|
+
<CardTitle>
|
|
80
|
+
<Activity size={14} /> Activity log
|
|
81
|
+
<span className="muted" style={{ fontWeight: 400, marginLeft: 8, fontSize: 12 }}>
|
|
82
|
+
{items.length} total · {hidden.size} hidden
|
|
83
|
+
</span>
|
|
84
|
+
<Button variant="ghost" size="sm" style={{ marginLeft: 'auto' }} onClick={refresh} title="Reload">
|
|
85
|
+
<RefreshCw size={12} />
|
|
86
|
+
</Button>
|
|
87
|
+
</CardTitle>
|
|
88
|
+
<CardMeta>
|
|
89
|
+
Full history from <code>~/.bizar/activity.log</code>. Hiding an item in the Overview only hides it there — the entry stays here.
|
|
90
|
+
</CardMeta>
|
|
91
|
+
|
|
92
|
+
<div className="activity-log-toolbar">
|
|
93
|
+
<div className="activity-log-search">
|
|
94
|
+
<Search size={12} />
|
|
95
|
+
<input
|
|
96
|
+
type="text"
|
|
97
|
+
className="input"
|
|
98
|
+
placeholder="Filter by kind, slug, or message…"
|
|
99
|
+
value={filter}
|
|
100
|
+
onChange={(e) => setFilter(e.target.value)}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
<label className="activity-log-toggle">
|
|
104
|
+
<input
|
|
105
|
+
type="checkbox"
|
|
106
|
+
checked={showHidden}
|
|
107
|
+
onChange={(e) => setShowHidden(e.target.checked)}
|
|
108
|
+
/>
|
|
109
|
+
Show hidden
|
|
110
|
+
</label>
|
|
111
|
+
<Button
|
|
112
|
+
variant="ghost"
|
|
113
|
+
size="sm"
|
|
114
|
+
disabled={hidden.size === 0}
|
|
115
|
+
onClick={onRestoreAll}
|
|
116
|
+
title="Restore all hidden items to the Overview"
|
|
117
|
+
>
|
|
118
|
+
<Eye size={12} /> Restore all
|
|
119
|
+
</Button>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{loading ? (
|
|
123
|
+
<div className="muted" style={{ padding: '12px 0', fontSize: 12 }}>Loading…</div>
|
|
124
|
+
) : filtered.length === 0 ? (
|
|
125
|
+
<div className="muted" style={{ padding: '12px 0', fontSize: 12 }}>
|
|
126
|
+
{items.length === 0 ? 'No activity yet.' : 'No items match the current filter.'}
|
|
127
|
+
</div>
|
|
128
|
+
) : (
|
|
129
|
+
<div className="activity-log-table-wrap">
|
|
130
|
+
<table className="activity-log-table">
|
|
131
|
+
<thead>
|
|
132
|
+
<tr>
|
|
133
|
+
<th>Kind</th>
|
|
134
|
+
<th>Detail</th>
|
|
135
|
+
<th>Time</th>
|
|
136
|
+
<th></th>
|
|
137
|
+
</tr>
|
|
138
|
+
</thead>
|
|
139
|
+
<tbody>
|
|
140
|
+
{filtered.slice(0, 200).map((it, idx) => {
|
|
141
|
+
const k = keyOf(it, idx);
|
|
142
|
+
const isHidden = hidden.has(k);
|
|
143
|
+
return (
|
|
144
|
+
<tr key={`${it.ts}-${idx}`} className={cn(isHidden && 'activity-log-row-hidden')}>
|
|
145
|
+
<td className="activity-log-kind">{it.kind || 'activity'}</td>
|
|
146
|
+
<td className="activity-log-detail">
|
|
147
|
+
{it.message || it.slug || '—'}
|
|
148
|
+
</td>
|
|
149
|
+
<td className="activity-log-time mono">
|
|
150
|
+
{it.ts ? new Date(it.ts).toLocaleString() : '—'}
|
|
151
|
+
</td>
|
|
152
|
+
<td>
|
|
153
|
+
{isHidden ? (
|
|
154
|
+
<Button variant="ghost" size="sm" onClick={() => onRestoreOne(k)} title="Restore to Overview">
|
|
155
|
+
<Eye size={12} />
|
|
156
|
+
</Button>
|
|
157
|
+
) : (
|
|
158
|
+
<span className="activity-log-state-tag">shown</span>
|
|
159
|
+
)}
|
|
160
|
+
</td>
|
|
161
|
+
</tr>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
164
|
+
</tbody>
|
|
165
|
+
</table>
|
|
166
|
+
{filtered.length > 200 && (
|
|
167
|
+
<div className="muted" style={{ fontSize: 11, padding: '8px 0' }}>
|
|
168
|
+
Showing first 200 of {filtered.length}.
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
</Card>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function AboutCard({ about }: Props) {
|
|
178
|
+
return (
|
|
179
|
+
<Card id="settings-about" data-section="about">
|
|
180
|
+
<CardTitle><Info size={14} /> About</CardTitle>
|
|
181
|
+
<CardMeta>Build metadata.</CardMeta>
|
|
182
|
+
<dl className="about-table">
|
|
183
|
+
<dt>Version</dt>
|
|
184
|
+
<dd className="mono">{about?.version || '—'}</dd>
|
|
185
|
+
<dt>Homepage</dt>
|
|
186
|
+
<dd>
|
|
187
|
+
<a href={about?.homepage || 'https://github.com/DrB0rk/BizarHarness'} target="_blank" rel="noopener noreferrer">
|
|
188
|
+
{about?.homepage || 'https://github.com/DrB0rk/BizarHarness'}
|
|
189
|
+
</a>
|
|
190
|
+
</dd>
|
|
191
|
+
<dt>License</dt>
|
|
192
|
+
<dd>{about?.license || 'MIT'}</dd>
|
|
193
|
+
</dl>
|
|
194
|
+
</Card>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function ActivitySection({ about }: Props) {
|
|
199
|
+
return (
|
|
200
|
+
<>
|
|
201
|
+
<ActivityLogCard />
|
|
202
|
+
<AboutCard about={about} />
|
|
203
|
+
</>
|
|
204
|
+
);
|
|
205
|
+
}
|