Add OIDC logout, fix OpenAPI schema types, expose end_session_endpoint
Backend: - Expose end_session_endpoint from OIDC provider metadata in /auth/oidc/config - Add getEndSessionEndpoint() to OidcTokenExchanger Frontend: - On OIDC logout, redirect to provider's end_session_endpoint to clear SSO session - Strip /api/v1 prefix from OpenAPI paths to match client baseUrl convention - Add schema-types.ts with convenience type re-exports from generated schema - Fix all type imports to use schema-types instead of raw generated schema - Fix optional field access (processors, children, duration) with proper typing - Fix AgentInstance.state → status field name Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -60,11 +61,15 @@ public class OidcAuthController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
OidcConfig oidc = config.get();
|
OidcConfig oidc = config.get();
|
||||||
return ResponseEntity.ok(Map.of(
|
Map<String, Object> response = new LinkedHashMap<>();
|
||||||
"issuer", oidc.issuerUri(),
|
response.put("issuer", oidc.issuerUri());
|
||||||
"clientId", oidc.clientId(),
|
response.put("clientId", oidc.clientId());
|
||||||
"authorizationEndpoint", tokenExchanger.getAuthorizationEndpoint()
|
response.put("authorizationEndpoint", tokenExchanger.getAuthorizationEndpoint());
|
||||||
));
|
String endSessionEndpoint = tokenExchanger.getEndSessionEndpoint();
|
||||||
|
if (endSessionEndpoint != null) {
|
||||||
|
response.put("endSessionEndpoint", endSessionEndpoint);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to retrieve OIDC provider metadata: {}", e.getMessage());
|
log.error("Failed to retrieve OIDC provider metadata: {}", e.getMessage());
|
||||||
return ResponseEntity.internalServerError()
|
return ResponseEntity.internalServerError()
|
||||||
|
|||||||
@@ -114,6 +114,15 @@ public class OidcTokenExchanger {
|
|||||||
return getProviderMetadata(config.issuerUri()).getAuthorizationEndpointURI().toString();
|
return getProviderMetadata(config.issuerUri()).getAuthorizationEndpointURI().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the provider's end-session (logout) endpoint, or {@code null} if not advertised.
|
||||||
|
*/
|
||||||
|
public String getEndSessionEndpoint() throws Exception {
|
||||||
|
OidcConfig config = getConfig();
|
||||||
|
URI uri = getProviderMetadata(config.issuerUri()).getEndSessionEndpointURI();
|
||||||
|
return uri != null ? uri.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates cached provider metadata and JWKS processor.
|
* Invalidates cached provider metadata and JWKS processor.
|
||||||
* Call after OIDC configuration is updated in the database.
|
* Call after OIDC configuration is updated in the database.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,12 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { api } from '../client';
|
import { api } from '../client';
|
||||||
import type { SearchRequest } from '../schema';
|
import type {
|
||||||
|
SearchRequest,
|
||||||
|
ExecutionStats,
|
||||||
|
ExecutionSummary,
|
||||||
|
StatsTimeseries,
|
||||||
|
ExecutionDetail,
|
||||||
|
} from '../schema-types';
|
||||||
|
|
||||||
export function useExecutionStats(timeFrom: string | undefined, timeTo: string | undefined) {
|
export function useExecutionStats(timeFrom: string | undefined, timeTo: string | undefined) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -15,7 +21,7 @@ export function useExecutionStats(timeFrom: string | undefined, timeTo: string |
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (error) throw new Error('Failed to load stats');
|
if (error) throw new Error('Failed to load stats');
|
||||||
return data!;
|
return data as unknown as ExecutionStats;
|
||||||
},
|
},
|
||||||
enabled: !!timeFrom,
|
enabled: !!timeFrom,
|
||||||
placeholderData: (prev) => prev,
|
placeholderData: (prev) => prev,
|
||||||
@@ -31,7 +37,7 @@ export function useSearchExecutions(filters: SearchRequest, live = false) {
|
|||||||
body: filters,
|
body: filters,
|
||||||
});
|
});
|
||||||
if (error) throw new Error('Search failed');
|
if (error) throw new Error('Search failed');
|
||||||
return data!;
|
return data as unknown as { data: ExecutionSummary[]; total: number; offset: number; limit: number };
|
||||||
},
|
},
|
||||||
placeholderData: (prev) => prev,
|
placeholderData: (prev) => prev,
|
||||||
refetchInterval: live ? 5_000 : false,
|
refetchInterval: live ? 5_000 : false,
|
||||||
@@ -52,7 +58,7 @@ export function useStatsTimeseries(timeFrom: string | undefined, timeTo: string
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (error) throw new Error('Failed to load timeseries');
|
if (error) throw new Error('Failed to load timeseries');
|
||||||
return data!;
|
return data as unknown as StatsTimeseries;
|
||||||
},
|
},
|
||||||
enabled: !!timeFrom,
|
enabled: !!timeFrom,
|
||||||
placeholderData: (prev) => prev,
|
placeholderData: (prev) => prev,
|
||||||
@@ -68,7 +74,7 @@ export function useExecutionDetail(executionId: string | null) {
|
|||||||
params: { path: { executionId: executionId! } },
|
params: { path: { executionId: executionId! } },
|
||||||
});
|
});
|
||||||
if (error) throw new Error('Failed to load execution detail');
|
if (error) throw new Error('Failed to load execution detail');
|
||||||
return data!;
|
return data as unknown as ExecutionDetail;
|
||||||
},
|
},
|
||||||
enabled: !!executionId,
|
enabled: !!executionId,
|
||||||
});
|
});
|
||||||
@@ -90,7 +96,7 @@ export function useProcessorSnapshot(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (error) throw new Error('Failed to load snapshot');
|
if (error) throw new Error('Failed to load snapshot');
|
||||||
return data!;
|
return data as unknown as Record<string, string>;
|
||||||
},
|
},
|
||||||
enabled: !!executionId && index !== null,
|
enabled: !!executionId && index !== null,
|
||||||
});
|
});
|
||||||
|
|||||||
31
ui/src/api/schema-types.ts
Normal file
31
ui/src/api/schema-types.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import type { components } from './schema';
|
||||||
|
|
||||||
|
type Require<T> = {
|
||||||
|
[K in keyof T]-?: T[K] extends (infer U)[]
|
||||||
|
? Require<U>[]
|
||||||
|
: T[K] extends object | undefined
|
||||||
|
? Require<NonNullable<T[K]>>
|
||||||
|
: NonNullable<T[K]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExecutionSummary = Require<components['schemas']['ExecutionSummary']>;
|
||||||
|
export type SearchRequest = components['schemas']['SearchRequest'];
|
||||||
|
export type ExecutionDetail = Require<components['schemas']['ExecutionDetail']>;
|
||||||
|
export type ExecutionStats = Require<components['schemas']['ExecutionStats']>;
|
||||||
|
export type StatsTimeseries = Require<components['schemas']['StatsTimeseries']>;
|
||||||
|
export type TimeseriesBucket = Require<components['schemas']['TimeseriesBucket']>;
|
||||||
|
export type UserInfo = Require<components['schemas']['UserInfo']>;
|
||||||
|
|
||||||
|
export type ProcessorNode = Require<components['schemas']['ProcessorNode']> & {
|
||||||
|
children?: ProcessorNode[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface AgentInstance {
|
||||||
|
id: string;
|
||||||
|
applicationName: string;
|
||||||
|
group: string;
|
||||||
|
status: string;
|
||||||
|
routeIds: string[];
|
||||||
|
registeredAt: string;
|
||||||
|
lastHeartbeat: string;
|
||||||
|
}
|
||||||
54
ui/src/api/schema.d.ts
vendored
54
ui/src/api/schema.d.ts
vendored
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export interface paths {
|
export interface paths {
|
||||||
"/api/v1/admin/users/{userId}/roles": {
|
"/admin/users/{userId}/roles": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -21,7 +21,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/admin/oidc": {
|
"/admin/oidc": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -40,7 +40,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/search/executions": {
|
"/search/executions": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -58,7 +58,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/data/metrics": {
|
"/data/metrics": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -78,7 +78,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/data/executions": {
|
"/data/executions": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -98,7 +98,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/data/diagrams": {
|
"/data/diagrams": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -118,7 +118,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/auth/refresh": {
|
"/auth/refresh": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -134,7 +134,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/auth/oidc/callback": {
|
"/auth/oidc/callback": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -150,7 +150,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/auth/login": {
|
"/auth/login": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -166,7 +166,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/agents/{id}/refresh": {
|
"/agents/{id}/refresh": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -186,7 +186,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/agents/{id}/heartbeat": {
|
"/agents/{id}/heartbeat": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -206,7 +206,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/agents/{id}/commands": {
|
"/agents/{id}/commands": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -226,7 +226,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/agents/{id}/commands/{commandId}/ack": {
|
"/agents/{id}/commands/{commandId}/ack": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -246,7 +246,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/agents/register": {
|
"/agents/register": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -266,7 +266,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/agents/groups/{group}/commands": {
|
"/agents/groups/{group}/commands": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -286,7 +286,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/agents/commands": {
|
"/agents/commands": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -306,7 +306,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/admin/oidc/test": {
|
"/admin/oidc/test": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -323,7 +323,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/search/stats": {
|
"/search/stats": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -340,7 +340,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/search/stats/timeseries": {
|
"/search/stats/timeseries": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -357,7 +357,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/executions/{executionId}": {
|
"/executions/{executionId}": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -374,7 +374,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/executions/{executionId}/processors/{index}/snapshot": {
|
"/executions/{executionId}/processors/{index}/snapshot": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -391,7 +391,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/diagrams/{contentHash}/render": {
|
"/diagrams/{contentHash}/render": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -411,7 +411,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/auth/oidc/config": {
|
"/auth/oidc/config": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -427,7 +427,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/agents": {
|
"/agents": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -447,7 +447,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/agents/{id}/events": {
|
"/agents/{id}/events": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -467,7 +467,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/admin/users": {
|
"/admin/users": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -484,7 +484,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/v1/admin/users/{userId}": {
|
"/admin/users/{userId}": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ export function LoginPage() {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data?.authorizationEndpoint && data?.clientId) {
|
if (data?.authorizationEndpoint && data?.clientId) {
|
||||||
setOidc({ clientId: data.clientId, authorizationEndpoint: data.authorizationEndpoint });
|
setOidc({ clientId: data.clientId, authorizationEndpoint: data.authorizationEndpoint });
|
||||||
|
if (data.endSessionEndpoint) {
|
||||||
|
localStorage.setItem('cameleer-oidc-end-session', data.endSessionEndpoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
|||||||
throw new Error(body.message || 'Invalid credentials');
|
throw new Error(body.message || 'Invalid credentials');
|
||||||
}
|
}
|
||||||
const { accessToken, refreshToken } = await res.json();
|
const { accessToken, refreshToken } = await res.json();
|
||||||
|
localStorage.removeItem('cameleer-oidc-end-session');
|
||||||
persistTokens(accessToken, refreshToken, username);
|
persistTokens(accessToken, refreshToken, username);
|
||||||
set({
|
set({
|
||||||
accessToken,
|
accessToken,
|
||||||
@@ -143,7 +144,9 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
logout: () => {
|
logout: () => {
|
||||||
|
const endSessionEndpoint = localStorage.getItem('cameleer-oidc-end-session');
|
||||||
clearTokens();
|
clearTokens();
|
||||||
|
localStorage.removeItem('cameleer-oidc-end-session');
|
||||||
set({
|
set({
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
refreshToken: null,
|
refreshToken: null,
|
||||||
@@ -152,5 +155,12 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
|||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
|
if (endSessionEndpoint) {
|
||||||
|
const postLogoutRedirect = `${window.location.origin}/login`;
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
post_logout_redirect_uri: postLogoutRedirect,
|
||||||
|
});
|
||||||
|
window.location.href = `${endSessionEndpoint}?${params}`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ExecutionSummary, AgentInstance } from '../../api/schema';
|
import type { ExecutionSummary, AgentInstance } from '../../api/schema-types';
|
||||||
import type { PaletteResult, RouteInfo } from './use-palette-search';
|
import type { PaletteResult, RouteInfo } from './use-palette-search';
|
||||||
import { highlightMatch, formatRelativeTime } from './utils';
|
import { highlightMatch, formatRelativeTime } from './utils';
|
||||||
import { AppBadge } from '../shared/AppBadge';
|
import { AppBadge } from '../shared/AppBadge';
|
||||||
@@ -93,7 +93,7 @@ function ApplicationResult({ data, query }: { data: AgentInstance; query: string
|
|||||||
<div className={styles.resultBody}>
|
<div className={styles.resultBody}>
|
||||||
<div className={styles.resultTitle}>
|
<div className={styles.resultTitle}>
|
||||||
<HighlightedText text={data.id} query={query} />
|
<HighlightedText text={data.id} query={query} />
|
||||||
<span className={stateBadgeClass(data.state)}>{data.state}</span>
|
<span className={stateBadgeClass(data.status)}>{data.status}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.resultMeta}>
|
<div className={styles.resultMeta}>
|
||||||
<span>group: {data.group}</span>
|
<span>group: {data.group}</span>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { api } from '../../api/client';
|
import { api } from '../../api/client';
|
||||||
import type { ExecutionSummary, AgentInstance } from '../../api/schema';
|
import type { ExecutionSummary, AgentInstance } from '../../api/schema-types';
|
||||||
import { useCommandPalette, type PaletteScope } from './use-command-palette';
|
import { useCommandPalette, type PaletteScope } from './use-command-palette';
|
||||||
import { useDebouncedValue } from './utils';
|
import { useDebouncedValue } from './utils';
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ export function usePaletteSearch() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (error) throw new Error('Search failed');
|
if (error) throw new Error('Search failed');
|
||||||
return data!;
|
return data as unknown as { data: ExecutionSummary[]; total: number };
|
||||||
},
|
},
|
||||||
enabled: isOpen && isExecutionScope(scope),
|
enabled: isOpen && isExecutionScope(scope),
|
||||||
placeholderData: (prev) => prev,
|
placeholderData: (prev) => prev,
|
||||||
@@ -64,7 +64,7 @@ export function usePaletteSearch() {
|
|||||||
params: { query: {} },
|
params: { query: {} },
|
||||||
});
|
});
|
||||||
if (error) throw new Error('Failed to load agents');
|
if (error) throw new Error('Failed to load agents');
|
||||||
return data!;
|
return data as unknown as AgentInstance[];
|
||||||
},
|
},
|
||||||
enabled: isOpen && (isApplicationScope(scope) || isRouteScope(scope)),
|
enabled: isOpen && (isApplicationScope(scope) || isRouteScope(scope)),
|
||||||
staleTime: 30_000,
|
staleTime: 30_000,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useProcessorSnapshot } from '../../api/queries/executions';
|
import { useProcessorSnapshot } from '../../api/queries/executions';
|
||||||
import type { ExecutionSummary } from '../../api/schema';
|
import type { ExecutionSummary } from '../../api/schema-types';
|
||||||
import styles from './ExchangeDetail.module.css';
|
import styles from './ExchangeDetail.module.css';
|
||||||
|
|
||||||
interface ExchangeDetailProps {
|
interface ExchangeDetailProps {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useExecutionDetail } from '../../api/queries/executions';
|
import { useExecutionDetail } from '../../api/queries/executions';
|
||||||
import type { ProcessorNode as ProcessorNodeType } from '../../api/schema';
|
import type { ProcessorNode as ProcessorNodeType } from '../../api/schema-types';
|
||||||
import styles from './ProcessorTree.module.css';
|
import styles from './ProcessorTree.module.css';
|
||||||
|
|
||||||
const ICON_MAP: Record<string, { label: string; className: string }> = {
|
const ICON_MAP: Record<string, { label: string; className: string }> = {
|
||||||
@@ -35,7 +35,7 @@ export function ProcessorTree({ executionId }: { executionId: string }) {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.tree}>
|
<div className={styles.tree}>
|
||||||
<h4 className={styles.title}>Processor Execution Tree</h4>
|
<h4 className={styles.title}>Processor Execution Tree</h4>
|
||||||
{data.processors.map((proc, i) => (
|
{(data.processors as ProcessorNodeType[])?.map((proc, i) => (
|
||||||
<ProcessorNodeView key={proc.processorId ?? i} node={proc} />
|
<ProcessorNodeView key={proc.processorId ?? i} node={proc} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -57,7 +57,7 @@ function ProcessorNodeView({ node }: { node: ProcessorNodeType }) {
|
|||||||
<span className={styles.procDuration}>{node.durationMs}ms</span>
|
<span className={styles.procDuration}>{node.durationMs}ms</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{node.children.length > 0 && (
|
{node.children && node.children.length > 0 && (
|
||||||
<div className={styles.nested}>
|
<div className={styles.nested}>
|
||||||
{node.children.map((child, i) => (
|
{node.children.map((child, i) => (
|
||||||
<ProcessorNodeView key={child.processorId ?? i} node={child} />
|
<ProcessorNodeView key={child.processorId ?? i} node={child} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import type { ExecutionSummary } from '../../api/schema';
|
import type { ExecutionSummary } from '../../api/schema-types';
|
||||||
import { StatusPill } from '../../components/shared/StatusPill';
|
import { StatusPill } from '../../components/shared/StatusPill';
|
||||||
import { DurationBar } from '../../components/shared/DurationBar';
|
import { DurationBar } from '../../components/shared/DurationBar';
|
||||||
import { AppBadge } from '../../components/shared/AppBadge';
|
import { AppBadge } from '../../components/shared/AppBadge';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ScopeTabs } from '../../components/command-palette/ScopeTabs';
|
|||||||
import { ResultsList } from '../../components/command-palette/ResultsList';
|
import { ResultsList } from '../../components/command-palette/ResultsList';
|
||||||
import { PaletteFooter } from '../../components/command-palette/PaletteFooter';
|
import { PaletteFooter } from '../../components/command-palette/PaletteFooter';
|
||||||
import { FilterChip } from '../../components/shared/FilterChip';
|
import { FilterChip } from '../../components/shared/FilterChip';
|
||||||
import type { ExecutionSummary, AgentInstance } from '../../api/schema';
|
import type { ExecutionSummary, AgentInstance } from '../../api/schema-types';
|
||||||
import styles from './SearchFilters.module.css';
|
import styles from './SearchFilters.module.css';
|
||||||
|
|
||||||
export function SearchFilters() {
|
export function SearchFilters() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import type { SearchRequest } from '../../api/schema';
|
import type { SearchRequest } from '../../api/schema-types';
|
||||||
|
|
||||||
function todayMidnight(): string {
|
function todayMidnight(): string {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
@@ -94,8 +94,8 @@ export const useExecutionSearch = create<ExecutionSearchState>((set, get) => ({
|
|||||||
status: statusStr ?? undefined,
|
status: statusStr ?? undefined,
|
||||||
timeFrom: s.timeFrom ? new Date(s.timeFrom).toISOString() : undefined,
|
timeFrom: s.timeFrom ? new Date(s.timeFrom).toISOString() : undefined,
|
||||||
timeTo: s.timeTo ? new Date(s.timeTo).toISOString() : undefined,
|
timeTo: s.timeTo ? new Date(s.timeTo).toISOString() : undefined,
|
||||||
durationMin: s.durationMin,
|
durationMin: s.durationMin ?? undefined,
|
||||||
durationMax: s.durationMax,
|
durationMax: s.durationMax ?? undefined,
|
||||||
text: s.text || undefined,
|
text: s.text || undefined,
|
||||||
routeId: s.routeId || undefined,
|
routeId: s.routeId || undefined,
|
||||||
agentId: s.agentId || undefined,
|
agentId: s.agentId || undefined,
|
||||||
|
|||||||
Reference in New Issue
Block a user