Skip to content

Commit

Permalink
[frontend/backend] Public dashboard : public APIs and widgets (#4903)
Browse files Browse the repository at this point in the history
Co-authored-by: marie flores <marie.flores@filigran.io>
Co-authored-by: Laurent Bonnet <146674147+labo-flg@users.noreply.github.com>
  • Loading branch information
3 people committed Mar 20, 2024
1 parent 6f75bda commit ba2d2a5
Show file tree
Hide file tree
Showing 105 changed files with 14,179 additions and 4,040 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Grid from '@mui/material/Grid';
import Card from '@mui/material/Card';
import CardActionArea from '@mui/material/CardActionArea';
import { Link } from 'react-router-dom';
import CardHeader from '@mui/material/CardHeader';
import Avatar from '@mui/material/Avatar';
import React from 'react';
import { useTheme } from '@mui/styles';
import ItemIcon from '../ItemIcon';
import { resolveLink } from '../../utils/Entity';
import { useFormatter } from '../i18n';
import type { Theme } from '../Theme';

interface WidgetBookmarksProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
bookmarks: any[]
}

const WidgetBookmarks = ({ bookmarks }: WidgetBookmarksProps) => {
const theme = useTheme<Theme>();
const { t_i18n, fsd } = useFormatter();

return (
<div
id="container"
style={{
width: '100%',
height: '100%',
overflow: 'auto',
paddingBottom: 10,
marginBottom: 10,
}}
>
<Grid container={true} spacing={3}>
{bookmarks.map((bookmarkEdge) => {
const bookmark = bookmarkEdge.node;
const link = resolveLink(bookmark.entity_type);
return (
<Grid item={true} xs={4} key={bookmark.id}>
<Card
variant="outlined"
style={{
width: '100%',
height: 70,
borderRadius: 6,
}}
>
<CardActionArea
component={Link}
to={`${link}/${bookmark.id}`}
sx={{
width: '100%',
height: '100%',
}}
>
<CardHeader
sx={{
height: 55,
paddingBottom: 0,
marginBottom: 0,
}}
avatar={(
<Avatar sx={{ backgroundColor: theme.palette.primary.main }}>
<ItemIcon
type={bookmark.entity_type}
color={theme.palette.background.default}
/>
</Avatar>
)}
title={bookmark.name}
subheader={`${t_i18n('Updated on')} ${fsd(bookmark.modified)}`}
/>
</CardActionArea>
</Card>
</Grid>
);
})}
</Grid>
</div>
);
};

export default WidgetBookmarks;
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const useStyles = makeStyles({
interface WidgetContainerProps {
children: ReactNode
height?: CSSProperties['height']
title: string
title?: string
variant: string
withoutTitle?: boolean
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import List from '@mui/material/List';
import { Link } from 'react-router-dom';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import React from 'react';
import { useTheme } from '@mui/styles';
import { ListItemButton } from '@mui/material';
import ItemIcon from '../ItemIcon';
import { computeLink } from '../../utils/Entity';
import type { Theme } from '../Theme';
import { useFormatter } from '../i18n';

interface WidgetDistributionListProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any[]
hasSettingAccess?: boolean
overflow?: string
}

const WidgetDistributionList = ({
data,
hasSettingAccess = false,
overflow = 'auto',
}: WidgetDistributionListProps) => {
const theme = useTheme<Theme>();
const { n } = useFormatter();

return (
<div
id="container"
style={{
width: '100%',
height: '100%',
paddingBottom: 10,
marginBottom: 10,
overflow,
}}
>
<List style={{ marginTop: -10 }}>
{data.map((entry, key) => {
let link: string | null = null;
if (entry.type !== 'User' || hasSettingAccess) {
link = entry.id ? computeLink(entry) : null;
}
let linkProps = {};
if (link) {
linkProps = {
component: Link,
to: link,
};
}

return (
<ListItemButton
key={entry.label}
dense={true}
divider={true}
{...linkProps}
sx={{
height: 50,
minHeight: 50,
maxHeight: 50,
paddingRight: 0,
}}
style={overflow === 'hidden' && key === data.length - 1 ? { borderBottom: 0 } : {}}
>
<ListItemIcon>
<ItemIcon
color={
theme.palette.mode === 'light'
&& entry.color === '#ffffff'
? '#000000'
: entry.color
}
type={entry.id ? entry.type : 'default'}
/>
</ListItemIcon>
<ListItemText
primary={
<div
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
paddingRight: 10,
}}
>
{entry.label}
</div>
}
/>
<div
style={{
float: 'right',
marginRight: 20,
fontSize: 18,
fontWeight: 600,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
color: theme.palette.primary.main,
}}
>
{n(entry.value)}
</div>
</ListItemButton>
);
})}
</List>
</div>
);
};

export default WidgetDistributionList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import Chart from '@components/common/charts/Chart';
import React from 'react';
import { useTheme } from '@mui/styles';
import type { ApexOptions } from 'apexcharts';
import { defaultValue } from '../../utils/Graph';
import { donutChartOptions } from '../../utils/Charts';
import { useFormatter } from '../i18n';
import type { Theme } from '../Theme';

interface WidgetDonutProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any[]
groupBy: string
withExport?: boolean
readonly?: boolean
}

const WidgetDonut = ({
data,
groupBy,
withExport = false,
readonly = false,
}: WidgetDonutProps) => {
const theme = useTheme<Theme>();
const { t_i18n } = useFormatter();

const chartData = data.map((n) => n.value);
// eslint-disable-next-line no-nested-ternary
const labels = data.map((n) => (groupBy.endsWith('_id')
? defaultValue(n.entity)
: groupBy === 'entity_type' && t_i18n(`entity_${n.label}`) !== `entity_${n.label}`
? t_i18n(`entity_${n.label}`)
: n.label));

let chartColors = [];
if (data.at(0)?.entity?.color) {
chartColors = data.map((n) => (theme.palette.mode === 'light' && n.entity.color === '#ffffff'
? '#000000'
: n.entity.color));
}
if (data.at(0)?.entity?.x_opencti_color) {
chartColors = data.map((n) => (theme.palette.mode === 'light'
&& n.entity.x_opencti_color === '#ffffff'
? '#000000'
: n.entity.x_opencti_color));
}
if (data.at(0)?.entity?.template?.color) {
chartColors = data.map((n) => (theme.palette.mode === 'light' && n.entity.template.color === '#ffffff'
? '#000000'
: n.entity.template.color));
}

return (
<Chart
options={donutChartOptions(
theme,
labels,
'bottom',
false,
chartColors,
) as ApexOptions}
series={chartData}
type="donut"
width="100%"
height="100%"
withExportPopover={withExport}
isReadOnly={readonly}
/>
);
};

export default WidgetDonut;
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Chart from '@components/common/charts/Chart';
import React from 'react';
import { useTheme } from '@mui/styles';
import { useNavigate } from 'react-router-dom-v5-compat';
import { ApexOptions } from 'apexcharts';
import { horizontalBarsChartOptions } from '../../utils/Charts';
import { simpleNumberFormat } from '../../utils/Number';
import type { Theme } from '../Theme';

interface WidgetHorizontalBarsProps {
series: ApexAxisChartSeries
distributed?: boolean
stacked?: boolean
total?: boolean
legend?: boolean
categories?: string[]
withExport?: boolean
readonly?: boolean
redirectionUtils?: {
id?: string
entity_type?: string
}[]
}

const WidgetHorizontalBars = ({
series,
distributed,
stacked,
total,
legend,
categories,
withExport,
readonly,
redirectionUtils,
}: WidgetHorizontalBarsProps) => {
const theme = useTheme<Theme>();
const navigate = useNavigate();

return (
<Chart
options={horizontalBarsChartOptions(
theme,
true,
simpleNumberFormat,
undefined,
distributed,
navigate,
redirectionUtils,
stacked,
total,
categories,
legend,
) as ApexOptions}
series={series}
type="bar"
width="100%"
height="100%"
withExportPopover={withExport}
isReadOnly={readonly}
/>
);
};

export default WidgetHorizontalBars;

0 comments on commit ba2d2a5

Please sign in to comment.