Loading capif_frontend/src/pages/me/invokers/[id].js +98 −29 Changes for capif_frontend/src/pages/me/invokers/[id].js: 98 added lines, 29 removed lines. Original line number Diff line number Diff line import { useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/router' import Box from '@mui/material/Box' Loading @@ -7,49 +8,118 @@ import CardHeader from '@mui/material/CardHeader' import CardContent from '@mui/material/CardContent' import CircularProgress from '@mui/material/CircularProgress' import Alert from '@mui/material/Alert' import List from '@mui/material/List' import ListItem from '@mui/material/ListItem' import ListItemText from '@mui/material/ListItemText' import Typography from '@mui/material/Typography' import Table from '@mui/material/Table' import TableBody from '@mui/material/TableBody' import TableCell from '@mui/material/TableCell' import TableContainer from '@mui/material/TableContainer' import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import { useQuery } from '@tanstack/react-query' import { discoverApis } from 'src/configs/apiService' import { errorMessage, retryServerErrorsOnly } from 'src/configs/queryHelpers' const ApiList = ({ invokerId }) => { const { data, isPending, isError, error } = useQuery({ // Where an invoker would call the API: the first interface of the first AEF profile. // Providers can publish without some of these fields, so every step is optional. const endpointOf = api => { const iface = api?.aefProfiles?.[0]?.interfaceDescriptions?.[0] const host = iface?.fqdn ?? iface?.ipv4Addr ?? iface?.ipv6Addr if (!host) return '—' return iface?.port ? `${host}:${iface.port}` : host } const versionOf = api => api?.aefProfiles?.[0]?.versions?.[0]?.apiVersion ?? '—' const DiscoverResults = ({ apis }) => { if (apis.length === 0) { return <Typography>No APIs found.</Typography> } return ( <TableContainer> <Table aria-label='Discovered APIs'> <TableHead> <TableRow> <TableCell>API name</TableCell> <TableCell>API ID</TableCell> <TableCell>Description</TableCell> <TableCell>Version</TableCell> <TableCell>Endpoint</TableCell> </TableRow> </TableHead> <TableBody> {apis.map(api => ( <TableRow key={api.apiId ?? api.apiName}> <TableCell>{api.apiName ?? '—'}</TableCell> <TableCell>{api.apiId ?? '—'}</TableCell> <TableCell>{api.description || '—'}</TableCell> <TableCell>{versionOf(api)}</TableCell> <TableCell>{endpointOf(api)}</TableCell> </TableRow> ))} </TableBody> </Table> </TableContainer> ) } const DiscoverSection = ({ invokerId }) => { // A disabled query reports isPending before it has ever run, so "not run yet" comes from this flag. const [requested, setRequested] = useState(false) const { data, isPending, isFetching, isError, error, refetch } = useQuery({ queryKey: ['invoker-apis', invokerId], queryFn: () => discoverApis(invokerId), enabled: Boolean(invokerId), // router.query is empty on the first render of a dynamic route retry: retryServerErrorsOnly enabled: Boolean(invokerId) && requested, retry: retryServerErrorsOnly, // Discover is an explicit action: only the button runs it, not returning to the tab. refetchOnWindowFocus: false, refetchOnReconnect: false }) const apis = data?.serviceAPIDescriptions ?? [] if (isPending) { return ( // The first click enables the query; later clicks re-run it, since new publishes can appear. const handleDiscover = () => { if (requested) { refetch() } else { setRequested(true) } } let content if (!requested) { content = <Typography>Search for the APIs this invoker can call.</Typography> } else if (isPending) { content = ( <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}> <CircularProgress /> </Box> ) } if (isError) { return <Alert severity='error'>{errorMessage(error, 'Could not load the APIs.')}</Alert> } if (apis.length === 0) { return <Typography>No APIs are available to this invoker yet.</Typography> } else if (isError) { content = <Alert severity='error'>{errorMessage(error, 'Could not discover APIs.')}</Alert> } else { content = <DiscoverResults apis={apis} /> } return ( <List> {apis.map(api => ( <ListItem key={api.apiId} disableGutters> <ListItemText primary={api.apiName} secondary={api.apiId} /> </ListItem> ))} </List> <Card> <CardHeader title='Discover APIs' action={ <Button variant='contained' onClick={handleDiscover} disabled={isFetching}> {isFetching ? <CircularProgress size={24} /> : 'Discover'} </Button> } /> <CardContent>{content}</CardContent> </Card> ) } Loading @@ -62,12 +132,11 @@ const InvokerDetailPage = () => { <Button component={Link} href='/me/invokers' sx={{ mb: 4 }}> Back to invokers </Button> <Card> <CardHeader title={invokerId ?? ''} subheader='Available APIs' /> <CardContent> <ApiList invokerId={invokerId} /> </CardContent> <Card sx={{ mb: 6 }}> <CardHeader title={invokerId ?? ''} subheader='Invoker' /> </Card> {/* router.query is empty on the first render of a dynamic route */} {invokerId ? <DiscoverSection key={invokerId} invokerId={invokerId} /> : <CircularProgress />} </Box> ) } Loading Loading
capif_frontend/src/pages/me/invokers/[id].js +98 −29 Changes for capif_frontend/src/pages/me/invokers/[id].js: 98 added lines, 29 removed lines. Original line number Diff line number Diff line import { useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/router' import Box from '@mui/material/Box' Loading @@ -7,49 +8,118 @@ import CardHeader from '@mui/material/CardHeader' import CardContent from '@mui/material/CardContent' import CircularProgress from '@mui/material/CircularProgress' import Alert from '@mui/material/Alert' import List from '@mui/material/List' import ListItem from '@mui/material/ListItem' import ListItemText from '@mui/material/ListItemText' import Typography from '@mui/material/Typography' import Table from '@mui/material/Table' import TableBody from '@mui/material/TableBody' import TableCell from '@mui/material/TableCell' import TableContainer from '@mui/material/TableContainer' import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import { useQuery } from '@tanstack/react-query' import { discoverApis } from 'src/configs/apiService' import { errorMessage, retryServerErrorsOnly } from 'src/configs/queryHelpers' const ApiList = ({ invokerId }) => { const { data, isPending, isError, error } = useQuery({ // Where an invoker would call the API: the first interface of the first AEF profile. // Providers can publish without some of these fields, so every step is optional. const endpointOf = api => { const iface = api?.aefProfiles?.[0]?.interfaceDescriptions?.[0] const host = iface?.fqdn ?? iface?.ipv4Addr ?? iface?.ipv6Addr if (!host) return '—' return iface?.port ? `${host}:${iface.port}` : host } const versionOf = api => api?.aefProfiles?.[0]?.versions?.[0]?.apiVersion ?? '—' const DiscoverResults = ({ apis }) => { if (apis.length === 0) { return <Typography>No APIs found.</Typography> } return ( <TableContainer> <Table aria-label='Discovered APIs'> <TableHead> <TableRow> <TableCell>API name</TableCell> <TableCell>API ID</TableCell> <TableCell>Description</TableCell> <TableCell>Version</TableCell> <TableCell>Endpoint</TableCell> </TableRow> </TableHead> <TableBody> {apis.map(api => ( <TableRow key={api.apiId ?? api.apiName}> <TableCell>{api.apiName ?? '—'}</TableCell> <TableCell>{api.apiId ?? '—'}</TableCell> <TableCell>{api.description || '—'}</TableCell> <TableCell>{versionOf(api)}</TableCell> <TableCell>{endpointOf(api)}</TableCell> </TableRow> ))} </TableBody> </Table> </TableContainer> ) } const DiscoverSection = ({ invokerId }) => { // A disabled query reports isPending before it has ever run, so "not run yet" comes from this flag. const [requested, setRequested] = useState(false) const { data, isPending, isFetching, isError, error, refetch } = useQuery({ queryKey: ['invoker-apis', invokerId], queryFn: () => discoverApis(invokerId), enabled: Boolean(invokerId), // router.query is empty on the first render of a dynamic route retry: retryServerErrorsOnly enabled: Boolean(invokerId) && requested, retry: retryServerErrorsOnly, // Discover is an explicit action: only the button runs it, not returning to the tab. refetchOnWindowFocus: false, refetchOnReconnect: false }) const apis = data?.serviceAPIDescriptions ?? [] if (isPending) { return ( // The first click enables the query; later clicks re-run it, since new publishes can appear. const handleDiscover = () => { if (requested) { refetch() } else { setRequested(true) } } let content if (!requested) { content = <Typography>Search for the APIs this invoker can call.</Typography> } else if (isPending) { content = ( <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}> <CircularProgress /> </Box> ) } if (isError) { return <Alert severity='error'>{errorMessage(error, 'Could not load the APIs.')}</Alert> } if (apis.length === 0) { return <Typography>No APIs are available to this invoker yet.</Typography> } else if (isError) { content = <Alert severity='error'>{errorMessage(error, 'Could not discover APIs.')}</Alert> } else { content = <DiscoverResults apis={apis} /> } return ( <List> {apis.map(api => ( <ListItem key={api.apiId} disableGutters> <ListItemText primary={api.apiName} secondary={api.apiId} /> </ListItem> ))} </List> <Card> <CardHeader title='Discover APIs' action={ <Button variant='contained' onClick={handleDiscover} disabled={isFetching}> {isFetching ? <CircularProgress size={24} /> : 'Discover'} </Button> } /> <CardContent>{content}</CardContent> </Card> ) } Loading @@ -62,12 +132,11 @@ const InvokerDetailPage = () => { <Button component={Link} href='/me/invokers' sx={{ mb: 4 }}> Back to invokers </Button> <Card> <CardHeader title={invokerId ?? ''} subheader='Available APIs' /> <CardContent> <ApiList invokerId={invokerId} /> </CardContent> <Card sx={{ mb: 6 }}> <CardHeader title={invokerId ?? ''} subheader='Invoker' /> </Card> {/* router.query is empty on the first render of a dynamic route */} {invokerId ? <DiscoverSection key={invokerId} invokerId={invokerId} /> : <CircularProgress />} </Box> ) } Loading