@polderlabs/bizar 5.0.0 → 5.0.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/{icons-CFqu2M-c.js → icons-Bo0iH9EC.js} +166 -121
- package/bizar-dash/dist/assets/icons-Bo0iH9EC.js.map +1 -0
- package/bizar-dash/dist/assets/main-C1cpttnv.js +19 -0
- package/bizar-dash/dist/assets/main-C1cpttnv.js.map +1 -0
- package/bizar-dash/dist/assets/main-DTkNlLrw.css +1 -0
- package/bizar-dash/dist/assets/mobile-BtxQJftK.js +2 -0
- package/bizar-dash/dist/assets/mobile-BtxQJftK.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-DHXXbn1A.js → mobile-DD-FZrTC.js} +1 -1
- package/bizar-dash/dist/assets/{mobile-DHXXbn1A.js.map → mobile-DD-FZrTC.js.map} +1 -1
- package/bizar-dash/dist/index.html +4 -4
- package/bizar-dash/dist/mobile.html +3 -3
- package/bizar-dash/node_modules/.package-lock.json +6 -0
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/package-lock.json +6 -0
- package/bizar-dash/src/server/api.mjs +7 -0
- package/bizar-dash/src/server/diagnostics-store.mjs +452 -2
- package/bizar-dash/src/server/memory-store.mjs +46 -0
- package/bizar-dash/src/server/providers-store.mjs +11 -4
- package/bizar-dash/src/server/routes/_shared.mjs +2 -2
- package/bizar-dash/src/server/routes/doctor.mjs +71 -0
- package/bizar-dash/src/server/routes/env-vars.mjs +67 -1
- package/bizar-dash/src/server/routes/memory.mjs +3 -0
- package/bizar-dash/src/server/routes/opencode-session-detail.mjs +61 -42
- package/bizar-dash/src/server/routes/schedules.mjs +55 -0
- package/bizar-dash/src/server/serve-info.mjs +172 -0
- package/bizar-dash/src/web/App.tsx +48 -11
- package/bizar-dash/src/web/components/AutosaveField.tsx +50 -0
- package/bizar-dash/src/web/components/DoctorPanel.tsx +160 -0
- package/bizar-dash/src/web/components/EnvVarManager.tsx +245 -62
- package/bizar-dash/src/web/components/ScheduleTemplateCard.tsx +71 -0
- package/bizar-dash/src/web/components/SettingsNav.tsx +101 -0
- package/bizar-dash/src/web/components/Sidebar.tsx +25 -1
- package/bizar-dash/src/web/components/StatusBadge.tsx +33 -2
- package/bizar-dash/src/web/components/Topbar.tsx +11 -0
- package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +64 -0
- package/bizar-dash/src/web/components/chat/Composer.tsx +1 -1
- package/bizar-dash/src/web/components/chat/useChat.ts +118 -1
- package/bizar-dash/src/web/hooks/useAutosave.ts +107 -0
- package/bizar-dash/src/web/lib/types.ts +134 -0
- package/bizar-dash/src/web/styles/chat.css +8 -5
- package/bizar-dash/src/web/styles/main.css +77 -2
- package/bizar-dash/src/web/styles/settings.css +265 -0
- package/bizar-dash/src/web/views/Chat.tsx +15 -1
- package/bizar-dash/src/web/views/Doctor.tsx +317 -0
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +1 -0
- package/bizar-dash/src/web/views/Schedules.tsx +94 -12
- package/bizar-dash/src/web/views/Settings.tsx +76 -46
- package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +114 -133
- package/bizar-dash/src/web/views/settings/AgentSection.tsx +23 -12
- package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +5 -6
- package/bizar-dash/src/web/views/settings/GeneralSection.tsx +38 -15
- package/bizar-dash/src/web/views/settings/MemorySection.tsx +92 -8
- package/bizar-dash/tests/autosave.test.tsx +276 -0
- package/bizar-dash/tests/chat-composer.test.tsx +140 -0
- package/bizar-dash/tests/components/doctor-panel.test.tsx +105 -0
- package/bizar-dash/tests/diagnostics-store.test.mjs +206 -0
- package/bizar-dash/tests/env-vars-extended.test.mjs +190 -0
- package/bizar-dash/tests/layout-spacing.test.mjs +101 -0
- package/bizar-dash/tests/memory-default-vault.test.mjs +98 -0
- package/bizar-dash/tests/minimax-bar.test.tsx +113 -0
- package/bizar-dash/tests/minimax-models.test.mjs +123 -0
- package/bizar-dash/tests/opencode-sessions-detail.test.mjs +158 -0
- package/bizar-dash/tests/routes-doctor.test.mjs +159 -0
- package/bizar-dash/tests/schedules-templates.test.mjs +144 -0
- package/bizar-dash/tests/settings-layout.test.tsx +129 -0
- package/bizar-dash/tests/settings-mode-wiring.test.tsx +151 -0
- package/bizar-dash/tests/settings-nav.test.tsx +126 -0
- package/cli/commands/minimax.mjs +5 -2
- package/cli/tests/minimax-cli.test.mjs +79 -0
- package/config/agents/frigg.md +1 -1
- package/config/agents/heimdall.md +1 -1
- package/config/agents/mimir.md +1 -1
- package/config/agents/quick.md +1 -1
- package/config/agents/semble-search.md +1 -1
- package/config/agents/vor.md +1 -1
- package/config/opencode.json +28 -21
- package/config/opencode.json.template +8 -1
- package/package.json +1 -1
- package/plugins/bizar/index.ts +77 -0
- package/plugins/bizar/src/compaction.d.mts +48 -0
- package/plugins/bizar/src/compaction.mjs +192 -0
- package/plugins/bizar/tests/compaction.test.ts +264 -0
- package/templates/schedules/daily-backup.json +12 -0
- package/templates/schedules/daily-cleanup.json +12 -0
- package/templates/schedules/hourly-health-check.json +12 -0
- package/templates/schedules/webhook-on-push.json +13 -0
- package/templates/schedules/weekly-digest.json +13 -0
- package/bizar-dash/dist/assets/icons-CFqu2M-c.js.map +0 -1
- package/bizar-dash/dist/assets/main-Dl8yY5_H.js +0 -16
- package/bizar-dash/dist/assets/main-Dl8yY5_H.js.map +0 -1
- package/bizar-dash/dist/assets/main-ZAfGKENE.css +0 -1
- package/bizar-dash/dist/assets/mobile-C2gysFOZ.js +0 -2
- package/bizar-dash/dist/assets/mobile-C2gysFOZ.js.map +0 -1
- package/bizar-dash/src/web/views/settings/BackupSection.tsx +0 -16
- package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +0 -16
- package/bizar-dash/src/web/views/settings/SkillsSection.tsx +0 -16
|
@@ -38,6 +38,8 @@ import { Memory } from './views/Memory';
|
|
|
38
38
|
import { History } from './views/History';
|
|
39
39
|
import { MiniMaxUsage } from './views/MiniMaxUsage';
|
|
40
40
|
import { BackgroundAgents } from './views/BackgroundAgents';
|
|
41
|
+
import { Doctor } from './views/Doctor';
|
|
42
|
+
import { SettingsNav } from './components/SettingsNav';
|
|
41
43
|
import { Spinner } from './components/Spinner';
|
|
42
44
|
import { Button } from './components/Button';
|
|
43
45
|
import { AlertTriangle, Globe, LayoutTemplate, X } from 'lucide-react';
|
|
@@ -49,6 +51,17 @@ type ViewProps = {
|
|
|
49
51
|
activeTab: string;
|
|
50
52
|
setActiveTab: (id: string) => void;
|
|
51
53
|
refreshSnapshot: () => Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* v4.9.0 — When true, the sidebar shows the full settings navigation
|
|
56
|
+
* instead of the normal tab rail. Toggled by clicking the Settings tab.
|
|
57
|
+
*/
|
|
58
|
+
settingsMode: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* v4.9.0 — The active settings section highlighted in the sidebar nav.
|
|
61
|
+
* Null means "show all". Managed at App level so Sidebar can read it.
|
|
62
|
+
*/
|
|
63
|
+
settingsActiveSection: string | null;
|
|
64
|
+
setSettingsActiveSection: (id: string | null) => void;
|
|
52
65
|
/**
|
|
53
66
|
* v3.3.0 — Cross-view scratch state. The "Open in chat" button
|
|
54
67
|
* on the Agents tab sets `pendingInitialAgent`, which the Chat
|
|
@@ -79,6 +92,10 @@ const VIEW_MAP: Record<string, (p: ViewProps) => React.ReactNode> = {
|
|
|
79
92
|
skills: Skills,
|
|
80
93
|
history: History,
|
|
81
94
|
minimax: MiniMaxUsage,
|
|
95
|
+
// v6.0.0 — Doctor page (system health + diagnostics surface).
|
|
96
|
+
// Registered here so the tab id 'doctor' from Topbar.tsx TABS
|
|
97
|
+
// resolves to a real view instead of falling back to Overview.
|
|
98
|
+
doctor: Doctor,
|
|
82
99
|
};
|
|
83
100
|
|
|
84
101
|
const VERSION = 'v4.5.0';
|
|
@@ -196,6 +213,11 @@ function Shell() {
|
|
|
196
213
|
const [settings, setSettings] = useState<Settings | null>(null);
|
|
197
214
|
const [wsStatus, setWsStatus] = useState<WsStatus>('connecting');
|
|
198
215
|
const [bootError, setBootError] = useState<string | null>(null);
|
|
216
|
+
// v4.9.0 — Settings mode: when true, sidebar shows all settings sections
|
|
217
|
+
const [settingsMode, setSettingsMode] = useState(false);
|
|
218
|
+
// v4.9.0 — The active settings section shown in the sidebar nav.
|
|
219
|
+
// Null means "show all sections". Managed here so Sidebar can read it.
|
|
220
|
+
const [settingsActiveSection, setSettingsActiveSection] = useState<string | null>(null);
|
|
199
221
|
const [authPromptOpen, setAuthPromptOpen] = useState(false);
|
|
200
222
|
const [searchOpen, setSearchOpen] = useState(false);
|
|
201
223
|
const [stuckAgents, setStuckAgents] = useState<{ name: string }[]>([]);
|
|
@@ -592,7 +614,7 @@ function Shell() {
|
|
|
592
614
|
const id = map[e.key];
|
|
593
615
|
if (id) {
|
|
594
616
|
e.preventDefault();
|
|
595
|
-
|
|
617
|
+
handleTabChange(id);
|
|
596
618
|
}
|
|
597
619
|
};
|
|
598
620
|
document.addEventListener('keydown', handler);
|
|
@@ -611,6 +633,14 @@ function Shell() {
|
|
|
611
633
|
[toast],
|
|
612
634
|
);
|
|
613
635
|
|
|
636
|
+
// v4.9.0 — Settings mode: entering settings tab activates the sidebar layout.
|
|
637
|
+
// Navigating to any other tab exits settings mode.
|
|
638
|
+
const handleTabChange = useCallback((id: string) => {
|
|
639
|
+
setActiveTab(id);
|
|
640
|
+
if (id === 'settings') setSettingsMode(true);
|
|
641
|
+
else setSettingsMode(false);
|
|
642
|
+
}, []);
|
|
643
|
+
|
|
614
644
|
// v3.20.3 — Merge built-in tabs with installed mod views. Mod views
|
|
615
645
|
// are appended after the built-in tabs and use the same rendering
|
|
616
646
|
// surface; their tab ids are mod view ids (e.g. 'graphify:web').
|
|
@@ -634,13 +664,16 @@ function Shell() {
|
|
|
634
664
|
snapshot,
|
|
635
665
|
settings,
|
|
636
666
|
activeTab,
|
|
637
|
-
setActiveTab:
|
|
667
|
+
setActiveTab: handleTabChange,
|
|
638
668
|
refreshSnapshot,
|
|
669
|
+
settingsMode,
|
|
670
|
+
settingsActiveSection,
|
|
671
|
+
setSettingsActiveSection,
|
|
639
672
|
};
|
|
640
673
|
return renderActiveView(activeTab, viewProps, modViews, modViewsReloadKey);
|
|
641
674
|
}
|
|
642
675
|
return null;
|
|
643
|
-
}, [activeTab, snapshot, settings, modViews, modViewsReloadKey, refreshSnapshot]);
|
|
676
|
+
}, [activeTab, snapshot, settings, modViews, modViewsReloadKey, refreshSnapshot, settingsMode, settingsActiveSection, setSettingsActiveSection, handleTabChange]);
|
|
644
677
|
|
|
645
678
|
const refreshProjects = async () => {
|
|
646
679
|
try {
|
|
@@ -671,10 +704,10 @@ function Shell() {
|
|
|
671
704
|
|
|
672
705
|
const onSearchSelect = (r: SearchResult) => {
|
|
673
706
|
const t = r.type;
|
|
674
|
-
if (t === 'agent')
|
|
675
|
-
else if (t === 'task')
|
|
676
|
-
else if (t === 'mod')
|
|
677
|
-
else if (t === 'schedule')
|
|
707
|
+
if (t === 'agent') handleTabChange('agents');
|
|
708
|
+
else if (t === 'task') handleTabChange('tasks');
|
|
709
|
+
else if (t === 'mod') handleTabChange('mods');
|
|
710
|
+
else if (t === 'schedule') handleTabChange('schedules');
|
|
678
711
|
else if (t === 'project') {
|
|
679
712
|
const id = (r.item as ProjectRecord).id;
|
|
680
713
|
onActivateProject(id);
|
|
@@ -686,7 +719,7 @@ function Shell() {
|
|
|
686
719
|
const settingId = (r.item as { id?: string; path?: string }).id
|
|
687
720
|
|| (r.item as { path?: string }).path
|
|
688
721
|
|| '';
|
|
689
|
-
|
|
722
|
+
handleTabChange('settings');
|
|
690
723
|
// Defer the scroll to allow the view to mount, then apply a brief
|
|
691
724
|
// CSS highlight. We poll a few times because the Settings view
|
|
692
725
|
// mounts lazily after tab switch.
|
|
@@ -725,7 +758,7 @@ function Shell() {
|
|
|
725
758
|
{showHeader && (
|
|
726
759
|
<Topbar
|
|
727
760
|
activeTab={activeTab}
|
|
728
|
-
onTabChange={
|
|
761
|
+
onTabChange={handleTabChange}
|
|
729
762
|
wsStatus={wsStatus}
|
|
730
763
|
version={VERSION}
|
|
731
764
|
activeProject={snapshot?.activeProject || null}
|
|
@@ -752,7 +785,7 @@ function Shell() {
|
|
|
752
785
|
variant="secondary"
|
|
753
786
|
size="sm"
|
|
754
787
|
onClick={() => {
|
|
755
|
-
|
|
788
|
+
handleTabChange('agents');
|
|
756
789
|
setStuckBannerDismissed(true);
|
|
757
790
|
}}
|
|
758
791
|
>
|
|
@@ -773,7 +806,11 @@ function Shell() {
|
|
|
773
806
|
<Sidebar
|
|
774
807
|
tabs={mergedTabs}
|
|
775
808
|
activeTab={activeTab}
|
|
776
|
-
onTabChange={
|
|
809
|
+
onTabChange={handleTabChange}
|
|
810
|
+
settingsMode={settingsMode}
|
|
811
|
+
settingsActiveSection={settingsActiveSection}
|
|
812
|
+
onSettingsSectionChange={setSettingsActiveSection}
|
|
813
|
+
onExitSettings={() => handleTabChange('overview')}
|
|
777
814
|
/>
|
|
778
815
|
)}
|
|
779
816
|
<main className="content" id="main-content" tabIndex={-1}>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/web/components/AutosaveField.tsx
|
|
2
|
+
// Generic auto-saving field wrapper with inline save status animation.
|
|
3
|
+
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { Check, Loader, AlertCircle } from 'lucide-react';
|
|
6
|
+
import { useAutosave } from '../hooks/useAutosave';
|
|
7
|
+
|
|
8
|
+
export type { AutosaveStatus } from '../hooks/useAutosave';
|
|
9
|
+
|
|
10
|
+
export function AutosaveField<T = string>({
|
|
11
|
+
initialValue,
|
|
12
|
+
saveFn,
|
|
13
|
+
delay = 800,
|
|
14
|
+
render,
|
|
15
|
+
className,
|
|
16
|
+
}: {
|
|
17
|
+
initialValue: T;
|
|
18
|
+
saveFn: (value: T) => Promise<void>;
|
|
19
|
+
delay?: number;
|
|
20
|
+
render: (props: {
|
|
21
|
+
value: T;
|
|
22
|
+
onChange: (v: T) => void;
|
|
23
|
+
onBlur: () => void;
|
|
24
|
+
}) => React.ReactNode;
|
|
25
|
+
className?: string;
|
|
26
|
+
}) {
|
|
27
|
+
const { value, setValue, status, save, clearDebounce, hasPendingDebounceRef, pendingValueRef } = useAutosave(initialValue, saveFn, { delay });
|
|
28
|
+
|
|
29
|
+
const handleBlur = React.useCallback(() => {
|
|
30
|
+
// If a debounce is pending (user was actively typing), clear the timeout
|
|
31
|
+
// and save immediately — the user is done typing.
|
|
32
|
+
if (hasPendingDebounceRef.current) {
|
|
33
|
+
clearDebounce();
|
|
34
|
+
// pendingValueRef.current holds the typed value not yet saved by debounce
|
|
35
|
+
if (pendingValueRef.current !== null) save(pendingValueRef.current);
|
|
36
|
+
}
|
|
37
|
+
// If no debounce is pending: do nothing (value was already saved or unchanged).
|
|
38
|
+
}, [clearDebounce, hasPendingDebounceRef, save, pendingValueRef]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className={`autosave-field ${className || ''} ${status}`}>
|
|
42
|
+
{render({ value, onChange: setValue, onBlur: handleBlur })}
|
|
43
|
+
<span className="autosave-status" role="status" aria-live="polite">
|
|
44
|
+
{status === 'saving' && <Loader className="spinning" size={12} />}
|
|
45
|
+
{status === 'saved' && <Check size={12} />}
|
|
46
|
+
{status === 'error' && <AlertCircle size={12} />}
|
|
47
|
+
</span>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/components/DoctorPanel.tsx — v6.0.0 reusable Doctor panel.
|
|
2
|
+
//
|
|
3
|
+
// A Card-wrapped section of the Doctor page that renders a list of
|
|
4
|
+
// `DoctorCheck` rows with status pills, plus an optional info list
|
|
5
|
+
// (e.g. counts) below the checks. Each `category` carries a default
|
|
6
|
+
// icon and title so callers only need to pass the data.
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import {
|
|
10
|
+
Activity,
|
|
11
|
+
AlertOctagon,
|
|
12
|
+
CheckCircle2,
|
|
13
|
+
CircleDot,
|
|
14
|
+
type LucideIcon,
|
|
15
|
+
Server,
|
|
16
|
+
Settings2,
|
|
17
|
+
Wrench,
|
|
18
|
+
Zap,
|
|
19
|
+
} from 'lucide-react';
|
|
20
|
+
import { Card, CardTitle, CardMeta } from './Card';
|
|
21
|
+
import { StatusBadge } from './StatusBadge';
|
|
22
|
+
import { EmptyState } from './EmptyState';
|
|
23
|
+
import { cn } from '../lib/utils';
|
|
24
|
+
import type {
|
|
25
|
+
DoctorCategory,
|
|
26
|
+
DoctorCheck,
|
|
27
|
+
DoctorPanelProps,
|
|
28
|
+
} from '../lib/types';
|
|
29
|
+
|
|
30
|
+
const CATEGORY_DEFAULTS: Record<
|
|
31
|
+
DoctorCategory,
|
|
32
|
+
{ title: string; icon: LucideIcon; emptyTitle: string; emptyMessage: string }
|
|
33
|
+
> = {
|
|
34
|
+
system: {
|
|
35
|
+
title: 'System health',
|
|
36
|
+
icon: Server,
|
|
37
|
+
emptyTitle: 'No system checks',
|
|
38
|
+
emptyMessage: 'System checks did not return any rows.',
|
|
39
|
+
},
|
|
40
|
+
services: {
|
|
41
|
+
title: 'Services',
|
|
42
|
+
icon: Zap,
|
|
43
|
+
emptyTitle: 'No service probes',
|
|
44
|
+
emptyMessage: 'Service probes did not return any rows.',
|
|
45
|
+
},
|
|
46
|
+
counts: {
|
|
47
|
+
title: 'Counts',
|
|
48
|
+
icon: Activity,
|
|
49
|
+
emptyTitle: 'No counts',
|
|
50
|
+
emptyMessage: 'No counts reported by the dashboard stores.',
|
|
51
|
+
},
|
|
52
|
+
errors: {
|
|
53
|
+
title: 'Recent errors',
|
|
54
|
+
icon: AlertOctagon,
|
|
55
|
+
emptyTitle: 'No recent errors',
|
|
56
|
+
emptyMessage: 'No error lines in the service log within the last hour.',
|
|
57
|
+
},
|
|
58
|
+
actions: {
|
|
59
|
+
title: 'Actions',
|
|
60
|
+
icon: Wrench,
|
|
61
|
+
emptyTitle: 'No actions',
|
|
62
|
+
emptyMessage: 'No actions available.',
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function statusIcon(status: DoctorCheck['status']) {
|
|
67
|
+
if (status === 'ok') return CheckCircle2;
|
|
68
|
+
if (status === 'warn') return CircleDot;
|
|
69
|
+
return AlertOctagon;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function statusLabel(status: DoctorCheck['status']) {
|
|
73
|
+
if (status === 'ok') return 'ok';
|
|
74
|
+
if (status === 'warn') return 'warn';
|
|
75
|
+
return 'fail';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Reusable Doctor panel. Used by both the Doctor page and any future
|
|
80
|
+
* settings sub-panels that want a consistent status-list render.
|
|
81
|
+
*
|
|
82
|
+
* When `children` is supplied, the default check-list render is
|
|
83
|
+
* skipped — useful for the "Actions" category where the body is
|
|
84
|
+
* a row of buttons, not a list of checks.
|
|
85
|
+
*/
|
|
86
|
+
export function DoctorPanel({
|
|
87
|
+
category,
|
|
88
|
+
title,
|
|
89
|
+
icon,
|
|
90
|
+
checks,
|
|
91
|
+
info,
|
|
92
|
+
meta,
|
|
93
|
+
children,
|
|
94
|
+
className,
|
|
95
|
+
}: DoctorPanelProps) {
|
|
96
|
+
const def = CATEGORY_DEFAULTS[category];
|
|
97
|
+
const Icon = icon ?? def.icon;
|
|
98
|
+
const heading = title ?? def.title;
|
|
99
|
+
const hasContent = checks.length > 0 || (info && info.length > 0) || children;
|
|
100
|
+
return (
|
|
101
|
+
<Card className={cn('doctor-panel', `doctor-panel-${category}`, className)}>
|
|
102
|
+
<CardTitle>
|
|
103
|
+
<Icon size={14} aria-hidden />
|
|
104
|
+
<span>{heading}</span>
|
|
105
|
+
{meta ? <span className="card-meta-inline muted">{meta}</span> : null}
|
|
106
|
+
</CardTitle>
|
|
107
|
+
{!hasContent ? (
|
|
108
|
+
<EmptyState
|
|
109
|
+
icon={<Icon size={28} aria-hidden />}
|
|
110
|
+
title={def.emptyTitle}
|
|
111
|
+
message={def.emptyMessage}
|
|
112
|
+
/>
|
|
113
|
+
) : (
|
|
114
|
+
<div className="doctor-panel-body">
|
|
115
|
+
{children}
|
|
116
|
+
{checks.length > 0 ? (
|
|
117
|
+
<ul className="doctor-check-list" role="list">
|
|
118
|
+
{checks.map((c) => {
|
|
119
|
+
const CheckIcon = statusIcon(c.status);
|
|
120
|
+
return (
|
|
121
|
+
<li key={c.name} className={cn('doctor-check-row', `doctor-check-${c.status}`)}>
|
|
122
|
+
<span className="doctor-check-icon" aria-hidden>
|
|
123
|
+
<CheckIcon
|
|
124
|
+
size={14}
|
|
125
|
+
className={cn(
|
|
126
|
+
'doctor-check-icon-svg',
|
|
127
|
+
c.status === 'ok' && 'doctor-check-icon-ok',
|
|
128
|
+
c.status === 'warn' && 'doctor-check-icon-warn',
|
|
129
|
+
c.status === 'fail' && 'doctor-check-icon-fail',
|
|
130
|
+
)}
|
|
131
|
+
/>
|
|
132
|
+
</span>
|
|
133
|
+
<span className="doctor-check-name mono">{c.name}</span>
|
|
134
|
+
<span className="doctor-check-message">{c.message}</span>
|
|
135
|
+
<StatusBadge kind={c.status} dot>
|
|
136
|
+
{statusLabel(c.status)}
|
|
137
|
+
</StatusBadge>
|
|
138
|
+
</li>
|
|
139
|
+
);
|
|
140
|
+
})}
|
|
141
|
+
</ul>
|
|
142
|
+
) : null}
|
|
143
|
+
{info && info.length > 0 ? (
|
|
144
|
+
<dl className="doctor-info-list">
|
|
145
|
+
{info.map((row) => (
|
|
146
|
+
<React.Fragment key={row.label}>
|
|
147
|
+
<dt>{row.label}</dt>
|
|
148
|
+
<dd className="tabular-nums">{row.value}</dd>
|
|
149
|
+
</React.Fragment>
|
|
150
|
+
))}
|
|
151
|
+
</dl>
|
|
152
|
+
) : null}
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
</Card>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Re-export the Settings icon so callers can override without an extra import.
|
|
160
|
+
export { Settings2 };
|