Skip to content

Commit

Permalink
Flyout refactor (elastic#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
orouz committed Jan 13, 2022
1 parent 0cee201 commit edc8368
Showing 1 changed file with 116 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useState, useCallback } from 'react';
import React, { useState } from 'react';
import {
EuiFlexItem,
EuiSpacer,
EuiCode,
EuiDescriptionList,
Expand All @@ -18,42 +18,36 @@ import {
EuiBadge,
EuiTabs,
EuiTab,
EuiFlexGrid,
EuiCard,
PropsOf,
} from '@elastic/eui';
import { assertNever } from '@kbn/std';
import { CSPFinding } from './types';
import { CSPEvaluationBadge } from '../../components/csp_evaluation_badge';

const tabs = ['resource', 'rule', 'overview'] as const;
const tabs = ['result', 'rule', 'resource'] as const;

type FindingsTab = typeof tabs[number];

type EuiListItemsProps = NonNullable<PropsOf<typeof EuiDescriptionList>['listItems']>[number];

interface CSPTabProps {
data: CSPFinding;
interface Card {
title: string;
listItems: Array<[EuiListItemsProps['title'], EuiListItemsProps['description']]>;
}

interface FindingFlyoutProps {
onClose(): void;
findings: CSPFinding;
}

// TODO: fix scrollbar jumps
export const FindingsRuleFlyout = ({ onClose, findings }: FindingFlyoutProps) => {
const [tab, setTab] = useState<typeof tabs[number]>('resource');

const Tab = useCallback(() => {
switch (tab) {
case 'overview':
return <OverviewTab data={findings} />;
case 'rule':
return <RuleTab data={findings} />;
case 'resource':
return <ResourceTab data={findings} />;
}

assertNever(tab);
}, [findings, tab]);

const [tab, setTab] = useState<FindingsTab>('result');
return (
<EuiFlyout onClose={onClose}>
<EuiFlyoutHeader aria-labelledby={'foo'}>
<EuiFlyoutHeader>
<EuiTitle size="l">
<EuiTextColor color="primary">
<h2>{'Findings'}</h2>
Expand All @@ -74,120 +68,115 @@ export const FindingsRuleFlyout = ({ onClose, findings }: FindingFlyoutProps) =>
</EuiTabs>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<Tab />
<FindingsTab tab={tab} findings={findings} />
</EuiFlyoutBody>
</EuiFlyout>
);
};

const getTagsBadges = (v: string[]) => (
<>
{v.map((x) => (
<EuiBadge color="default">{x}</EuiBadge>
const Cards = ({ data }: { data: Card[] }) => (
<EuiFlexGrid direction="column" gutterSize={'l'}>
{data.map((card) => (
<EuiFlexItem key={card.title} style={{ display: 'block' }}>
<EuiCard textAlign="left" title={card.title}>
<EuiDescriptionList
compressed={false}
type="column"
listItems={card.listItems.map((v) => ({ title: v[0], description: v[1] }))}
/>
</EuiCard>
</EuiFlexItem>
))}
</>
</EuiFlexGrid>
);

const OverviewTab = ({ data }: CSPTabProps) => (
<TabWrapper>
<EuiCard textAlign="left" title={'Agent'}>
<EuiDescriptionList
compressed={false}
type="column"
listItems={[
{ title: 'Name', description: data.agent.name },
{ title: 'ID', description: data.agent.id },
{ title: 'Type', description: data.agent.type },
{ title: 'Version', description: data.agent.version },
]}
/>
</EuiCard>
<EuiCard textAlign="left" title={'Host'}>
<EuiDescriptionList
type="column"
compressed={false}
listItems={[
{ title: 'Architecture', description: data.host.architecture },
{ title: 'Containerized', description: data.host.containerized ? 'true' : 'false' },
{ title: 'Hostname', description: data.host.hostname },
{ title: 'ID', description: data.host.id },
{ title: 'IP', description: data.host.ip.join(',') },
{ title: 'Mac', description: data.host.mac.join(',') },
{ title: 'Name', description: data.host.name },
]}
/>
</EuiCard>
<EuiCard textAlign="left" title={'OS'}>
<EuiDescriptionList
type="column"
compressed={false}
listItems={[
{ title: 'Codename', description: data.host.os.codename },
{ title: 'Family', description: data.host.os.family },
{ title: 'Kernel', description: data.host.os.kernel },
{ title: 'Name', description: data.host.os.name },
{ title: 'Platform', description: data.host.os.platform },
{ title: 'Type', description: data.host.os.type },
{ title: 'Version', description: data.host.os.version },
]}
/>
</EuiCard>
</TabWrapper>
);
const FindingsTab = ({ tab, findings }: { findings: CSPFinding; tab: FindingsTab }) => {
switch (tab) {
case 'result':
return <Cards data={getResultCards(findings)} />;
case 'rule':
return <Cards data={getRuleCards(findings)} />;
case 'resource':
return <Cards data={getResourceCards(findings)} />;
}
assertNever(tab);
};

const RuleTab = ({ data }: CSPTabProps) => (
<TabWrapper>
<EuiCard textAlign="left" title={'Rule'}>
<EuiDescriptionList
compressed={false}
type="column"
listItems={[
{ title: 'Benchmark', description: data.rule.benchmark },
{ title: 'Name', description: data.rule.name },
{ title: 'Description', description: data.rule.description },
{ title: 'Tags', description: getTagsBadges(data.rule.tags) },
{ title: 'Remediation', description: <EuiCode>{data.rule.remediation}</EuiCode> },
]}
/>
</EuiCard>
</TabWrapper>
);
const getResourceCards = ({ resource, result }: CSPFinding): Card[] => [
{
title: 'Resource',
listItems: [
['Filename', <EuiCode>{resource.filename}</EuiCode>],
['Mode', resource.mode],
['Path', <EuiCode>{resource.path}</EuiCode>],
['Type', resource.type],
['UID', resource.uid],
['GID', resource.gid],
],
},
];

const ResourceTab = ({ data }: CSPTabProps) => (
<TabWrapper>
<EuiCard textAlign="left" title={'Resource'}>
<EuiDescriptionList
compressed={false}
type="column"
listItems={[
{ title: 'Filename', description: <EuiCode>{data.resource.filename}</EuiCode> },
{ title: 'Mode', description: data.resource.mode },
{ title: 'Path', description: <EuiCode>{data.resource.path}</EuiCode> },
{ title: 'Type', description: data.resource.type },
{ title: 'UID', description: data.resource.uid },
{ title: 'GID', description: data.resource.gid },
]}
/>
</EuiCard>
<EuiCard textAlign="left" title={'Result'}>
<EuiDescriptionList
compressed={false}
type="column"
listItems={[
{
title: 'Evaluation',
description: <CSPEvaluationBadge type={data.result.evaluation} />,
},
{
title: 'Evidence',
description: <EuiCode>{JSON.stringify(data.result.evidence, null, 2)}</EuiCode>,
},
]}
/>
</EuiCard>
</TabWrapper>
);
const getRuleCards = ({ rule }: CSPFinding): Card[] => [
{
title: 'Rule',
listItems: [
['Benchmark', rule.benchmark],
['Name', rule.name],
['Description', rule.description],
['Remediation', <EuiCode>{rule.remediation}</EuiCode>],
[
'Tags',
rule.tags.map((t) => (
<EuiBadge key={t} color="default">
{t}
</EuiBadge>
)),
],
],
},
];

const TabWrapper: React.FC = ({ children }) => (
<div style={{ display: 'grid', gridGap: 15, gridAutoFlow: 'rows' }}>{children}</div>
);
const getResultCards = ({ result, agent, host, ...rest }: CSPFinding): Card[] => [
{
title: 'Result',
listItems: [
['Evaluation', <CSPEvaluationBadge type={result.evaluation} />],
['Evidence', <EuiCode>{JSON.stringify(result.evidence, null, 2)}</EuiCode>],
['Timestamp', rest['@timestamp']],
result.evaluation === 'failed' && ['Remediation', rest.rule.remediation],
].filter(Boolean) as Card['listItems'], // TODO: is a type guard,
},
{
title: 'Agent',
listItems: [
['Name', agent.name],
['ID', agent.id],
['Type', agent.type],
['Version', agent.version],
],
},
{
title: 'Host',
listItems: [
['Architecture', host.architecture],
['Containerized', host.containerized ? 'true' : 'false'],
['Hostname', host.hostname],
['ID', host.id],
['IP', host.ip.join(',')],
['Mac', host.mac.join(',')],
['Name', host.name],
],
},
{
title: 'OS',
listItems: [
['Codename', host.os.codename],
['Family', host.os.family],
['Kernel', host.os.kernel],
['Name', host.os.name],
['Platform', host.os.platform],
['Type', host.os.type],
['Version', host.os.version],
],
},
];

0 comments on commit edc8368

Please sign in to comment.