diff --git a/src/App.js b/src/App.js index 73f721de9f319dd99315acb3ab60a2c3f6d1243b..6a885308af1fe1d7dd49fecaf4275f6ad6f36290 100644 --- a/src/App.js +++ b/src/App.js @@ -29,7 +29,8 @@ import { AppErrorBoundary } from "./components/navigation/ErrorBoundary/ErrorBou import { LoginView } from "./views/login/LoginView"; import { LoginSuggester } from "./components/navigation/LoginSuggester/LoginSuggester"; import { JobLogAccessControl } from "./views/jobs/JobLogAccessControl"; -import {RecordListView} from './views/records/RecordListView'; +import { RecordListView } from './views/records/RecordListView'; +import { RecordDetailsView } from './views/records/RecordDetailsView'; //setting up the application (TAB)title function App() { @@ -60,6 +61,7 @@ function App() { </Route> <Route path="/home" component={HomeAccessControl} exact /> <Route path="/records" component={RecordListView} exact /> + <Route path="/records/:name" component={RecordDetailsView} exact /> <Route path="/iocs/:id([0-9]+)" component={IOCDetailsAccessControl} exact /> <Route path="/iocs" component={IOCListView} exact /> <Route path="/deployments/:id([0-9]+)" component={DeploymentDetailsAccessControl} exact /> diff --git a/src/api/SwaggerApi.js b/src/api/SwaggerApi.js index 911a7e3ea15a6bcaa45ff060c2b8122a7108e82c..0989e1b563eacd3f3c45b9dfa9a8a9a450045897 100644 --- a/src/api/SwaggerApi.js +++ b/src/api/SwaggerApi.js @@ -560,7 +560,7 @@ export function unpackRecordList(records) { return unpackedRecordList; } -export function useChannelSearch() { +export function useRecordSearch() { const api = useContext(apiContext); const method = useCallAndUnpack( (params) => api.apis.Records.findAllRecords(params), @@ -577,6 +577,13 @@ const emptyRecordListResponse = { recordList: [] } +export function useRecord(name, onError) { + const api = useContext(apiContext); + const method = useCallAndUnpack(api.apis.Records.getRecord, unpackRecord) + const boundMethod = useCallback(method.bind(null, { name: name }), [name]); + return useAsync({ fcn: boundMethod, onError: onError }); +} + export function unpackHostIOCList(iocs) { const deployedArr = iocs.deployedIocs.map(ioc => unpackIocInfo(ioc)) diff --git a/src/components/IOC/IOCLiveStatus.js b/src/components/IOC/IOCLiveStatus.js index 02cf651c39a9f494428eed7fbdd5c256de89194e..cbe54a2233df4f2f43c1dad2808df34f4a849746 100644 --- a/src/components/IOC/IOCLiveStatus.js +++ b/src/components/IOC/IOCLiveStatus.js @@ -68,7 +68,7 @@ export function IOCLiveStatus({ ioc }) { <LokiContainer csEntryId={liveIOC.activeDeployment?.host.csEntryId} iocName={ioc.namingName} isDeployed={isIocDeployed(ioc)} /> </SimpleAccordion> <SimpleAccordion summary="Records"> - <RecordSearch iocName={ioc.namingName} /> + <RecordSearch iocName={ioc.namingName} rowType="iocDetails"/> </SimpleAccordion> </AccessControl> } diff --git a/src/components/records/RecordBadge.js b/src/components/records/RecordBadge.js new file mode 100644 index 0000000000000000000000000000000000000000..c6284c9057a17c7bf421b2fcadabcf3a77d8163b --- /dev/null +++ b/src/components/records/RecordBadge.js @@ -0,0 +1,12 @@ +import React from "react"; +import { IconBadge } from "../common/Badge/IconBadge"; +import { RecordStatusIcon } from "./RecordIcons"; + +export function RecordBadge({ record }) { + return ( + <IconBadge + icon={<RecordStatusIcon record={record} />} + title={record.name} + subheader={record.hostName} /> + ) +} \ No newline at end of file diff --git a/src/components/records/RecordSearch.js b/src/components/records/RecordSearch.js index 95395278ebe625141d4c73b77b2d0cfd52a59e4b..e5fbff393124c95a04c5a059f2fc83f2695a2782 100644 --- a/src/components/records/RecordSearch.js +++ b/src/components/records/RecordSearch.js @@ -1,12 +1,12 @@ import React, { useState, useEffect } from "react"; -import { useChannelSearch } from "../../api/SwaggerApi"; +import { useRecordSearch } from "../../api/SwaggerApi"; import { initRequestParams } from "../common/Helper"; import { RecordTable } from "./RecordTable"; import { SearchBar } from "../common/SearchBar/SearchBar"; -export function RecordSearch({iocName}) { +export function RecordSearch({iocName, rowType}) { - const [records, getRecords, /*reset*/, loading] = useChannelSearch(); + const [records, getRecords, /*reset*/, loading] = useRecordSearch(); const [query, setQuery] = useState(""); const [lazyParams, setLazyParams] = useState({ @@ -36,7 +36,7 @@ export function RecordSearch({iocName}) { return ( <SearchBar search={setQuery} loading={loading} placeholder="Search in Record"> - <RecordTable records={records} rowType="iocDetails" loading={loading} lazyParams={lazyParams} setLazyParams={setLazyParams} + <RecordTable records={records} rowType={rowType} loading={loading} lazyParams={lazyParams} setLazyParams={setLazyParams} rowsPerPage={rowsPerPage} /> </SearchBar> ); diff --git a/src/components/records/RecordTable.js b/src/components/records/RecordTable.js index 39e716196a5d27a696ee7cc6a44782e2f0b8bb73..ef2779ae8066297e3581b89bd3bb30dc9f2d8dfc 100644 --- a/src/components/records/RecordTable.js +++ b/src/components/records/RecordTable.js @@ -2,6 +2,7 @@ import React from "react"; import { CustomTable } from '../common/table/CustomTable'; import { RecordStatusIcon } from "./RecordIcons"; import { Grid } from "@material-ui/core"; +import { useRedirect } from '../../hooks/Redirect'; const recordsColumns = [ { id: 'bulb', label: 'Status', width: '5ch', textAlign: "center" }, @@ -13,11 +14,12 @@ const recordsColumns = [ const iocDetailsColumns = [ { id: 'bulb', label: 'Status', width: '5ch', textAlign: "center" }, { id: 'name', label: 'Record', width: '15ch' }, - { id: 'iocVersion', label: 'IOC version', width: '15ch' }, + { id: 'iocVersion', label: 'IOC version', width: '15ch' }, ]; export function createRecordsRow(record) { return { + id: record.name, bulb: <Grid container direction="column" justifyContent="center" alignItems="center"> <RecordStatusIcon record={record} /> @@ -30,6 +32,7 @@ export function createRecordsRow(record) { export function createIocDetailsRow(record) { return { + id: record.name, bulb: <Grid container direction="column" justifyContent="center" alignItems="center"> <RecordStatusIcon record={record} /> @@ -47,17 +50,26 @@ export function RecordTable({ records, rowType = "records", lazyParams, setLazyP const [columns, createRow] = tableTypeSpecifics[rowType]; + const redirect = useRedirect(); + + const itemLink = name => `/records/${encodeURIComponent(name)}`; + + const onRowClicked = (id) => { + redirect(itemLink(id)); + }; + return( - <CustomTable - columns={columns} - rows={records.recordList.map(record => createRow(record))} - // Total count must be practically infinity for now (till total count can be retrieved from API) - totalCount={records.listSize < records.limit ? records.listSize + records.limit * records.pageNumber : 1000000} - lazyParams={lazyParams} - setLazyParams={setLazyParams} - rowsPerPage={rowsPerPage} - loading={loading} - paginatorTemplate="FirstPageLink PrevPageLink NextPageLink RowsPerPageDropdown" - /> + <CustomTable + columns={columns} + rows={records.recordList.map(record => createRow(record))} + // Total count must be practically infinity for now (till total count can be retrieved from API) + totalCount={records.listSize < records.limit ? records.listSize + records.limit * records.pageNumber : 1000000} + lazyParams={lazyParams} + setLazyParams={setLazyParams} + rowsPerPage={rowsPerPage} + loading={loading} + paginatorTemplate="FirstPageLink PrevPageLink NextPageLink RowsPerPageDropdown" + handleRowClick={onRowClicked} + /> ) } \ No newline at end of file diff --git a/src/views/records/RecordDetailsView.js b/src/views/records/RecordDetailsView.js new file mode 100644 index 0000000000000000000000000000000000000000..d382b65f39c946792ad80245aaa50aa0afeab702 --- /dev/null +++ b/src/views/records/RecordDetailsView.js @@ -0,0 +1,85 @@ +import React, { useEffect, useCallback } from 'react'; +import { useRecord, useCSEntrySearch } from '../../api/SwaggerApi'; +import { + Paper, + IconButton, + Typography, + LinearProgress +} from '@material-ui/core'; +import { Link } from 'react-router-dom'; +import { RootContainer } from "../../components/common/Container/RootContainer"; +import ArrowBackIcon from '@material-ui/icons/ArrowBack'; +import { useHistory } from "react-router-dom"; +import { useGlobalAppBar } from '../../components/navigation/GlobalAppBar/GlobalAppBar'; +import { SimpleAccordion } from "../../components/common/Accordion/SimpleAccordion"; +import { KeyValueTable } from '../../components/common/KeyValueTable/KeyValueTable'; +import { RecordBadge } from '../../components/records/RecordBadge'; +import { formatDate, transformHostQuery } from '../../components/common/Helper'; + +export function RecordDetailsView({ match }) { + const name = decodeURIComponent(match.params.name); + const [record, /*getRecord*/, /*reset*/, recordLoading] = useRecord(name); + const [hosts, getHosts, /*reset*/, hostLoading] = useCSEntrySearch(); + const history = useHistory(); + + const { setTitle } = useGlobalAppBar("Record Details"); + + useEffect(() => { + if(record) { + setTitle("Record Details: " + record.name); + getHosts( + { + query: transformHostQuery(record.hostName), + page: 0, + limit: 1 + } + ) + } + }, [record, setTitle, getHosts]); + + const handleClick = (event) => { + history.goBack(); + }; + + const getSubset = useCallback((record) => { + let subset = { + "IOC name": record.iocId ? + <Typography> + <Link to={`/iocs/${record.iocId}`}>{record?.iocName}</Link> + </Typography> + : record?.iocName, + "Host name": hosts && hosts.hostList.length === 1 ? + <Typography> + <Link to={`/hosts/${hosts.hostList[0].csEntryHost.id}`}>{record?.hostName}</Link> + </Typography> + : record?.hostName, + "IOC version": record?.iocVersion + } + + for (const [key, value] of Object.entries(record.properties)) { + if (key.toLowerCase().includes("time")) { + subset[key] = formatDate(value) + } else { + subset[key] = value + } + } + + return subset + }, [hosts]) + + return ( + <RootContainer> + {recordLoading || hostLoading ? <LinearProgress color="primary" /> : + <Paper> + <IconButton color="inherit" onClick={handleClick}> + <ArrowBackIcon /> + </IconButton> + { record && + <SimpleAccordion defaultExpanded summary={<RecordBadge record={record} />} > + <KeyValueTable obj={getSubset(record)} variant="overline" /> + </SimpleAccordion> + } + </Paper>} + </RootContainer> + ) +} \ No newline at end of file diff --git a/src/views/records/RecordListView.js b/src/views/records/RecordListView.js index fbab42499e8dcb28e02de523e4b646865e9ab12e..a5e4d8b0a2ef025cbc1c82499635e7026b98d34b 100644 --- a/src/views/records/RecordListView.js +++ b/src/views/records/RecordListView.js @@ -14,7 +14,7 @@ export function RecordListView() { <Paper> <Grid container spacing={1}> <Grid item xs={12} md={12}> - <RecordSearch /> + <RecordSearch/> </Grid> </Grid> </Paper>