1- import { useState } from "react" ;
2- import { Box , Text , useInput } from "ink" ;
31import { useQuery } from "@tanstack/react-query" ;
42import { useNavigate , useParams } from "react-router" ;
53import type { ScreenProps } from "../../types" ;
64import { coreOptsFromCtx } from "../../utils" ;
7- import { Spinner } from "../../../components/ui/spinner" ;
8- import { Layout } from "../../../components/Layout" ;
95import { JsonDetail } from "../../../components/JsonDetail" ;
10- import { darkTheme } from "../../../components/ui/_core.js" ;
11- import { KeyValueTable } from "../../../components/KeyValueTable.js" ;
12- import { Divider } from "../../../components/ui/divider/Divider.js" ;
13-
14- const theme = darkTheme ;
6+ import { ResourceDetailScreen } from "../../../components/ResourceDetailScreen" ;
157
168// The actions offered for a harness, in menu order. Each routes into the
179// corresponding flow with the harness preselected.
@@ -52,102 +44,52 @@ const ACTIONS: { name: string; description: string; to: (id: string) => string }
5244// ARN, execution role, status) above an action selector that jumps into the
5345// harness's flows (detail JSON, endpoints, versions, invoke, exec). The harness
5446// ID comes from the `:harnessId` route path value.
55- export function HarnessGetScreen ( { ctx, core } : ScreenProps ) {
47+ function useHarnessDetail ( { ctx, core } : ScreenProps , harnessId : string | undefined ) {
5648 const opts = coreOptsFromCtx ( ctx ) ;
57- const navigate = useNavigate ( ) ;
58- const { harnessId } = useParams ( ) ;
59-
60- const detail = useQuery ( {
49+ return useQuery ( {
6150 queryKey : [ "harness" , opts . region , harnessId ] ,
6251 queryFn : ( ) => core . harness . getHarness ( harnessId ! , opts ) ,
6352 enabled : harnessId !== undefined ,
6453 } ) ;
54+ }
6555
66- const [ index , setIndex ] = useState ( 0 ) ;
67-
68- useInput ( ( input , key ) => {
69- if ( key . escape ) {
70- navigate ( - 1 ) ;
71- return ;
72- }
73- if ( key . upArrow || input === "k" ) {
74- setIndex ( ( i ) => Math . max ( 0 , i - 1 ) ) ;
75- return ;
76- }
77- if ( key . downArrow || input == "j" ) {
78- setIndex ( ( i ) => Math . min ( ACTIONS . length - 1 , i + 1 ) ) ;
79- return ;
80- }
81- if ( key . return && harnessId ) {
82- navigate ( ACTIONS [ index ] ! . to ( harnessId ) ) ;
83- }
84- } ) ;
85-
56+ export function HarnessGetScreen ( props : ScreenProps ) {
57+ const navigate = useNavigate ( ) ;
58+ const { harnessId } = useParams ( ) ;
59+ const detail = useHarnessDetail ( props , harnessId ) ;
8660 const harness = detail . data ?. harness ;
87- const nameWidth = ACTIONS . reduce ( ( m , a ) => Math . max ( m , a . name . length ) , 0 ) + 3 ;
8861
8962 return (
90- < Layout
63+ < ResourceDetailScreen
9164 breadcrumb = { [ "agentcore" , "harness" , "get" , harnessId ?? "" ] }
92- keyHints = { [
93- { key : "↑↓/kj" , label : "navigate" } ,
94- { key : "enter" , label : "select" } ,
95- { key : "esc" , label : "back" } ,
96- { key : "ctl+c" , label : "quit" } ,
97- ] }
98- >
99- { detail . isPending ? (
100- < Spinner label = "Loading harness…" />
101- ) : detail . isError ? (
102- < Text color = "red" > Error: { ( detail . error as Error ) . message } </ Text >
103- ) : (
104- < Box flexDirection = "column" >
105- { /* Summary overlay */ }
106- < Box flexDirection = "column" paddingLeft = { 1 } >
107- < KeyValueTable
108- items = { {
109- id : harness ?. harnessId ?? "" ,
110- status : harness ?. status ?? "" ,
111- version : harness ?. harnessVersion ?. toString ( ) ?? "0" ,
112- arn : harness ?. arn ?? "" ,
113- } }
114- />
115- </ Box >
116-
117- < Divider />
118-
119- { /* Action selector */ }
120- < Box flexDirection = "column" paddingLeft = { 1 } >
121- { ACTIONS . map ( ( action , i ) => {
122- const isHl = i === index ;
123- return (
124- < Box key = { action . name } >
125- < Text color = { theme . colors . focus } > { isHl ? "❯ " : " " } </ Text >
126- < Text bold = { isHl } color = { isHl ? theme . colors . focus : theme . colors . text } >
127- { action . name . padEnd ( nameWidth ) }
128- </ Text >
129- < Text color = { theme . colors . muted } > { action . description } </ Text >
130- </ Box >
131- ) ;
132- } ) }
133- </ Box >
134- </ Box >
135- ) }
136- </ Layout >
65+ isPending = { detail . isPending }
66+ error = { detail . isError ? ( detail . error as Error ) : null }
67+ items = { {
68+ id : harness ?. harnessId ?? "" ,
69+ status : harness ?. status ?? "" ,
70+ version : harness ?. harnessVersion ?. toString ( ) ?? "0" ,
71+ arn : harness ?. arn ?? "" ,
72+ } }
73+ actions = {
74+ harnessId && harness
75+ ? ACTIONS . map ( ( action ) => ( {
76+ name : action . name ,
77+ description : action . description ,
78+ onSelect : ( ) => navigate ( action . to ( harnessId ) ) ,
79+ } ) )
80+ : [ ]
81+ }
82+ loadingLabel = "Loading harness…"
83+ onRetry = { ( ) => void detail . refetch ( ) }
84+ />
13785 ) ;
13886}
13987
14088// HarnessGetJsonScreen renders the harness's full definition as scrollable JSON
14189// (the hub's "detail" action).
142- export function HarnessGetJsonScreen ( { ctx, core } : ScreenProps ) {
143- const opts = coreOptsFromCtx ( ctx ) ;
90+ export function HarnessGetJsonScreen ( props : ScreenProps ) {
14491 const { harnessId } = useParams ( ) ;
145-
146- const detail = useQuery ( {
147- queryKey : [ "harness" , opts . region , harnessId ] ,
148- queryFn : ( ) => core . harness . getHarness ( harnessId ! , opts ) ,
149- enabled : harnessId !== undefined ,
150- } ) ;
92+ const detail = useHarnessDetail ( props , harnessId ) ;
15193
15294 return (
15395 < JsonDetail
@@ -156,6 +98,7 @@ export function HarnessGetJsonScreen({ ctx, core }: ScreenProps) {
15698 error = { detail . isError ? ( detail . error as Error ) : null }
15799 data = { detail . data ?. harness }
158100 loadingLabel = "Loading harness…"
101+ onRetry = { ( ) => void detail . refetch ( ) }
159102 />
160103 ) ;
161104}
0 commit comments