diff --git a/package-comments.json b/package-comments.json new file mode 100644 index 0000000000000000000000000000000000000000..ba389cb1c0f831a819f8af06c2e1a980a6bee374 --- /dev/null +++ b/package-comments.json @@ -0,0 +1,7 @@ +{ + "jest": { + "moduleNameMapperComments": { + "react-markdown": "Fix for: Unexpected token 'export' when Jest test imports. Details: https://github.com/facebook/jest/issues/12036" + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index ca09cf72f10654227cab91c3f66d21fb80d7513d..a75837f6398615e9d16539ca645b8c03639f186f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-chartjs-2": "3.0.4", "react-dom": "^17.0.1", "react-error-boundary": "^3.1.4", + "react-markdown": "^8.0.0", "react-router-dom": "^5.2.0", "react-transition-group": "^4.4.1", "swagger-client": "^3.13.1", @@ -96,6 +97,9 @@ "text", "text-summary", "lcov" - ] + ], + "moduleNameMapper": { + "react-markdown": "<rootDir>/node_modules/react-markdown/react-markdown.min.js" + } } } diff --git a/src/api/SwaggerApi.js b/src/api/SwaggerApi.js index 69c86b2137d69aaa15d7e703390c075873f68b9e..c4b25b3f9e68469e51d3af2bd6f12e76b21680c9 100644 --- a/src/api/SwaggerApi.js +++ b/src/api/SwaggerApi.js @@ -795,4 +795,33 @@ export function unpackUpdateIoc(updateIoc) { const d = { ...updateIoc }; console.log(d); return d; -} \ No newline at end of file +} + +export function useAnnouncements() { + const api = useContext(apiContext); + const method = useCallAndUnpack(api.apis.Broadcasts.fetchAnnouncements, unpackAnnouncement) + + return useAsync({ fcn: method, call: true }); +} + +export function unpackAnnouncement(announcements) { + return { ...announcements }; +} + +export function usePersonalStatistics() { + const api = useContext(apiContext); + const method = useCallAndUnpack(api.apis.Statistics.personalStatistics, unpackPersonalStatistics) + + return useAsync({ fcn: method, call: true }); +} + +export function unpackPersonalStatistics(statistics) { + return { ...statistics }; +} + +export function useOwnIocsWithAlarms() { + const api = useContext(apiContext); + const method = useCallAndUnpack(api.apis.IOCs.listOwnIocsWithAlarms) + + return useAsync({ fcn: method, call: true, init: [] }); +} diff --git a/src/components/IOC/IOCTable.js b/src/components/IOC/IOCTable.js index d386e5e7856fb55a6ace0f653167ca35709590be..e87932526e1a0d716563a5127b09507c9a512258 100644 --- a/src/components/IOC/IOCTable.js +++ b/src/components/IOC/IOCTable.js @@ -6,10 +6,10 @@ import { IOCStatusIcon } from './IOCIcons'; const ownIocsColumns = [ { id: "status", label: 'Status', width: '8%' }, - { id: "namingName", label: 'IOC name', width: '20%', sortable: true }, + { id: "namingName", label: 'IOC name', width: '20%', sortable: false }, { id: "host", label: 'Host', width: '15%', sortable: false }, { id: "network", label: 'Network', width: '15%', sortable: false }, - { id: "sourceVersion", label: 'Git reference', width: '42%', sortable: true }, + { id: "sourceVersion", label: 'Git reference', width: '42%', sortable: false }, ] const exploreIocsColumns = [ @@ -95,7 +95,7 @@ function createTableRowForExploreIocs(ioc) { }; } -export function IOCTable({ iocs, rowType="own", totalCount, lazyParams, setLazyParams, columnSort, setColumnSort, rowsPerPage, loading}) { +export function IOCTable({ iocs, rowType="own", totalCount, lazyParams, setLazyParams, columnSort, setColumnSort, rowsPerPage, loading, paginator}) { const history = useHistory(); const onRowClicked = useCallback((id) => { @@ -123,6 +123,7 @@ export function IOCTable({ iocs, rowType="own", totalCount, lazyParams, setLazyP setColumnSort={setColumnSort} rowsPerPage={rowsPerPage} loading={loading} + paginator={paginator} /> ); } diff --git a/src/components/common/table/CustomTable.js b/src/components/common/table/CustomTable.js index 2dadc74159423d19ef016d662541b30b619ab15f..8c90b2d088c5f98106a2825b220a698e502e0a6d 100644 --- a/src/components/common/table/CustomTable.js +++ b/src/components/common/table/CustomTable.js @@ -10,7 +10,7 @@ import 'primeflex/primeflex.css'; import './CustomTable.css'; export function CustomTable(props) { - const {columns, rows, handleRowClick, totalCount, lazyParams, setLazyParams, columnSort, setColumnSort, rowsPerPage, loading=false} = props; + const {columns, rows, handleRowClick, totalCount, lazyParams, setLazyParams, columnSort, setColumnSort, rowsPerPage, loading=false, paginator=true} = props; const dynamicColumns = columns.map((col,i) => { return <Column key={col.id} field={col.id} header={col.label} style={{width: col.width}} body={col.body} sortable={col.sortable}/>; @@ -75,7 +75,7 @@ export function CustomTable(props) { scrollable resizableColumns columnResizeMode="fit" - paginator + paginator={paginator} paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown" currentPageReportTemplate="Showing {first} to {last} of {totalRecords}" rows={lazyParams.rows} rowsPerPageOptions={rowsPerPage} rowHover diff --git a/src/views/IOC/IOCListView.js b/src/views/IOC/IOCListView.js index dc39e318e050b7db2c4294ec3573d05392dfac7c..4e0e8ba3ded1898935ca729e554f99fcbd0521be 100644 --- a/src/views/IOC/IOCListView.js +++ b/src/views/IOC/IOCListView.js @@ -1,10 +1,10 @@ -import { Container, Grid, Hidden, Paper } from "@material-ui/core"; +import { Container, Grid, Hidden, Paper, FormControlLabel, Switch, Typography, Box } from "@material-ui/core"; import { RootContainer } from "../../components/common/Container/RootContainer"; -import React, { useState, useEffect } from "react" +import React, { useState, useEffect, useContext } from "react" import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; import { IOCList } from "../../components/IOC/IOCList"; import { IOCTable } from "../../components/IOC/IOCTable"; -import { useIOCSearch } from "../../api/SwaggerApi"; +import { useIOCSearch, userContext } from "../../api/SwaggerApi"; import { makeStyles } from '@material-ui/core/styles'; import { initRequestParams } from "../../components/common/Helper"; import { SearchBar } from "../../components/common/SearchBar/SearchBar"; @@ -15,9 +15,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -export function IOCListView({user = null}) { +export function IOCListView() { const [iocs, getIocs, /*reset*/, loading] = useIOCSearch(); const [query, setQuery] = useState(""); + const [ownOnly, setOwnOnly] = useState(false); + const {user} = useContext(userContext); const classes = useStyles(); const [lazyParams, setLazyParams] = useState({ @@ -31,12 +33,21 @@ export function IOCListView({user = null}) { sortOrder: null }); + const handleChangeOwn = (event) => { + const own = event.target.checked; + setOwnOnly(own); + }; + const rowsPerPage = [5, 10, 20, 50, 100]; + + console.log("rendering Explore IOCs") useEffect(() => { let requestParams = initRequestParams(lazyParams, query, columnSort); - - requestParams.owner = user?.loginName; + + if(ownOnly) { + requestParams.owner = user?.loginName; + } if(columnSort.sortField === "owner") { requestParams.orderBy = "OWNER"; @@ -51,8 +62,7 @@ export function IOCListView({user = null}) { } getIocs(requestParams); - - }, [getIocs, user, lazyParams, columnSort, query]) + }, [getIocs, lazyParams, columnSort, query, user, ownOnly]) const title = "Explore IOCs"; useGlobalAppBar(title); @@ -75,7 +85,15 @@ export function IOCListView({user = null}) { <Container> <Paper className={classes.root}> <Grid container spacing={1}> - <Grid item xs={12}> + <Grid item xs={12} md={12}> + <Box display="flex" justifyContent="end" p={2} m={1}> + <FormControlLabel className={classes.formControl} + control={<Switch checked={ownOnly} onChange={handleChangeOwn} />} + label={<Typography variant="h5">My IOCs</Typography>} + /> + </Box> + </Grid> + <Grid item xs={12} md={12}> {content} </Grid> </Grid> diff --git a/src/views/home/HomeView.js b/src/views/home/HomeView.js index 1fc5a8eda6e19d1bff620313f1b819ea49f8fc66..9a0f0c7833550101cebef3e478fbf6f4062b0bef 100644 --- a/src/views/home/HomeView.js +++ b/src/views/home/HomeView.js @@ -1,23 +1,44 @@ -import { Container, Grid, Paper, Button, Box, Typography } from "@material-ui/core"; -import React, { useContext, useState } from "react"; +import { Container, Grid, Paper, Button, Box, Typography, Hidden } from "@material-ui/core"; +import React, { useState} from "react"; import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; import { CreateIOC } from "../../components/IOC/CreateIOC"; +import { KeyValueTable } from "../../components/common/KeyValueTable/KeyValueTable"; import { SimpleModal } from "../../components/common/SimpleModal/SimpleModal"; -import { useCreateIOC, userContext } from "../../api/SwaggerApi"; +import { IOCList } from "../../components/IOC/IOCList"; +import { IOCTable } from "../../components/IOC/IOCTable"; +import { SimpleAccordion } from '../../components/common/Accordion/SimpleAccordion'; +import { useCreateIOC, useAnnouncements, usePersonalStatistics, useOwnIocsWithAlarms } from "../../api/SwaggerApi"; +import ReactMarkdown from 'react-markdown' import { makeStyles } from '@material-ui/core/styles'; -import { IOCListView } from "../IOC/IOCListView"; const useStyles = makeStyles((theme) => ({ root: { marginBottom: theme.spacing(2), }, + announcements: { + marginLeft: theme.spacing(3), + marginRight: theme.spacing(3), + } })); export function HomeView() { const [iocFormOpen, setIOCFormOpen] = useState(false); - const {user} = useContext(userContext); + const [announcements] = useAnnouncements(); + const [statistics] = usePersonalStatistics(); + const [iocsWithAlarms, loading] = useOwnIocsWithAlarms(); const classes = useStyles(); + const [lazyParams, setLazyParams] = useState({ + first: 0, + rows: 100, + page: 0 + }); + + const [columnSort, setColumnSort] = useState({ + sortField: null, + sortOrder: null + }); + console.log("rendering Own IOCs") useGlobalAppBar("Home"); @@ -33,7 +54,7 @@ export function HomeView() { <Grid item xs={10}> <Box display="flex" flexDirection="row" p={2} m={1}> <Typography variant="h2"> - My IOCs + My statistics </Typography> </Box> </Grid> @@ -42,13 +63,38 @@ export function HomeView() { <Button variant="contained" color="secondary" onClick={() => { setIOCFormOpen(true) }}>New IOC</Button> </Box> </Grid> + {statistics && + <Grid item xs={12}> + <KeyValueTable obj={{"Total number of my IOCs": statistics.numberOfActiveIocs}} variant="table" /> + </Grid> + } + <Grid item xs={12}> + <SimpleAccordion summary="My IOCs with alarms" defaultExpanded> + <Hidden smUp> + <IOCList iocs={iocsWithAlarms}/> + </Hidden> + <Hidden xsDown> + <IOCTable iocs={iocsWithAlarms} rowType="own" loading={loading} + lazyParams={lazyParams} setLazyParams={setLazyParams} + columnSort={columnSort} setColumnSort={setColumnSort} + totalCount={iocsWithAlarms?.length} + paginator={false} /> + </Hidden> + </SimpleAccordion> + </Grid> + </Grid> + {announcements?.announcementsText && + <Grid container justify="flex-start" className={classes.announcements}> <Grid item xs={12}> - <IOCListView user={user} /> + <ReactMarkdown> + {announcements.announcementsText} + </ReactMarkdown> </Grid> - <SimpleModal open={iocFormOpen} setOpen={setIOCFormOpen}> - <CreateIOC open={iocFormOpen} setOpen={setIOCFormOpen} isUpdateIoc={false} submitCallback={closeModal} hook={useCreateIOC} title="Create new IOC" buttonText="Create"/> - </SimpleModal> </Grid> + } + <SimpleModal open={iocFormOpen} setOpen={setIOCFormOpen}> + <CreateIOC open={iocFormOpen} setOpen={setIOCFormOpen} isUpdateIoc={false} submitCallback={closeModal} hook={useCreateIOC} title="Create new IOC" buttonText="Create"/> + </SimpleModal> </Paper> </Container> );