@tokentop/ttop 0.2.2 → 0.3.0
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/README.md +2 -5
- package/package.json +8 -5
- package/src/agents/aggregator.ts +39 -19
- package/src/agents/costing.ts +11 -9
- package/src/agents/index.ts +7 -9
- package/src/agents/types.ts +2 -2
- package/src/cli.ts +28 -23
- package/src/config/schema.test.ts +42 -40
- package/src/config/schema.ts +26 -26
- package/src/demo/simulator.ts +161 -122
- package/src/plugins/agents/index.ts +2 -2
- package/src/plugins/auth-sources.ts +42 -38
- package/src/plugins/lifecycle.ts +32 -32
- package/src/plugins/loader.test.ts +49 -49
- package/src/plugins/loader.ts +67 -63
- package/src/plugins/notification-bus.ts +21 -16
- package/src/plugins/notifications/index.ts +2 -2
- package/src/plugins/notifications/terminal-bell.ts +29 -23
- package/src/plugins/notifications/visual-flash.ts +21 -19
- package/src/plugins/npm-installer.ts +23 -25
- package/src/plugins/plugin-context-factory.ts +10 -10
- package/src/plugins/plugin-host.ts +12 -5
- package/src/plugins/providers/anthropic.ts +64 -57
- package/src/plugins/providers/antigravity.ts +81 -69
- package/src/plugins/providers/codex.ts +42 -42
- package/src/plugins/providers/gemini.ts +106 -87
- package/src/plugins/providers/github-copilot.ts +49 -45
- package/src/plugins/providers/index.ts +10 -12
- package/src/plugins/providers/minimax.ts +50 -44
- package/src/plugins/providers/openai-api.ts +82 -62
- package/src/plugins/providers/opencode-zen.ts +46 -39
- package/src/plugins/providers/perplexity.ts +55 -46
- package/src/plugins/providers/zai.ts +66 -57
- package/src/plugins/registry.ts +47 -43
- package/src/plugins/sandbox-guard.test.ts +45 -48
- package/src/plugins/sandbox-guard.ts +20 -25
- package/src/plugins/sandbox.ts +26 -48
- package/src/plugins/themes/catppuccin-latte.ts +29 -29
- package/src/plugins/themes/catppuccin-mocha.ts +29 -29
- package/src/plugins/themes/claude-code.ts +29 -29
- package/src/plugins/themes/dracula.ts +29 -29
- package/src/plugins/themes/github-light.ts +29 -29
- package/src/plugins/themes/gruvbox-dark.ts +29 -29
- package/src/plugins/themes/gruvbox-light.ts +29 -29
- package/src/plugins/themes/index.ts +15 -15
- package/src/plugins/themes/kanagawa.ts +29 -29
- package/src/plugins/themes/nord.ts +29 -29
- package/src/plugins/themes/one-dark.ts +29 -29
- package/src/plugins/themes/opencode.ts +31 -31
- package/src/plugins/themes/rose-pine-dawn.ts +29 -29
- package/src/plugins/themes/rose-pine.ts +29 -29
- package/src/plugins/themes/solarized-light.ts +29 -29
- package/src/plugins/themes/tokyo-night.ts +29 -29
- package/src/plugins/types/agent.ts +5 -5
- package/src/plugins/types/base.ts +35 -24
- package/src/plugins/types/index.ts +9 -9
- package/src/plugins/types/notification.ts +12 -12
- package/src/plugins/types/provider.ts +12 -8
- package/src/plugins/types/theme.ts +31 -23
- package/src/plugins/update-checker.ts +6 -6
- package/src/pricing/estimator.test.ts +39 -44
- package/src/pricing/estimator.ts +7 -13
- package/src/pricing/fallback.test.ts +30 -34
- package/src/pricing/fallback.ts +54 -27
- package/src/pricing/index.ts +18 -12
- package/src/pricing/models-dev.ts +6 -6
- package/src/storage/database.ts +56 -46
- package/src/storage/db.ts +12 -12
- package/src/storage/index.ts +44 -45
- package/src/storage/migrations/index.ts +2 -2
- package/src/storage/paths.ts +13 -13
- package/src/storage/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- package/src/storage/types.ts +5 -5
- package/src/tui/App.tsx +180 -137
- package/src/tui/components/CommandPalette.tsx +46 -34
- package/src/tui/components/DebugConsole.tsx +39 -33
- package/src/tui/components/DebugPanel.tsx +295 -177
- package/src/tui/components/FooterHints.tsx +2 -2
- package/src/tui/components/GhostProviderCard.tsx +37 -35
- package/src/tui/components/Header.tsx +68 -35
- package/src/tui/components/HelpOverlay.tsx +99 -29
- package/src/tui/components/InlineGauge.tsx +10 -7
- package/src/tui/components/InlineSparkline.tsx +17 -11
- package/src/tui/components/KpiStrip.tsx +128 -98
- package/src/tui/components/LimitGauge.tsx +96 -59
- package/src/tui/components/ModalBackdrop.tsx +4 -4
- package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
- package/src/tui/components/ProviderCard.tsx +232 -226
- package/src/tui/components/ProviderDetailPanel.tsx +133 -58
- package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
- package/src/tui/components/ProvidersList.tsx +215 -126
- package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
- package/src/tui/components/SessionsTable.tsx +313 -166
- package/src/tui/components/SettingsModal.tsx +231 -214
- package/src/tui/components/SidebarBreakdown.tsx +30 -16
- package/src/tui/components/Skeleton.tsx +24 -17
- package/src/tui/components/SmartSidebar.tsx +360 -258
- package/src/tui/components/Sparkline.tsx +64 -52
- package/src/tui/components/Spinner.tsx +3 -3
- package/src/tui/components/StatusBar.tsx +27 -27
- package/src/tui/components/ThemePicker.tsx +67 -66
- package/src/tui/components/Toast.tsx +9 -6
- package/src/tui/components/UsageGauge.tsx +10 -13
- package/src/tui/components/index.ts +5 -5
- package/src/tui/contexts/AgentSessionContext.tsx +193 -144
- package/src/tui/contexts/ConfigContext.tsx +15 -13
- package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
- package/src/tui/contexts/DemoModeContext.tsx +22 -15
- package/src/tui/contexts/DrawerContext.tsx +11 -9
- package/src/tui/contexts/InputContext.tsx +2 -2
- package/src/tui/contexts/LogContext.tsx +62 -47
- package/src/tui/contexts/PluginContext.tsx +232 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +151 -117
- package/src/tui/contexts/ThemeContext.tsx +21 -16
- package/src/tui/contexts/TimeWindowContext.tsx +72 -61
- package/src/tui/contexts/ToastContext.tsx +10 -7
- package/src/tui/contexts/index.ts +9 -5
- package/src/tui/createApp.tsx +8 -12
- package/src/tui/debug/captureFrame.ts +25 -28
- package/src/tui/debug/snapshot.tsx +179 -162
- package/src/tui/driver/assertions.ts +30 -22
- package/src/tui/driver/cli.ts +231 -221
- package/src/tui/driver/coverage.ts +27 -44
- package/src/tui/driver/demo-snapshot.ts +33 -33
- package/src/tui/driver/diff.ts +26 -32
- package/src/tui/driver/driver.ts +43 -48
- package/src/tui/driver/index.ts +36 -40
- package/src/tui/driver/recorder.ts +37 -48
- package/src/tui/driver/test-workflow.ts +51 -51
- package/src/tui/driver/test.ts +25 -25
- package/src/tui/hooks/index.ts +24 -20
- package/src/tui/hooks/useAnimatedValue.ts +13 -18
- package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
- package/src/tui/hooks/useDashboardState.ts +26 -24
- package/src/tui/hooks/useEmaActivity.ts +82 -74
- package/src/tui/hooks/useEntranceAnimation.ts +11 -11
- package/src/tui/hooks/useExitAnimation.ts +3 -3
- package/src/tui/hooks/usePulse.ts +4 -4
- package/src/tui/hooks/useSafeRenderer.ts +5 -5
- package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
- package/src/tui/hooks/useValueFlash.ts +10 -14
- package/src/tui/index.tsx +29 -6
- package/src/tui/shutdown.ts +19 -0
- package/src/tui/utils/providerColor.ts +1 -1
- package/src/tui/views/Dashboard.tsx +127 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +466 -237
- package/src/tui/views/RealTimeDashboard.tsx +150 -111
- package/src/tui/views/SettingsView.tsx +222 -191
- package/src/tui/views/index.ts +2 -2
- package/src/types/opentui-test-utils.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/currency.test.ts +53 -53
- package/src/utils/currency.ts +35 -32
- package/src/version.ts +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type ShutdownFn = () => Promise<void>;
|
|
2
|
+
|
|
3
|
+
let _shutdown: ShutdownFn | null = null;
|
|
4
|
+
let _inProgress = false;
|
|
5
|
+
|
|
6
|
+
export function registerShutdown(fn: ShutdownFn): void {
|
|
7
|
+
_shutdown = fn;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Safe to call multiple times — only the first invocation executes. */
|
|
11
|
+
export async function triggerShutdown(): Promise<void> {
|
|
12
|
+
if (_inProgress) return;
|
|
13
|
+
_inProgress = true;
|
|
14
|
+
try {
|
|
15
|
+
await _shutdown?.();
|
|
16
|
+
} catch {
|
|
17
|
+
/* swallow — don't let failed cleanup prevent exit */
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,27 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useKeyboard, useTerminalDimensions } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import type { BoxRenderable, InputRenderable, ScrollBoxRenderable } from "@opentui/core";
|
|
2
|
+
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { GhostProviderCard } from "../components/GhostProviderCard.tsx";
|
|
5
|
+
import { ProviderAggregateStrip } from "../components/ProviderAggregateStrip.tsx";
|
|
6
|
+
import { ProviderCard } from "../components/ProviderCard.tsx";
|
|
7
|
+
import { ProvidersList } from "../components/ProvidersList.tsx";
|
|
8
|
+
import { useAgentSessions } from "../contexts/AgentSessionContext.tsx";
|
|
9
|
+
import { useInputFocus } from "../contexts/InputContext.tsx";
|
|
10
|
+
import { type ProviderState, usePlugins } from "../contexts/PluginContext.tsx";
|
|
11
|
+
import { useColors } from "../contexts/ThemeContext.tsx";
|
|
12
|
+
import { useSessionEnrichedProviders } from "../hooks/useSessionEnrichedProviders.ts";
|
|
13
|
+
|
|
14
|
+
type SortMode = "name" | "usage" | "status";
|
|
15
|
+
type ViewMode = "cards" | "list";
|
|
13
16
|
|
|
14
17
|
export function Dashboard() {
|
|
15
18
|
const colors = useColors();
|
|
16
19
|
const { width: termWidth } = useTerminalDimensions();
|
|
17
|
-
const {
|
|
20
|
+
const {
|
|
21
|
+
providers: rawProviders,
|
|
22
|
+
isInitialized,
|
|
23
|
+
refreshAllProviders,
|
|
24
|
+
refreshProvider,
|
|
25
|
+
} = usePlugins();
|
|
26
|
+
const { sessions } = useAgentSessions();
|
|
27
|
+
const providers = useSessionEnrichedProviders(rawProviders, sessions);
|
|
18
28
|
const { setInputFocused } = useInputFocus();
|
|
19
29
|
const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
|
|
20
|
-
|
|
21
|
-
const [filterQuery, setFilterQuery] = useState(
|
|
30
|
+
|
|
31
|
+
const [filterQuery, setFilterQuery] = useState("");
|
|
22
32
|
const [isFiltering, setIsFiltering] = useState(false);
|
|
23
|
-
const [sortMode, setSortMode] = useState<SortMode>(
|
|
24
|
-
const [viewMode, setViewMode] = useState<ViewMode>(
|
|
33
|
+
const [sortMode, setSortMode] = useState<SortMode>("status");
|
|
34
|
+
const [viewMode, setViewMode] = useState<ViewMode>("list");
|
|
25
35
|
const [showUnconfigured, setShowUnconfigured] = useState(true);
|
|
26
36
|
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
|
|
27
37
|
const [atRiskFocus, setAtRiskFocus] = useState(false);
|
|
@@ -29,10 +39,16 @@ export function Dashboard() {
|
|
|
29
39
|
const isFilteringRef = useRef(isFiltering);
|
|
30
40
|
const expandedIndexRef = useRef(expandedIndex);
|
|
31
41
|
const focusedIndexRef = useRef(focusedIndex);
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
isFilteringRef.current = isFiltering;
|
|
45
|
+
}, [isFiltering]);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
expandedIndexRef.current = expandedIndex;
|
|
48
|
+
}, [expandedIndex]);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
focusedIndexRef.current = focusedIndex;
|
|
51
|
+
}, [focusedIndex]);
|
|
36
52
|
|
|
37
53
|
useEffect(() => {
|
|
38
54
|
setInputFocused(isFiltering);
|
|
@@ -52,25 +68,25 @@ export function Dashboard() {
|
|
|
52
68
|
|
|
53
69
|
const getMaxUsage = useCallback((state: ProviderState) => {
|
|
54
70
|
if (!state.usage?.limits) return 0;
|
|
55
|
-
|
|
71
|
+
|
|
56
72
|
const items = state.usage.limits.items ?? [];
|
|
57
73
|
if (items.length > 0) {
|
|
58
74
|
return Math.max(...items.map((item) => item.usedPercent ?? 0));
|
|
59
75
|
}
|
|
60
|
-
|
|
76
|
+
|
|
61
77
|
const primary = state.usage.limits.primary?.usedPercent || 0;
|
|
62
78
|
const secondary = state.usage.limits.secondary?.usedPercent || 0;
|
|
63
|
-
|
|
79
|
+
|
|
64
80
|
return Math.max(primary, secondary);
|
|
65
81
|
}, []);
|
|
66
82
|
|
|
67
83
|
const providerList = Array.from(providers.values());
|
|
68
|
-
|
|
84
|
+
|
|
69
85
|
const filteredAndSortedProviders = useMemo(() => {
|
|
70
86
|
let result = providerList.filter((p) => p.configured);
|
|
71
87
|
|
|
72
88
|
if (atRiskFocus) {
|
|
73
|
-
result = result.filter(p => {
|
|
89
|
+
result = result.filter((p) => {
|
|
74
90
|
if (p.usage?.error) return true;
|
|
75
91
|
if (p.usage?.limitReached) return true;
|
|
76
92
|
const maxUsage = getMaxUsage(p);
|
|
@@ -80,17 +96,17 @@ export function Dashboard() {
|
|
|
80
96
|
|
|
81
97
|
if (filterQuery) {
|
|
82
98
|
const query = filterQuery.toLowerCase();
|
|
83
|
-
result = result.filter(p => p.plugin.name.toLowerCase().includes(query));
|
|
99
|
+
result = result.filter((p) => p.plugin.name.toLowerCase().includes(query));
|
|
84
100
|
}
|
|
85
101
|
|
|
86
102
|
result.sort((a, b) => {
|
|
87
103
|
switch (sortMode) {
|
|
88
|
-
case
|
|
104
|
+
case "name":
|
|
89
105
|
return a.plugin.name.localeCompare(b.plugin.name);
|
|
90
|
-
case
|
|
106
|
+
case "usage":
|
|
91
107
|
return getMaxUsage(b) - getMaxUsage(a);
|
|
92
|
-
case
|
|
93
|
-
if (a.usage?.limitReached !== b.usage?.limitReached) {
|
|
108
|
+
case "status": {
|
|
109
|
+
if (!!a.usage?.limitReached !== !!b.usage?.limitReached) {
|
|
94
110
|
return a.usage?.limitReached ? -1 : 1;
|
|
95
111
|
}
|
|
96
112
|
if (!!a.usage?.error !== !!b.usage?.error) {
|
|
@@ -109,30 +125,31 @@ export function Dashboard() {
|
|
|
109
125
|
}, [providerList, filterQuery, sortMode, getMaxUsage, atRiskFocus]);
|
|
110
126
|
|
|
111
127
|
const configured = filteredAndSortedProviders;
|
|
112
|
-
const unconfigured = useMemo(() =>
|
|
113
|
-
providerList.filter((p) => !p.configured),
|
|
114
|
-
[providerList]
|
|
115
|
-
);
|
|
128
|
+
const unconfigured = useMemo(() => providerList.filter((p) => !p.configured), [providerList]);
|
|
116
129
|
|
|
117
|
-
const cycleFocus = useCallback(
|
|
118
|
-
|
|
119
|
-
|
|
130
|
+
const cycleFocus = useCallback(
|
|
131
|
+
(direction: 1 | -1) => {
|
|
132
|
+
const includeUnconfigured = viewMode === "cards" && showUnconfigured;
|
|
133
|
+
const total = configured.length + (includeUnconfigured ? unconfigured.length : 0);
|
|
134
|
+
if (total === 0) return;
|
|
120
135
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
setFocusedIndex((current) => {
|
|
137
|
+
if (current === null) {
|
|
138
|
+
return direction === 1 ? 0 : total - 1;
|
|
139
|
+
}
|
|
140
|
+
const next = current + direction;
|
|
141
|
+
if (next < 0) return total - 1;
|
|
142
|
+
if (next >= total) return 0;
|
|
143
|
+
return next;
|
|
144
|
+
});
|
|
145
|
+
},
|
|
146
|
+
[configured.length, unconfigured.length, showUnconfigured, viewMode],
|
|
147
|
+
);
|
|
131
148
|
|
|
132
149
|
const toggleExpanded = useCallback(() => {
|
|
133
150
|
const current = focusedIndexRef.current;
|
|
134
151
|
if (current === null) return;
|
|
135
|
-
setExpandedIndex(prev => prev === current ? null : current);
|
|
152
|
+
setExpandedIndex((prev) => (prev === current ? null : current));
|
|
136
153
|
}, []);
|
|
137
154
|
|
|
138
155
|
const refreshSelected = useCallback(() => {
|
|
@@ -146,53 +163,53 @@ export function Dashboard() {
|
|
|
146
163
|
|
|
147
164
|
useKeyboard((key) => {
|
|
148
165
|
if (isFilteringRef.current) {
|
|
149
|
-
if (key.name ===
|
|
166
|
+
if (key.name === "escape") {
|
|
150
167
|
setIsFiltering(false);
|
|
151
|
-
setFilterQuery(
|
|
168
|
+
setFilterQuery("");
|
|
152
169
|
return;
|
|
153
170
|
}
|
|
154
|
-
if (key.name ===
|
|
171
|
+
if (key.name === "enter" || key.name === "return") {
|
|
155
172
|
setIsFiltering(false);
|
|
156
173
|
return;
|
|
157
174
|
}
|
|
158
|
-
return;
|
|
175
|
+
return;
|
|
159
176
|
}
|
|
160
177
|
|
|
161
|
-
if (key.name ===
|
|
178
|
+
if (key.name === "down" || key.name === "j") {
|
|
162
179
|
cycleFocus(1);
|
|
163
|
-
} else if (key.name ===
|
|
180
|
+
} else if (key.name === "up" || key.name === "k") {
|
|
164
181
|
cycleFocus(-1);
|
|
165
|
-
} else if (key.name ===
|
|
182
|
+
} else if (key.name === "tab" && !key.shift) {
|
|
166
183
|
cycleFocus(1);
|
|
167
|
-
} else if (key.name ===
|
|
184
|
+
} else if (key.name === "tab" && key.shift) {
|
|
168
185
|
cycleFocus(-1);
|
|
169
|
-
} else if (key.name ===
|
|
186
|
+
} else if (key.name === "escape") {
|
|
170
187
|
if (expandedIndexRef.current !== null) {
|
|
171
188
|
setExpandedIndex(null);
|
|
172
189
|
} else if (focusedIndex !== null) {
|
|
173
190
|
setFocusedIndex(null);
|
|
174
191
|
} else if (filterQuery) {
|
|
175
|
-
setFilterQuery(
|
|
192
|
+
setFilterQuery("");
|
|
176
193
|
}
|
|
177
|
-
} else if (key.name ===
|
|
194
|
+
} else if (key.name === "enter" || key.name === "return") {
|
|
178
195
|
toggleExpanded();
|
|
179
|
-
} else if (key.name ===
|
|
196
|
+
} else if (key.name === "/" || key.name === "f") {
|
|
180
197
|
setIsFiltering(true);
|
|
181
|
-
} else if (key.name ===
|
|
182
|
-
setSortMode(current => {
|
|
183
|
-
if (current ===
|
|
184
|
-
if (current ===
|
|
185
|
-
return
|
|
198
|
+
} else if (key.name === "s") {
|
|
199
|
+
setSortMode((current) => {
|
|
200
|
+
if (current === "status") return "usage";
|
|
201
|
+
if (current === "usage") return "name";
|
|
202
|
+
return "status";
|
|
186
203
|
});
|
|
187
|
-
} else if (key.name ===
|
|
188
|
-
setViewMode(current => current ===
|
|
189
|
-
} else if (key.name ===
|
|
190
|
-
setShowUnconfigured(current => !current);
|
|
191
|
-
} else if (key.name ===
|
|
192
|
-
setAtRiskFocus(current => !current);
|
|
193
|
-
} else if (key.sequence ===
|
|
204
|
+
} else if (key.name === "v") {
|
|
205
|
+
setViewMode((current) => (current === "cards" ? "list" : "cards"));
|
|
206
|
+
} else if (key.name === "u") {
|
|
207
|
+
setShowUnconfigured((current) => !current);
|
|
208
|
+
} else if (key.name === "x") {
|
|
209
|
+
setAtRiskFocus((current) => !current);
|
|
210
|
+
} else if (key.sequence === "R") {
|
|
194
211
|
refreshAllProviders();
|
|
195
|
-
} else if (key.sequence ===
|
|
212
|
+
} else if (key.sequence === "r" && focusedIndex !== null) {
|
|
196
213
|
refreshSelected();
|
|
197
214
|
}
|
|
198
215
|
});
|
|
@@ -210,9 +227,18 @@ export function Dashboard() {
|
|
|
210
227
|
|
|
211
228
|
return (
|
|
212
229
|
<box flexDirection="column" flexGrow={1} padding={1} gap={0}>
|
|
213
|
-
<ProviderAggregateStrip
|
|
230
|
+
<ProviderAggregateStrip
|
|
231
|
+
providers={atRiskFocus ? configured : providerList.filter((p) => p.configured)}
|
|
232
|
+
/>
|
|
214
233
|
|
|
215
|
-
<box
|
|
234
|
+
<box
|
|
235
|
+
flexDirection="row"
|
|
236
|
+
gap={2}
|
|
237
|
+
alignItems="center"
|
|
238
|
+
height={1}
|
|
239
|
+
justifyContent="space-between"
|
|
240
|
+
paddingX={1}
|
|
241
|
+
>
|
|
216
242
|
<box flexDirection="row" gap={2} alignItems="center">
|
|
217
243
|
{isFiltering ? (
|
|
218
244
|
<box flexDirection="row" gap={1} alignItems="center">
|
|
@@ -231,12 +257,12 @@ export function Dashboard() {
|
|
|
231
257
|
</box>
|
|
232
258
|
) : (
|
|
233
259
|
<text fg={colors.textSubtle}>
|
|
234
|
-
{filterQuery ? `Filter: "${filterQuery}"` :
|
|
260
|
+
{filterQuery ? `Filter: "${filterQuery}"` : "/ filter"}
|
|
235
261
|
</text>
|
|
236
262
|
)}
|
|
237
|
-
|
|
263
|
+
|
|
238
264
|
<text fg={colors.textSubtle}>|</text>
|
|
239
|
-
|
|
265
|
+
|
|
240
266
|
<text>
|
|
241
267
|
<span fg={colors.textSubtle}>Sort: </span>
|
|
242
268
|
<span fg={colors.primary}>{sortMode.toUpperCase()}</span>
|
|
@@ -246,7 +272,7 @@ export function Dashboard() {
|
|
|
246
272
|
|
|
247
273
|
<text>
|
|
248
274
|
<span fg={colors.textSubtle}>View: </span>
|
|
249
|
-
<span fg={colors.primary}>{viewMode ===
|
|
275
|
+
<span fg={colors.primary}>{viewMode === "cards" ? "Cards" : "Console"}</span>
|
|
250
276
|
</text>
|
|
251
277
|
|
|
252
278
|
{atRiskFocus && (
|
|
@@ -258,11 +284,12 @@ export function Dashboard() {
|
|
|
258
284
|
</box>
|
|
259
285
|
|
|
260
286
|
<text fg={colors.textMuted}>
|
|
261
|
-
{totalConfigured} configured
|
|
287
|
+
{totalConfigured} configured
|
|
288
|
+
{totalUnconfigured > 0 ? `, ${totalUnconfigured} unconfigured` : ""}
|
|
262
289
|
</text>
|
|
263
290
|
</box>
|
|
264
291
|
|
|
265
|
-
{viewMode ===
|
|
292
|
+
{viewMode === "list" ? (
|
|
266
293
|
<box flexDirection="column" flexGrow={1}>
|
|
267
294
|
<ProvidersList
|
|
268
295
|
providers={configured}
|
|
@@ -285,15 +312,12 @@ export function Dashboard() {
|
|
|
285
312
|
},
|
|
286
313
|
}}
|
|
287
314
|
>
|
|
288
|
-
<box
|
|
289
|
-
ref={containerRef}
|
|
290
|
-
flexDirection="row"
|
|
291
|
-
flexWrap="wrap"
|
|
292
|
-
gap={1}
|
|
293
|
-
>
|
|
315
|
+
<box ref={containerRef} flexDirection="row" flexWrap="wrap" gap={1}>
|
|
294
316
|
{configured.map((state, index) => (
|
|
295
317
|
<ProviderCard
|
|
296
|
-
ref={(el) => {
|
|
318
|
+
ref={(el) => {
|
|
319
|
+
cardRefs.current[index] = el;
|
|
320
|
+
}}
|
|
297
321
|
key={state.plugin.id}
|
|
298
322
|
name={state.plugin.name}
|
|
299
323
|
configured={state.configured}
|
|
@@ -304,22 +328,23 @@ export function Dashboard() {
|
|
|
304
328
|
onFocus={() => setFocusedIndex(index)}
|
|
305
329
|
/>
|
|
306
330
|
))}
|
|
307
|
-
|
|
308
|
-
{showUnconfigured &&
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
331
|
+
|
|
332
|
+
{showUnconfigured &&
|
|
333
|
+
unconfigured.map((state, index) => (
|
|
334
|
+
<GhostProviderCard
|
|
335
|
+
key={state.plugin.id}
|
|
336
|
+
name={state.plugin.name}
|
|
337
|
+
focused={focusedIndex === configured.length + index && !isFiltering}
|
|
338
|
+
onFocus={() => setFocusedIndex(configured.length + index)}
|
|
339
|
+
/>
|
|
340
|
+
))}
|
|
316
341
|
</box>
|
|
317
342
|
</scrollbox>
|
|
318
343
|
</box>
|
|
319
344
|
) : (
|
|
320
345
|
<box flexGrow={1} justifyContent="center" alignItems="center">
|
|
321
346
|
<text fg={colors.textMuted}>
|
|
322
|
-
{filterQuery ?
|
|
347
|
+
{filterQuery ? "No matching providers found" : "No configured providers"}
|
|
323
348
|
</text>
|
|
324
349
|
</box>
|
|
325
350
|
)}
|
|
@@ -329,10 +354,10 @@ export function Dashboard() {
|
|
|
329
354
|
<box flexDirection="row" paddingLeft={1} height={1}>
|
|
330
355
|
<text fg={colors.textSubtle} height={1}>
|
|
331
356
|
{isFiltering
|
|
332
|
-
?
|
|
357
|
+
? "Type to filter Esc cancel Enter apply"
|
|
333
358
|
: termWidth < 90
|
|
334
|
-
?
|
|
335
|
-
:
|
|
359
|
+
? "↑↓ nav Enter detail / filter s sort v view u unconf x risk"
|
|
360
|
+
: "↑↓ navigate Enter detail / filter s sort v view u unconfigured x at-risk R refresh"}
|
|
336
361
|
</text>
|
|
337
362
|
</box>
|
|
338
363
|
</box>
|