@material-ui/icons#ArrowDropDown TypeScript Examples
The following examples show how to use
@material-ui/icons#ArrowDropDown.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ItemGroup.tsx From projectboard with MIT License | 6 votes |
ItemGroup = ({ title, children }: Props) => {
const [showItems, setshowItems] = useState(true);
let Icon = showItems ? ArrowDropDown : ArrowRight;
return (
<div className='flex flex-col w-full text-sm'>
<div
className='px-2 relative w-full mt-0.5 h-7 flex items-center rounded hover:bg-gray-100 cursor-pointer'
onClick={() => setshowItems(!showItems)}
>
<Icon className='w-3 h-3 mr-2 -ml-1' />
{title}
</div>
{showItems && children}
</div>
);
}
Example #2
Source File: Selector.tsx From anchor-web-app with Apache License 2.0 | 5 votes |
function SelectorBase<T>({
className,
items,
selectedItem,
onChange,
labelFunction,
keyFunction,
}: SelectorProps<T>) {
const {
isOpen,
getToggleButtonProps,
getMenuProps,
highlightedIndex,
getItemProps,
} = useSelect({
items,
selectedItem,
onSelectedItemChange: ({ selectedItem }) => onChange(selectedItem ?? null),
});
return (
<div className={className} aria-expanded={isOpen}>
<button type="button" {...getToggleButtonProps()}>
<span aria-selected={!!selectedItem}>
{labelFunction(selectedItem)}
</span>
<i>{isOpen ? <ArrowDropUp /> : <ArrowDropDown />}</i>
</button>
{isOpen && <HorizontalRuler />}
<ul {...getMenuProps()}>
{isOpen &&
items.map((item, index) => (
<li
data-selected={highlightedIndex === index}
key={`${keyFunction(item)}${index}`}
{...getItemProps({ item, index })}
>
{labelFunction(item)}
</li>
))}
</ul>
</div>
);
}
Example #3
Source File: SwapTokenDetails.tsx From interface-v2 with GNU General Public License v3.0 | 4 votes |
SwapTokenDetails: React.FC<{
token: Token;
}> = ({ token }) => {
const classes = useStyles();
const currency = unwrappedToken(token);
const tokenAddress = token.address;
const { palette } = useTheme();
const latestBlock = useBlockNumber();
const { tokenDetails, updateTokenDetails } = useTokenDetails();
const [tokenData, setTokenData] = useState<any>(null);
const [priceData, setPriceData] = useState<any>(null);
const priceUp = Number(tokenData?.priceChangeUSD) > 0;
const priceUpPercent = Number(tokenData?.priceChangeUSD).toFixed(2);
const [isCopied, setCopied] = useCopyClipboard();
const prices = priceData ? priceData.map((price: any) => price.close) : [];
useEffect(() => {
async function fetchTokenData() {
const tokenDetail = tokenDetails.find(
(item) => item.address === tokenAddress,
);
setTokenData(tokenDetail?.tokenData);
setPriceData(tokenDetail?.priceData);
const currentTime = dayjs.utc();
const startTime = currentTime
.subtract(1, 'day')
.startOf('hour')
.unix();
const tokenPriceData = await getIntervalTokenData(
tokenAddress,
startTime,
3600,
latestBlock,
);
setPriceData(tokenPriceData);
const [newPrice, oneDayPrice] = await getEthPrice();
const tokenInfo = await getTokenInfo(newPrice, oneDayPrice, tokenAddress);
if (tokenInfo) {
const token0 = tokenInfo[0];
setTokenData(token0);
const tokenDetailToUpdate = {
address: tokenAddress,
tokenData: token0,
priceData: tokenPriceData,
};
updateTokenDetails(tokenDetailToUpdate);
}
}
fetchTokenData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tokenAddress]);
return (
<Box>
<Box
display='flex'
alignItems='center'
justifyContent='space-between'
px={2}
py={1.5}
>
<Box display='flex' alignItems='center'>
<CurrencyLogo currency={currency} size='28px' />
<Box ml={1}>
<Typography variant='body2'>{currency.symbol}</Typography>
{tokenData ? (
<Box display='flex' alignItems='center'>
<Typography variant='body2'>
${formatNumber(tokenData.priceUSD)}
</Typography>
<Box
ml={0.5}
display='flex'
alignItems='center'
className={priceUp ? classes.success : classes.danger}
>
{priceUp ? <ArrowDropUp /> : <ArrowDropDown />}
<Typography variant='body2'>{priceUpPercent}%</Typography>
</Box>
</Box>
) : (
<Skeleton variant='rect' width={100} height={20} />
)}
</Box>
</Box>
{tokenData && priceData ? (
<Box width={88} height={47} position='relative'>
<Box position='absolute' top={-30} width={1}>
{prices.length > 0 && (
<LineChart
data={prices}
width='100%'
height={120}
color={priceUp ? palette.success.main : palette.error.main}
/>
)}
</Box>
</Box>
) : (
<Skeleton variant='rect' width={88} height={47} />
)}
</Box>
<Box
borderTop={`1px solid ${palette.secondary.light}`}
borderBottom={`1px solid ${palette.secondary.light}`}
px={2}
>
<Grid container>
<Grid item xs={6}>
<Box borderRight={`1px solid ${palette.secondary.light}`} py={1}>
{tokenData ? (
<Typography
variant='body2'
style={{ color: palette.text.secondary }}
>
TVL: {formatCompact(tokenData?.totalLiquidityUSD)}
</Typography>
) : (
<Skeleton variant='rect' width={100} height={16} />
)}
</Box>
</Grid>
<Grid item xs={6}>
<Box py={1} pl={2}>
{tokenData ? (
<Typography
variant='body2'
style={{ color: palette.text.secondary }}
>
24h VOL: {formatCompact(tokenData?.oneDayVolumeUSD)}
</Typography>
) : (
<Skeleton variant='rect' width={100} height={16} />
)}
</Box>
</Grid>
</Grid>
</Box>
<Box
display='flex'
justifyContent='space-between'
alignItems='center'
py={1}
px={2}
>
<a
href={`https://polygonscan.com/token/${tokenAddress}`}
target='_blank'
rel='noopener noreferrer'
style={{ textDecoration: 'none' }}
>
<Typography variant='body2' style={{ color: palette.primary.main }}>
{shortenAddress(tokenAddress)}
</Typography>
</a>
<Box
display='flex'
style={{ cursor: 'pointer', opacity: isCopied ? 0.5 : 1 }}
onClick={() => {
setCopied(tokenAddress);
}}
>
<CopyIcon />
</Box>
</Box>
</Box>
);
}
Example #4
Source File: TopMovers.tsx From interface-v2 with GNU General Public License v3.0 | 4 votes |
TopMovers: React.FC<TopMoversProps> = ({
background,
hideArrow = false,
}) => {
const classes = useStyles();
const { palette, breakpoints } = useTheme();
const [topTokens, updateTopTokens] = useState<any[] | null>(null);
const smallWindowSize = useMediaQuery(breakpoints.down('xs'));
const topMoverTokens = useMemo(
() => (topTokens && topTokens.length >= 5 ? topTokens.slice(0, 5) : null),
[topTokens],
);
useEffect(() => {
async function fetchTopTokens() {
const [newPrice, oneDayPrice] = await getEthPrice();
const topTokensData = await getTopTokens(newPrice, oneDayPrice, 5);
if (topTokensData) {
updateTopTokens(topTokensData);
}
}
fetchTopTokens();
}, [updateTopTokens]);
return (
<Box
width='100%'
display='flex'
flexWrap='wrap'
flexDirection='column'
justifyContent='center'
alignItems={smallWindowSize ? 'center' : 'flex-start'}
bgcolor={background}
border={`1px solid ${
background === 'transparent' ? palette.background.paper : 'transparent'
}`}
borderRadius={10}
px={2.5}
pt={2.5}
pb={0.5}
>
<Typography variant='h6' style={{ color: palette.text.secondary }}>
24h TOP MOVERS
</Typography>
<Box width={1} pb={2} style={{ overflowX: 'auto' }}>
{topMoverTokens ? (
<Box className={classes.content}>
{topMoverTokens.map((token: any, index: number) => {
const currency = new Token(
ChainId.MATIC,
getAddress(token.id),
token.decimals,
);
const priceColor = getPriceColor(
Number(token.priceChangeUSD),
palette,
);
const priceUp = Number(token.priceChangeUSD) > 0;
const priceDown = Number(token.priceChangeUSD) < 0;
const priceUpPercent = Number(token.priceChangeUSD).toFixed(2);
return (
<Box
mr={index < topMoverTokens.length ? 2 : 0}
width={smallWindowSize ? 180 : 'unset'}
mt={2}
key={token.id}
display='flex'
flexDirection='row'
justifyContent={smallWindowSize ? 'flex-start' : 'center'}
alignItems='center'
>
<CurrencyLogo currency={currency} size='28px' />
<Box ml={1}>
<Typography variant='body2' style={{ fontWeight: 'bold' }}>
{token.symbol}
</Typography>
<Box
display='flex'
flexDirection='row'
justifyContent='center'
alignItems='center'
>
<Typography variant='body2'>
${formatNumber(token.priceUSD)}
</Typography>
<Box
ml={hideArrow ? 1 : 0}
display='flex'
flexDirection='row'
justifyContent='center'
alignItems='center'
px={0.75}
py={0.25}
borderRadius={12}
bgcolor={
!hideArrow ? 'transparent' : priceColor.bgColor
}
color={priceColor.textColor}
>
{!hideArrow && priceUp && <ArrowDropUp />}
{!hideArrow && priceDown && <ArrowDropDown />}
<Typography variant='caption'>
{hideArrow && priceUp ? '+' : ''}
{priceUpPercent}%
</Typography>
</Box>
</Box>
</Box>
</Box>
);
})}
</Box>
) : (
<Skeleton variant='rect' width='100%' height={100} />
)}
</Box>
</Box>
);
}
Example #5
Source File: index.tsx From prism-frontend with MIT License | 4 votes |
function AlertForm({ classes, isOpen, setOpen }: AlertFormProps) {
const boundaryLayerData = useSelector(layerDataSelector(boundaryLayer.id)) as
| LayerData<BoundaryLayerProps>
| undefined;
const regionsList = useSelector(getSelectedBoundaries);
const dispatch = useDispatch();
// form elements
const [hazardLayerId, setHazardLayerId] = useState<LayerKey>();
const [emailValid, setEmailValid] = useState<boolean>(false);
const [email, setEmail] = useState('');
const [belowThreshold, setBelowThreshold] = useState('');
const [aboveThreshold, setAboveThreshold] = useState('');
const [thresholdError, setThresholdError] = useState<string | null>(null);
const [alertName, setAlertName] = useState('');
const [alertWaiting, setAlertWaiting] = useState(false);
const regionCodesToFeatureData: { [k: string]: object } = useMemo(() => {
if (!boundaryLayerData) {
// Not loaded yet. Will proceed when it is.
return {};
}
return Object.fromEntries(
boundaryLayerData.data.features
.filter(feature => feature.properties !== null)
.map(feature => {
return [feature.properties?.[boundaryLayer.adminCode], feature];
}),
);
}, [boundaryLayerData]);
const generateGeoJsonForRegionNames = () => {
// TODO - Handle these errors properly.
if (!boundaryLayerData) {
throw new Error('Boundary layer data is not loaded yet.');
}
if (regionsList.length === 0) {
throw new Error('Please select at least one region boundary.');
}
const features = regionsList.map(region => {
return regionCodesToFeatureData[region];
});
// Generate a copy of admin layer data (to preserve top-level properties)
// and replace the 'features' property with just the selected regions.
const mutableFeatureCollection = JSON.parse(
JSON.stringify(boundaryLayerData.data),
);
// eslint-disable-next-line fp/no-mutation
mutableFeatureCollection.features = features;
return mutableFeatureCollection;
};
const onChangeEmail = (event: React.ChangeEvent<HTMLInputElement>) => {
const newEmail = event.target.value;
setEmailValid(!!newEmail.match(EMAIL_REGEX));
setEmail(newEmail);
};
const onOptionChange = <T extends string>(
setterFunc: Dispatch<SetStateAction<T>>,
) => (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value as T;
setterFunc(value);
return value;
};
// specially for threshold values, also does error checking
const onThresholdOptionChange = (thresholdType: 'above' | 'below') => (
event: React.ChangeEvent<HTMLInputElement>,
) => {
const setterFunc =
thresholdType === 'above' ? setAboveThreshold : setBelowThreshold;
const changedOption = onOptionChange(setterFunc)(event);
// setting a value doesn't update the existing value until next render,
// therefore we must decide whether to access the old one or the newly change one here.
const aboveThresholdValue = parseFloat(
thresholdType === 'above' ? changedOption : aboveThreshold,
);
const belowThresholdValue = parseFloat(
thresholdType === 'below' ? changedOption : belowThreshold,
);
if (belowThresholdValue > aboveThresholdValue) {
setThresholdError('Below threshold is larger than above threshold!');
} else {
setThresholdError(null);
}
};
const runAlertForm = async () => {
if (!hazardLayerId) {
throw new Error('Layer should be selected to create alert.');
}
const request: AlertRequest = {
alert_name: alertName,
alert_config: LayerDefinitions[hazardLayerId] as WMSLayerProps,
max: parseFloat(aboveThreshold) || undefined,
min: parseFloat(belowThreshold) || undefined,
zones: generateGeoJsonForRegionNames(),
email,
prism_url: getPrismUrl(),
};
setAlertWaiting(true);
const response = await fetchApiData(ALERT_API_URL, request);
setAlertWaiting(false);
if ('message' in response) {
// TODO response isn't typed correctly because fetchApiData is too strict.
dispatch(
addNotification({
message: (response as { message: string }).message,
type: 'success',
}),
);
}
};
if (!ALERT_FORM_ENABLED) {
return null;
}
return (
<div className={classes.alertForm}>
<Button
variant="contained"
color="primary"
onClick={() => {
setOpen(!isOpen);
}}
>
<Notifications fontSize="small" />
<Typography variant="body2" className={classes.alertLabel}>
Create Alert
</Typography>
<ArrowDropDown fontSize="small" />
</Button>
<Box
className={classes.alertFormMenu}
width={isOpen ? 'min-content' : 0}
padding={isOpen ? '10px' : 0}
>
{isOpen ? (
<div>
<div className={classes.newAlertFormContainer}>
<div className={classes.alertFormOptions}>
<Typography variant="body2">Hazard Layer</Typography>
<LayerDropdown
type="wms"
value={hazardLayerId}
setValue={setHazardLayerId}
className={classes.selector}
placeholder="Choose hazard layer"
/>
</div>
<div className={classes.alertFormOptions}>
<Typography variant="body2">Threshold</Typography>
<TextField
id="filled-number"
error={!!thresholdError}
helperText={thresholdError}
className={classes.numberField}
label="Below"
type="number"
value={belowThreshold}
onChange={onThresholdOptionChange('below')}
variant="filled"
/>
<TextField
id="filled-number"
label="Above"
className={classes.numberField}
style={{ paddingLeft: '10px' }}
value={aboveThreshold}
onChange={onThresholdOptionChange('above')}
type="number"
variant="filled"
/>
</div>
<div className={classes.alertFormOptions}>
<Typography variant="body2">Regions</Typography>
<BoundaryDropdown className={classes.regionSelector} />
</div>
<div className={classes.alertFormOptions}>
<TextField
id="alert-name"
label="Alert Name"
type="text"
variant="filled"
value={alertName}
onChange={e => setAlertName(e.target.value)}
fullWidth
/>
</div>
<div className={classes.alertFormOptions}>
<TextField
id="email-address"
label="Email Address"
type="text"
variant="filled"
onChange={onChangeEmail}
fullWidth
/>
</div>
</div>
<Button
className={classes.innerCreateAlertButton}
onClick={runAlertForm}
disabled={
!hazardLayerId ||
!!thresholdError ||
!emailValid ||
alertWaiting ||
regionsList.length === 0
}
>
<Typography variant="body2">Create Alert</Typography>
</Button>
</div>
) : null}
</Box>
</div>
);
}
Example #6
Source File: index.tsx From prism-frontend with MIT License | 4 votes |
function Analyser({ extent, classes }: AnalyserProps) {
const dispatch = useDispatch();
const map = useSelector(mapSelector);
const selectedLayers = useSelector(layersSelector);
const {
updateHistory,
removeKeyFromUrl,
resetAnalysisParams,
updateAnalysisParams,
getAnalysisParams,
} = useUrlHistory();
const availableDates = useSelector(availableDatesSelector);
const analysisResult = useSelector(analysisResultSelector);
const isAnalysisLoading = useSelector(isAnalysisLoadingSelector);
const isMapLayerActive = useSelector(isAnalysisLayerActiveSelector);
const {
analysisHazardLayerId: hazardLayerIdFromUrl,
analysisBaselineLayerId: baselineLayerIdFromUrl,
analysisDate: selectedDateFromUrl,
analysisStatistic: selectedStatisticFromUrl,
analysisThresholdAbove: aboveThresholdFromUrl,
analysisThresholdBelow: belowThresholdFromUrl,
analysisAdminLevel: adminLevelFromUrl,
analysisStartDate: selectedStartDateFromUrl,
analysisEndDate: selectedEndDateFromUrl,
} = getAnalysisParams();
// form elements
const [hazardLayerId, setHazardLayerId] = useState<LayerKey | undefined>(
hazardLayerIdFromUrl,
);
const [statistic, setStatistic] = useState(
(selectedStatisticFromUrl as AggregationOperations) ||
AggregationOperations.Mean,
);
const [baselineLayerId, setBaselineLayerId] = useState<LayerKey | undefined>(
baselineLayerIdFromUrl,
);
const [selectedDate, setSelectedDate] = useState<number | null>(null);
const [belowThreshold, setBelowThreshold] = useState(
belowThresholdFromUrl || '',
);
const [aboveThreshold, setAboveThreshold] = useState(
aboveThresholdFromUrl || '',
);
const [thresholdError, setThresholdError] = useState<string | null>(null);
const [isAnalyserFormOpen, setIsAnalyserFormOpen] = useState<boolean>(
hazardLayerIdFromUrl !== undefined,
);
const [isTableViewOpen, setIsTableViewOpen] = useState(true);
// for polygon intersection analysis
const [adminLevel, setAdminLevel] = useState<AdminLevelType>(
Number(adminLevelFromUrl || '1') as AdminLevelType,
);
const [startDate, setStartDate] = useState<number | null>(null);
const [endDate, setEndDate] = useState<number | null>(null);
// find layer for the given adminLevel
const adminLevelLayer = getAdminLevelLayer(adminLevel);
const adminLevelLayerData = useSelector(
// if we couldn't find an admin layer, just return undefined
adminLevelLayer ? layerDataSelector(adminLevelLayer.id) : () => undefined,
) as LayerData<BoundaryLayerProps> | undefined;
// get variables derived from state
const selectedHazardLayer = hazardLayerId
? (LayerDefinitions[hazardLayerId] as WMSLayerProps)
: null;
const hazardDataType: HazardDataType | null = selectedHazardLayer
? selectedHazardLayer.geometry || RasterType.Raster
: null;
const availableHazardDates = selectedHazardLayer
? getPossibleDatesForLayer(selectedHazardLayer, availableDates)?.map(
d => new Date(d),
) || []
: undefined;
const BASELINE_URL_LAYER_KEY = 'baselineLayerId';
const preSelectedBaselineLayer = selectedLayers.find(
l => l.type === 'admin_level_data',
);
const [previousBaselineId, setPreviousBaselineId] = useState<
LayerKey | undefined
>(preSelectedBaselineLayer?.id);
const { t } = useSafeTranslation();
// check if there is any available date from the url, otherwise use last available date for the selected hazard layer
const lastAvailableHazardDate = availableHazardDates
? getDateFromList(
selectedDateFromUrl ? new Date(selectedDateFromUrl) : null,
availableHazardDates,
)?.getTime() || null
: null;
const lastAvailableHazardStartDate = availableHazardDates
? getDateFromList(
selectedStartDateFromUrl ? new Date(selectedStartDateFromUrl) : null,
availableHazardDates,
)?.getTime() || null
: null;
const lastAvailableHazardEndDate = availableHazardDates
? getDateFromList(
selectedEndDateFromUrl ? new Date(selectedEndDateFromUrl) : null,
availableHazardDates,
)?.getTime() || null
: null;
const { translatedColumns } = useAnalysisTableColumns(analysisResult);
// set default date after dates finish loading and when hazard layer changes
useEffect(() => {
if (isNil(lastAvailableHazardDate)) {
setSelectedDate(null);
} else {
setSelectedDate(lastAvailableHazardDate);
}
if (isNil(lastAvailableHazardStartDate)) {
setStartDate(null);
} else {
setStartDate(lastAvailableHazardStartDate);
}
if (isNil(lastAvailableHazardEndDate)) {
setEndDate(null);
} else {
setEndDate(lastAvailableHazardEndDate);
}
}, [
availableDates,
hazardLayerId,
lastAvailableHazardDate,
lastAvailableHazardStartDate,
lastAvailableHazardEndDate,
]);
const onOptionChange = <T extends string>(
setterFunc: Dispatch<SetStateAction<T>>,
) => (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value as T;
setterFunc(value);
return value;
};
// specially for threshold values, also does error checking
const onThresholdOptionChange = (thresholdType: 'above' | 'below') => (
event: React.ChangeEvent<HTMLInputElement>,
) => {
const setterFunc =
thresholdType === 'above' ? setAboveThreshold : setBelowThreshold;
const changedOption = onOptionChange(setterFunc)(event);
// setting a value doesn't update the existing value until next render, therefore we must decide whether to access the old one or the newly change one here.
const aboveThresholdValue = parseFloat(
thresholdType === 'above' ? changedOption : aboveThreshold,
);
const belowThresholdValue = parseFloat(
thresholdType === 'below' ? changedOption : belowThreshold,
);
if (belowThresholdValue > aboveThresholdValue) {
setThresholdError('Below threshold is larger than above threshold!');
} else {
setThresholdError(null);
}
};
const statisticOptions = Object.entries(AggregationOperations)
.filter(([, value]) => value !== AggregationOperations.Sum) // sum is used only for exposure analysis.
.map(([key, value]) => (
<FormControlLabel
key={key}
value={value}
control={
<Radio
className={classes.radioOptions}
color="default"
size="small"
/>
}
label={t(key)}
/>
));
const activateUniqueBoundary = (forceAdminLevel?: BoundaryLayerProps) => {
if (forceAdminLevel) {
// remove displayed boundaries
getDisplayBoundaryLayers().forEach(l => {
if (l.id !== forceAdminLevel.id) {
safeDispatchRemoveLayer(map, l, dispatch);
}
});
safeDispatchAddLayer(
map,
{ ...forceAdminLevel, isPrimary: true },
dispatch,
);
return;
}
if (!baselineLayerId) {
throw new Error('Layer should be selected to run analysis');
}
const baselineLayer = LayerDefinitions[
baselineLayerId
] as AdminLevelDataLayerProps;
if (baselineLayer.boundary) {
const boundaryLayer = LayerDefinitions[
baselineLayer.boundary
] as BoundaryLayerProps;
// remove displayed boundaries
getDisplayBoundaryLayers().forEach(l => {
if (l.id !== boundaryLayer.id) {
safeDispatchRemoveLayer(map, l, dispatch);
}
});
safeDispatchAddLayer(
map,
{ ...boundaryLayer, isPrimary: true },
dispatch,
);
} else {
getDisplayBoundaryLayers().forEach(l => {
safeDispatchAddLayer(map, l, dispatch);
});
}
};
const deactivateUniqueBoundary = () => {
if (!baselineLayerId) {
throw new Error('Layer should be selected to run analysis');
}
const baselineLayer = LayerDefinitions[
baselineLayerId
] as AdminLevelDataLayerProps;
if (baselineLayer.boundary) {
const boundaryLayer = LayerDefinitions[
baselineLayer.boundary
] as BoundaryLayerProps;
if (!getDisplayBoundaryLayers().includes(boundaryLayer)) {
safeDispatchRemoveLayer(map, boundaryLayer, dispatch);
}
}
getDisplayBoundaryLayers().forEach(l => {
safeDispatchAddLayer(map, l, dispatch);
});
};
const clearAnalysis = () => {
dispatch(clearAnalysisResult());
resetAnalysisParams();
if (previousBaselineId) {
const previousBaseline = LayerDefinitions[
previousBaselineId
] as AdminLevelDataLayerProps;
updateHistory(BASELINE_URL_LAYER_KEY, previousBaselineId);
safeDispatchAddLayer(map, previousBaseline, dispatch);
// check isMapLayerActive on analysis clear
// to avoid miss behaviour on boundary layers
dispatch(setIsMapLayerActive(true));
}
};
const shareAnalysis = () => {
copyTextToClipboard(window.location.href).then(() => {
dispatch(
addNotification({
message: 'Link to this analysis copied to clipboard!',
type: 'success',
}),
);
});
};
const onMapSwitchChange = (e: ChangeEvent<HTMLInputElement>) => {
dispatch(setIsMapLayerActive(e.target.checked));
// hazard layer doesn't needs a display boundary
// because it is already a vector
if (hazardDataType === GeometryType.Polygon) {
return;
}
if (isMapLayerActive) {
deactivateUniqueBoundary();
// check for previous baseline and bring it back
if (previousBaselineId) {
const previousBaseline = LayerDefinitions[
previousBaselineId
] as AdminLevelDataLayerProps;
updateHistory(BASELINE_URL_LAYER_KEY, previousBaselineId);
safeDispatchAddLayer(map, previousBaseline, dispatch);
}
} else {
// check for previous baseline and remove it before...
if (previousBaselineId) {
const previousBaseline = LayerDefinitions[
previousBaselineId
] as AdminLevelDataLayerProps;
removeKeyFromUrl(BASELINE_URL_LAYER_KEY);
safeDispatchRemoveLayer(map, previousBaseline, dispatch);
}
// activating the unique boundary layer
activateUniqueBoundary();
}
};
const runAnalyser = async () => {
if (preSelectedBaselineLayer) {
setPreviousBaselineId(preSelectedBaselineLayer.id);
removeKeyFromUrl(BASELINE_URL_LAYER_KEY);
// no need to safely dispatch remove we are sure
dispatch(removeLayer(preSelectedBaselineLayer));
}
if (analysisResult) {
clearAnalysis();
}
if (!extent) {
return;
} // hasn't been calculated yet
if (!selectedHazardLayer) {
throw new Error('Hazard layer should be selected to run analysis');
}
if (hazardDataType === GeometryType.Polygon) {
if (!startDate) {
throw new Error('Date Range must be given to run analysis');
}
if (!endDate) {
throw new Error('Date Range must be given to run analysis');
}
if (!adminLevelLayer || !adminLevelLayerData) {
// technically we can't get here because the run analaysis button
// is disabled while the admin level data loads
// but we have to put this in so the typescript compiler
// doesn't throw an error when we try to access the data
// property of adminLevelLayerData
throw new Error('Admin level data is still loading');
}
const params: PolygonAnalysisDispatchParams = {
hazardLayer: selectedHazardLayer,
adminLevel,
adminLevelLayer,
adminLevelData: adminLevelLayerData.data,
startDate,
endDate,
extent,
};
activateUniqueBoundary(adminLevelLayer);
// update history
updateAnalysisParams({
analysisHazardLayerId: hazardLayerId,
analysisAdminLevel: adminLevel.toString(),
analysisStartDate: moment(startDate).format(DEFAULT_DATE_FORMAT),
analysisEndDate: moment(endDate).format(DEFAULT_DATE_FORMAT),
analysisStatistic: statistic,
});
dispatch(requestAndStorePolygonAnalysis(params));
} else {
if (!selectedDate) {
throw new Error('Date must be given to run analysis');
}
if (!baselineLayerId) {
throw new Error('Baseline layer should be selected to run analysis');
}
const selectedBaselineLayer = LayerDefinitions[
baselineLayerId
] as AdminLevelDataLayerProps;
activateUniqueBoundary();
const params: AnalysisDispatchParams = {
hazardLayer: selectedHazardLayer,
baselineLayer: selectedBaselineLayer,
date: selectedDate,
statistic,
extent,
threshold: {
above: parseFloat(aboveThreshold) || undefined,
below: parseFloat(belowThreshold) || undefined,
},
};
// update history
updateAnalysisParams({
analysisHazardLayerId: hazardLayerId,
analysisBaselineLayerId: baselineLayerId,
analysisDate: moment(selectedDate).format(DEFAULT_DATE_FORMAT),
analysisStatistic: statistic,
analysisThresholdAbove: aboveThreshold || undefined,
analysisThresholdBelow: belowThreshold || undefined,
});
dispatch(requestAndStoreAnalysis(params));
}
};
return (
<div className={classes.analyser}>
<Button
variant="contained"
color="primary"
onClick={() => {
setIsAnalyserFormOpen(!isAnalyserFormOpen);
}}
>
<BarChart fontSize="small" />
<Typography variant="body2" className={classes.analyserLabel}>
{t('Run Analysis')}
</Typography>
<ArrowDropDown fontSize="small" />
</Button>
<Box
className={classes.analyserMenu}
width={isAnalyserFormOpen ? 'min-content' : 0}
padding={isAnalyserFormOpen ? '10px' : 0}
>
{isAnalyserFormOpen ? (
<div>
<div className={classes.newAnalyserContainer}>
<div className={classes.analyserOptions}>
<Typography variant="body2">{t('Hazard Layer')}</Typography>
<LayerDropdown
type="wms"
value={hazardLayerId}
setValue={setHazardLayerId}
className={classes.selector}
placeholder="Choose hazard layer"
/>
</div>
{hazardDataType === GeometryType.Polygon && (
<>
<div className={classes.analyserOptions}>
<Typography variant="body2">Admin Level</Typography>
<SimpleDropdown
value={adminLevel}
options={range(getAdminLevelCount()).map(i => [
(i + 1) as AdminLevelType,
`Admin ${i + 1}`,
])}
onChange={setAdminLevel}
/>
</div>
<div className={classes.analyserOptions}>
<Typography variant="body2">{t('Date Range')}</Typography>
<div className={classes.dateRangePicker}>
<Typography variant="body2">{t('Start')}</Typography>
<DatePicker
selected={startDate ? new Date(startDate) : null}
onChange={date =>
setStartDate(date?.getTime() || startDate)
}
maxDate={new Date()}
todayButton="Today"
peekNextMonth
showMonthDropdown
showYearDropdown
dropdownMode="select"
customInput={<Input />}
popperClassName={classes.calendarPopper}
includeDates={availableHazardDates}
/>
</div>
<div className={classes.dateRangePicker}>
<Typography variant="body2">{t('End')}</Typography>
<DatePicker
selected={endDate ? new Date(endDate) : null}
onChange={date =>
setEndDate(date?.getTime() || endDate)
}
maxDate={new Date()}
todayButton="Today"
peekNextMonth
showMonthDropdown
showYearDropdown
dropdownMode="select"
customInput={<Input />}
popperClassName={classes.calendarPopper}
includeDates={availableHazardDates}
/>
</div>
</div>
</>
)}
{hazardDataType === RasterType.Raster && (
<>
<div className={classes.analyserOptions}>
<Typography variant="body2">{t('Statistic')}</Typography>
<FormControl component="div">
<RadioGroup
name="statistics"
value={statistic}
onChange={onOptionChange(setStatistic)}
row
>
{statisticOptions}
</RadioGroup>
</FormControl>
</div>
<div className={classes.analyserOptions}>
<Typography variant="body2">
{t('Baseline Layer')}
</Typography>
<LayerDropdown
type="admin_level_data"
value={baselineLayerId || undefined}
setValue={setBaselineLayerId}
className={classes.selector}
placeholder="Choose baseline layer"
/>
</div>
<div className={classes.analyserOptions}>
<Typography variant="body2">{t('Threshold')}</Typography>
<TextField
id="filled-number"
error={!!thresholdError}
helperText={thresholdError}
className={classes.numberField}
label={t('Below')}
type="number"
value={belowThreshold}
onChange={onThresholdOptionChange('below')}
variant="filled"
/>
<TextField
id="filled-number"
label={t('Above')}
className={classes.numberField}
value={aboveThreshold}
onChange={onThresholdOptionChange('above')}
type="number"
variant="filled"
/>
</div>
<div className={classes.analyserOptions}>
<Typography variant="body2">{t('Date')}</Typography>
<DatePicker
locale={t('date_locale')}
dateFormat="PP"
selected={selectedDate ? new Date(selectedDate) : null}
onChange={date =>
setSelectedDate(date?.getTime() || selectedDate)
}
maxDate={new Date()}
todayButton={t('Today')}
peekNextMonth
showMonthDropdown
showYearDropdown
dropdownMode="select"
customInput={<Input />}
popperClassName={classes.calendarPopper}
includeDates={availableHazardDates}
/>
</div>
</>
)}
</div>
{!isAnalysisLoading &&
analysisResult &&
(analysisResult instanceof BaselineLayerResult ||
analysisResult instanceof PolygonAnalysisResult) && (
<>
<FormGroup>
<FormControlLabel
control={
<Switch
color="default"
checked={isMapLayerActive}
onChange={onMapSwitchChange}
/>
}
label={t('Map View')}
/>
<FormControlLabel
control={
<Switch
color="default"
checked={isTableViewOpen}
onChange={e => setIsTableViewOpen(e.target.checked)}
/>
}
label={t('Table View')}
/>
</FormGroup>
{isTableViewOpen && (
<AnalysisTable
tableData={analysisResult.tableData}
columns={translatedColumns}
/>
)}
<Button
className={classes.innerAnalysisButton}
onClick={() =>
downloadCSVFromTableData(
analysisResult,
translatedColumns,
selectedDate,
)
}
>
<Typography variant="body2">{t('Download')}</Typography>
</Button>
<Button
className={classes.innerAnalysisButton}
onClick={clearAnalysis}
>
<Typography variant="body2">
{t('Clear Analysis')}
</Typography>
</Button>
<Button
className={classes.innerAnalysisButton}
onClick={shareAnalysis}
>
<Typography variant="body2">
{t('Share Analysis')}
</Typography>
</Button>
</>
)}
{(!analysisResult ||
analysisResult instanceof ExposedPopulationResult) && (
<Button
className={classes.innerAnalysisButton}
onClick={runAnalyser}
disabled={
!!thresholdError || // if there is a threshold error
isAnalysisLoading || // or analysis is currently loading
!hazardLayerId || // or hazard layer hasn't been selected
(hazardDataType === GeometryType.Polygon
? !startDate || !endDate || !adminLevelLayerData
: !selectedDate || !baselineLayerId) // or date hasn't been selected // or baseline layer hasn't been selected
}
>
<Typography variant="body2">{t('Run Analysis')}</Typography>
</Button>
)}
{isAnalysisLoading ? <LinearProgress /> : null}
</div>
) : null}
</Box>
</div>
);
}
Example #7
Source File: index.tsx From prism-frontend with MIT License | 4 votes |
function Download({ classes }: DownloadProps) {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [open, setOpen] = useState(false);
const selectedMap = useSelector(mapSelector);
const previewRef = useRef<HTMLCanvasElement>(null);
const { t } = useSafeTranslation();
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const openModal = () => {
if (selectedMap) {
const activeLayers = selectedMap.getCanvas();
const canvas = previewRef.current;
if (canvas) {
canvas.setAttribute('width', activeLayers.width.toString());
canvas.setAttribute('height', activeLayers.height.toString());
const context = canvas.getContext('2d');
if (context) {
context.drawImage(activeLayers, 0, 0);
}
}
setOpen(true);
}
handleClose();
};
const download = (format: string) => {
const ext = format === 'pdf' ? 'png' : format;
const canvas = previewRef.current;
if (canvas) {
const file = canvas.toDataURL(`image/${ext}`);
if (format === 'pdf') {
// eslint-disable-next-line new-cap
const pdf = new jsPDF({
orientation: 'landscape',
});
const imgProps = pdf.getImageProperties(file);
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
pdf.addImage(file, 'PNG', 0, 0, pdfWidth, pdfHeight);
pdf.save('map.pdf');
} else {
const link = document.createElement('a');
link.setAttribute('href', file);
link.setAttribute('download', `map.${ext}`);
link.click();
}
setOpen(false);
handleClose();
}
};
return (
<Grid item>
<Button variant="contained" color="primary" onClick={handleClick}>
<CloudDownload fontSize="small" />
<Hidden smDown>
<Typography className={classes.label} variant="body2">
{t('Export')}
</Typography>
</Hidden>
<ArrowDropDown fontSize="small" />
</Button>
<ExportMenu
id="export-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<ExportMenuItem onClick={openModal}>
<ListItemIcon>
<Image fontSize="small" style={{ color: 'white' }} />
</ListItemIcon>
<ListItemText primary={t('IMAGE')} />
</ExportMenuItem>
</ExportMenu>
<Dialog
maxWidth="xl"
open={open}
keepMounted
onClose={() => setOpen(false)}
aria-labelledby="dialog-preview"
>
<DialogTitle className={classes.title} id="dialog-preview">
{t('Map Preview')}
</DialogTitle>
<DialogContent>
<canvas ref={previewRef} />
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)} color="primary">
{t('Cancel')}
</Button>
<Button
variant="contained"
onClick={() => download('png')}
color="primary"
>
{t('Download PNG')}
</Button>
<Button
variant="contained"
onClick={() => download('jpeg')}
color="primary"
>
{t('Download JPEG')}
</Button>
<Button
variant="contained"
onClick={() => download('pdf')}
color="primary"
>
{t('Download PDF')}
</Button>
</DialogActions>
</Dialog>
</Grid>
);
}
Example #8
Source File: Header.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
HeaderNoCtx: React.FC<ContextType> = (props) => {
const { searchTerm, setSearchTerm } = props;
const classes = useStyles();
const history = useHistory();
const location = useLocation();
const {
currentOrganization,
setOrganization,
showAllOrganizations,
setShowAllOrganizations,
user,
logout,
apiGet
} = useAuthContext();
const [navOpen, setNavOpen] = useState(false);
const [organizations, setOrganizations] = useState<
(Organization | OrganizationTag)[]
>([]);
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down('md'));
let userLevel = 0;
if (user && user.isRegistered) {
if (user.userType === 'standard') {
userLevel = STANDARD_USER;
} else {
userLevel = GLOBAL_ADMIN;
}
}
const fetchOrganizations = useCallback(async () => {
try {
const rows = await apiGet<Organization[]>('/organizations/');
let tags: (OrganizationTag | Organization)[] = [];
if (userLevel === GLOBAL_ADMIN) {
tags = await apiGet<OrganizationTag[]>('/organizations/tags');
}
setOrganizations(tags.concat(rows));
} catch (e) {
console.error(e);
}
}, [apiGet, setOrganizations, userLevel]);
React.useEffect(() => {
if (userLevel > 0) {
fetchOrganizations();
}
}, [fetchOrganizations, userLevel]);
const navItems: NavItemType[] = [
{
title: 'Overview',
path: '/',
users: ALL_USERS,
exact: true
},
{
title: 'Inventory',
path: '/inventory',
users: ALL_USERS,
exact: false
},
{ title: 'Feeds', path: '/feeds', users: ALL_USERS, exact: false },
{
title: 'Scans',
path: '/scans',
users: GLOBAL_ADMIN,
exact: true
}
].filter(({ users }) => (users & userLevel) > 0);
const userMenu: NavItemType = {
title: (
<div className={classes.userLink}>
<UserIcon /> My Account <ArrowDropDown />
</div>
),
path: '#',
exact: false,
nested: [
{
title: 'Manage Organizations',
path: '/organizations',
users: GLOBAL_ADMIN,
exact: true
},
{
title: 'My Organizations',
path: '/organizations',
users: STANDARD_USER,
exact: true
},
{
title: 'Manage Users',
path: '/users',
users: GLOBAL_ADMIN,
exact: true
},
{
title: 'My Settings',
path: '/settings',
users: ALL_USERS,
exact: true
},
{
title: 'Logout',
path: '/settings',
users: ALL_USERS,
onClick: logout,
exact: true
}
].filter(({ users }) => (users & userLevel) > 0)
};
const userItemsSmall: NavItemType[] = [
{
title: 'My Account',
path: '#',
users: ALL_USERS,
exact: true
},
{
title: 'Manage Organizations',
path: '/organizations',
users: GLOBAL_ADMIN,
exact: true
},
{
title: 'My Organizations',
path: '/organizations',
users: STANDARD_USER,
exact: true
},
{
title: 'Manage Users',
path: '/users',
users: GLOBAL_ADMIN,
exact: true
},
{
title: 'My Settings',
path: '/settings',
users: ALL_USERS,
exact: true
},
{
title: 'Logout',
path: '/',
users: ALL_USERS,
onClick: logout,
exact: true
}
].filter(({ users }) => (users & userLevel) > 0);
const desktopNavItems: JSX.Element[] = navItems.map((item) => (
<NavItem key={item.title.toString()} {...item} />
));
const navItemsToUse = () => {
if (isSmall) {
return userItemsSmall;
} else {
return navItems;
}
};
return (
<div>
<AppBar position="static" elevation={0}>
<div className={classes.inner}>
<Toolbar>
<Link to="/">
<img
src={logo}
className={classes.logo}
alt="Crossfeed Icon Navigate Home"
/>
</Link>
<div className={classes.lgNav}>{desktopNavItems.slice()}</div>
<div className={classes.spacing} />
{userLevel > 0 && (
<>
<SearchBar
initialValue={searchTerm}
value={searchTerm}
onChange={(value) => {
if (location.pathname !== '/inventory')
history.push('/inventory?q=' + value);
setSearchTerm(value, {
shouldClearFilters: false,
autocompleteResults: false
});
}}
/>
{organizations.length > 1 && (
<>
<div className={classes.spacing} />
<Autocomplete
options={[{ name: 'All Organizations' }].concat(
organizations
)}
autoComplete={false}
className={classes.selectOrg}
classes={{
option: classes.option
}}
value={
showAllOrganizations
? { name: 'All Organizations' }
: currentOrganization ?? undefined
}
filterOptions={(options, state) => {
// If already selected, show all
if (
options.find(
(option) =>
option.name.toLowerCase() ===
state.inputValue.toLowerCase()
)
) {
return options;
}
return options.filter((option) =>
option.name
.toLowerCase()
.includes(state.inputValue.toLowerCase())
);
}}
disableClearable
blurOnSelect
selectOnFocus
getOptionLabel={(option) => option.name}
renderOption={(option) => (
<React.Fragment>{option.name}</React.Fragment>
)}
onChange={(
event: any,
value:
| Organization
| {
name: string;
}
| undefined
) => {
if (value && 'id' in value) {
setOrganization(value);
setShowAllOrganizations(false);
} else {
setShowAllOrganizations(true);
}
}}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
inputProps={{
...params.inputProps,
id: 'autocomplete-input',
autoComplete: 'new-password' // disable autocomplete and autofill
}}
/>
)}
/>
</>
)}
{isSmall ? null : <NavItem {...userMenu} />}
</>
)}
<IconButton
edge="start"
className={classes.menuButton}
aria-label="toggle mobile menu"
color="inherit"
onClick={() => setNavOpen((open) => !open)}
>
<MenuIcon />
</IconButton>
</Toolbar>
</div>
</AppBar>
<Drawer
anchor="right"
open={navOpen}
onClose={() => setNavOpen(false)}
data-testid="mobilenav"
>
<List className={classes.mobileNav}>
{navItemsToUse().map(({ title, path, nested, onClick }) => (
<React.Fragment key={title.toString()}>
{path && (
<ListItem
button
exact
component={NavLink}
to={path}
activeClassName={classes.activeMobileLink}
onClick={onClick ? onClick : undefined}
>
{title}
</ListItem>
)}
{nested?.map((nested) => (
<ListItem
button
exact
key={nested.title.toString()}
component={NavLink}
to={nested.onClick ? '#' : nested.path}
activeClassName={classes.activeMobileLink}
onClick={nested.onClick ? nested.onClick : undefined}
>
{nested.title}
</ListItem>
))}
</React.Fragment>
))}
</List>
</Drawer>
</div>
);
}
Example #9
Source File: Vulnerability.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
Vulnerability: React.FC = () => {
const { vulnerabilityId } = useParams();
const { apiGet, apiPut } = useAuthContext();
const [vulnerability, setVulnerability] = useState<VulnerabilityType>();
const [comment, setComment] = useState<string>('');
const [showCommentForm, setShowCommentForm] = useState<boolean>(false);
const [menuAnchor, setMenuAnchor] = React.useState<null | HTMLElement>(null);
const classes = useStyles();
const history = useHistory();
const formatDate = (date: string) => {
return format(parseISO(date), 'MM-dd-yyyy');
};
const fetchVulnerability = useCallback(async () => {
try {
const result = await apiGet<VulnerabilityType>(
`/vulnerabilities/${vulnerabilityId}`
);
setVulnerability(result);
} catch (e) {
console.error(e);
}
}, [vulnerabilityId, apiGet]);
const updateVulnerability = async (body: { [key: string]: string }) => {
try {
if (!vulnerability) return;
const res = await apiPut<VulnerabilityType>(
'/vulnerabilities/' + vulnerability.id,
{
body: body
}
);
setVulnerability({
...vulnerability,
state: res.state,
substate: res.substate,
actions: res.actions
});
} catch (e) {
console.error(e);
}
};
useEffect(() => {
fetchVulnerability();
}, [fetchVulnerability]);
if (!vulnerability) return <></>;
const references = vulnerability.references.map((ref) => ref);
if (vulnerability.cve)
references.unshift({
name: 'NIST National Vulnerability Database',
url: `https://nvd.nist.gov/vuln/detail/${vulnerability.cve}`,
source: '',
tags: []
});
const states = [
'unconfirmed',
'exploitable',
'false-positive',
'accepted-risk',
'remediated'
];
interface dnstwist {
'domain-name': string;
fuzzer: string;
'dns-a'?: string;
'dns-aaas'?: string;
'dns-mx'?: string;
'dns-ns'?: string;
'date-first-observed'?: string;
}
return (
<>
{/* <Alert severity="info">
This vulnerability is found on 17 domains you have access to.
</Alert> */}
<div className={classes.root}>
<p>
<Link
to="# "
onClick={() => history.goBack()}
className={classes.backLink}
>
<ChevronLeft
style={{
height: '100%',
verticalAlign: 'middle',
marginTop: '-2px'
}}
></ChevronLeft>
Go back
</Link>
</p>
<div className={classes.contentWrapper}>
<div className={classes.content}>
<div
className={classes.panel}
style={{
flex: '0 0 45%'
}}
>
<Paper classes={{ root: classes.cardRoot }}>
<div className={classes.title}>
<h4>{vulnerability.title}</h4>
<Button
aria-haspopup="true"
onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
setMenuAnchor(event.currentTarget)
}
>
<Flag
style={{
fontSize: '14px',
color: '#A9AEB1',
marginRight: '5px'
}}
></Flag>
Mark Item <ArrowDropDown />
</Button>
<Menu
anchorEl={menuAnchor}
keepMounted
open={Boolean(menuAnchor)}
getContentAnchorEl={null}
onClose={() => setMenuAnchor(null)}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
>
{states.map((state) => (
<MenuItem
key={state}
onClick={() => {
updateVulnerability({
substate: state
});
setMenuAnchor(null);
}}
style={{ outline: 'none' }}
>
{stateMap[state]}
</MenuItem>
))}
</Menu>
</div>
<Chip
style={{
marginLeft: '1.5rem'
}}
// icon={<Check></Check>}
label={`${vulnerability.state[0].toUpperCase()}${vulnerability.state.slice(
1
)} (${stateMap[vulnerability.substate]})`}
color={
vulnerability.state === 'open' ? 'secondary' : 'default'
}
/>
<div className={classes.inner}>
<div className={classes.section}>
<h4 className={classes.subtitle}>Description</h4>
{vulnerability.description}
</div>
<div className={classes.section}>
<h4 className={classes.subtitle}>References</h4>
{references &&
references.map((ref, index) => (
<p key={index}>
<a
href={ref.url}
target="_blank"
rel="noopener noreferrer"
>
{ref.name ? ref.name : ref.url}
</a>
{ref.tags.length > 0
? ' - ' + ref.tags.join(',')
: ''}
</p>
))}
</div>
{vulnerability.source === 'hibp' && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Data</h4>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Exposed Emails</TableCell>
<TableCell align="right">Breaches</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.keys(
vulnerability.structuredData['emails']
).map((keyName, keyIndex) => (
<TableRow key={keyName}>
<TableCell component="th" scope="row">
{keyName}
</TableCell>
<TableCell align="right">
{vulnerability.structuredData['emails'][
keyName
].join(', ')}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
{vulnerability.source === 'lookingGlass' && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Data</h4>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>First Seen</TableCell>
<TableCell align="right">Last Seen</TableCell>
<TableCell align="right">Vuln Name</TableCell>
<TableCell align="right">Type</TableCell>
</TableRow>
</TableHead>
<TableBody>
{vulnerability.structuredData['lookingGlassData'].map(
(col: any) => (
<TableRow key={col.right_name}>
<TableCell component="th" scope="row">
{formatDistanceToNow(
parseISO(col.firstSeen)
) + ' ago'}
</TableCell>
<TableCell align="right">
{formatDistanceToNow(parseISO(col.lastSeen)) +
' ago'}
</TableCell>
<TableCell align="right">
{col.right_name}
</TableCell>
<TableCell align="right">
{col.vulnOrMal}
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</div>
)}
{vulnerability.source === 'dnstwist' && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Data</h4>
<TableContainer>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Domain Name</TableCell>
<TableCell>IP Address / A Record</TableCell>
<TableCell>MX Record</TableCell>
<TableCell>NS Record</TableCell>
<TableCell>Date Observed</TableCell>
<TableCell>Fuzzer</TableCell>
</TableRow>
</TableHead>
<TableBody>
{vulnerability.structuredData['domains'].map(
(dom: dnstwist) => (
<TableRow key={dom['domain-name']}>
<TableCell component="th" scope="row">
{dom['domain-name']}
</TableCell>
<TableCell>{dom['dns-a']}</TableCell>
<TableCell>{dom['dns-mx']}</TableCell>
<TableCell>{dom['dns-ns']}</TableCell>
<TableCell>
{dom['date-first-observed']}
</TableCell>
<TableCell>{dom['fuzzer']}</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</TableContainer>
</div>
)}
</div>
</Paper>
</div>
<div
className={classes.panel}
style={{
flex: '0 0 30%'
}}
>
<Paper className={classes.cardRoot}>
<div className={classes.inner}>
<div className={classes.section}>
<h2 className={classes.subtitle}>Team notes</h2>
<button
onClick={() => {
setShowCommentForm(!showCommentForm);
}}
className={classes.linkSmall}
>
Add new note
</button>
</div>
{showCommentForm && (
<div>
<TextareaAutosize
style={{
width: '100%',
padding: 10,
marginBottom: '20px'
}}
rowsMin={4}
placeholder="Leave a Note"
onChange={(e) => setComment(e.target.value)}
/>
<Button
onClick={() => {
updateVulnerability({
comment
});
setComment('');
setShowCommentForm(false);
}}
style={{
width: 150,
marginBottom: '20px'
}}
variant="contained"
color="secondary"
>
Save
</Button>
</div>
)}
{vulnerability.actions &&
vulnerability.actions
.filter((action) => action.type === 'comment')
.map((action, index) => (
<div className={classes.section} key={index}>
<h4
className={classes.subtitle}
style={{ fontSize: '16px', display: 'inline' }}
>
{action.userName}
</h4>
<span style={{ float: 'right', display: 'inline' }}>
{formatDistanceToNow(parseISO(action.date))} ago
</span>
<ReactMarkdown linkTarget="_blank">
{action.value || ''}
</ReactMarkdown>
</div>
))}
</div>
</Paper>
<Paper className={classes.cardRoot}>
<div className={classes.inner}>
<div className={classes.section}>
<h2 className={classes.subtitle}>Vulnerability History</h2>
</div>
<Timeline
style={{
position: 'relative',
marginLeft: '-90%'
}}
align="left"
>
{vulnerability.actions &&
vulnerability.actions
.filter(
(action) =>
action.type === 'state-change' && action.substate
)
.map((action, index) => (
<TimelineItem key={index}>
<TimelineSeparator>
<TimelineDot />
<TimelineConnector />
</TimelineSeparator>{' '}
<TimelineContent>
State {action.automatic ? 'automatically ' : ''}
changed to {action.state} (
{stateMap[action.substate!].toLowerCase()})
{action.userName ? ' by ' + action.userName : ''}{' '}
<br></br>
<span
style={{
color: '#A9AEB1'
}}
>
{formatDate(action.date)}
</span>
</TimelineContent>
</TimelineItem>
))}
<TimelineItem>
<TimelineSeparator>
<TimelineDot />
</TimelineSeparator>
<TimelineContent>
Vulnerability opened<br></br>
<span
style={{
color: '#A9AEB1'
}}
>
{formatDate(vulnerability.createdAt)}
</span>
</TimelineContent>
</TimelineItem>
</Timeline>
</div>
</Paper>
<Paper className={classes.cardRoot}>
<div className={classes.inner}>
<div className={classes.section}>
<h2 className={classes.subtitle}>Provenance</h2>
<p>
<strong>Root Domain: </strong>
{vulnerability.domain.fromRootDomain}
</p>
<p>
<strong>Subdomain: </strong>
{vulnerability.domain.name} (
{vulnerability.domain.subdomainSource})
</p>
{vulnerability.service && (
<p>
<strong>Service/Port: </strong>
{vulnerability.service.service
? vulnerability.service.service
: vulnerability.service.port}{' '}
({vulnerability.service.serviceSource})
</p>
)}
{vulnerability.cpe && (
<>
<p>
<strong>Product: </strong>
{vulnerability.cpe}
</p>
</>
)}
<p>
<strong>Vulnerability: </strong>
{vulnerability.title} ({vulnerability.source})
</p>
</div>
</div>
</Paper>
{vulnerability.source === 'hibp' && (
<Paper className={classes.cardRoot}>
<div className={classes.inner}>
<div className={classes.section}>
<h2 className={classes.subtitle}>Breaches</h2>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Breach Name</TableCell>
<TableCell align="right">Date Added</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.keys(vulnerability.structuredData['breaches'])
.sort(
(a, b) =>
parseISO(
vulnerability.structuredData['breaches'][b][
'AddedDate'
]
).getTime() -
parseISO(
vulnerability.structuredData['breaches'][a][
'AddedDate'
]
).getTime()
)
.map((keyName, keyIndex) => (
<TableRow key={keyName}>
<TableCell component="th" scope="row">
{keyName}
</TableCell>
<TableCell align="right">
{formatDistanceToNow(
parseISO(
vulnerability.structuredData['breaches'][
keyName
]['AddedDate']
)
) + ' ago'}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</Paper>
)}
</div>
</div>
</div>
</div>
</>
);
}