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 ./
|
COPY package.json package-lock.json .npmrc ./
|
||||||
RUN echo "//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}" >> .npmrc && \
|
RUN echo "//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}" >> .npmrc && \
|
||||||
npm ci && \
|
npm ci && \
|
||||||
|
npm install @cameleer/design-system@dev && \
|
||||||
rm -f .npmrc
|
rm -f .npmrc
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
8
ui/package-lock.json
generated
8
ui/package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "ui",
|
"name": "ui",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cameleer/design-system": "^0.1.8",
|
"@cameleer/design-system": "^0.0.0-snapshot.20260325.499c86b",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
@@ -276,9 +276,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@cameleer/design-system": {
|
"node_modules/@cameleer/design-system": {
|
||||||
"version": "0.1.8",
|
"version": "0.0.0-snapshot.20260325.499c86b",
|
||||||
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.8/design-system-0.1.8.tgz",
|
"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-mc7IQOYYez0UItvwiNbbYFrJehG3JtdVlOUsdLXcN8zmgtpImleVro4MsPxCX4/OOGI4EGoX1oIVpFi91qEI6A==",
|
"integrity": "sha512-uiBdWYTT0wzIgL8QX21oHyb7xjeepnXvGAl/YHapd1o4u+GuXuB23kcyECurM/OTUN4dM7RGjGBp46Mbe8xcIQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^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"
|
"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": {
|
"dependencies": {
|
||||||
"@cameleer/design-system": "^0.1.8",
|
"@cameleer/design-system": "^0.0.0-snapshot.20260325.499c86b",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { AppShell, Sidebar, TopBar, CommandPalette, CommandPaletteProvider, Glob
|
|||||||
import type { SidebarApp, SearchResult } from '@cameleer/design-system';
|
import type { SidebarApp, SearchResult } from '@cameleer/design-system';
|
||||||
import { useRouteCatalog } from '../api/queries/catalog';
|
import { useRouteCatalog } from '../api/queries/catalog';
|
||||||
import { useAgents } from '../api/queries/agents';
|
import { useAgents } from '../api/queries/agents';
|
||||||
|
import { useSearchExecutions } from '../api/queries/executions';
|
||||||
import { useAuthStore } from '../auth/auth-store';
|
import { useAuthStore } from '../auth/auth-store';
|
||||||
import { useMemo, useCallback } from 'react';
|
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
function healthToColor(health: string): string {
|
function healthToColor(health: string): string {
|
||||||
switch (health) {
|
switch (health) {
|
||||||
@@ -61,6 +62,30 @@ function buildSearchData(
|
|||||||
return results;
|
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() {
|
function LayoutContent() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -69,6 +94,14 @@ function LayoutContent() {
|
|||||||
const { username, logout } = useAuthStore();
|
const { username, logout } = useAuthStore();
|
||||||
const { open: paletteOpen, setOpen: setPaletteOpen } = useCommandPalette();
|
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(() => {
|
const sidebarApps: SidebarApp[] = useMemo(() => {
|
||||||
if (!catalog) return [];
|
if (!catalog) return [];
|
||||||
return catalog.map((app: any) => ({
|
return catalog.map((app: any) => ({
|
||||||
@@ -90,11 +123,24 @@ function LayoutContent() {
|
|||||||
}));
|
}));
|
||||||
}, [catalog]);
|
}, [catalog]);
|
||||||
|
|
||||||
const searchData = useMemo(
|
const catalogData = useMemo(
|
||||||
() => buildSearchData(catalog, agents as any[]),
|
() => buildSearchData(catalog, agents as any[]),
|
||||||
[catalog, agents],
|
[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 breadcrumb = useMemo(() => {
|
||||||
const parts = location.pathname.split('/').filter(Boolean);
|
const parts = location.pathname.split('/').filter(Boolean);
|
||||||
return parts.map((part, i) => ({
|
return parts.map((part, i) => ({
|
||||||
@@ -131,7 +177,9 @@ function LayoutContent() {
|
|||||||
<CommandPalette
|
<CommandPalette
|
||||||
open={paletteOpen}
|
open={paletteOpen}
|
||||||
onClose={() => setPaletteOpen(false)}
|
onClose={() => setPaletteOpen(false)}
|
||||||
|
onOpen={() => setPaletteOpen(true)}
|
||||||
onSelect={handlePaletteSelect}
|
onSelect={handlePaletteSelect}
|
||||||
|
onQueryChange={setPaletteQuery}
|
||||||
data={searchData}
|
data={searchData}
|
||||||
/>
|
/>
|
||||||
<main style={{ flex: 1, overflow: 'auto', padding: '1.5rem' }}>
|
<main style={{ flex: 1, overflow: 'auto', padding: '1.5rem' }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user