feat: fix Cmd-K shortcut and add exchange full-text search to command palette
- Add missing onOpen prop to CommandPalette (fixes Ctrl+K/Cmd+K) - Wire server-side exchange search with debounced text query - Use design system dev snapshot from Gitea registry in CI builds Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ ARG REGISTRY_TOKEN
|
||||
COPY package.json package-lock.json .npmrc ./
|
||||
RUN echo "//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}" >> .npmrc && \
|
||||
npm ci && \
|
||||
npm install @cameleer/design-system@dev && \
|
||||
rm -f .npmrc
|
||||
|
||||
COPY . .
|
||||
|
||||
8
ui/package-lock.json
generated
8
ui/package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "ui",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@cameleer/design-system": "^0.1.8",
|
||||
"@cameleer/design-system": "^0.0.0-snapshot.20260325.499c86b",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"openapi-fetch": "^0.17.0",
|
||||
"react": "^19.2.4",
|
||||
@@ -276,9 +276,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cameleer/design-system": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.8/design-system-0.1.8.tgz",
|
||||
"integrity": "sha512-mc7IQOYYez0UItvwiNbbYFrJehG3JtdVlOUsdLXcN8zmgtpImleVro4MsPxCX4/OOGI4EGoX1oIVpFi91qEI6A==",
|
||||
"version": "0.0.0-snapshot.20260325.499c86b",
|
||||
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.0.0-snapshot.20260325.499c86b/design-system-0.0.0-snapshot.20260325.499c86b.tgz",
|
||||
"integrity": "sha512-uiBdWYTT0wzIgL8QX21oHyb7xjeepnXvGAl/YHapd1o4u+GuXuB23kcyECurM/OTUN4dM7RGjGBp46Mbe8xcIQ==",
|
||||
"dependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cameleer/design-system": "^0.1.8",
|
||||
"@cameleer/design-system": "^0.0.0-snapshot.20260325.499c86b",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"openapi-fetch": "^0.17.0",
|
||||
"react": "^19.2.4",
|
||||
|
||||
@@ -3,8 +3,9 @@ import { AppShell, Sidebar, TopBar, CommandPalette, CommandPaletteProvider, Glob
|
||||
import type { SidebarApp, SearchResult } from '@cameleer/design-system';
|
||||
import { useRouteCatalog } from '../api/queries/catalog';
|
||||
import { useAgents } from '../api/queries/agents';
|
||||
import { useSearchExecutions } from '../api/queries/executions';
|
||||
import { useAuthStore } from '../auth/auth-store';
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
|
||||
function healthToColor(health: string): string {
|
||||
switch (health) {
|
||||
@@ -61,6 +62,30 @@ function buildSearchData(
|
||||
return results;
|
||||
}
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s`;
|
||||
if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s`;
|
||||
return `${ms}ms`;
|
||||
}
|
||||
|
||||
function statusToColor(status: string): string {
|
||||
switch (status) {
|
||||
case 'COMPLETED': return 'success';
|
||||
case 'FAILED': return 'error';
|
||||
case 'RUNNING': return 'running';
|
||||
default: return 'warning';
|
||||
}
|
||||
}
|
||||
|
||||
function useDebouncedValue<T>(value: T, delayMs: number): T {
|
||||
const [debounced, setDebounced] = useState(value);
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebounced(value), delayMs);
|
||||
return () => clearTimeout(timer);
|
||||
}, [value, delayMs]);
|
||||
return debounced;
|
||||
}
|
||||
|
||||
function LayoutContent() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -69,6 +94,14 @@ function LayoutContent() {
|
||||
const { username, logout } = useAuthStore();
|
||||
const { open: paletteOpen, setOpen: setPaletteOpen } = useCommandPalette();
|
||||
|
||||
// Exchange full-text search via command palette
|
||||
const [paletteQuery, setPaletteQuery] = useState('');
|
||||
const debouncedQuery = useDebouncedValue(paletteQuery, 300);
|
||||
const { data: exchangeResults } = useSearchExecutions(
|
||||
{ text: debouncedQuery || undefined, offset: 0, limit: 10 },
|
||||
false,
|
||||
);
|
||||
|
||||
const sidebarApps: SidebarApp[] = useMemo(() => {
|
||||
if (!catalog) return [];
|
||||
return catalog.map((app: any) => ({
|
||||
@@ -90,11 +123,24 @@ function LayoutContent() {
|
||||
}));
|
||||
}, [catalog]);
|
||||
|
||||
const searchData = useMemo(
|
||||
const catalogData = useMemo(
|
||||
() => buildSearchData(catalog, agents as any[]),
|
||||
[catalog, agents],
|
||||
);
|
||||
|
||||
const searchData: SearchResult[] = useMemo(() => {
|
||||
const exchangeItems: SearchResult[] = (exchangeResults?.data || []).map((e: any) => ({
|
||||
id: e.executionId,
|
||||
category: 'exchange' as const,
|
||||
title: e.executionId,
|
||||
badges: [{ label: e.status, color: statusToColor(e.status) }],
|
||||
meta: `${e.routeId} · ${e.applicationName ?? ''} · ${formatDuration(e.durationMs)}`,
|
||||
path: `/exchanges/${e.executionId}`,
|
||||
serverFiltered: true,
|
||||
}));
|
||||
return [...catalogData, ...exchangeItems];
|
||||
}, [catalogData, exchangeResults]);
|
||||
|
||||
const breadcrumb = useMemo(() => {
|
||||
const parts = location.pathname.split('/').filter(Boolean);
|
||||
return parts.map((part, i) => ({
|
||||
@@ -131,7 +177,9 @@ function LayoutContent() {
|
||||
<CommandPalette
|
||||
open={paletteOpen}
|
||||
onClose={() => setPaletteOpen(false)}
|
||||
onOpen={() => setPaletteOpen(true)}
|
||||
onSelect={handlePaletteSelect}
|
||||
onQueryChange={setPaletteQuery}
|
||||
data={searchData}
|
||||
/>
|
||||
<main style={{ flex: 1, overflow: 'auto', padding: '1.5rem' }}>
|
||||
|
||||
Reference in New Issue
Block a user